{ | |||||
"version": 1, | |||||
"packages": { | |||||
"range-v3": { | |||||
"0.10.0": { | |||||
"git": { | |||||
"url": "https://github.com/ericniebler/range-v3.git", | |||||
"ref": "0.10.0", | |||||
"auto-lib": "Niebler/range-v3" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"spdlog": { | |||||
"1.4.2": { | |||||
"git": { | |||||
"url": "https://github.com/gabime/spdlog.git", | |||||
"ref": "v1.4.2", | |||||
"auto-lib": "spdlog/spdlog" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"nlohmann-json": { | |||||
"3.7.1": { | |||||
"git": { | |||||
"url": "https://github.com/vector-of-bool/json.git", | |||||
"ref": "dds/3.7.1" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"ms-wil": { | |||||
"2019.11.10": { | |||||
"git": { | |||||
"url": "https://github.com/vector-of-bool/wil.git", | |||||
"ref": "dds/2019.11.10" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"neo-buffer": { | |||||
"0.1.0": { | |||||
"git": { | |||||
"url": "https://github.com/vector-of-bool/neo-buffer.git", | |||||
"ref": "develop" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"neo-sqlite3": { | |||||
"0.2.2": { | |||||
"git": { | |||||
"url": "https://github.com/vector-of-bool/neo-sqlite3.git", | |||||
"ref": "0.2.2" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"semver": { | |||||
"0.2.1": { | |||||
"git": { | |||||
"url": "https://github.com/vector-of-bool/semver.git", | |||||
"ref": "0.2.1" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
}, | |||||
"pubgrub": { | |||||
"0.1.2": { | |||||
"git": { | |||||
"url": "https://github.com/vector-of-bool/pubgrub.git", | |||||
"ref": "0.1.2" | |||||
}, | |||||
"depends": {} | |||||
} | |||||
} | |||||
} | |||||
} |
Remote-Package: range-v3 0.9.1; git url=https://github.com/ericniebler/range-v3.git ref=0.9.1 auto=Niebler/range-v3 | |||||
Remote-Package: range-v3 0.10.0; git url=https://github.com/ericniebler/range-v3.git ref=0.10.0 auto=Niebler/range-v3 | Remote-Package: range-v3 0.10.0; git url=https://github.com/ericniebler/range-v3.git ref=0.10.0 auto=Niebler/range-v3 | ||||
Remote-Package: spdlog 1.4.2; git url=https://github.com/gabime/spdlog.git ref=v1.4.2 auto=spdlog/spdlog | Remote-Package: spdlog 1.4.2; git url=https://github.com/gabime/spdlog.git ref=v1.4.2 auto=spdlog/spdlog | ||||
#include <libman/index.hpp> | #include <libman/index.hpp> | ||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
#include <range/v3/algorithm/transform.hpp> | |||||
#include <range/v3/range/conversion.hpp> | |||||
#include <range/v3/view/transform.hpp> | |||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
#include <array> | #include <array> | ||||
#include <map> | |||||
#include <set> | |||||
#include <stdexcept> | #include <stdexcept> | ||||
using namespace dds; | using namespace dds; | ||||
} | } | ||||
} | } | ||||
void add_ureqs(usage_requirement_map& ureqs, | |||||
const sdist& sd, | |||||
const library& lib, | |||||
const library_plan& lib_plan, | |||||
build_env_ref env) { | |||||
lm::library& reqs = ureqs.add(sd.manifest.namespace_, lib.manifest().name); | |||||
reqs.include_paths.push_back(lib.public_include_dir()); | |||||
reqs.name = lib.manifest().name; | |||||
reqs.uses = lib.manifest().uses; | |||||
reqs.links = lib.manifest().links; | |||||
if (lib_plan.create_archive()) { | |||||
reqs.linkable_path = lib_plan.create_archive()->calc_archive_file_path(env); | |||||
} | |||||
// TODO: preprocessor definitions | |||||
} | |||||
using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | |||||
using sdist_names = std::set<std::string>; | |||||
void add_sdist_to_build(build_plan& plan, | |||||
const sdist& sd, | |||||
const sdist_index_type& sd_idx, | |||||
build_env_ref env, | |||||
usage_requirement_map& ureqs, | |||||
sdist_names& already_added) { | |||||
if (already_added.find(sd.manifest.pkg_id.name) != already_added.end()) { | |||||
// This one has already been added | |||||
return; | |||||
} | |||||
spdlog::debug("Adding dependent build: {}", sd.manifest.pkg_id.name); | |||||
// Ensure that ever dependency is loaded up first) | |||||
for (const auto& dep : sd.manifest.dependencies) { | |||||
auto other = sd_idx.find(dep.name); | |||||
assert(other != sd_idx.end() | |||||
&& "Failed to load a transitive dependency shortly after initializing them. What?"); | |||||
add_sdist_to_build(plan, other->second, sd_idx, env, ureqs, already_added); | |||||
} | |||||
// Record that we have been processed | |||||
already_added.insert(sd.manifest.pkg_id.name); | |||||
// Finally, actually add the package: | |||||
auto& pkg = plan.add_package(package_plan(sd.manifest.pkg_id.name, sd.manifest.namespace_)); | |||||
auto libs = collect_libraries(sd.path); | |||||
for (const auto& lib : libs) { | |||||
shared_compile_file_rules comp_rules = lib.base_compile_rules(); | |||||
library_build_params lib_params; | |||||
lib_params.out_subdir = fs::path("deps") / sd.manifest.pkg_id.name; | |||||
auto lib_plan = library_plan::create(lib, lib_params, ureqs); | |||||
// Create usage requirements for this libary. | |||||
add_ureqs(ureqs, sd, lib, lib_plan, env); | |||||
// Add it to the plan: | |||||
pkg.add_library(std::move(lib_plan)); | |||||
} | |||||
} | |||||
void add_deps_to_build(build_plan& plan, | |||||
usage_requirement_map& ureqs, | |||||
const build_params& params, | |||||
build_env_ref env) { | |||||
auto sd_idx = params.dep_sdists // | |||||
| ranges::views::transform([](const auto& sd) { | |||||
return std::pair(sd.manifest.pkg_id.name, std::cref(sd)); | |||||
}) // | |||||
| ranges::to<sdist_index_type>(); | |||||
sdist_names already_added; | |||||
for (const sdist& sd : params.dep_sdists) { | |||||
add_sdist_to_build(plan, sd, sd_idx, env, ureqs, already_added); | |||||
} | |||||
} | |||||
} // namespace | } // namespace | ||||
void dds::build(const build_params& params, const package_manifest& man) { | void dds::build(const build_params& params, const package_manifest& man) { | ||||
fs::create_directories(params.out_root); | |||||
auto db = database::open(params.out_root / ".dds.db"); | |||||
dds::build_env env{params.toolchain, params.out_root, db}; | |||||
// The build plan we will fill out: | |||||
build_plan plan; | |||||
// Collect libraries for the current project | |||||
auto libs = collect_libraries(params.root); | auto libs = collect_libraries(params.root); | ||||
if (!libs.size()) { | if (!libs.size()) { | ||||
spdlog::warn("Nothing found to build!"); | spdlog::warn("Nothing found to build!"); | ||||
return; | return; | ||||
} | } | ||||
build_plan plan; | |||||
auto& pkg = plan.add_package(package_plan(man.pk_id.name, man.namespace_)); | |||||
usage_requirement_map ureqs; | |||||
usage_requirement_map ureqs | |||||
= load_usage_requirements(params.root, params.out_root, params.lm_index); | |||||
if (params.existing_lm_index) { | |||||
ureqs = load_usage_requirements(params.root, params.out_root, *params.existing_lm_index); | |||||
} else { | |||||
add_deps_to_build(plan, ureqs, params, env); | |||||
} | |||||
// Initialize the build plan for this project. | |||||
auto& pkg = plan.add_package(package_plan(man.pkg_id.name, man.namespace_)); | |||||
// assert(false && "Not ready yet!"); | |||||
library_build_params lib_params; | library_build_params lib_params; | ||||
lib_params.build_tests = params.build_tests; | lib_params.build_tests = params.build_tests; | ||||
lib_params.build_apps = params.build_apps; | lib_params.build_apps = params.build_apps; | ||||
lib_params.enable_warnings = params.enable_warnings; | lib_params.enable_warnings = params.enable_warnings; | ||||
fs::create_directories(params.out_root); | |||||
auto db = database::open(params.out_root / ".dds.db"); | |||||
dds::build_env env{params.toolchain, params.out_root, db}; | |||||
if (man.test_driver) { | if (man.test_driver) { | ||||
prepare_test_driver(lib_params, params, man, env); | prepare_test_driver(lib_params, params, man, env); | ||||
} | } |
#pragma once | #pragma once | ||||
#include <dds/build/params.hpp> | #include <dds/build/params.hpp> | ||||
#include <dds/package_manifest.hpp> | |||||
#include <dds/package/manifest.hpp> | |||||
namespace dds { | namespace dds { | ||||
#include <dds/toolchain/toolchain.hpp> | #include <dds/toolchain/toolchain.hpp> | ||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <dds/sdist.hpp> | |||||
#include <optional> | |||||
namespace dds { | namespace dds { | ||||
struct build_params { | struct build_params { | ||||
fs::path root; | |||||
fs::path out_root; | |||||
fs::path lm_index; | |||||
dds::toolchain toolchain; | |||||
bool do_export = false; | |||||
bool build_tests = false; | |||||
bool enable_warnings = false; | |||||
bool build_apps = false; | |||||
bool build_deps = false; | |||||
bool generate_compdb = true; | |||||
int parallel_jobs = 0; | |||||
fs::path root; | |||||
fs::path out_root; | |||||
std::optional<fs::path> existing_lm_index; | |||||
dds::toolchain toolchain; | |||||
std::vector<sdist> dep_sdists; | |||||
bool do_export = false; | |||||
bool build_tests = false; | |||||
bool enable_warnings = false; | |||||
bool build_apps = false; | |||||
bool generate_compdb = true; | |||||
int parallel_jobs = 0; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
using namespace dds; | using namespace dds; | ||||
fs::path create_archive_plan::calc_archive_file_path(const build_env& env) const noexcept { | fs::path create_archive_plan::calc_archive_file_path(const build_env& env) const noexcept { | ||||
return env.output_root / fmt::format("{}{}{}", "lib", _name, env.toolchain.archive_suffix()); | |||||
return env.output_root / _subdir | |||||
/ fmt::format("{}{}{}", "lib", _name, env.toolchain.archive_suffix()); | |||||
} | } | ||||
void create_archive_plan::archive(const build_env& env) const { | void create_archive_plan::archive(const build_env& env) const { | ||||
} | } | ||||
spdlog::info("[{}] Archive: {}", _name, out_relpath); | spdlog::info("[{}] Archive: {}", _name, out_relpath); | ||||
fs::create_directories(ar.out_path.parent_path()); | |||||
auto&& [dur_ms, ar_res] = timed<std::chrono::milliseconds>([&] { return run_proc(ar_cmd); }); | auto&& [dur_ms, ar_res] = timed<std::chrono::milliseconds>([&] { return run_proc(ar_cmd); }); | ||||
spdlog::info("[{}] Archive: {} - {:n}ms", _name, out_relpath, dur_ms.count()); | spdlog::info("[{}] Archive: {} - {:n}ms", _name, out_relpath, dur_ms.count()); | ||||
} | } | ||||
bool should_compile(const compile_file_full& comp, build_env_ref env) { | bool should_compile(const compile_file_full& comp, build_env_ref env) { | ||||
if (!fs::exists(comp.object_file_path)) { | |||||
// The output file simply doesn't exist. We have to recompile, of course. | |||||
return true; | |||||
} | |||||
database& db = env.db; | database& db = env.db; | ||||
auto rb_info = get_rebuild_info(db, comp.object_file_path); | auto rb_info = get_rebuild_info(db, comp.object_file_path); | ||||
if (rb_info.previous_command.empty()) { | if (rb_info.previous_command.empty()) { | ||||
auto each_realized = // | auto each_realized = // | ||||
compiles // | compiles // | ||||
| views::transform([&](auto&& plan) { return realize_plan(plan, env); }) // | | views::transform([&](auto&& plan) { return realize_plan(plan, env); }) // | ||||
| views::filter([&](auto&& real) { return should_compile(real, env); }); | |||||
| views::filter([&](auto&& real) { return should_compile(real, env); }) // | |||||
| ranges::to_vector; | |||||
const auto total = compiles.size(); | |||||
const auto total = each_realized.size(); | |||||
const auto max_digits = fmt::format("{}", total).size(); | const auto max_digits = fmt::format("{}", total).size(); | ||||
compile_counter counter{{0}, total, max_digits}; | |||||
compile_counter counter{{1}, total, max_digits}; | |||||
std::vector<deps_info> all_new_deps; | std::vector<deps_info> all_new_deps; | ||||
std::mutex mut; | std::mutex mut; |
#include <dds/build/plan/archive.hpp> | #include <dds/build/plan/archive.hpp> | ||||
#include <dds/build/plan/exe.hpp> | #include <dds/build/plan/exe.hpp> | ||||
#include <dds/library.hpp> | |||||
#include <dds/library/library.hpp> | |||||
#include <dds/usage_reqs.hpp> | #include <dds/usage_reqs.hpp> | ||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
namespace dds { | namespace dds { | ||||
struct library_build_params { | |||||
fs::path out_subdir; | |||||
bool build_tests = false; | |||||
bool build_apps = false; | |||||
bool enable_warnings = false; | |||||
// Extras for compiling tests: | |||||
std::vector<fs::path> test_include_dirs; | |||||
std::vector<fs::path> test_link_files; | |||||
}; | |||||
class library_plan { | class library_plan { | ||||
std::string _name; | std::string _name; | ||||
fs::path _source_root; | fs::path _source_root; |
lm_name TEXT, | lm_name TEXT, | ||||
lm_namespace TEXT, | lm_namespace TEXT, | ||||
UNIQUE(name, version), | UNIQUE(name, version), | ||||
CONSTRAINT has_remote_info CHECK( | |||||
CONSTRAINT has_source_info CHECK( | |||||
( | ( | ||||
git_url NOT NULL | git_url NOT NULL | ||||
AND git_ref NOT NULL | AND git_ref NOT NULL | ||||
) | ) | ||||
= 1 | |||||
), | ), | ||||
CONSTRAINT valid_lm_info CHECK( | CONSTRAINT valid_lm_info CHECK( | ||||
( | ( | ||||
} // namespace | } // namespace | ||||
catalog catalog::open(const std::string& db_path) { | catalog catalog::open(const std::string& db_path) { | ||||
if (db_path != ":memory:") { | |||||
fs::create_directories(fs::weakly_canonical(db_path).parent_path()); | |||||
} | |||||
auto db = sqlite3::database::open(db_path); | auto db = sqlite3::database::open(db_path); | ||||
try { | try { | ||||
ensure_migrated(db); | ensure_migrated(db); | ||||
git_remote_listing{ | git_remote_listing{ | ||||
*git_url, | *git_url, | ||||
*git_ref, | *git_ref, | ||||
lm_name ? std::make_optional(lm::usage{*lm_name, *lm_namespace}) : std::nullopt, | |||||
lm_name ? std::make_optional(lm::usage{*lm_namespace, *lm_name}) : std::nullopt, | |||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
std::vector<package_id> catalog::solve_requirements(const std::vector<dependency>& deps) const { | |||||
return dds::solve(deps, | |||||
[&](std::string_view pkg_name) { return this->by_name(pkg_name); }, | |||||
[&](const package_id& pkg) { return this->dependencies_of(pkg); }); | |||||
} |
#include <dds/catalog/git.hpp> | #include <dds/catalog/git.hpp> | ||||
#include <dds/deps.hpp> | #include <dds/deps.hpp> | ||||
#include <dds/package_id.hpp> | |||||
#include <dds/package/id.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <neo/sqlite3/database.hpp> | #include <neo/sqlite3/database.hpp> | ||||
auto content = dds::slurp_file(json_path); | auto content = dds::slurp_file(json_path); | ||||
import_json_str(content); | import_json_str(content); | ||||
} | } | ||||
std::vector<package_id> solve_requirements(const std::vector<dependency>& deps) const; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
== dds::version_range_set{semver::version::parse("4.2.1"), | == dds::version_range_set{semver::version::parse("4.2.1"), | ||||
semver::version::parse("4.3.0")}); | semver::version::parse("4.3.0")}); | ||||
} | } | ||||
TEST_CASE_METHOD(catalog_test_case, "Simple solve") { | |||||
db.import_json_str(R"({ | |||||
"version": 1, | |||||
"packages": { | |||||
"foo": { | |||||
"1.2.3": { | |||||
"depends": { | |||||
"bar": "~4.2.1" | |||||
}, | |||||
"git": { | |||||
"url": "http://example.com", | |||||
"ref": "master" | |||||
} | |||||
} | |||||
}, | |||||
"bar": { | |||||
"4.2.3": { | |||||
"depends": {}, | |||||
"git": { | |||||
"url": "http://example.com", | |||||
"ref": "master" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
})"); | |||||
auto sln = db.solve_requirements({{"foo", | |||||
dds::version_range_set{semver::version::parse("1.0.0"), | |||||
semver::version::parse("2.0.0")}}}); | |||||
REQUIRE(sln.size() == 2); | |||||
CHECK(sln[0].name == "foo"); | |||||
CHECK(sln[0].version == semver::version::parse("1.2.3")); | |||||
CHECK(sln[1].name == "bar"); | |||||
CHECK(sln[1].version == semver::version::parse("4.2.3")); | |||||
} |
temporary_sdist dds::get_package_sdist(const package_info& pkg) { | temporary_sdist dds::get_package_sdist(const package_info& pkg) { | ||||
auto tsd = std::visit([&](auto&& remote) { return do_pull_sdist(pkg, remote); }, pkg.remote); | auto tsd = std::visit([&](auto&& remote) { return do_pull_sdist(pkg, remote); }, pkg.remote); | ||||
if (!(tsd.sdist.manifest.pk_id == pkg.ident)) { | |||||
if (!(tsd.sdist.manifest.pkg_id == pkg.ident)) { | |||||
throw std::runtime_error(fmt::format( | throw std::runtime_error(fmt::format( | ||||
"The package name@version in the generated sdist does not match the name listed in " | "The package name@version in the generated sdist does not match the name listed in " | ||||
"the remote listing file (expected '{}', but got '{}')", | "the remote listing file (expected '{}', but got '{}')", | ||||
pkg.ident.to_string(), | pkg.ident.to_string(), | ||||
tsd.sdist.manifest.pk_id.to_string())); | |||||
tsd.sdist.manifest.pkg_id.to_string())); | |||||
} | } | ||||
return tsd; | return tsd; | ||||
} | } |
return get.run(); | return get.run(); | ||||
} else if (add.cmd) { | } else if (add.cmd) { | ||||
return add.run(); | return add.run(); | ||||
} else if (list.cmd) { | |||||
return list.run(); | |||||
} else { | } else { | ||||
assert(false); | assert(false); | ||||
std::terminate(); | std::terminate(); | ||||
int run() { | int run() { | ||||
auto list_contents = [&](dds::repository repo) { | auto list_contents = [&](dds::repository repo) { | ||||
auto same_name = [](auto&& a, auto&& b) { | auto same_name = [](auto&& a, auto&& b) { | ||||
return a.manifest.pk_id.name == b.manifest.pk_id.name; | |||||
return a.manifest.pkg_id.name == b.manifest.pkg_id.name; | |||||
}; | }; | ||||
auto all = repo.iter_sdists(); | auto all = repo.iter_sdists(); | ||||
| ranges::views::transform(ranges::to_vector) // | | ranges::views::transform(ranges::to_vector) // | ||||
| ranges::views::transform([](auto&& grp) { | | ranges::views::transform([](auto&& grp) { | ||||
assert(grp.size() > 0); | assert(grp.size() > 0); | ||||
return std::pair(grp[0].manifest.pk_id.name, grp); | |||||
return std::pair(grp[0].manifest.pkg_id.name, grp); | |||||
}); | }); | ||||
for (const auto& [name, grp] : grp_by_name) { | for (const auto& [name, grp] : grp_by_name) { | ||||
spdlog::info("{}:", name); | spdlog::info("{}:", name); | ||||
for (const dds::sdist& sd : grp) { | for (const dds::sdist& sd : grp) { | ||||
spdlog::info(" - {}", sd.manifest.pk_id.version.to_string()); | |||||
spdlog::info(" - {}", sd.manifest.pkg_id.version.to_string()); | |||||
} | } | ||||
} | } | ||||
common_project_flags project{cmd}; | common_project_flags project{cmd}; | ||||
args::Flag build_tests{cmd, "build_tests", "Build and run the tests", {"tests", 'T'}}; | |||||
args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | |||||
args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | |||||
catalog_path_flag cat_path{cmd}; | |||||
repo_path_flag repo_path{cmd}; | |||||
args::Flag no_tests{cmd, "no-tests", "Do not build and run tests", {"no-tests"}}; | |||||
args::Flag no_apps{cmd, "no-apps", "Do not compile and link applications", {"no-apps"}}; | |||||
args::Flag no_warnings{cmd, "no-warings", "Disable build warnings", {"no-warnings"}}; | |||||
toolchain_flag tc_filepath{cmd}; | toolchain_flag tc_filepath{cmd}; | ||||
args::Flag enable_warnings{cmd, | |||||
"enable_warnings", | |||||
"Enable compiler warnings", | |||||
{"warnings", 'W'}}; | |||||
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()}; | |||||
path_flag | |||||
lm_index{cmd, | |||||
"lm_index", | |||||
"Path to an existing libman index from which to load deps (usually INDEX.lmi)", | |||||
{"lm-index", 'I'}}; | |||||
args::ValueFlag<int> num_jobs{cmd, | args::ValueFlag<int> num_jobs{cmd, | ||||
"jobs", | "jobs", | ||||
params.out_root = out.Get(); | params.out_root = out.Get(); | ||||
params.toolchain = tc_filepath.get_toolchain(); | params.toolchain = tc_filepath.get_toolchain(); | ||||
params.do_export = export_.Get(); | params.do_export = export_.Get(); | ||||
params.build_tests = build_tests.Get(); | |||||
params.build_apps = build_apps.Get(); | |||||
params.enable_warnings = enable_warnings.Get(); | |||||
params.build_tests = !no_tests.Get(); | |||||
params.build_apps = !no_apps.Get(); | |||||
params.enable_warnings = !no_warnings.Get(); | |||||
params.parallel_jobs = num_jobs.Get(); | params.parallel_jobs = num_jobs.Get(); | ||||
params.lm_index = lm_index.Get(); | |||||
dds::package_manifest man; | dds::package_manifest man; | ||||
const auto man_filepath = params.root / "package.dds"; | const auto man_filepath = params.root / "package.dds"; | ||||
if (exists(man_filepath)) { | if (exists(man_filepath)) { | ||||
man = dds::package_manifest::load_from_file(man_filepath); | man = dds::package_manifest::load_from_file(man_filepath); | ||||
} | } | ||||
dds::build(params, man); | |||||
return 0; | |||||
} | |||||
}; | |||||
/* | |||||
######## ######## ######## ###### | |||||
## ## ## ## ## ## ## | |||||
## ## ## ## ## ## | |||||
## ## ###### ######## ###### | |||||
## ## ## ## ## | |||||
## ## ## ## ## ## | |||||
######## ######## ## ###### | |||||
*/ | |||||
struct cli_deps { | |||||
cli_base& base; | |||||
args::Command cmd{base.cmd_group, "deps", "Obtain/inspect/build deps for the project"}; | |||||
common_flags _flags{cmd}; | |||||
common_project_flags project{cmd}; | |||||
args::Group deps_group{cmd, "Subcommands"}; | |||||
dds::package_manifest load_package_manifest() { | |||||
return dds::package_manifest::load_from_file(project.root.Get() / "package.dds"); | |||||
} | |||||
struct { | |||||
cli_deps& parent; | |||||
args::Command cmd{parent.deps_group, "ls", "List project dependencies"}; | |||||
common_flags _common{cmd}; | |||||
int run() { | |||||
const auto man = parent.load_package_manifest(); | |||||
for (const auto& dep : man.dependencies) { | |||||
std::cout << dep.name << " " << dep.versions << '\n'; | |||||
} | |||||
return 0; | |||||
} | |||||
} ls{*this}; | |||||
struct { | |||||
cli_deps& parent; | |||||
args::Command cmd{parent.deps_group, | |||||
"get", | |||||
"Ensure we have local copies of the project dependencies"}; | |||||
common_flags _common{cmd}; | |||||
repo_path_flag repo_where{cmd}; | |||||
catalog_path_flag catalog_path{cmd}; | |||||
int run() { | |||||
auto man = parent.load_package_manifest(); | |||||
auto catalog = catalog_path.open(); | |||||
bool failed = false; | |||||
auto solved_deps = catalog.solve_requirements(man.dependencies); | |||||
dds::repository::with_repository( // | |||||
repo_where.Get(), | |||||
if (lm_index) { | |||||
params.existing_lm_index = lm_index.Get(); | |||||
} else { | |||||
// Download and build dependencies | |||||
// Build the dependencies | |||||
auto cat = cat_path.open(); | |||||
params.dep_sdists = dds::repository::with_repository( // | |||||
this->repo_path.Get(), | |||||
dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | ||||
[&](dds::repository repo) { | [&](dds::repository repo) { | ||||
for (const dds::package_id& pk : solved_deps) { | |||||
// Download dependencies | |||||
auto deps = repo.solve(man.dependencies, cat); | |||||
for (const dds::package_id& pk : deps) { | |||||
auto exists = !!repo.find(pk); | auto exists = !!repo.find(pk); | ||||
if (!exists) { | if (!exists) { | ||||
spdlog::info("Pull remote: {}", pk.to_string()); | |||||
auto opt_pkg = catalog.get(pk); | |||||
if (opt_pkg) { | |||||
auto tsd = dds::get_package_sdist(*opt_pkg); | |||||
repo.add_sdist(tsd.sdist, dds::if_exists::ignore); | |||||
} else { | |||||
spdlog::error("No remote listing for {}", pk.to_string()); | |||||
failed = true; | |||||
} | |||||
} else { | |||||
spdlog::info("Okay: {}", pk.to_string()); | |||||
spdlog::info("Download dependency: {}", pk.to_string()); | |||||
auto opt_pkg = cat.get(pk); | |||||
assert(opt_pkg); | |||||
auto tsd = dds::get_package_sdist(*opt_pkg); | |||||
repo.add_sdist(tsd.sdist, dds::if_exists::throw_exc); | |||||
} | } | ||||
} | } | ||||
return deps // | |||||
| ranges::views::transform([&](auto& id) { | |||||
auto ptr = repo.find(id); | |||||
assert(ptr); | |||||
return *ptr; | |||||
}) | |||||
| ranges::to_vector; | |||||
}); | }); | ||||
if (failed) { | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
} get{*this}; | |||||
struct { | |||||
cli_deps& parent; | |||||
args::Command cmd{parent.deps_group, "build", "Build project dependencies"}; | |||||
common_flags _common{cmd}; | |||||
path_flag build_dir{cmd, | |||||
"build_dir", | |||||
"Directory where build results will be stored", | |||||
{"deps-build-dir"}, | |||||
dds::fs::current_path() / "_build/deps"}; | |||||
path_flag lmi_path{cmd, | |||||
"lmi_path", | |||||
"Destination for the INDEX.lmi file", | |||||
{"lmi-path"}, | |||||
dds::fs::current_path() / "_build/INDEX.lmi"}; | |||||
args::Flag no_lmi{cmd, | |||||
"no_lmi", | |||||
"If specified, will not generate an INDEX.lmi", | |||||
{"skip-lmi"}}; | |||||
repo_path_flag repo_where{cmd}; | |||||
toolchain_flag tc_filepath{cmd}; | |||||
int run() { | |||||
auto man = parent.load_package_manifest(); | |||||
auto deps = dds::repository::with_repository( // | |||||
repo_where.Get(), | |||||
dds::repo_flags::read, | |||||
[&](dds::repository repo) { | |||||
return repo.solve(man.dependencies); | |||||
}); | |||||
auto tc = tc_filepath.get_toolchain(); | |||||
auto bdir = build_dir.Get(); | |||||
dds::fs::create_directories(bdir); | |||||
auto db = dds::database::open(bdir / ".dds.db"); | |||||
dds::build_env env{std::move(tc), bdir, db}; | |||||
auto plan = dds::create_deps_build_plan(deps, env); | |||||
plan.compile_all(env, 6); | |||||
plan.archive_all(env, 6); | |||||
if (!no_lmi.Get()) { | |||||
write_libman_index(lmi_path.Get(), plan, env); | |||||
} | |||||
return 0; | |||||
} | } | ||||
} build{*this}; | |||||
int run() { | |||||
if (ls.cmd) { | |||||
return ls.run(); | |||||
} else if (build.cmd) { | |||||
return build.run(); | |||||
} else if (get.cmd) { | |||||
return get.run(); | |||||
} | |||||
std::terminate(); | |||||
dds::build(params, man); | |||||
return 0; | |||||
} | } | ||||
}; | }; | ||||
cli_build build{cli}; | cli_build build{cli}; | ||||
cli_sdist sdist{cli}; | cli_sdist sdist{cli}; | ||||
cli_repo repo{cli}; | cli_repo repo{cli}; | ||||
cli_deps deps{cli}; | |||||
cli_catalog catalog{cli}; | cli_catalog catalog{cli}; | ||||
try { | try { | ||||
parser.ParseCLI(argc, argv); | parser.ParseCLI(argc, argv); | ||||
return sdist.run(); | return sdist.run(); | ||||
} else if (repo.cmd) { | } else if (repo.cmd) { | ||||
return repo.run(); | return repo.run(); | ||||
} else if (deps.cmd) { | |||||
return deps.run(); | |||||
} else if (catalog.cmd) { | } else if (catalog.cmd) { | ||||
return catalog.run(); | return catalog.run(); | ||||
} else { | } else { |
str)); | str)); | ||||
} | } | ||||
} | } | ||||
using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | |||||
using sdist_names = std::set<std::string>; | |||||
namespace { | |||||
void resolve_ureqs_(shared_compile_file_rules& rules, | |||||
const package_manifest& man, | |||||
const sdist_index_type& sd_idx) { | |||||
for (const dependency& dep : man.dependencies) { | |||||
auto found = sd_idx.find(dep.name); | |||||
if (found == sd_idx.end()) { | |||||
throw std::runtime_error( | |||||
fmt::format("Unable to resolve dependency '{}' (required by '{}')", | |||||
dep.name, | |||||
man.pk_id.to_string())); | |||||
} | |||||
resolve_ureqs_(rules, found->second.get().manifest, sd_idx); | |||||
auto lib_src = found->second.get().path / "src"; | |||||
auto lib_include = found->second.get().path / "include"; | |||||
if (fs::exists(lib_include)) { | |||||
rules.include_dirs().push_back(lib_include); | |||||
} else { | |||||
rules.include_dirs().push_back(lib_src); | |||||
} | |||||
} | |||||
} | |||||
void resolve_ureqs(shared_compile_file_rules rules, | |||||
const sdist& sd, | |||||
const library& lib, | |||||
const library_plan& lib_plan, | |||||
build_env_ref env, | |||||
usage_requirement_map& ureqs) { | |||||
// Add the transitive requirements for this library to our compile rules. | |||||
for (auto&& use : lib.manifest().uses) { | |||||
ureqs.apply(rules, use.namespace_, use.name); | |||||
} | |||||
// Create usage requirements for this libary. | |||||
lm::library& reqs = ureqs.add(sd.manifest.namespace_, lib.manifest().name); | |||||
reqs.include_paths.push_back(lib.public_include_dir()); | |||||
reqs.name = lib.manifest().name; | |||||
reqs.uses = lib.manifest().uses; | |||||
reqs.links = lib.manifest().links; | |||||
if (lib_plan.create_archive()) { | |||||
reqs.linkable_path = lib_plan.create_archive()->calc_archive_file_path(env); | |||||
} | |||||
// TODO: preprocessor definitions | |||||
} | |||||
void add_sdist_to_dep_plan(build_plan& plan, | |||||
const sdist& sd, | |||||
build_env_ref env, | |||||
const sdist_index_type& sd_idx, | |||||
usage_requirement_map& ureqs, | |||||
sdist_names& already_added) { | |||||
if (already_added.find(sd.manifest.pk_id.name) != already_added.end()) { | |||||
// We've already loaded this package into the plan. | |||||
return; | |||||
} | |||||
spdlog::debug("Add to plan: {}", sd.manifest.pk_id.name); | |||||
// First, load every dependency | |||||
for (const auto& dep : sd.manifest.dependencies) { | |||||
auto other = sd_idx.find(dep.name); | |||||
assert(other != sd_idx.end() | |||||
&& "Failed to load a transitive dependency shortly after initializing them. What?"); | |||||
add_sdist_to_dep_plan(plan, other->second, env, sd_idx, ureqs, already_added); | |||||
} | |||||
// Record that we have been processed: | |||||
already_added.insert(sd.manifest.pk_id.name); | |||||
// Add the package: | |||||
auto& pkg = plan.add_package(package_plan(sd.manifest.pk_id.name, sd.manifest.namespace_)); | |||||
auto libs = collect_libraries(sd.path); | |||||
for (const auto& lib : libs) { | |||||
shared_compile_file_rules comp_rules = lib.base_compile_rules(); | |||||
library_build_params params; | |||||
auto lib_plan = library_plan::create(lib, params, ureqs); | |||||
resolve_ureqs(comp_rules, sd, lib, lib_plan, env, ureqs); | |||||
pkg.add_library(std::move(lib_plan)); | |||||
} | |||||
} | |||||
} // namespace | |||||
build_plan dds::create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env) { | |||||
auto sd_idx = deps // | |||||
| ranges::views::transform([](const auto& sd) { | |||||
return std::pair(sd.manifest.pk_id.name, std::cref(sd)); | |||||
}) // | |||||
| ranges::to<sdist_index_type>(); | |||||
build_plan plan; | |||||
usage_requirement_map ureqs; | |||||
sdist_names already_added; | |||||
for (const sdist& sd : deps) { | |||||
spdlog::info("Recording dependency: {}", sd.manifest.pk_id.name); | |||||
add_sdist_to_dep_plan(plan, sd, env, sd_idx, ureqs, already_added); | |||||
} | |||||
return plan; | |||||
} | |||||
namespace { | |||||
fs::path generate_lml(const library_plan& lib, path_ref libdir, const build_env& env) { | |||||
auto fname = lib.name() + ".lml"; | |||||
auto lml_path = libdir / fname; | |||||
std::vector<lm::pair> kvs; | |||||
kvs.emplace_back("Type", "Library"); | |||||
kvs.emplace_back("Name", lib.name()); | |||||
if (lib.create_archive()) { | |||||
kvs.emplace_back("Path", | |||||
fs::relative(lib.create_archive()->calc_archive_file_path(env), | |||||
lml_path.parent_path()) | |||||
.string()); | |||||
} | |||||
auto pub_inc_dir = lib.source_root() / "include"; | |||||
auto src_dir = lib.source_root() / "src"; | |||||
if (!fs::exists(pub_inc_dir)) { | |||||
pub_inc_dir = src_dir; | |||||
} | |||||
kvs.emplace_back("Include-Path", pub_inc_dir.string()); | |||||
// TODO: Uses, Preprocessor-Define, and Special-Uses | |||||
fs::create_directories(lml_path.parent_path()); | |||||
lm::write_pairs(lml_path, kvs); | |||||
return lml_path; | |||||
} | |||||
fs::path generate_lmp(const package_plan& pkg, path_ref basedir, const build_env& env) { | |||||
auto fname = pkg.name() + ".lmp"; | |||||
auto lmp_path = basedir / fname; | |||||
std::vector<lm::pair> kvs; | |||||
kvs.emplace_back("Type", "Package"); | |||||
kvs.emplace_back("Name", pkg.name()); | |||||
kvs.emplace_back("Namespace", pkg.namespace_()); | |||||
for (auto&& lib : pkg.libraries()) { | |||||
auto lml = generate_lml(lib, basedir / pkg.name(), env); | |||||
kvs.emplace_back("Library", fs::relative(lml, lmp_path.parent_path()).string()); | |||||
} | |||||
// TODO: `Requires` for transitive package imports | |||||
fs::create_directories(lmp_path.parent_path()); | |||||
lm::write_pairs(lmp_path, kvs); | |||||
return lmp_path; | |||||
} | |||||
} // namespace | |||||
void dds::write_libman_index(path_ref out_filepath, const build_plan& plan, const build_env& env) { | |||||
fs::create_directories(out_filepath.parent_path()); | |||||
auto lm_items_dir = out_filepath.parent_path() / "_libman"; | |||||
std::vector<lm::pair> kvs; | |||||
kvs.emplace_back("Type", "Index"); | |||||
for (const package_plan& pkg : plan.packages()) { | |||||
auto pkg_lmp = generate_lmp(pkg, lm_items_dir, env); | |||||
kvs.emplace_back("Package", | |||||
fmt::format("{}; {}", | |||||
pkg.name(), | |||||
fs::relative(pkg_lmp, out_filepath.parent_path()).string())); | |||||
} | |||||
lm::write_pairs(out_filepath, kvs); | |||||
} |
namespace dds { | namespace dds { | ||||
struct sdist; | |||||
class repository; | |||||
enum class version_strength { | |||||
exact, | |||||
patch, | |||||
minor, | |||||
major, | |||||
}; | |||||
using version_range_set = pubgrub::interval_set<semver::version>; | using version_range_set = pubgrub::interval_set<semver::version>; | ||||
struct dependency { | struct dependency { | ||||
static dependency parse_depends_string(std::string_view str); | static dependency parse_depends_string(std::string_view str); | ||||
}; | }; | ||||
build_plan create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env); | |||||
void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); | |||||
} // namespace dds | } // namespace dds |
#pragma once | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/build/source_dir.hpp> | |||||
#include <dds/library_manifest.hpp> | |||||
#include <dds/source.hpp> | |||||
#include <string> | |||||
namespace dds { | |||||
struct library_ident { | |||||
std::string namespace_; | |||||
std::string name; | |||||
}; | |||||
class library { | |||||
fs::path _path; | |||||
source_list _sources; | |||||
library_manifest _man; | |||||
library(path_ref dir, source_list&& src, library_manifest&& man) | |||||
: _path(dir) | |||||
, _sources(std::move(src)) | |||||
, _man(std::move(man)) {} | |||||
public: | |||||
static library from_directory(path_ref); | |||||
auto& manifest() const noexcept { return _man; } | |||||
source_directory src_dir() const noexcept { return source_directory{path() / "src"}; } | |||||
source_directory include_dir() const noexcept { return source_directory{path() / "include"}; } | |||||
path_ref path() const noexcept { return _path; } | |||||
fs::path public_include_dir() const noexcept; | |||||
fs::path private_include_dir() const noexcept; | |||||
const source_list& all_sources() const noexcept { return _sources; } | |||||
shared_compile_file_rules base_compile_rules() const noexcept; | |||||
}; | |||||
struct library_build_params { | |||||
fs::path out_subdir; | |||||
bool build_tests = false; | |||||
bool build_apps = false; | |||||
bool enable_warnings = false; | |||||
// Extras for compiling tests: | |||||
std::vector<fs::path> test_include_dirs; | |||||
std::vector<fs::path> test_link_files; | |||||
}; | |||||
std::vector<library> collect_libraries(path_ref where); | |||||
} // namespace dds |
#include <dds/library.hpp> | |||||
#include <dds/library/library.hpp> | |||||
#include <dds/build/plan/compile_file.hpp> | #include <dds/build/plan/compile_file.hpp> | ||||
#include <dds/build/source_dir.hpp> | #include <dds/build/source_dir.hpp> |
#pragma once | |||||
#include <dds/build/plan/compile_file.hpp> | |||||
#include <dds/build/source_dir.hpp> | |||||
#include <dds/library/manifest.hpp> | |||||
#include <dds/source.hpp> | |||||
#include <string> | |||||
namespace dds { | |||||
/** | |||||
* Represents a library that exists on the filesystem | |||||
*/ | |||||
class library { | |||||
// The path containing the source directories for this library | |||||
fs::path _path; | |||||
// The sources that are part of this library | |||||
source_list _sources; | |||||
// The library manifest associated with this library (may be generated) | |||||
library_manifest _man; | |||||
// Private constructor. Use named constructor `from_directory`, which will build | |||||
// the construct arguments approperiately | |||||
library(path_ref dir, source_list&& src, library_manifest&& man) | |||||
: _path(dir) | |||||
, _sources(std::move(src)) | |||||
, _man(std::move(man)) {} | |||||
public: | |||||
/** | |||||
* Create a library object that refers to the library contained at the given | |||||
* directory path. This will load the sources and manifest properly and | |||||
* return the resulting library object. | |||||
*/ | |||||
static library from_directory(path_ref); | |||||
/** | |||||
* Obtain the manifest for this library | |||||
*/ | |||||
const library_manifest& manifest() const noexcept { return _man; } | |||||
/** | |||||
* The `src/` directory for this library. | |||||
*/ | |||||
source_directory src_dir() const noexcept { return source_directory{path() / "src"}; } | |||||
/** | |||||
* The `include/` directory for this library | |||||
*/ | |||||
source_directory include_dir() const noexcept { return source_directory{path() / "include"}; } | |||||
/** | |||||
* The root path for this library (parent of `src/` and `include/`, if present) | |||||
*/ | |||||
path_ref path() const noexcept { return _path; } | |||||
/** | |||||
* The directory that should be considered the "public" include directory. | |||||
* Dependees that want to use this library should add this to their #include | |||||
* search path. | |||||
*/ | |||||
fs::path public_include_dir() const noexcept; | |||||
/** | |||||
* The directory that contains the "private" heders for this libary. This | |||||
* directory should be added to the search path of the library when it is | |||||
* being built, but NOT to the search path of the dependees. | |||||
*/ | |||||
fs::path private_include_dir() const noexcept; | |||||
/** | |||||
* Get the sources that this library contains | |||||
*/ | |||||
const source_list& all_sources() const noexcept { return _sources; } | |||||
/** | |||||
* Generate a compile rules object that should be used when compiling | |||||
* this library. | |||||
*/ | |||||
shared_compile_file_rules base_compile_rules() const noexcept; | |||||
}; | |||||
/** | |||||
* Given the root source directory of a project/package/sdist, collect all of | |||||
* the libraries that it contains. There may be a library directly in `where`, | |||||
* but there might also be libraries in `where/libs`. This function will find | |||||
* them all. | |||||
*/ | |||||
std::vector<library> collect_libraries(path_ref where); | |||||
} // namespace dds |
#include "./library_manifest.hpp" | |||||
#include "./manifest.hpp" | |||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> |
#pragma once | |||||
#include <dds/util/fs.hpp> | |||||
#include <libman/library.hpp> | |||||
#include <vector> | |||||
namespace dds { | |||||
/** | |||||
* Represents the contents of a `library.dds`. This is somewhat a stripped-down | |||||
* version of lm::library, to only represent exactly the parts that we want to | |||||
* offer via `library.dds`. | |||||
*/ | |||||
struct library_manifest { | |||||
/// The name of the library | |||||
std::string name; | |||||
/// The libraries that the owning library "uses" | |||||
std::vector<lm::usage> uses; | |||||
/// The libraries that the owning library must be linked with | |||||
std::vector<lm::usage> links; | |||||
/** | |||||
* Load the library manifest from an existing file | |||||
*/ | |||||
static library_manifest load_from_file(const fs::path&); | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <dds/util/fs.hpp> | |||||
#include <libman/library.hpp> | |||||
#include <vector> | |||||
namespace dds { | |||||
struct library_manifest { | |||||
std::string name; | |||||
std::vector<lm::usage> uses; | |||||
std::vector<lm::usage> links; | |||||
static library_manifest load_from_file(const fs::path&); | |||||
}; | |||||
} // namespace dds |
#include <dds/package_id.hpp> | |||||
#include <dds/package/id.hpp> | |||||
#include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
#pragma once | |||||
#include <semver/version.hpp> | |||||
#include <string> | |||||
#include <string_view> | |||||
#include <tuple> | |||||
namespace dds { | |||||
/** | |||||
* Represents a unique package ID. We store this as a simple name-version pair. | |||||
* | |||||
* In text, this is represented with an `@` symbol in between. The `parse` and | |||||
* `to_string` method convert between this textual representation, and supports | |||||
* full round-trips. | |||||
*/ | |||||
struct package_id { | |||||
/// The name of the package | |||||
std::string name; | |||||
/// The version of the package | |||||
semver::version version; | |||||
/// Default-initialize a package_id with a blank name and a default version | |||||
package_id() = default; | |||||
/// Construct a package ID from a name-version pair | |||||
package_id(std::string_view s, semver::version v); | |||||
/** | |||||
* Parse the given string into a package_id object. | |||||
*/ | |||||
static package_id parse(std::string_view); | |||||
/** | |||||
* Convert this package_id into its corresponding textual representation. | |||||
* The returned string can be passed back to `parse()` for a round-trip | |||||
*/ | |||||
std::string to_string() const noexcept; | |||||
friend bool operator<(const package_id& lhs, const package_id& rhs) noexcept { | |||||
return std::tie(lhs.name, lhs.version) < std::tie(rhs.name, rhs.version); | |||||
} | |||||
friend bool operator==(const package_id& lhs, const package_id& rhs) noexcept { | |||||
return std::tie(lhs.name, lhs.version) == std::tie(rhs.name, rhs.version); | |||||
} | |||||
}; | |||||
} // namespace dds |
#include <dds/package_id.hpp> | |||||
#include <dds/package/id.hpp> | |||||
#include <catch2/catch.hpp> | #include <catch2/catch.hpp> | ||||
#include "./package_manifest.hpp" | |||||
#include "./manifest.hpp" | |||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
std::optional<std::string> opt_test_driver; | std::optional<std::string> opt_test_driver; | ||||
lm::read(fmt::format("Reading package manifest '{}'", fpath.string()), | lm::read(fmt::format("Reading package manifest '{}'", fpath.string()), | ||||
kvs, | kvs, | ||||
lm::read_required("Name", ret.pk_id.name), | |||||
lm::read_required("Name", ret.pkg_id.name), | |||||
lm::read_opt("Namespace", ret.namespace_), | lm::read_opt("Namespace", ret.namespace_), | ||||
lm::read_required("Version", version_str), | lm::read_required("Version", version_str), | ||||
lm::read_accumulate("Depends", depends_strs), | lm::read_accumulate("Depends", depends_strs), | ||||
lm::read_opt("Test-Driver", opt_test_driver), | lm::read_opt("Test-Driver", opt_test_driver), | ||||
lm::reject_unknown()); | lm::reject_unknown()); | ||||
if (ret.pk_id.name.empty()) { | |||||
if (ret.pkg_id.name.empty()) { | |||||
throw std::runtime_error( | throw std::runtime_error( | ||||
fmt::format("'Name' field in [{}] may not be an empty string", fpath.string())); | fmt::format("'Name' field in [{}] may not be an empty string", fpath.string())); | ||||
} | } | ||||
} | } | ||||
if (ret.namespace_.empty()) { | if (ret.namespace_.empty()) { | ||||
ret.namespace_ = ret.pk_id.name; | |||||
ret.namespace_ = ret.pkg_id.name; | |||||
} | } | ||||
ret.pk_id.version = semver::version::parse(version_str); | |||||
ret.pkg_id.version = semver::version::parse(version_str); | |||||
ret.dependencies = depends_strs // | ret.dependencies = depends_strs // | ||||
| ranges::views::transform(dependency::parse_depends_string) // | | ranges::views::transform(dependency::parse_depends_string) // |
#pragma once | |||||
#include <dds/deps.hpp> | |||||
#include <dds/package/id.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
#include <optional> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace dds { | |||||
/** | |||||
* Possible values for Test-Driver in a package.dds | |||||
*/ | |||||
enum class test_lib { | |||||
catch_, | |||||
catch_main, | |||||
}; | |||||
/** | |||||
* Struct representing the contents of a `packaeg.dds` file. | |||||
*/ | |||||
struct package_manifest { | |||||
/// The package ID, as determined by `Name` and `Version` together | |||||
package_id pkg_id; | |||||
/// The declared `Namespace` of the package. This directly corresponds with the libman Namespace | |||||
std::string namespace_; | |||||
/// The `Test-Driver` that this package declares, or `nullopt` if absent. | |||||
std::optional<test_lib> test_driver; | |||||
/// The dependencies declared with the `Depends` fields, if any. | |||||
std::vector<dependency> dependencies; | |||||
/** | |||||
* Load a package manifest from a file on disk. | |||||
*/ | |||||
static package_manifest load_from_file(path_ref); | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <semver/version.hpp> | |||||
#include <string> | |||||
#include <string_view> | |||||
#include <tuple> | |||||
namespace dds { | |||||
struct package_id { | |||||
std::string name; | |||||
semver::version version; | |||||
package_id() = default; | |||||
package_id(std::string_view s, semver::version v); | |||||
static package_id parse(std::string_view); | |||||
std::string to_string() const noexcept; | |||||
auto tie() const noexcept { return std::tie(name, version); } | |||||
friend bool operator<(const package_id& lhs, const package_id& rhs) noexcept { | |||||
return lhs.tie() < rhs.tie(); | |||||
} | |||||
friend bool operator==(const package_id& lhs, const package_id& rhs) noexcept { | |||||
return lhs.tie() == rhs.tie(); | |||||
} | |||||
}; | |||||
} // namespace dds |
#pragma once | |||||
#include <dds/deps.hpp> | |||||
#include <dds/package_id.hpp> | |||||
#include <dds/util/fs.hpp> | |||||
#include <semver/version.hpp> | |||||
#include <optional> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace dds { | |||||
enum class test_lib { | |||||
catch_, | |||||
catch_main, | |||||
}; | |||||
struct package_manifest { | |||||
package_id pk_id; | |||||
std::string namespace_; | |||||
std::optional<test_lib> test_driver; | |||||
std::vector<dependency> dependencies; | |||||
static package_manifest load_from_file(path_ref); | |||||
}; | |||||
} // namespace dds |
#include "./repo.hpp" | #include "./repo.hpp" | ||||
#include <dds/catalog/catalog.hpp> | |||||
#include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
#include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
#include <range/v3/action/sort.hpp> | #include <range/v3/action/sort.hpp> | ||||
#include <range/v3/action/unique.hpp> | |||||
#include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
#include <range/v3/view/concat.hpp> | |||||
#include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
"repository, we'll hard-exit immediately."); | "repository, we'll hard-exit immediately."); | ||||
std::terminate(); | std::terminate(); | ||||
} | } | ||||
auto sd_dest = _root / sd.manifest.pk_id.to_string(); | |||||
auto sd_dest = _root / sd.manifest.pkg_id.to_string(); | |||||
if (fs::exists(sd_dest)) { | if (fs::exists(sd_dest)) { | ||||
auto msg = fmt::format("Source distribution '{}' is already available in the local repo", | auto msg = fmt::format("Source distribution '{}' is already available in the local repo", | ||||
sd.path.string()); | sd.path.string()); | ||||
fs::remove_all(sd_dest); | fs::remove_all(sd_dest); | ||||
} | } | ||||
fs::rename(tmp_copy, sd_dest); | fs::rename(tmp_copy, sd_dest); | ||||
spdlog::info("Source distribution '{}' successfully exported", sd.manifest.pk_id.to_string()); | |||||
_sdists.insert(sdist::from_directory(sd_dest)); | |||||
spdlog::info("Source distribution '{}' successfully exported", sd.manifest.pkg_id.to_string()); | |||||
} | } | ||||
const sdist* repository::find(const package_id& pkg) const noexcept { | const sdist* repository::find(const package_id& pkg) const noexcept { | ||||
return &*found; | return &*found; | ||||
} | } | ||||
std::vector<sdist> repository::solve(const std::vector<dependency>& deps) const { | |||||
auto ids = dds::solve(deps, | |||||
[&](std::string_view name) -> std::vector<package_id> { | |||||
auto items = ranges::views::all(_sdists) // | |||||
| ranges::views::filter([&](const sdist& sd) { | |||||
return sd.manifest.pk_id.name == name; | |||||
}) | |||||
| ranges::views::transform( | |||||
[](const sdist& sd) { return sd.manifest.pk_id; }) | |||||
| ranges::to_vector; | |||||
ranges::sort(items, std::less<>{}); | |||||
return items; | |||||
}, | |||||
[&](const package_id& pkg_id) { | |||||
auto found = find(pkg_id); | |||||
assert(found); | |||||
std::vector<package_id> repository::solve(const std::vector<dependency>& deps, | |||||
const catalog& ctlg) const { | |||||
return dds::solve(deps, | |||||
[&](std::string_view name) -> std::vector<package_id> { | |||||
auto mine = ranges::views::all(_sdists) // | |||||
| ranges::views::filter([&](const sdist& sd) { | |||||
return sd.manifest.pkg_id.name == name; | |||||
}) | |||||
| ranges::views::transform( | |||||
[](const sdist& sd) { return sd.manifest.pkg_id; }); | |||||
auto avail = ctlg.by_name(name); | |||||
auto all = ranges::views::concat(mine, avail) | ranges::to_vector; | |||||
ranges::sort(all, std::less<>{}); | |||||
ranges::unique(all, std::less<>{}); | |||||
return all; | |||||
}, | |||||
[&](const package_id& pkg_id) { | |||||
auto found = find(pkg_id); | |||||
if (found) { | |||||
return found->manifest.dependencies; | return found->manifest.dependencies; | ||||
}); | |||||
return ids // | |||||
| ranges::views::transform([&](const package_id& pk_id) { | |||||
auto found = find(pk_id); | |||||
assert(found); | |||||
return *found; | |||||
}) // | |||||
| ranges::to_vector; | |||||
} | |||||
return ctlg.dependencies_of(pkg_id); | |||||
}); | |||||
} | } |
#include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
#include <dds/util/flock.hpp> | #include <dds/util/flock.hpp> | ||||
#include <dds/catalog/catalog.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <functional> | #include <functional> | ||||
return r; | return r; | ||||
} | } | ||||
std::vector<sdist> solve(const std::vector<dependency>& deps) const; | |||||
std::vector<package_id> solve(const std::vector<dependency>& deps, const catalog&) const; | |||||
}; | }; | ||||
} // namespace dds | } // namespace dds |
sdist_export_file(out, params.project_dir, man_path); | sdist_export_file(out, params.project_dir, man_path); | ||||
auto pkg_man = package_manifest::load_from_file(man_path); | auto pkg_man = package_manifest::load_from_file(man_path); | ||||
spdlog::info("Generated export as {}", pkg_man.pk_id.to_string()); | |||||
spdlog::info("Generated export as {}", pkg_man.pkg_id.to_string()); | |||||
return sdist::from_directory(out); | return sdist::from_directory(out); | ||||
} | } |
#include <tuple> | #include <tuple> | ||||
#include <dds/package_manifest.hpp> | |||||
#include <dds/package/manifest.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
namespace dds { | namespace dds { | ||||
inline constexpr struct sdist_compare_t { | inline constexpr struct sdist_compare_t { | ||||
bool operator()(const sdist& lhs, const sdist& rhs) const { | bool operator()(const sdist& lhs, const sdist& rhs) const { | ||||
return lhs.manifest.pk_id < rhs.manifest.pk_id; | |||||
return lhs.manifest.pkg_id < rhs.manifest.pkg_id; | |||||
} | } | ||||
bool operator()(const sdist& lhs, const package_id& rhs) const { | bool operator()(const sdist& lhs, const package_id& rhs) const { | ||||
return lhs.manifest.pk_id < rhs; | |||||
return lhs.manifest.pkg_id < rhs; | |||||
} | } | ||||
bool operator()(const package_id& lhs, const sdist& rhs) const { | bool operator()(const package_id& lhs, const sdist& rhs) const { | ||||
return lhs < rhs.manifest.pk_id; | |||||
return lhs < rhs.manifest.pkg_id; | |||||
} | } | ||||
using is_transparent = int; | using is_transparent = int; | ||||
} sdist_compare; | } sdist_compare; |
#pragma once | #pragma once | ||||
#include <dds/deps.hpp> | #include <dds/deps.hpp> | ||||
#include <dds/package_id.hpp> | |||||
#include <dds/package/id.hpp> | |||||
#include <functional> | #include <functional> | ||||
def project_dir_arg(self) -> str: | def project_dir_arg(self) -> str: | ||||
return f'--project-dir={self.source_root}' | return f'--project-dir={self.source_root}' | ||||
def deps_ls(self) -> subprocess.CompletedProcess: | |||||
return self.run(['deps', 'ls']) | |||||
def deps_get(self) -> subprocess.CompletedProcess: | |||||
return self.run([ | |||||
'deps', | |||||
'get', | |||||
f'--catalog={self.catalog_path}', | |||||
self.repo_dir_arg, | |||||
]) | |||||
def deps_build(self, *, | def deps_build(self, *, | ||||
toolchain: str = None) -> subprocess.CompletedProcess: | toolchain: str = None) -> subprocess.CompletedProcess: | ||||
return self.run([ | return self.run([ | ||||
return self.run([ | return self.run([ | ||||
'build', | 'build', | ||||
f'--out={self.build_dir}', | f'--out={self.build_dir}', | ||||
['--tests'] if tests else [], | |||||
['--apps'] if apps else [], | |||||
['--warnings'] if warnings else [], | |||||
['--export'] if export else [], | |||||
f'--toolchain={toolchain or self.default_builtin_toolchain}', | f'--toolchain={toolchain or self.default_builtin_toolchain}', | ||||
f'--lm-index={self.lmi_path}', | |||||
f'--catalog={self.catalog_path}', | |||||
f'--repo-dir={self.repo_dir}', | |||||
['--no-tests'] if not tests else [], | |||||
['--no-apps'] if not apps else [], | |||||
['--no-warnings'] if not warnings else [], | |||||
['--export'] if export else [], | |||||
self.project_dir_arg, | self.project_dir_arg, | ||||
]) | ]) | ||||
) | ) | ||||
@dds_conf | |||||
def test_ls(dds: DDS): | |||||
dds.run(['deps', 'ls']) | |||||
@dds_conf | @dds_conf | ||||
def test_deps_build(dds: DDS): | def test_deps_build(dds: DDS): | ||||
dds.catalog_import(dds.source_root / 'catalog.json') | dds.catalog_import(dds.source_root / 'catalog.json') | ||||
assert not dds.repo_dir.exists() | assert not dds.repo_dir.exists() | ||||
dds.deps_get() | |||||
assert dds.repo_dir.exists(), '`deps get` did not generate a repo directory' | |||||
assert not dds.lmi_path.exists() | |||||
dds.deps_build() | |||||
assert dds.lmi_path.exists(), '`deps build` did not generate the build dir' | |||||
dds.build() | |||||
assert dds.repo_dir.exists(), '`Building` did not generate a repo directory' | |||||
@dds_fixture_conf_1('use-remote') | @dds_fixture_conf_1('use-remote') | ||||
def test_use_nlohmann_json_remote(dds: DDS): | def test_use_nlohmann_json_remote(dds: DDS): | ||||
dds.catalog_import(dds.source_root / 'catalog.json') | dds.catalog_import(dds.source_root / 'catalog.json') | ||||
dds.deps_get() | |||||
dds.deps_build() | |||||
dds.build(apps=True) | dds.build(apps=True) | ||||
app_exe = dds.build_dir / f'app{dds.exe_suffix}' | app_exe = dds.build_dir / f'app{dds.exe_suffix}' |
def test_get_build_use_spdlog(dds: DDS): | def test_get_build_use_spdlog(dds: DDS): | ||||
dds.catalog_import(dds.source_root / 'catalog.json') | dds.catalog_import(dds.source_root / 'catalog.json') | ||||
dds.deps_get() | |||||
tc_fname = 'gcc.tc.dds' if 'gcc' in dds.default_builtin_toolchain else 'msvc.tc.dds' | tc_fname = 'gcc.tc.dds' if 'gcc' in dds.default_builtin_toolchain else 'msvc.tc.dds' | ||||
tc = str(dds.test_dir / tc_fname) | tc = str(dds.test_dir / tc_fname) | ||||
dds.deps_build(toolchain=tc) | |||||
dds.build(toolchain=tc, apps=True) | dds.build(toolchain=tc, apps=True) | ||||
proc.check_run((dds.build_dir / 'use-spdlog').with_suffix(dds.exe_suffix)) | proc.check_run((dds.build_dir / 'use-spdlog').with_suffix(dds.exe_suffix)) |
else: | else: | ||||
assert False, 'impossible' | assert False, 'impossible' | ||||
ci_repo_dir = paths.BUILD_DIR / '_ci-repo' | |||||
if not opts.skip_deps: | if not opts.skip_deps: | ||||
ci_repo_dir = paths.BUILD_DIR / '_ci-repo' | |||||
if ci_repo_dir.exists(): | if ci_repo_dir.exists(): | ||||
shutil.rmtree(ci_repo_dir) | shutil.rmtree(ci_repo_dir) | ||||
self_deps_get(paths.PREBUILT_DDS, ci_repo_dir) | self_deps_get(paths.PREBUILT_DDS, ci_repo_dir) | ||||
dds_flags=['--warnings', '--tests', '--apps']) | dds_flags=['--warnings', '--tests', '--apps']) | ||||
print('Main build PASSED!') | print('Main build PASSED!') | ||||
cat_path = paths.BUILD_DIR / 'catalog.db' | |||||
proc.check_run([ | |||||
paths.CUR_BUILT_DDS, | |||||
'catalog', | |||||
'import', | |||||
('--catalog', cat_path), | |||||
('--json', paths.PROJECT_ROOT / 'catalog.json'), | |||||
]) | |||||
self_build( | self_build( | ||||
paths.CUR_BUILT_DDS, | paths.CUR_BUILT_DDS, | ||||
toolchain=opts.toolchain, | toolchain=opts.toolchain, | ||||
dds_flags=['--warnings', '--tests', '--apps']) | |||||
dds_flags=[f'--repo-dir={ci_repo_dir}', f'--catalog={cat_path}']) | |||||
print('Bootstrap test PASSED!') | print('Bootstrap test PASSED!') | ||||
return pytest.main([ | return pytest.main([ |