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