| #include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
| #include <dds/source.hpp> | #include <dds/source.hpp> | ||||
| #include <dds/toolchain.hpp> | #include <dds/toolchain.hpp> | ||||
| #include <libman/index.hpp> | |||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| dds::compilation_set collect_compiles(const build_params& params, const library_manifest& man) { | dds::compilation_set collect_compiles(const build_params& params, const library_manifest& man) { | ||||
| source_list sources = source_file::collect_pf_sources(params.root); | source_list sources = source_file::collect_pf_sources(params.root); | ||||
| const bool need_compile_deps = params.build_tests || params.build_apps || params.build_deps; | 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"); | spdlog::critical("Dependency resolution isn't done yet"); | ||||
| } | } | ||||
| compilation_rules rules; | compilation_rules rules; | ||||
| rules.base_path() = params.root / "src"; | rules.base_path() = params.root / "src"; | ||||
| extend(rules.defs(), man.private_defines); | extend(rules.defs(), man.private_defines); | ||||
| extend(rules.defs(), dep_defines); | |||||
| extend(rules.include_dirs(), man.private_includes); | 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 / "src")); | ||||
| rules.include_dirs().push_back(fs::absolute(params.root / "include")); | rules.include_dirs().push_back(fs::absolute(params.root / "include")); | ||||
| struct build_params { | struct build_params { | ||||
| fs::path root; | fs::path root; | ||||
| fs::path out_root; | fs::path out_root; | ||||
| fs::path lm_index; | |||||
| dds::toolchain toolchain; | dds::toolchain toolchain; | ||||
| std::string export_name; | std::string export_name; | ||||
| bool do_export = false; | bool do_export = false; |
| #include <dds/build.hpp> | #include <dds/build.hpp> | ||||
| #include <libman/parse.hpp> | |||||
| #include <dds/logging.hpp> | #include <dds/logging.hpp> | ||||
| #include <dds/util.hpp> | #include <dds/util.hpp> | ||||
| #include <libman/parse.hpp> | |||||
| #include <args.hxx> | #include <args.hxx> | ||||
| args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | ||||
| args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | 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, | args::Flag enable_warnings{cmd, | ||||
| "enable_warnings", | "enable_warnings", | ||||
| "Enable compiler warnings", | "Enable compiler warnings", | ||||
| params.build_apps = build_apps.Get(); | params.build_apps = build_apps.Get(); | ||||
| params.enable_warnings = enable_warnings.Get(); | params.enable_warnings = enable_warnings.Get(); | ||||
| params.parallel_jobs = num_jobs.Get(); | params.parallel_jobs = num_jobs.Get(); | ||||
| params.lm_index = lm_index.Get(); | |||||
| dds::library_manifest man; | dds::library_manifest man; | ||||
| const auto man_filepath = params.root / "manifest.dds"; | const auto man_filepath = params.root / "manifest.dds"; | ||||
| if (exists(man_filepath)) { | if (exists(man_filepath)) { |
| namespace dds { | namespace dds { | ||||
| inline namespace file_utils { | |||||
| namespace fs = std::filesystem; | namespace fs = std::filesystem; | ||||
| using path_ref = const fs::path&; | using path_ref = const fs::path&; | ||||
| return contents; | 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) { | inline bool ends_with(std::string_view s, std::string_view key) { | ||||
| auto found = s.rfind(key); | auto found = s.rfind(key); | ||||
| return found != s.npos && found == s.size() - key.size(); | return found != s.npos && found == s.size() - key.size(); | ||||
| return strings; | 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 | } // namespace dds | ||||
| #pragma once | |||||
| #include <spdlog/fmt/fmt.h> |
| #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; | |||||
| } |
| #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 |
| #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; | |||||
| } |
| #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 |
| #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; | |||||
| } |
| #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 |
| #include "./parse.hpp" | #include "./parse.hpp" | ||||
| #include <dds/util.hpp> | |||||
| #include <libman/util.hpp> | |||||
| #include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
| namespace { | 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) { | void parse_line(std::vector<pair>& pairs, const std::string_view whole_line) { | ||||
| const auto line = trim(whole_line); | const auto line = trim(whole_line); | ||||
| if (line.empty() || line[0] == '#') { | if (line.empty() || line[0] == '#') { |
| #include <cassert> | #include <cassert> | ||||
| #include <filesystem> | #include <filesystem> | ||||
| #include <optional> | |||||
| #include <string> | #include <string> | ||||
| #include <tuple> | |||||
| #include <utility> | #include <utility> | ||||
| #include <vector> | #include <vector> | ||||
| auto& key() const noexcept { return _key; } | auto& key() const noexcept { return _key; } | ||||
| auto& value() const noexcept { return _value; } | 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 { | class pair_iterator { | ||||
| write_pairs(fpath, pairs.items()); | 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 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 |
| #pragma once | |||||
| #include <dds/util.hpp> | |||||
| namespace lm { | |||||
| using namespace dds::file_utils; | |||||
| using namespace dds::string_utils; | |||||
| } // namespace lm |