@@ -5,6 +5,7 @@ | |||
#include <dds/proc.hpp> | |||
#include <dds/source.hpp> | |||
#include <dds/toolchain.hpp> | |||
#include <libman/index.hpp> | |||
#include <libman/parse.hpp> | |||
#include <algorithm> | |||
@@ -167,7 +168,58 @@ void link_apps(const source_list& sources, | |||
dds::compilation_set collect_compiles(const build_params& params, const library_manifest& man) { | |||
source_list sources = source_file::collect_pf_sources(params.root); | |||
const bool need_compile_deps = params.build_tests || params.build_apps || params.build_deps; | |||
if (need_compile_deps) { | |||
std::vector<fs::path> dep_includes; | |||
std::vector<std::string> dep_defines; | |||
if (need_compile_deps && (!man.uses.empty() || !man.links.empty())) { | |||
fs::path lm_index_path = params.lm_index; | |||
for (auto cand : {"INDEX.lmi", "_build/INDEX.lmi"}) { | |||
if (!lm_index_path.empty()) { | |||
break; | |||
} | |||
lm_index_path = params.root / cand; | |||
} | |||
if (!fs::exists(lm_index_path)) { | |||
throw compile_failure( | |||
"No `INDEX.lmi` found, but we need to pull in dependencies." | |||
"Use a package manager to generate an INDEX.lmi"); | |||
} | |||
auto lm_index = lm::index::from_file(lm_index_path); | |||
auto lib_index = lm_index.build_library_index(); | |||
auto collect_more_deps = [&](auto& uses_key) { | |||
auto pair = split(uses_key, "/"); | |||
if (pair.size() != 2) { | |||
throw compile_failure(fmt::format("Invalid `Uses`: {}", uses_key)); | |||
} | |||
auto& pkg_ns = pair[0]; | |||
auto& lib = pair[1]; | |||
auto found = lib_index.find(std::pair(pkg_ns, lib)); | |||
if (found == lib_index.end()) { | |||
throw compile_failure( | |||
fmt::format("No library '{}/{}': Check that it is installed and available", | |||
pkg_ns, | |||
lib)); | |||
} | |||
const lm::library& lm_lib = found->second; | |||
extend(dep_includes, lm_lib.include_paths); | |||
extend(dep_defines, lm_lib.preproc_defs); | |||
// TODO: RECURSE! | |||
// for (auto next_usage : lm_lib.uses) { | |||
// recurse(recurse, next_usage); | |||
// } | |||
}; | |||
// TODO: Set compilation flags on each file set (as needed) | |||
for (auto& uses : man.uses) { | |||
collect_more_deps(uses); | |||
} | |||
spdlog::critical("Dependency resolution isn't done yet"); | |||
} | |||
@@ -190,7 +242,9 @@ dds::compilation_set collect_compiles(const build_params& params, const library_ | |||
compilation_rules rules; | |||
rules.base_path() = params.root / "src"; | |||
extend(rules.defs(), man.private_defines); | |||
extend(rules.defs(), dep_defines); | |||
extend(rules.include_dirs(), man.private_includes); | |||
extend(rules.include_dirs(), dep_includes); | |||
rules.include_dirs().push_back(fs::absolute(params.root / "src")); | |||
rules.include_dirs().push_back(fs::absolute(params.root / "include")); | |||
@@ -12,6 +12,7 @@ namespace dds { | |||
struct build_params { | |||
fs::path root; | |||
fs::path out_root; | |||
fs::path lm_index; | |||
dds::toolchain toolchain; | |||
std::string export_name; | |||
bool do_export = false; |
@@ -1,7 +1,7 @@ | |||
#include <dds/build.hpp> | |||
#include <libman/parse.hpp> | |||
#include <dds/logging.hpp> | |||
#include <dds/util.hpp> | |||
#include <libman/parse.hpp> | |||
#include <args.hxx> | |||
@@ -53,6 +53,12 @@ struct cli_build { | |||
args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | |||
args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | |||
path_flag lm_index{cmd, | |||
"lm_index", | |||
"Path to a libman index (usually INDEX.lmi)", | |||
{"--lm-index", 'I'}, | |||
dds::fs::path()}; | |||
args::Flag enable_warnings{cmd, | |||
"enable_warnings", | |||
"Enable compiler warnings", | |||
@@ -95,6 +101,7 @@ struct cli_build { | |||
params.build_apps = build_apps.Get(); | |||
params.enable_warnings = enable_warnings.Get(); | |||
params.parallel_jobs = num_jobs.Get(); | |||
params.lm_index = lm_index.Get(); | |||
dds::library_manifest man; | |||
const auto man_filepath = params.root / "manifest.dds"; | |||
if (exists(man_filepath)) { |
@@ -7,6 +7,8 @@ | |||
namespace dds { | |||
inline namespace file_utils { | |||
namespace fs = std::filesystem; | |||
using path_ref = const fs::path&; | |||
@@ -32,6 +34,48 @@ inline std::string slurp_file(const fs::path& path) { | |||
return contents; | |||
} | |||
} // namespace file_utils | |||
template <typename Container, typename Predicate> | |||
void erase_if(Container& c, Predicate&& p) { | |||
auto erase_point = std::remove_if(c.begin(), c.end(), p); | |||
c.erase(erase_point, c.end()); | |||
} | |||
template <typename Container, typename Other> | |||
void extend(Container& c, const Other& o) { | |||
c.insert(c.end(), o.begin(), o.end()); | |||
} | |||
template <typename Container, typename Item> | |||
void extend(Container& c, std::initializer_list<Item> il) { | |||
c.insert(c.end(), il.begin(), il.end()); | |||
} | |||
inline namespace string_utils { | |||
inline std::string_view sview(std::string_view::const_iterator beg, | |||
std::string_view::const_iterator end) { | |||
return std::string_view(&*beg, static_cast<std::size_t>(std::distance(beg, end))); | |||
} | |||
inline std::string_view trim(std::string_view s) { | |||
auto iter = s.begin(); | |||
auto end = s.end(); | |||
while (iter != end && std::isspace(*iter)) { | |||
++iter; | |||
} | |||
auto riter = s.rbegin(); | |||
auto rend = s.rend(); | |||
while (riter != rend && std::isspace(*riter)) { | |||
++riter; | |||
} | |||
auto new_end = riter.base(); | |||
return sview(iter, new_end); | |||
} | |||
inline std::string trim(std::string&& s) { return std::string(trim(s)); } | |||
inline bool ends_with(std::string_view s, std::string_view key) { | |||
auto found = s.rfind(key); | |||
return found != s.npos && found == s.size() - key.size(); | |||
@@ -74,21 +118,7 @@ replace(std::vector<std::string> strings, std::string_view key, std::string_view | |||
return strings; | |||
} | |||
template <typename Container, typename Predicate> | |||
void erase_if(Container& c, Predicate&& p) { | |||
auto erase_point = std::remove_if(c.begin(), c.end(), p); | |||
c.erase(erase_point, c.end()); | |||
} | |||
template <typename Container, typename Other> | |||
void extend(Container& c, const Other& o) { | |||
c.insert(c.end(), o.begin(), o.end()); | |||
} | |||
template <typename Container, typename Item> | |||
void extend(Container& c, std::initializer_list<Item> il) { | |||
c.insert(c.end(), il.begin(), il.end()); | |||
} | |||
} // namespace string_utils | |||
} // namespace dds | |||
@@ -0,0 +1,3 @@ | |||
#pragma once | |||
#include <spdlog/fmt/fmt.h> |
@@ -0,0 +1,66 @@ | |||
#include "./index.hpp" | |||
#include <libman/fmt.hpp> | |||
#include <libman/parse.hpp> | |||
using namespace lm; | |||
lm::index index::from_file(path_ref fpath) { | |||
const auto kvs = parse_file(fpath); | |||
// const auto type = kvs.find("Type"); | |||
// if (!type || type->value() != "Index") { | |||
// throw std::runtime_error( | |||
// fmt::format("Libman file has missing/incorrect 'Type' ({})", fpath.string())); | |||
// } | |||
index ret; | |||
std::optional<std::string> type; | |||
std::vector<std::string> package_lines; | |||
read(fmt::format("Reading libman index file '{}'", fpath.string()), | |||
kvs, | |||
read_required("Type", type), | |||
read_check_eq("Type", "Index"), | |||
read_accumulate("Package", package_lines)); | |||
for (const auto& pkg_line : package_lines) { | |||
auto items = dds::split(pkg_line, ";"); | |||
std::transform(items.begin(), items.end(), items.begin(), [](auto s) { return trim(s); }); | |||
if (items.size() != 2) { | |||
throw std::runtime_error( | |||
fmt::format("Invalid 'Package' field in index file ({}): 'Package: {}'", | |||
fpath.string(), | |||
pkg_line)); | |||
} | |||
auto pkg = package::from_file(fpath.parent_path() / items[1]); | |||
if (pkg.name != items[0]) { | |||
// throw std::runtime_error(fmt::format( | |||
// "Package file ({}) listed different name '{}' than the index file '{}'", | |||
// items[1], | |||
// pkg.name, | |||
// items[0])); | |||
} | |||
ret.packages.push_back(std::move(pkg)); | |||
} | |||
return ret; | |||
} | |||
index::library_index index::build_library_index() const { | |||
library_index ret; | |||
for (auto& pkg : packages) { | |||
for (auto& lib : pkg.libraries) { | |||
auto pair = std::pair(pkg.namespace_, lib.name); | |||
bool did_insert = ret.try_emplace(pair, lib).second; | |||
if (!did_insert) { | |||
throw std::runtime_error( | |||
fmt::format("Duplicate library '{}/{}' defined in the libman tree", | |||
pair.first, | |||
pair.second)); | |||
} | |||
} | |||
} | |||
return ret; | |||
} |
@@ -0,0 +1,21 @@ | |||
#pragma once | |||
#include <libman/package.hpp> | |||
#include <libman/util.hpp> | |||
#include <functional> | |||
#include <map> | |||
namespace lm { | |||
class index { | |||
public: | |||
std::vector<package> packages; | |||
static index from_file(path_ref); | |||
using library_index | |||
= std::map<std::pair<std::string, std::string>, std::reference_wrapper<const library>>; | |||
library_index build_library_index() const; | |||
}; | |||
} // namespace lm |
@@ -0,0 +1,37 @@ | |||
#include "./library.hpp" | |||
#include <libman/parse.hpp> | |||
#include <spdlog/spdlog.h> | |||
using namespace lm; | |||
library library::from_file(path_ref fpath) { | |||
auto pairs = parse_file(fpath); | |||
library ret; | |||
std::string _type_; | |||
read(fmt::format("Reading library manifest file '{}'", fpath.string()), | |||
pairs, | |||
read_required("Type", _type_), | |||
read_check_eq("Type", "Library"), | |||
read_required("Name", ret.name), | |||
read_opt("Path", ret.linkable_path), | |||
read_accumulate("Include-Path", ret.include_paths), | |||
read_accumulate("Preprocessor-Define", ret.preproc_defs), | |||
read_accumulate("Uses", ret.uses), | |||
read_accumulate("Special-Uses", ret.special_uses)); | |||
auto make_absolute = [&](path_ref p) { return fpath.parent_path() / p; }; | |||
std::transform(ret.include_paths.begin(), | |||
ret.include_paths.end(), | |||
ret.include_paths.begin(), | |||
make_absolute); | |||
if (ret.linkable_path) { | |||
ret.linkable_path = make_absolute(*ret.linkable_path); | |||
} | |||
return ret; | |||
} |
@@ -0,0 +1,22 @@ | |||
#pragma once | |||
#include <libman/util.hpp> | |||
#include <optional> | |||
#include <string> | |||
namespace lm { | |||
class library { | |||
public: | |||
std::string name; | |||
std::optional<fs::path> linkable_path; | |||
std::vector<fs::path> include_paths; | |||
std::vector<std::string> preproc_defs; | |||
std::vector<std::string> uses; | |||
std::vector<std::string> special_uses; | |||
static library from_file(path_ref); | |||
}; | |||
} // namespace lm |
@@ -0,0 +1,28 @@ | |||
#include "./package.hpp" | |||
#include <libman/fmt.hpp> | |||
#include <libman/parse.hpp> | |||
using namespace lm; | |||
package package::from_file(path_ref fpath) { | |||
package ret; | |||
auto pairs = parse_file(fpath); | |||
std::string _type_; | |||
std::vector<fs::path> libraries; | |||
read(fmt::format("Reading package file '{}'", fpath.string()), | |||
pairs, | |||
read_required("Type", _type_), | |||
read_check_eq("Type", "Package"), | |||
read_required("Name", ret.name), | |||
read_required("Namespace", ret.namespace_), | |||
read_accumulate("Requires", ret.requires), | |||
read_accumulate("Library", libraries)); | |||
for (path_ref lib_path : libraries) { | |||
ret.libraries.push_back(library::from_file(fpath.parent_path() / lib_path)); | |||
} | |||
return ret; | |||
} |
@@ -0,0 +1,19 @@ | |||
#pragma once | |||
#include <libman/library.hpp> | |||
#include <libman/util.hpp> | |||
namespace lm { | |||
class package { | |||
public: | |||
std::string name; | |||
std::string namespace_; | |||
std::vector<std::string> requires; | |||
std::vector<library> libraries; | |||
fs::path lmp_path; | |||
static package from_file(path_ref); | |||
}; | |||
} // namespace lm |
@@ -1,6 +1,6 @@ | |||
#include "./parse.hpp" | |||
#include <dds/util.hpp> | |||
#include <libman/util.hpp> | |||
#include <spdlog/fmt/fmt.h> | |||
@@ -15,25 +15,6 @@ using namespace lm; | |||
namespace { | |||
std::string_view sview(std::string_view::const_iterator beg, std::string_view::const_iterator end) { | |||
return std::string_view(&*beg, static_cast<std::size_t>(std::distance(beg, end))); | |||
} | |||
std::string_view trim(std::string_view s) { | |||
auto iter = s.begin(); | |||
auto end = s.end(); | |||
while (iter != end && std::isspace(*iter)) { | |||
++iter; | |||
} | |||
auto riter = s.rbegin(); | |||
auto rend = s.rend(); | |||
while (riter != rend && std::isspace(*riter)) { | |||
++riter; | |||
} | |||
auto new_end = riter.base(); | |||
return sview(iter, new_end); | |||
} | |||
void parse_line(std::vector<pair>& pairs, const std::string_view whole_line) { | |||
const auto line = trim(whole_line); | |||
if (line.empty() || line[0] == '#') { |
@@ -2,7 +2,9 @@ | |||
#include <cassert> | |||
#include <filesystem> | |||
#include <optional> | |||
#include <string> | |||
#include <tuple> | |||
#include <utility> | |||
#include <vector> | |||
@@ -19,6 +21,15 @@ public: | |||
auto& key() const noexcept { return _key; } | |||
auto& value() const noexcept { return _value; } | |||
template <std::size_t I> | |||
std::string_view get() const { | |||
if constexpr (I == 0) { | |||
return key(); | |||
} else if constexpr (I == 1) { | |||
return value(); | |||
} | |||
} | |||
}; | |||
class pair_iterator { | |||
@@ -112,4 +123,131 @@ inline void write_pairs(const std::filesystem::path& fpath, const pair_list& pai | |||
write_pairs(fpath, pairs.items()); | |||
} | |||
template <typename What> | |||
class read_required { | |||
std::string_view _key; | |||
What& _ref; | |||
bool _did_read = false; | |||
public: | |||
read_required(std::string_view key, What& ref) | |||
: _key(key) | |||
, _ref(ref) {} | |||
int read_one(std::string_view context, std::string_view key, std::string_view value) { | |||
if (key != _key) { | |||
return 0; | |||
} | |||
if (_did_read) { | |||
throw std::runtime_error(std::string(context) + ": Duplicated key '" + std::string(key) | |||
+ "' is not allowed"); | |||
} | |||
_did_read = true; | |||
_ref = What(value); | |||
return 1; | |||
} | |||
void validate(std::string_view context) const { | |||
if (!_did_read) { | |||
throw std::runtime_error(std::string(context) + ": Missing required key '" | |||
+ std::string(_key) + "'"); | |||
} | |||
} | |||
}; | |||
template <typename T> | |||
class read_opt { | |||
std::string_view _key; | |||
std::optional<T>& _ref; | |||
public: | |||
read_opt(std::string_view key, std::optional<T>& ref) | |||
: _key(key) | |||
, _ref(ref) {} | |||
int read_one(std::string_view context, std::string_view key, std::string_view value) { | |||
if (key != _key) { | |||
return 0; | |||
} | |||
if (_ref.has_value()) { | |||
throw std::runtime_error(std::string(context) + ": Duplicated key '" + std::string(key) | |||
+ "' is not allowed."); | |||
} | |||
_ref.emplace(value); | |||
return 1; | |||
} | |||
void validate(std::string_view) {} | |||
}; | |||
class read_check_eq { | |||
std::string_view _key; | |||
std::string_view _expect; | |||
public: | |||
read_check_eq(std::string_view key, std::string_view value) | |||
: _key(key) | |||
, _expect(value) {} | |||
int read_one(std::string_view context, std::string_view key, std::string_view value) const { | |||
if (key != _key) { | |||
return 0; | |||
} | |||
if (value != _expect) { | |||
throw std::runtime_error(std::string(context) + ": Expected key '" + std::string(key) | |||
+ "' to have value '" + std::string(_expect) + "' (Got '" | |||
+ std::string(value) + "')"); | |||
} | |||
return 1; | |||
} | |||
void validate(std::string_view) {} | |||
}; | |||
template <typename Container> | |||
class read_accumulate { | |||
std::string_view _key; | |||
Container& _items; | |||
public: | |||
read_accumulate(std::string_view key, Container& c) | |||
: _key(key) | |||
, _items(c) {} | |||
int read_one(std::string_view, std::string_view key, std::string_view value) const { | |||
if (key == _key) { | |||
_items.emplace_back(value); | |||
return 1; | |||
} | |||
return 0; | |||
} | |||
void validate(std::string_view) {} | |||
}; | |||
template <typename... Items> | |||
auto read(std::string_view context[[maybe_unused]], const pair_list& pairs, Items... is) { | |||
std::vector<pair> bad_pairs; | |||
for (auto [key, value] : pairs.items()) { | |||
auto nread = (is.read_one(context, key, value) + ... + 0); | |||
if (nread == 0) { | |||
bad_pairs.emplace_back(key, value); | |||
} | |||
} | |||
(is.validate(context), ...); | |||
return bad_pairs; | |||
} | |||
} // namespace lm | |||
namespace std { | |||
template <> | |||
struct tuple_size<lm::pair> : std::integral_constant<int, 2> {}; | |||
template <std::size_t N> | |||
struct tuple_element<N, lm::pair> { | |||
using type = std::string_view; | |||
}; | |||
} // namespace std |
@@ -0,0 +1,10 @@ | |||
#pragma once | |||
#include <dds/util.hpp> | |||
namespace lm { | |||
using namespace dds::file_utils; | |||
using namespace dds::string_utils; | |||
} // namespace lm |