| @@ -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 | |||