@@ -0,0 +1,40 @@ | |||
#include "../options.hpp" | |||
#include "./build_common.hpp" | |||
#include <dds/build/builder.hpp> | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/remote/remote.hpp> | |||
#include <dds/toolchain/from_json.hpp> | |||
using namespace dds; | |||
namespace dds::cli::cmd { | |||
int build(const options& opts) { | |||
if (!opts.build.add_repos.empty()) { | |||
auto cat = opts.open_catalog(); | |||
for (auto& str : opts.build.add_repos) { | |||
auto repo = remote_repository::connect(str); | |||
repo.store(cat.database()); | |||
} | |||
} | |||
if (opts.build.update_repos || !opts.build.add_repos.empty()) { | |||
update_all_remotes(opts.open_catalog().database()); | |||
} | |||
auto builder = create_project_builder(opts); | |||
builder.build({ | |||
.out_root = opts.out_path.value_or(fs::current_path() / "_build"), | |||
.existing_lm_index = opts.build.lm_index, | |||
.emit_lmi = {}, | |||
.toolchain = opts.load_toolchain(), | |||
.parallel_jobs = opts.jobs, | |||
}); | |||
return 0; | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,44 @@ | |||
#include "./build_common.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/catalog/get.hpp> | |||
#include <dds/repo/repo.hpp> | |||
using namespace dds; | |||
builder dds::cli::create_project_builder(const dds::cli::options& opts) { | |||
sdist_build_params main_params = { | |||
.subdir = "", | |||
.build_tests = opts.build.want_tests, | |||
.run_tests = opts.build.want_tests, | |||
.build_apps = opts.build.want_apps, | |||
.enable_warnings = !opts.disable_warnings, | |||
}; | |||
auto man = package_manifest::load_from_directory(opts.project_dir).value_or(package_manifest{}); | |||
auto cat_path = opts.pkg_db_dir.value_or(catalog::default_path()); | |||
auto repo_path = opts.pkg_cache_dir.value_or(repository::default_local_path()); | |||
builder builder; | |||
if (!opts.build.lm_index.has_value()) { | |||
auto cat = catalog::open(cat_path); | |||
// Build the dependencies | |||
repository::with_repository( // | |||
repo_path, | |||
repo_flags::write_lock | repo_flags::create_if_absent, | |||
[&](repository repo) { | |||
// Download dependencies | |||
auto deps = repo.solve(man.dependencies, cat); | |||
get_all(deps, repo, cat); | |||
for (const package_id& pk : deps) { | |||
auto sdist_ptr = repo.find(pk); | |||
assert(sdist_ptr); | |||
sdist_build_params deps_params; | |||
deps_params.subdir = fs::path("_deps") / sdist_ptr->manifest.pkg_id.to_string(); | |||
builder.add(*sdist_ptr, deps_params); | |||
} | |||
}); | |||
} | |||
builder.add(sdist{std::move(man), opts.project_dir}, main_params); | |||
return builder; | |||
} |
@@ -0,0 +1,11 @@ | |||
#include "../options.hpp" | |||
#include <dds/build/builder.hpp> | |||
#include <functional> | |||
namespace dds::cli { | |||
dds::builder create_project_builder(const options& opts); | |||
} // namespace dds::cli |
@@ -0,0 +1,63 @@ | |||
#include "../options.hpp" | |||
#include <dds/build/builder.hpp> | |||
#include <dds/build/params.hpp> | |||
#include <dds/catalog/get.hpp> | |||
#include <dds/repo/repo.hpp> | |||
#include <range/v3/action/join.hpp> | |||
#include <range/v3/range/conversion.hpp> | |||
#include <range/v3/view/concat.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
namespace dds::cli::cmd { | |||
int build_deps(const options& opts) { | |||
dds::build_params params{ | |||
.out_root = opts.out_path.value_or(fs::current_path() / "_deps"), | |||
.existing_lm_index = {}, | |||
.emit_lmi = opts.build.lm_index.value_or("INDEX.lmi"), | |||
.toolchain = opts.load_toolchain(), | |||
.parallel_jobs = opts.jobs, | |||
}; | |||
dds::builder bd; | |||
dds::sdist_build_params sdist_params; | |||
auto all_file_deps = opts.build_deps.deps_files // | |||
| ranges::views::transform([&](auto dep_fpath) { | |||
dds_log(info, "Reading deps from {}", dep_fpath.string()); | |||
return dds::dependency_manifest::from_file(dep_fpath).dependencies; | |||
}) | |||
| ranges::actions::join; | |||
auto cmd_deps = ranges::views::transform(opts.build_deps.deps, [&](auto dep_str) { | |||
return dds::dependency::parse_depends_string(dep_str); | |||
}); | |||
auto all_deps = ranges::views::concat(all_file_deps, cmd_deps) | ranges::to_vector; | |||
auto cat = opts.open_catalog(); | |||
dds::repository::with_repository( // | |||
opts.pkg_cache_dir.value_or(repository::default_local_path()), | |||
dds::repo_flags::write_lock | dds::repo_flags::create_if_absent, | |||
[&](dds::repository repo) { | |||
// Download dependencies | |||
dds_log(info, "Loading {} dependencies", all_deps.size()); | |||
auto deps = repo.solve(all_deps, cat); | |||
dds::get_all(deps, repo, cat); | |||
for (const dds::package_id& pk : deps) { | |||
auto sdist_ptr = repo.find(pk); | |||
assert(sdist_ptr); | |||
dds::sdist_build_params deps_params; | |||
deps_params.subdir = sdist_ptr->manifest.pkg_id.to_string(); | |||
dds_log(info, "Dependency: {}", sdist_ptr->manifest.pkg_id.to_string()); | |||
bd.add(*sdist_ptr, deps_params); | |||
} | |||
}); | |||
bd.build(params); | |||
return 0; | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,20 @@ | |||
#include "../options.hpp" | |||
#include "./build_common.hpp" | |||
namespace dds::cli::cmd { | |||
int compile_file(const options& opts) { | |||
auto builder = create_project_builder(opts); | |||
builder.compile_files(opts.compile_file.files, | |||
{ | |||
.out_root = opts.out_path.value_or(fs::current_path() / "_build"), | |||
.existing_lm_index = opts.build.lm_index, | |||
.emit_lmi = {}, | |||
.toolchain = opts.load_toolchain(), | |||
.parallel_jobs = opts.jobs, | |||
}); | |||
return 0; | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,71 @@ | |||
#include "../options.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/catalog/get.hpp> | |||
#include <dds/dym.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/http/session.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <json5/parse_data.hpp> | |||
namespace dds::cli::cmd { | |||
static int _pkg_get(const options& opts) { | |||
auto cat = opts.open_catalog(); | |||
for (const auto& item : opts.pkg.get.pkgs) { | |||
auto id = package_id::parse(item); | |||
dds::dym_target dym; | |||
auto info = cat.get(id); | |||
if (!info) { | |||
dds::throw_user_error<dds::errc::no_such_catalog_package>( | |||
"No package in the catalog matched the ID '{}'.{}", item, dym.sentence_suffix()); | |||
} | |||
auto tsd = get_package_sdist(*info); | |||
auto dest = opts.out_path.value_or(fs::current_path()) / id.to_string(); | |||
dds_log(info, "Create sdist at {}", dest.string()); | |||
fs::remove_all(dest); | |||
safe_rename(tsd.sdist.path, dest); | |||
} | |||
return 0; | |||
} | |||
int pkg_get(const options& opts) { | |||
return boost::leaf::try_catch( // | |||
[&] { | |||
try { | |||
return _pkg_get(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[&](neo::url_validation_error url_err, dds::e_url_string bad_url) { | |||
dds_log(error, | |||
"Invalid package URL in the database [{}]: {}", | |||
bad_url.value, | |||
url_err.what()); | |||
return 1; | |||
}, | |||
[&](const json5::parse_error& e, dds::e_http_url bad_url) { | |||
dds_log(error, | |||
"Error parsing JSON5 document package downloaded from [{}]: {}", | |||
bad_url.value, | |||
e.what()); | |||
return 1; | |||
}, | |||
[](dds::e_sqlite3_error_exc e) { | |||
dds_log(error, "Error accessing the package database: {}", e.message); | |||
return 1; | |||
}, | |||
[&](dds::e_system_error_exc e, dds::e_http_connect conn) { | |||
dds_log(error, | |||
"Error opening connection to [{}:{}]: {}", | |||
conn.host, | |||
conn.port, | |||
e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,58 @@ | |||
#include "../options.hpp" | |||
#include <dds/http/session.hpp> | |||
#include <dds/repo/repo.hpp> | |||
#include <dds/source/dist.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <json5/parse_data.hpp> | |||
#include <neo/assert.hpp> | |||
#include <neo/url/parse.hpp> | |||
#include <iostream> | |||
#include <string_view> | |||
namespace dds::cli::cmd { | |||
static int _pkg_import(const options& opts) { | |||
return repository::with_repository( // | |||
opts.pkg_cache_dir.value_or(repository::default_local_path()), | |||
repo_flags::write_lock | repo_flags::create_if_absent, | |||
[&](auto repo) { | |||
for (std::string_view tgz_where : opts.pkg.import.items) { | |||
neo_assertion_breadcrumbs("Importing sdist", tgz_where); | |||
auto tmp_sd | |||
= (tgz_where.starts_with("http://") || tgz_where.starts_with("https://")) | |||
? download_expand_sdist_targz(tgz_where) | |||
: expand_sdist_targz(tgz_where); | |||
neo_assertion_breadcrumbs("Importing from temporary directory", | |||
tmp_sd.tmpdir.path()); | |||
repo.add_sdist(tmp_sd.sdist, dds::if_exists(opts.if_exists)); | |||
} | |||
if (opts.pkg.import.from_stdin) { | |||
auto tmp_sd = dds::expand_sdist_from_istream(std::cin, "<stdin>"); | |||
repo.add_sdist(tmp_sd.sdist, dds::if_exists(opts.if_exists)); | |||
} | |||
return 0; | |||
}); | |||
} | |||
int pkg_import(const options& opts) { | |||
return boost::leaf::try_catch( | |||
[&] { | |||
try { | |||
return _pkg_import(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[&](const json5::parse_error& e) { | |||
dds_log(error, "Error parsing JSON in package archive: {}", e.what()); | |||
return 1; | |||
}, | |||
[](dds::e_sqlite3_error_exc e) { | |||
dds_log(error, "Unexpected database error: {}", e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,61 @@ | |||
#include "../options.hpp" | |||
#include <dds/repo/repo.hpp> | |||
#include <dds/source/dist.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <neo/assert.hpp> | |||
#include <range/v3/range/conversion.hpp> | |||
#include <range/v3/view/group_by.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <iostream> | |||
#include <string_view> | |||
namespace dds::cli::cmd { | |||
static int _pkg_ls(const options& opts) { | |||
auto list_contents = [&](repository repo) { | |||
auto same_name | |||
= [](auto&& a, auto&& b) { return a.manifest.pkg_id.name == b.manifest.pkg_id.name; }; | |||
auto all = repo.iter_sdists(); | |||
auto grp_by_name = all // | |||
| ranges::views::group_by(same_name) // | |||
| ranges::views::transform(ranges::to_vector) // | |||
| ranges::views::transform([](auto&& grp) { | |||
assert(grp.size() > 0); | |||
return std::pair(grp[0].manifest.pkg_id.name, grp); | |||
}); | |||
for (const auto& [name, grp] : grp_by_name) { | |||
dds_log(info, "{}:", name); | |||
for (const dds::sdist& sd : grp) { | |||
dds_log(info, " - {}", sd.manifest.pkg_id.version.to_string()); | |||
} | |||
} | |||
return 0; | |||
}; | |||
return dds::repository::with_repository(opts.pkg_cache_dir.value_or( | |||
repository::default_local_path()), | |||
dds::repo_flags::read, | |||
list_contents); | |||
} | |||
int pkg_ls(const options& opts) { | |||
return boost::leaf::try_catch( | |||
[&] { | |||
try { | |||
return _pkg_ls(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[](dds::e_sqlite3_error_exc e) { | |||
dds_log(error, "Unexpected database error: {}", e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,24 @@ | |||
#include "../options.hpp" | |||
#include "./pkg_repo_err_handle.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/remote/remote.hpp> | |||
namespace dds::cli::cmd { | |||
static int _pkg_repo_add(const options& opts) { | |||
auto cat = opts.open_catalog(); | |||
auto repo = remote_repository::connect(opts.pkg.repo.add.url); | |||
repo.store(cat.database()); | |||
if (opts.pkg.repo.add.update) { | |||
repo.update_catalog(cat.database()); | |||
} | |||
return 0; | |||
} | |||
int pkg_repo_add(const options& opts) { | |||
return handle_pkg_repo_remote_errors([&] { return _pkg_repo_add(opts); }); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,47 @@ | |||
#include "./pkg_repo_err_handle.hpp" | |||
#include <dds/http/session.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <json5/parse_data.hpp> | |||
#include <neo/url/parse.hpp> | |||
int dds::cli::cmd::handle_pkg_repo_remote_errors(std::function<int()> fn) { | |||
return boost::leaf::try_catch( | |||
[&] { | |||
try { | |||
return fn(); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[&](neo::url_validation_error url_err, dds::e_url_string bad_url) { | |||
dds_log(error, "Invalid URL [{}]: {}", bad_url.value, url_err.what()); | |||
return 1; | |||
}, | |||
[&](const json5::parse_error& e, dds::e_http_url bad_url) { | |||
dds_log(error, | |||
"Error parsing JSON downloaded from URL [{}]: {}", | |||
bad_url.value, | |||
e.what()); | |||
return 1; | |||
}, | |||
[](dds::e_sqlite3_error_exc e, dds::e_url_string url) { | |||
dds_log(error, "Error accessing remote database (From {}): {}", url.value, e.message); | |||
return 1; | |||
}, | |||
[](dds::e_sqlite3_error_exc e) { | |||
dds_log(error, "Unexpected database error: {}", e.message); | |||
return 1; | |||
}, | |||
[&](dds::e_system_error_exc e, dds::e_http_connect conn) { | |||
dds_log(error, | |||
"Error opening connection to [{}:{}]: {}", | |||
conn.host, | |||
conn.port, | |||
e.message); | |||
return 1; | |||
}); | |||
} |
@@ -0,0 +1,9 @@ | |||
#pragma once | |||
#include <functional> | |||
namespace dds::cli::cmd { | |||
int handle_pkg_repo_remote_errors(std::function<int()>); | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,19 @@ | |||
#include "../options.hpp" | |||
#include "./pkg_repo_err_handle.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/remote/remote.hpp> | |||
namespace dds::cli::cmd { | |||
static int _pkg_repo_update(const options& opts) { | |||
update_all_remotes(opts.open_catalog().database()); | |||
return 0; | |||
} | |||
int pkg_repo_update(const options& opts) { | |||
return handle_pkg_repo_remote_errors([&] { return _pkg_repo_update(opts); }); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,57 @@ | |||
#include "../options.hpp" | |||
#include <dds/repoman/repoman.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <fmt/ostream.h> | |||
#include <neo/sqlite3/error.hpp> | |||
namespace dds::cli::cmd { | |||
static int _repoman_import(const options& opts) { | |||
auto repo = repo_manager::open(opts.repoman.repo_dir); | |||
for (auto pkg : opts.repoman.import.files) { | |||
repo.import_targz(pkg); | |||
} | |||
return 0; | |||
} | |||
int repoman_import(const options& opts) { | |||
return boost::leaf::try_catch( // | |||
[&] { | |||
try { | |||
return _repoman_import(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[](dds::e_sqlite3_error_exc, | |||
boost::leaf::match<neo::sqlite3::errc, neo::sqlite3::errc::constraint_unique>, | |||
dds::e_repo_import_targz tgz, | |||
dds::package_id pkg_id) { | |||
dds_log(error, | |||
"Package {} (from {}) is already present in the repository", | |||
pkg_id.to_string(), | |||
tgz.path); | |||
return 1; | |||
}, | |||
[](dds::e_system_error_exc e, dds::e_repo_import_targz tgz) { | |||
dds_log(error, "Failed to import file {}: {}", tgz.path, e.message); | |||
return 1; | |||
}, | |||
[](const std::runtime_error& e, dds::e_repo_import_targz tgz) { | |||
dds_log(error, "Unknown error while importing file {}: {}", tgz.path, e.what()); | |||
return 1; | |||
}, | |||
[](dds::e_sqlite3_error_exc e, dds::e_repo_import_targz tgz) { | |||
dds_log(error, "Database error while importing tar file {}: {}", tgz.path, e.message); | |||
return 1; | |||
}, | |||
[](dds::e_system_error_exc e, dds::e_open_repo_db db) { | |||
dds_log(error, "Error while opening repository database {}: {}", db.path, e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,48 @@ | |||
#include "../options.hpp" | |||
#include <dds/repoman/repoman.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <fmt/ostream.h> | |||
namespace dds::cli::cmd { | |||
static int _repoman_init(const options& opts) { | |||
auto repo = repo_manager::create(opts.repoman.repo_dir, opts.repoman.init.name); | |||
dds_log(info, "Created new repository '{}' in {}", repo.name(), repo.root()); | |||
return 0; | |||
} | |||
int repoman_init(const options& opts) { | |||
return boost::leaf::try_catch( // | |||
[&] { | |||
try { | |||
return _repoman_init(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[](dds::e_sqlite3_error_exc e, dds::e_init_repo init, dds::e_init_repo_db init_db) { | |||
dds_log(error, | |||
"SQLite error while initializing repository in [{}] (SQlite database {}): {}", | |||
init.path, | |||
init_db.path, | |||
e.message); | |||
return 1; | |||
}, | |||
[](dds::e_system_error_exc e, dds::e_open_repo_db db) { | |||
dds_log(error, "Error while opening repository database {}: {}", db.path, e.message); | |||
return 1; | |||
}, | |||
[](dds::e_sqlite3_error_exc e, dds::e_init_repo init) { | |||
dds_log(error, | |||
"SQLite error while initializing repository in [{}]: {}", | |||
init.path, | |||
e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,37 @@ | |||
#include "../options.hpp" | |||
#include <dds/repoman/repoman.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <fmt/ostream.h> | |||
#include <iostream> | |||
namespace dds::cli::cmd { | |||
static int _repoman_ls(const options& opts) { | |||
auto repo = repo_manager::open(opts.repoman.repo_dir); | |||
for (auto pkg_id : repo.all_packages()) { | |||
std::cout << pkg_id.to_string() << '\n'; | |||
} | |||
return 0; | |||
} | |||
int repoman_ls(const options& opts) { | |||
return boost::leaf::try_catch( // | |||
[&] { | |||
try { | |||
return _repoman_ls(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[](dds::e_system_error_exc e, dds::e_open_repo_db db) { | |||
dds_log(error, "Error while opening repository database {}: {}", db.path, e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,54 @@ | |||
#include "../options.hpp" | |||
#include <dds/repoman/repoman.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <fmt/ostream.h> | |||
#include <neo/sqlite3/error.hpp> | |||
namespace dds::cli::cmd { | |||
static int _repoman_remove(const options& opts) { | |||
auto repo = repo_manager::open(opts.repoman.repo_dir); | |||
for (auto& str : opts.repoman.remove.pkgs) { | |||
auto pkg_id = dds::package_id::parse(str); | |||
repo.delete_package(pkg_id); | |||
} | |||
return 0; | |||
} | |||
int repoman_remove(const options& opts) { | |||
return boost::leaf::try_catch( // | |||
[&] { | |||
try { | |||
return _repoman_remove(opts); | |||
} catch (...) { | |||
dds::capture_exception(); | |||
} | |||
}, | |||
[](dds::e_sqlite3_error_exc, | |||
boost::leaf::match<neo::sqlite3::errc, neo::sqlite3::errc::constraint_unique>, | |||
dds::e_repo_import_targz tgz, | |||
dds::package_id pkg_id) { | |||
dds_log(error, | |||
"Package {} (from {}) is already present in the repository", | |||
pkg_id.to_string(), | |||
tgz.path); | |||
return 1; | |||
}, | |||
[](dds::e_system_error_exc e, dds::e_repo_delete_path tgz, dds::package_id pkg_id) { | |||
dds_log(error, | |||
"Cannot delete requested package '{}' from repository (Path {}): {}", | |||
pkg_id.to_string(), | |||
tgz.path, | |||
e.message); | |||
return 1; | |||
}, | |||
[](dds::e_system_error_exc e, dds::e_open_repo_db db) { | |||
dds_log(error, "Error while opening repository database {}: {}", db.path, e.message); | |||
return 1; | |||
}); | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,24 @@ | |||
#include "../options.hpp" | |||
#include <dds/source/dist.hpp> | |||
#include <fmt/core.h> | |||
namespace dds::cli::cmd { | |||
int sdist_create(const options& opts) { | |||
dds::sdist_params params{ | |||
.project_dir = opts.project_dir, | |||
.dest_path = {}, | |||
.force = opts.if_exists == if_exists::replace, | |||
.include_apps = true, | |||
.include_tests = true, | |||
}; | |||
auto pkg_man = package_manifest::load_from_directory(params.project_dir); | |||
auto default_filename = fmt::format("{}.tar.gz", pkg_man->pkg_id.to_string()); | |||
auto filepath = opts.out_path.value_or(fs::current_path() / default_filename); | |||
create_sdist_targz(filepath, params); | |||
return 0; | |||
} | |||
} // namespace dds::cli::cmd |
@@ -0,0 +1,91 @@ | |||
#include "./dispatch_main.hpp" | |||
#include "./error_handler.hpp" | |||
#include "./options.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/remote/remote.hpp> | |||
#include <dds/util/paths.hpp> | |||
#include <dds/util/result.hpp> | |||
using namespace dds; | |||
namespace dds::cli { | |||
namespace cmd { | |||
using command = int(const options&); | |||
command build_deps; | |||
command build; | |||
command compile_file; | |||
command pkg_get; | |||
command pkg_import; | |||
command pkg_ls; | |||
command pkg_repo_add; | |||
command pkg_repo_update; | |||
command repoman_import; | |||
command repoman_init; | |||
command repoman_ls; | |||
command repoman_remove; | |||
command sdist_create; | |||
} // namespace cmd | |||
int dispatch_main(const options& opts) noexcept { | |||
dds::log::current_log_level = opts.log_level; | |||
return dds::handle_cli_errors([&] { | |||
switch (opts.subcommand) { | |||
case subcommand::build: | |||
return cmd::build(opts); | |||
case subcommand::sdist: | |||
switch (opts.sdist.subcommand) { | |||
case sdist_subcommand::create: | |||
return cmd::sdist_create(opts); | |||
case sdist_subcommand::_none_:; | |||
} | |||
neo::unreachable(); | |||
case subcommand::pkg: | |||
switch (opts.pkg.subcommand) { | |||
case pkg_subcommand::ls: | |||
return cmd::pkg_ls(opts); | |||
case pkg_subcommand::get: | |||
return cmd::pkg_get(opts); | |||
case pkg_subcommand::import: | |||
return cmd::pkg_import(opts); | |||
case pkg_subcommand::repo: | |||
switch (opts.pkg.repo.subcommand) { | |||
case cli_pkg_repo_subcommand::add: | |||
return cmd::pkg_repo_add(opts); | |||
case cli_pkg_repo_subcommand::update: | |||
return cmd::pkg_repo_update(opts); | |||
case cli_pkg_repo_subcommand::_none_:; | |||
} | |||
neo::unreachable(); | |||
case pkg_subcommand::_none_:; | |||
} | |||
neo::unreachable(); | |||
case subcommand::repoman: | |||
switch (opts.repoman.subcommand) { | |||
case repoman_subcommand::import: | |||
return cmd::repoman_import(opts); | |||
case repoman_subcommand::init: | |||
return cmd::repoman_init(opts); | |||
case repoman_subcommand::remove: | |||
return cmd::repoman_remove(opts); | |||
case repoman_subcommand::ls: | |||
return cmd::repoman_ls(opts); | |||
case repoman_subcommand::_none_:; | |||
} | |||
neo::unreachable(); | |||
case subcommand::compile_file: | |||
return cmd::compile_file(opts); | |||
case subcommand::build_deps: | |||
return cmd::build_deps(opts); | |||
case subcommand::_none_:; | |||
} | |||
neo::unreachable(); | |||
return 6; | |||
}); | |||
} | |||
} // namespace dds::cli |
@@ -0,0 +1,9 @@ | |||
#pragma once | |||
namespace dds::cli { | |||
struct options; | |||
int dispatch_main(const options&) noexcept; | |||
} // namespace dds |
@@ -0,0 +1,44 @@ | |||
#include "./error_handler.hpp" | |||
#include "./options.hpp" | |||
#include <dds/error/errors.hpp> | |||
#include <dds/util/log.hpp> | |||
#include <dds/util/result.hpp> | |||
#include <dds/util/signal.hpp> | |||
#include <boost/leaf/handle_error.hpp> | |||
#include <boost/leaf/handle_exception.hpp> | |||
#include <boost/leaf/result.hpp> | |||
#include <fmt/ostream.h> | |||
#include <neo/url/parse.hpp> | |||
namespace { | |||
template <dds::cli::subcommand Val> | |||
using subcommand = boost::leaf::match<dds::cli::subcommand, Val>; | |||
auto handlers = std::tuple( // | |||
[](neo::url_validation_error exc, dds::e_url_string bad_url) { | |||
dds_log(error, "Invalid URL '{}': {}", bad_url.value, exc.what()); | |||
return 1; | |||
}, | |||
[](boost::leaf::catch_<dds::error_base> exc) { | |||
dds_log(error, "{}", exc.value().what()); | |||
dds_log(error, "{}", exc.value().explanation()); | |||
dds_log(error, "Refer: {}", exc.value().error_reference()); | |||
return 1; | |||
}, | |||
[](dds::user_cancelled) { | |||
dds_log(critical, "Operation cancelled by the user"); | |||
return 2; | |||
}, | |||
[](boost::leaf::verbose_diagnostic_info const& diag) { | |||
dds_log(critical, "An unhandled error arose. THIS IS A DDS BUG! Info: {}", diag); | |||
return 42; | |||
}); | |||
} // namespace | |||
int dds::handle_cli_errors(std::function<int()> fn) noexcept { | |||
return boost::leaf::try_handle_all([&]() -> boost::leaf::result<int> { return fn(); }, | |||
handlers); | |||
} |
@@ -0,0 +1,9 @@ | |||
#pragma once | |||
#include <functional> | |||
namespace dds { | |||
int handle_cli_errors(std::function<int()>) noexcept; | |||
} // namespace dds |
@@ -0,0 +1,425 @@ | |||
#include "./options.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/toolchain/from_json.hpp> | |||
#include <dds/toolchain/toolchain.hpp> | |||
#include <debate/enum.hpp> | |||
using namespace dds; | |||
using namespace debate; | |||
namespace { | |||
struct setup { | |||
dds::cli::options& opts; | |||
// Util argument common to a lot of operations | |||
argument if_exists_arg{ | |||
.long_spellings = {"if-exists"}, | |||
.help = "What to do if the resource already exists", | |||
.valname = "{replace,skip,fail}", | |||
.action = put_into(opts.if_exists), | |||
}; | |||
argument toolchain_arg{ | |||
.long_spellings = {"toolchain"}, | |||
.short_spellings = {"t"}, | |||
.help = "The toolchain to use when building", | |||
.valname = "<file-or-id>", | |||
.action = put_into(opts.toolchain), | |||
}; | |||
argument project_arg{ | |||
.long_spellings = {"project"}, | |||
.short_spellings = {"p"}, | |||
.help = "The project to build. If not given, uses the current working directory", | |||
.valname = "<project-path>", | |||
.action = put_into(opts.project_dir), | |||
}; | |||
argument no_warn_arg{ | |||
.long_spellings = {"no-warn", "no-warnings"}, | |||
.help = "Disable build warnings", | |||
.nargs = 0, | |||
.action = store_true(opts.disable_warnings), | |||
}; | |||
argument out_arg{ | |||
.long_spellings = {"out", "output"}, | |||
.short_spellings = {"o"}, | |||
.help = "Path to the output", | |||
.valname = "<path>", | |||
.action = put_into(opts.out_path), | |||
}; | |||
argument lm_index_arg{ | |||
.long_spellings = {"libman-index"}, | |||
.help = "Path to a libman index to use", | |||
.valname = "<lmi-path>", | |||
.action = put_into(opts.build.lm_index), | |||
}; | |||
argument jobs_arg{ | |||
.long_spellings = {"jobs"}, | |||
.short_spellings = {"j"}, | |||
.help = "Set the maximum number of parallel jobs to execute", | |||
.valname = "<job-count>", | |||
.action = put_into(opts.jobs), | |||
}; | |||
argument repoman_repo_dir_arg{ | |||
.help = "The directory of the repository to manage", | |||
.valname = "<repo-dir>", | |||
.required = true, | |||
.action = put_into(opts.repoman.repo_dir), | |||
}; | |||
void do_setup(argument_parser& parser) noexcept { | |||
parser.add_argument({ | |||
.long_spellings = {"log-level"}, | |||
.short_spellings = {"l"}, | |||
.help = "" | |||
"Set the dds logging level. One of 'trace', 'debug', 'info', \n" | |||
"'warn', 'error', 'critical', or 'silent'", | |||
.valname = "<level>", | |||
.action = put_into(opts.log_level), | |||
}); | |||
parser.add_argument({ | |||
.long_spellings = {"data-dir"}, | |||
.help | |||
= "" | |||
"(Advanced) " | |||
"Override dds's data directory. This is used for various caches and databases.\n" | |||
"The default is a user-local directory that differs depending on platform.", | |||
.valname = "<directory>", | |||
.action = put_into(opts.data_dir), | |||
}); | |||
parser.add_argument({ | |||
.long_spellings = {"pkg-cache-dir"}, | |||
.help = "(Advanced) Override dds's local package cache directory.", | |||
.valname = "<directory>", | |||
.action = put_into(opts.pkg_cache_dir), | |||
}); | |||
parser.add_argument({ | |||
.long_spellings = {"pkg-db-path"}, | |||
.help = "(Advanced) Override dds's default package database path.", | |||
.valname = "<database-path>", | |||
.action = put_into(opts.pkg_db_dir), | |||
}); | |||
setup_main_commands(parser.add_subparsers({ | |||
.description = "The operation to perform", | |||
.action = put_into(opts.subcommand), | |||
})); | |||
} | |||
void setup_main_commands(subparser_group& group) { | |||
setup_build_cmd(group.add_parser({ | |||
.name = "build", | |||
.help = "Build a project", | |||
})); | |||
setup_compile_file_cmd(group.add_parser({ | |||
.name = "compile-file", | |||
.help = "Compile individual files in the project", | |||
})); | |||
setup_build_deps_cmd(group.add_parser({ | |||
.name = "build-deps", | |||
.help = "Build a set of dependencies and generate a libman index", | |||
})); | |||
setup_pkg_cmd(group.add_parser({ | |||
.name = "pkg", | |||
.help = "Manage packages and package remotes", | |||
})); | |||
setup_sdist_cmd(group.add_parser({ | |||
.name = "sdist", | |||
.help = "Work with source distribution packages", | |||
})); | |||
setup_repoman_cmd(group.add_parser({ | |||
.name = "repoman", | |||
.help = "Manage a dds package repository", | |||
})); | |||
} | |||
void setup_build_cmd(argument_parser& build_cmd) { | |||
build_cmd.add_argument(toolchain_arg.dup()); | |||
build_cmd.add_argument(project_arg.dup()); | |||
build_cmd.add_argument({ | |||
.long_spellings = {"no-tests"}, | |||
.help = "Do not build and run project tests", | |||
.nargs = 0, | |||
.action = debate::store_false(opts.build.want_tests), | |||
}); | |||
build_cmd.add_argument({ | |||
.long_spellings = {"no-apps"}, | |||
.help = "Do not build project applications", | |||
.nargs = 0, | |||
.action = debate::store_false(opts.build.want_apps), | |||
}); | |||
build_cmd.add_argument(no_warn_arg.dup()); | |||
build_cmd.add_argument(out_arg.dup()).help = "Directory where dds will write build results"; | |||
build_cmd.add_argument({ | |||
.long_spellings = {"add-repo"}, | |||
.help = "" | |||
"Add remote repositories to the package catalog before building\n" | |||
"(Implies --update-repos)", | |||
.valname = "<repo-url>", | |||
.can_repeat = true, | |||
.action = debate::push_back_onto(opts.build.add_repos), | |||
}); | |||
build_cmd.add_argument({ | |||
.long_spellings = {"update-repos"}, | |||
.short_spellings = {"U"}, | |||
.help = "Update package repositories before building", | |||
.nargs = 0, | |||
.action = debate::store_true(opts.build.update_repos), | |||
}); | |||
build_cmd.add_argument(lm_index_arg.dup()).help | |||
= "Path to a libman index file to use for loading project dependencies"; | |||
build_cmd.add_argument(jobs_arg.dup()); | |||
} | |||
void setup_compile_file_cmd(argument_parser& compile_file_cmd) noexcept { | |||
compile_file_cmd.add_argument(project_arg.dup()); | |||
compile_file_cmd.add_argument(toolchain_arg.dup()); | |||
compile_file_cmd.add_argument(no_warn_arg.dup()).help = "Disable compiler warnings"; | |||
compile_file_cmd.add_argument(jobs_arg.dup()).help | |||
= "Set the maximum number of files to compile in parallel"; | |||
compile_file_cmd.add_argument(lm_index_arg.dup()); | |||
compile_file_cmd.add_argument(out_arg.dup()); | |||
compile_file_cmd.add_argument({ | |||
.help = "One or more source files to compile", | |||
.valname = "<source-files>", | |||
.can_repeat = true, | |||
.action = debate::push_back_onto(opts.compile_file.files), | |||
}); | |||
} | |||
void setup_build_deps_cmd(argument_parser& build_deps_cmd) noexcept { | |||
build_deps_cmd.add_argument(toolchain_arg.dup()).required; | |||
build_deps_cmd.add_argument(jobs_arg.dup()); | |||
build_deps_cmd.add_argument(out_arg.dup()); | |||
build_deps_cmd.add_argument(lm_index_arg.dup()).help | |||
= "Destination path for the generated libman index file"; | |||
build_deps_cmd.add_argument({ | |||
.long_spellings = {"deps-file"}, | |||
.short_spellings = {"d"}, | |||
.help = "Path to a JSON5 file listing dependencies", | |||
.valname = "<deps-file>", | |||
.can_repeat = true, | |||
.action = debate::push_back_onto(opts.build_deps.deps_files), | |||
}); | |||
build_deps_cmd.add_argument({ | |||
.help = "Dependency statement strings", | |||
.valname = "<dependency>", | |||
.can_repeat = true, | |||
.action = debate::push_back_onto(opts.build_deps.deps), | |||
}); | |||
} | |||
void setup_pkg_cmd(argument_parser& pkg_cmd) { | |||
auto& pkg_group = pkg_cmd.add_subparsers({ | |||
.valname = "<pkg-subcommand>", | |||
.action = put_into(opts.pkg.subcommand), | |||
}); | |||
pkg_group.add_parser({ | |||
.name = "ls", | |||
.help = "List locally available packages", | |||
}); | |||
setup_pkg_get_cmd(pkg_group.add_parser({ | |||
.name = "get", | |||
.help = "Obtain a copy of a package from a remote", | |||
})); | |||
setup_pkg_init_db_cmd(pkg_group.add_parser({ | |||
.name = "init-db", | |||
.help = "Initialize a new package database file (Path specified with '--pkg-db-path')", | |||
})); | |||
setup_pkg_import_cmd(pkg_group.add_parser({ | |||
.name = "import", | |||
.help = "Import a source distribution archive into the local package cache", | |||
})); | |||
setup_pkg_repo_cmd(pkg_group.add_parser({ | |||
.name = "repo", | |||
.help = "Manage package repositories", | |||
})); | |||
} | |||
void setup_pkg_get_cmd(argument_parser& pkg_get_cmd) { | |||
pkg_get_cmd.add_argument({ | |||
.valname = "<pkg-id>", | |||
.can_repeat = true, | |||
.action = push_back_onto(opts.pkg.get.pkgs), | |||
}); | |||
pkg_get_cmd.add_argument(out_arg.dup()).help | |||
= "Directory where obtained packages will be placed.\n" | |||
"Default is the current working directory."; | |||
} | |||
void setup_pkg_init_db_cmd(argument_parser& pkg_init_db_cmd) { | |||
pkg_init_db_cmd.add_argument(if_exists_arg.dup()).help | |||
= "What to do if the database file already exists"; | |||
} | |||
void setup_pkg_import_cmd(argument_parser& pkg_import_cmd) noexcept { | |||
pkg_import_cmd.add_argument({ | |||
.long_spellings = {"stdin"}, | |||
.help = "Import a source distribution archive from standard input", | |||
.nargs = 0, | |||
.action = debate::store_true(opts.pkg.import.from_stdin), | |||
}); | |||
pkg_import_cmd.add_argument(if_exists_arg.dup()).help | |||
= "What to do if the package already exists in the local cache"; | |||
pkg_import_cmd.add_argument({ | |||
.help = "One or more paths/URLs to source distribution archives to import", | |||
.valname = "<path-or-url>", | |||
.can_repeat = true, | |||
.action = debate::push_back_onto(opts.pkg.import.items), | |||
}); | |||
} | |||
void setup_pkg_repo_cmd(argument_parser& pkg_repo_cmd) noexcept { | |||
auto& pkg_repo_grp = pkg_repo_cmd.add_subparsers({ | |||
.valname = "<pkg-repo-subcommand>", | |||
.action = put_into(opts.pkg.repo.subcommand), | |||
}); | |||
setup_pkg_repo_add_cmd(pkg_repo_grp.add_parser({ | |||
.name = "add", | |||
.help = "Add a package repository", | |||
})); | |||
pkg_repo_grp.add_parser({ | |||
.name = "update", | |||
.help = "Update package repository information", | |||
}); | |||
} | |||
void setup_pkg_repo_add_cmd(argument_parser& pkg_repo_add_cmd) noexcept { | |||
pkg_repo_add_cmd.add_argument({ | |||
.help = "URL of a repository to add", | |||
.valname = "<url>", | |||
.required = true, | |||
.action = debate::put_into(opts.pkg.repo.add.url), | |||
}); | |||
pkg_repo_add_cmd.add_argument({ | |||
.long_spellings = {"no-update"}, | |||
.help = "Do not immediately update for the new package repository", | |||
.nargs = 0, | |||
.action = debate::store_false(opts.pkg.repo.add.update), | |||
}); | |||
} | |||
void setup_sdist_cmd(argument_parser& sdist_cmd) noexcept { | |||
auto& sdist_grp = sdist_cmd.add_subparsers({ | |||
.valname = "<sdist-subcommand>", | |||
.action = put_into(opts.sdist.subcommand), | |||
}); | |||
setup_sdist_create_cmd(sdist_grp.add_parser({ | |||
.name = "create", | |||
.help = "Create a source distribution from a project tree", | |||
})); | |||
} | |||
void setup_sdist_create_cmd(argument_parser& sdist_create_cmd) { | |||
sdist_create_cmd.add_argument(project_arg.dup()).help | |||
= "Path to the project for which to create a source distribution.\n" | |||
"Default is the current working directory."; | |||
sdist_create_cmd.add_argument(out_arg.dup()).help | |||
= "Destination path for the source distributnion archive"; | |||
sdist_create_cmd.add_argument(if_exists_arg.dup()).help | |||
= "What to do if the destination names an existing file"; | |||
} | |||
void setup_repoman_cmd(argument_parser& repoman_cmd) { | |||
auto& grp = repoman_cmd.add_subparsers({ | |||
.valname = "<repoman-subcommand>", | |||
.action = put_into(opts.repoman.subcommand), | |||
}); | |||
setup_repoman_init_cmd(grp.add_parser({ | |||
.name = "init", | |||
.help = "Initialize a directory as a new repository", | |||
})); | |||
setup_repoman_import_cmd(grp.add_parser({ | |||
.name = "import", | |||
.help = "Import a source distribution into the repository", | |||
})); | |||
auto& ls_cmd = grp.add_parser({ | |||
.name = "ls", | |||
.help = "List the contents of a package repository directory", | |||
}); | |||
ls_cmd.add_argument(repoman_repo_dir_arg.dup()); | |||
setup_repoman_remove_cmd(grp.add_parser({ | |||
.name = "remove", | |||
.help = "Remove packages from a package repository", | |||
})); | |||
} | |||
void setup_repoman_init_cmd(argument_parser& repoman_init_cmd) { | |||
repoman_init_cmd.add_argument(repoman_repo_dir_arg.dup()); | |||
repoman_init_cmd.add_argument(if_exists_arg.dup()).help | |||
= "What to do if the directory exists and is already repository"; | |||
repoman_init_cmd.add_argument({ | |||
.long_spellings = {"name"}, | |||
.short_spellings = {"n"}, | |||
.help = "Specifiy the name of the new repository", | |||
.valname = "<name>", | |||
.action = put_into(opts.repoman.init.name), | |||
}); | |||
} | |||
void setup_repoman_import_cmd(argument_parser& repoman_import_cmd) { | |||
repoman_import_cmd.add_argument(repoman_repo_dir_arg.dup()); | |||
repoman_import_cmd.add_argument({ | |||
.help = "Paths to source distribution archives to import", | |||
.valname = "<sdist-file-path>", | |||
.can_repeat = true, | |||
.action = push_back_onto(opts.repoman.import.files), | |||
}); | |||
} | |||
void setup_repoman_remove_cmd(argument_parser& repoman_remove_cmd) { | |||
repoman_remove_cmd.add_argument(repoman_repo_dir_arg.dup()); | |||
repoman_remove_cmd.add_argument({ | |||
.help = "One or more identifiers of packages to remove", | |||
.valname = "<pkg-id>", | |||
.can_repeat = true, | |||
.action = push_back_onto(opts.repoman.remove.pkgs), | |||
}); | |||
} | |||
}; | |||
} // namespace | |||
void cli::options::setup_parser(debate::argument_parser& parser) noexcept { | |||
setup{*this}.do_setup(parser); | |||
} | |||
catalog dds::cli::options::open_catalog() const { | |||
return catalog::open(this->pkg_db_dir.value_or(catalog::default_path())); | |||
} | |||
toolchain dds::cli::options::load_toolchain() const { | |||
if (!toolchain) { | |||
auto def = dds::toolchain::get_default(); | |||
if (!def) { | |||
throw_user_error<errc::no_default_toolchain>(); | |||
} | |||
return *def; | |||
} | |||
// Convert the given string to a toolchain | |||
auto& tc_str = *toolchain; | |||
if (tc_str.starts_with(":")) { | |||
auto default_tc = tc_str.substr(1); | |||
auto tc = dds::toolchain::get_builtin(default_tc); | |||
if (!tc.has_value()) { | |||
throw_user_error< | |||
errc::invalid_builtin_toolchain>("Invalid built-in toolchain name '{}'", | |||
default_tc); | |||
} | |||
return std::move(*tc); | |||
} else { | |||
return parse_toolchain_json5(slurp_file(tc_str)); | |||
} | |||
} |
@@ -0,0 +1,242 @@ | |||
#pragma once | |||
#include <dds/util/log.hpp> | |||
#include <debate/argument_parser.hpp> | |||
#include <filesystem> | |||
#include <optional> | |||
#include <string> | |||
#include <vector> | |||
namespace dds { | |||
namespace fs = std::filesystem; | |||
class catalog; | |||
class toolchain; | |||
namespace cli { | |||
/** | |||
* @brief Top-level dds subcommands | |||
*/ | |||
enum class subcommand { | |||
_none_, | |||
build, | |||
compile_file, | |||
build_deps, | |||
pkg, | |||
sdist, | |||
repoman, | |||
}; | |||
/** | |||
* @brief 'dds sdist' subcommands | |||
*/ | |||
enum class sdist_subcommand { | |||
_none_, | |||
create, | |||
}; | |||
/** | |||
* @brief 'dds pkg' subcommands | |||
*/ | |||
enum class pkg_subcommand { | |||
_none_, | |||
ls, | |||
get, | |||
import, | |||
repo, | |||
}; | |||
/** | |||
* @brief 'dds pkg repo' subcommands | |||
*/ | |||
enum class cli_pkg_repo_subcommand { | |||
_none_, | |||
add, | |||
update, | |||
}; | |||
/** | |||
* @brief 'dds repoman' subcommands | |||
* | |||
*/ | |||
enum class repoman_subcommand { | |||
_none_, | |||
init, | |||
import, | |||
remove, | |||
ls, | |||
}; | |||
/** | |||
* @brief Options for `--if-exists` on the CLI | |||
*/ | |||
enum class if_exists { | |||
replace, | |||
fail, | |||
ignore, | |||
}; | |||
/** | |||
* @brief Complete aggregate of all dds command-line options, and some utilities | |||
*/ | |||
struct options { | |||
using path = fs::path; | |||
using opt_path = std::optional<fs::path>; | |||
using string = std::string; | |||
using opt_string = std::optional<std::string>; | |||
// The `--data-dir` argument | |||
opt_path data_dir; | |||
// The `--pkg-cache-dir' argument | |||
opt_path pkg_cache_dir; | |||
// The `--pkg-db-dir` argument | |||
opt_path pkg_db_dir; | |||
// The `--log-level` argument | |||
log::level log_level = log::level::info; | |||
// The top-most selected subcommand | |||
enum subcommand subcommand; | |||
// Many subcommands use a '--project' argument, stored here, using the CWD as the default | |||
path project_dir = fs::current_path(); | |||
// Compile and build commands with `--no-warnings`/`--no-warn` | |||
bool disable_warnings = true; | |||
// Compile and build commands' `--jobs` parameter | |||
int jobs = 0; | |||
// Compile and build commands' `--toolchain` option: | |||
opt_string toolchain; | |||
opt_path out_path; | |||
// Shared `--if-exists` argument: | |||
cli::if_exists if_exists = cli::if_exists::fail; | |||
/** | |||
* @brief Open the package catalog based on the user-specified options. | |||
* @return catalog | |||
*/ | |||
catalog open_catalog() const; | |||
/** | |||
* @brief Load a dds toolchain as specified by the user, or a default. | |||
* @return dds::toolchain | |||
*/ | |||
dds::toolchain load_toolchain() const; | |||
/** | |||
* @brief Parameters specific to 'dds build' | |||
*/ | |||
struct { | |||
bool want_tests = true; | |||
bool want_apps = true; | |||
opt_path lm_index; | |||
std::vector<string> add_repos; | |||
bool update_repos = false; | |||
} build; | |||
/** | |||
* @brief Parameters specific to 'dds compile-file' | |||
*/ | |||
struct { | |||
/// The files that the user has requested to be compiled | |||
std::vector<fs::path> files; | |||
} compile_file; | |||
/** | |||
* @brief Parameters specific to 'dds build-deps' | |||
*/ | |||
struct { | |||
/// Files listed with '--deps-file' | |||
std::vector<fs::path> deps_files; | |||
/// Dependency strings provided directly in the command-line | |||
std::vector<string> deps; | |||
} build_deps; | |||
/** | |||
* @brief Parameters and subcommands for 'dds pkg' | |||
* | |||
*/ | |||
struct { | |||
/// The 'dds pkg' subcommand | |||
pkg_subcommand subcommand; | |||
/** | |||
* @brief Parameters for 'dds pkg import' | |||
*/ | |||
struct { | |||
/// File paths or URLs of packages to import | |||
std::vector<string> items; | |||
/// Allow piping a package tarball in through stdin | |||
bool from_stdin = false; | |||
} import; | |||
/** | |||
* @brief Parameters for 'dds pkg repo' | |||
*/ | |||
struct { | |||
/// The 'pkg repo' subcommand | |||
cli_pkg_repo_subcommand subcommand; | |||
/** | |||
* @brief Parameters of 'dds pkg repo add' | |||
*/ | |||
struct { | |||
/// The repository URL | |||
string url; | |||
/// Whether we should update repo data after adding the repository | |||
bool update = true; | |||
} add; | |||
} repo; | |||
/** | |||
* @brief Paramters for 'dds pkg get' | |||
*/ | |||
struct { | |||
/// Package IDs to download | |||
std::vector<string> pkgs; | |||
} get; | |||
} pkg; | |||
struct { | |||
sdist_subcommand subcommand; | |||
} sdist; | |||
/** | |||
* @brief Parameters for 'dds repoman' | |||
*/ | |||
struct { | |||
/// Shared parameter between repoman subcommands: The directory we are acting upon | |||
path repo_dir; | |||
/// The actual operation we are performing on the repository dir | |||
repoman_subcommand subcommand; | |||
/// Options for 'dds repoman init' | |||
struct { | |||
/// The name of the new repository. If not provided, a random one will be generated | |||
opt_string name; | |||
} init; | |||
/// Options for 'dds repoman import' | |||
struct { | |||
/// sdist tarball file paths to import into the repository | |||
std::vector<fs::path> files; | |||
} import; | |||
/// Options for 'dds repoman remove' | |||
struct { | |||
/// Package IDs of packages to remove | |||
std::vector<string> pkgs; | |||
} remove; | |||
} repoman; | |||
/** | |||
* @brief Attach arguments and subcommands to the given argument parser, binding those arguments | |||
* to the values in this object. | |||
*/ | |||
void setup_parser(debate::argument_parser& parser) noexcept; | |||
}; | |||
} // namespace cli | |||
} // namespace dds |
@@ -205,7 +205,7 @@ void repo_manager::delete_package(package_id pkg_id) { | |||
auto name_dir = pkg_dir() / pkg_id.name; | |||
auto ver_dir = name_dir / pkg_id.version.to_string(); | |||
DDS_E_SCOPE(e_repo_delete_targz{ver_dir}); | |||
DDS_E_SCOPE(e_repo_delete_path{ver_dir}); | |||
if (!fs::is_directory(ver_dir)) { | |||
throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory), |
@@ -30,7 +30,7 @@ struct e_repo_import_targz { | |||
fs::path path; | |||
}; | |||
struct e_repo_delete_targz { | |||
struct e_repo_delete_path { | |||
fs::path path; | |||
}; | |||
@@ -45,7 +45,7 @@ void dds::log::log_print(dds::log::level l, std::string_view msg) noexcept { | |||
return spdlog::level::err; | |||
case level::critical: | |||
return spdlog::level::critical; | |||
case level::_silent: | |||
case level::silent: | |||
return spdlog::level::off; | |||
} | |||
neo_assert_always(invariant, false, "Invalid log level", msg, int(l)); |
@@ -13,7 +13,7 @@ enum class level : int { | |||
warn, | |||
error, | |||
critical, | |||
_silent, | |||
silent, | |||
}; | |||
inline level current_log_level = level::info; |
@@ -2,14 +2,16 @@ | |||
#include <neo/sqlite3/error.hpp> | |||
dds::error_id dds::capture_exception() { | |||
void dds::capture_exception() { | |||
try { | |||
throw; | |||
} catch (const neo::sqlite3::sqlite3_error& e) { | |||
return current_error().load(e_sqlite3_error_exc{std::string(e.what()), e.code()}, | |||
e.code(), | |||
neo::sqlite3::errc{e.code().value()}); | |||
current_error().load(e_sqlite3_error_exc{std::string(e.what()), e.code()}, | |||
e.code(), | |||
neo::sqlite3::errc{e.code().value()}); | |||
} catch (const std::system_error& e) { | |||
return current_error().load(e_system_error_exc{std::string(e.what()), e.code()}, e.code()); | |||
current_error().load(e_system_error_exc{std::string(e.what()), e.code()}, e.code()); | |||
} | |||
// Re-throw as a bare exception. | |||
throw std::exception(); | |||
} |
@@ -38,7 +38,7 @@ struct e_url_string { | |||
* @brief Capture currently in-flight special exceptions as new error object. Works around a bug in | |||
* Boost.LEAF when catching std::system error. | |||
*/ | |||
error_id capture_exception(); | |||
[[noreturn]] void capture_exception(); | |||
/** | |||
* @brief Generate a leaf::on_error object that loads the given expression into the currently |
@@ -31,7 +31,7 @@ def test_build_simple(tmp_project: Project) -> None: | |||
tmp_project.write('src/f.cpp', r'void f() {}') | |||
tmp_project.build() | |||
# Writing again will build again: | |||
time.sleep(0.2) # Sleep long enough to register a file change | |||
time.sleep(0.5) # Sleep long enough to register a file change | |||
tmp_project.write('src/f.cpp', r'bad again') | |||
with pytest.raises(CalledProcessError): | |||
tmp_project.build() | |||
@@ -67,12 +67,7 @@ TEST_PACKAGE: PackageJSON = { | |||
} | |||
def test_empty_with_pkg_dds(tmp_project: Project) -> None: | |||
tmp_project.package_json = TEST_PACKAGE | |||
tmp_project.build() | |||
def test_empty_with_lib_dds(tmp_project: Project) -> None: | |||
def test_empty_with_pkg_json(tmp_project: Project) -> None: | |||
tmp_project.package_json = TEST_PACKAGE | |||
tmp_project.build() | |||
@@ -80,8 +75,5 @@ def test_empty_with_lib_dds(tmp_project: Project) -> None: | |||
def test_empty_sdist_create(tmp_project: Project) -> None: | |||
tmp_project.package_json = TEST_PACKAGE | |||
tmp_project.sdist_create() | |||
def test_empty_sdist_export(tmp_project: Project) -> None: | |||
tmp_project.package_json = TEST_PACKAGE | |||
tmp_project.sdist_export() | |||
assert tmp_project.build_root.joinpath('test-pkg@0.2.2.tar.gz').is_file(), \ | |||
'The expected sdist tarball was not generated' |
@@ -1,17 +1,7 @@ | |||
from pathlib import Path | |||
from dds_ci.testing import Project, RepoFixture | |||
from dds_ci.dds import DDSWrapper | |||
def test_catalog_create(dds_2: DDSWrapper, tmp_path: Path) -> None: | |||
cat_db = tmp_path / 'catalog.db' | |||
assert not cat_db.is_file() | |||
dds_2.run(['catalog', 'create', '--catalog', cat_db]) | |||
assert cat_db.is_file() | |||
def test_catalog_get_git(http_repo: RepoFixture, tmp_project: Project) -> None: | |||
def test_pkg_get(http_repo: RepoFixture, tmp_project: Project) -> None: | |||
http_repo.import_json_data({ | |||
'packages': { | |||
'neo-sqlite3': { | |||
@@ -27,6 +17,6 @@ def test_catalog_get_git(http_repo: RepoFixture, tmp_project: Project) -> None: | |||
} | |||
}) | |||
tmp_project.dds.repo_add(http_repo.url) | |||
tmp_project.dds.catalog_get('neo-sqlite3@0.3.0') | |||
tmp_project.dds.pkg_get('neo-sqlite3@0.3.0') | |||
assert tmp_project.root.joinpath('neo-sqlite3@0.3.0').is_dir() | |||
assert tmp_project.root.joinpath('neo-sqlite3@0.3.0/package.jsonc').is_file() |
@@ -0,0 +1,22 @@ | |||
import subprocess | |||
import pytest | |||
import time | |||
from dds_ci.testing import Project | |||
def test_simple_compile_file(tmp_project: Project) -> None: | |||
""" | |||
Check that changing a source file will update the resulting application. | |||
""" | |||
with pytest.raises(subprocess.CalledProcessError): | |||
tmp_project.compile_file('src/answer.cpp') | |||
tmp_project.write('src/answer.cpp', 'int get_answer() { return 42; }') | |||
# No error: | |||
tmp_project.compile_file('src/answer.cpp') | |||
# Fail: | |||
time.sleep(0.5) | |||
tmp_project.write('src/answer.cpp', 'int get_answer() { return "How many roads must a man walk down?"; }') | |||
with pytest.raises(subprocess.CalledProcessError): | |||
tmp_project.compile_file('src/answer.cpp') |
@@ -1,5 +1,9 @@ | |||
import pytest | |||
from pathlib import Path | |||
from typing import Tuple | |||
import subprocess | |||
from dds_ci import proc | |||
from dds_ci.testing import ProjectOpener, Project | |||
@@ -8,24 +12,67 @@ def test_project(project_opener: ProjectOpener) -> Project: | |||
return project_opener.open('projects/sdist') | |||
def test_create_sdist(test_project: Project) -> None: | |||
def test_create_sdist(test_project: Project, tmp_path: Path) -> None: | |||
# Create in the default location | |||
test_project.sdist_create() | |||
sd_dir = test_project.build_root / 'foo@1.2.3.tar.gz' | |||
assert sd_dir.is_file() | |||
assert sd_dir.is_file(), 'Did not create an sdist in the default location' | |||
# Create in a different location | |||
dest = tmp_path / 'dummy.tar.gz' | |||
test_project.sdist_create(dest=dest) | |||
assert dest.is_file(), 'Did not create an sdist in the new location' | |||
def test_export_sdist(test_project: Project) -> None: | |||
test_project.sdist_export() | |||
assert (test_project.dds.repo_dir / 'foo@1.2.3').is_dir() | |||
def test_import_sdist_archive(test_project: Project) -> None: | |||
@pytest.fixture() | |||
def test_sdist(test_project: Project) -> Tuple[Path, Project]: | |||
repo_content_path = test_project.dds.repo_dir / 'foo@1.2.3' | |||
assert not repo_content_path.is_dir() | |||
test_project.sdist_create() | |||
assert not repo_content_path.is_dir() | |||
test_project.dds.repo_import(test_project.build_root / 'foo@1.2.3.tar.gz') | |||
assert repo_content_path.is_dir() | |||
assert repo_content_path.joinpath('library.jsonc').is_file() | |||
return test_project.build_root / 'foo@1.2.3.tar.gz', test_project | |||
def test_import_sdist_archive(test_sdist: Tuple[Path, Project]) -> None: | |||
sdist, project = test_sdist | |||
repo_content_path = project.dds.repo_dir / 'foo@1.2.3' | |||
project.dds.pkg_import(sdist) | |||
assert repo_content_path.is_dir(), \ | |||
'The package did not appear in the local cache' | |||
assert repo_content_path.joinpath('library.jsonc').is_file(), \ | |||
'The package\'s library.jsonc did not get imported' | |||
# Excluded file will not be in the sdist: | |||
assert not repo_content_path.joinpath('other-file.txt').is_file(), \ | |||
'Non-package content appeared in the package cache' | |||
def test_import_sdist_stdin(test_sdist: Tuple[Path, Project]) -> None: | |||
sdist, project = test_sdist | |||
repo_content_path = project.dds.repo_dir / 'foo@1.2.3' | |||
pipe = subprocess.Popen( | |||
list(proc.flatten_cmd([ | |||
project.dds.path, | |||
project.dds.repo_dir_arg, | |||
'pkg', | |||
'import', | |||
'--stdin', | |||
])), | |||
stdin=subprocess.PIPE, | |||
) | |||
assert pipe.stdin | |||
with sdist.open('rb') as sdist_bin: | |||
buf = sdist_bin.read(1024) | |||
while buf: | |||
pipe.stdin.write(buf) | |||
buf = sdist_bin.read(1024) | |||
pipe.stdin.close() | |||
rc = pipe.wait() | |||
assert rc == 0, 'Subprocess failed' | |||
# project.dds.pkg_import(sdist) | |||
assert repo_content_path.is_dir(), \ | |||
'The package did not appear in the local cache' | |||
assert repo_content_path.joinpath('library.jsonc').is_file(), \ | |||
'The package\'s library.jsonc did not get imported' | |||
# Excluded file will not be in the sdist: | |||
assert not repo_content_path.joinpath('other-file.txt').is_file() | |||
assert not repo_content_path.joinpath('other-file.txt').is_file(), \ | |||
'Non-package content appeared in the package cache' |
@@ -1,11 +1,14 @@ | |||
import multiprocessing | |||
import shutil | |||
from pathlib import Path | |||
from typing import Optional | |||
import copy | |||
from typing import Optional, TypeVar, Iterable | |||
from . import paths, proc, toolchain as tc_mod | |||
from dds_ci.util import Pathish | |||
T = TypeVar('T') | |||
class DDSWrapper: | |||
""" | |||
@@ -23,11 +26,8 @@ class DDSWrapper: | |||
self.catalog_path = Path(catalog_path or (self.repo_dir.parent / 'ci-catalog.db')) | |||
self.default_cwd = default_cwd or Path.cwd() | |||
def clone(self) -> 'DDSWrapper': | |||
return DDSWrapper(self.path, | |||
repo_dir=self.repo_dir, | |||
catalog_path=self.catalog_path, | |||
default_cwd=self.default_cwd) | |||
def clone(self: T) -> T: | |||
return copy.deepcopy(self) | |||
@property | |||
def catalog_path_arg(self) -> str: | |||
@@ -39,6 +39,10 @@ class DDSWrapper: | |||
"""The arguments for --repo-dir""" | |||
return f'--repo-dir={self.repo_dir}' | |||
@property | |||
def project_dir_flag(self) -> str: | |||
return '--project-dir' | |||
def set_repo_scratch(self, path: Pathish) -> None: | |||
self.repo_dir = Path(path) / 'data' | |||
self.catalog_path = Path(path) / 'catalog.db' | |||
@@ -66,12 +70,18 @@ class DDSWrapper: | |||
def catalog_get(self, what: str) -> None: | |||
self.run(['catalog', 'get', self.catalog_path_arg, what]) | |||
def pkg_get(self, what: str) -> None: | |||
self.run(['pkg', 'get', self.catalog_path_arg, what]) | |||
def repo_add(self, url: str) -> None: | |||
self.run(['repo', 'add', self.catalog_path_arg, url, '--update']) | |||
self.run(['pkg', 'repo', 'add', self.catalog_path_arg, url]) | |||
def repo_import(self, sdist: Path) -> None: | |||
self.run(['repo', self.repo_dir_arg, 'import', sdist]) | |||
def pkg_import(self, filepath: Pathish) -> None: | |||
self.run(['pkg', 'import', filepath, self.repo_dir_arg]) | |||
def build(self, | |||
*, | |||
root: Path, | |||
@@ -94,10 +104,28 @@ class DDSWrapper: | |||
self.repo_dir_arg, | |||
self.catalog_path_arg, | |||
f'--jobs={jobs}', | |||
f'--project-dir={root}', | |||
f'{self.project_dir_flag}={root}', | |||
f'--out={build_root}', | |||
]) | |||
def compile_file(self, | |||
paths: Iterable[Pathish], | |||
*, | |||
toolchain: Optional[Pathish] = None, | |||
project_dir: Pathish, | |||
out: Optional[Pathish] = None) -> None: | |||
""" | |||
Run 'dds compile-file' for the given paths. | |||
""" | |||
toolchain = toolchain or tc_mod.get_default_audit_toolchain() | |||
self.run([ | |||
'compile-file', | |||
paths, | |||
f'--toolchain={toolchain}', | |||
f'{self.project_dir_flag}={project_dir}', | |||
f'--out={out}', | |||
]) | |||
def build_deps(self, args: proc.CommandLine, *, toolchain: Optional[Path] = None) -> None: | |||
toolchain = toolchain or tc_mod.get_default_audit_toolchain() | |||
self.run([ | |||
@@ -107,3 +135,20 @@ class DDSWrapper: | |||
self.repo_dir_arg, | |||
args, | |||
]) | |||
class NewDDSWrapper(DDSWrapper): | |||
""" | |||
Wraps the new 'dds' executable with some convenience APIs | |||
""" | |||
@property | |||
def repo_dir_arg(self) -> str: | |||
return f'--pkg-cache-dir={self.repo_dir}' | |||
@property | |||
def catalog_path_arg(self) -> str: | |||
return f'--pkg-db-path={self.catalog_path}' | |||
@property | |||
def project_dir_flag(self) -> str: | |||
return '--project' |
@@ -5,7 +5,7 @@ import subprocess | |||
from .util import Pathish | |||
CommandLineArg = Union[str, PurePath, int, float] | |||
CommandLineArg = Union[str, Pathish, int, float] | |||
CommandLineArg1 = Union[CommandLineArg, Iterable[CommandLineArg]] | |||
CommandLineArg2 = Union[CommandLineArg1, Iterable[CommandLineArg1]] | |||
CommandLineArg3 = Union[CommandLineArg2, Iterable[CommandLineArg2]] | |||
@@ -39,16 +39,10 @@ def flatten_cmd(cmd: CommandLine) -> Iterable[str]: | |||
def run(*cmd: CommandLine, cwd: Optional[Pathish] = None, check: bool = False) -> ProcessResult: | |||
return subprocess.run( | |||
list(flatten_cmd(cmd)), | |||
cwd=cwd, | |||
check=check, | |||
) | |||
command = list(flatten_cmd(cmd)) | |||
return subprocess.run(command, cwd=cwd, check=check) | |||
def check_run(*cmd: CommandLine, cwd: Optional[Pathish] = None) -> ProcessResult: | |||
return subprocess.run( | |||
list(flatten_cmd(cmd)), | |||
cwd=cwd, | |||
check=True, | |||
) | |||
command = list(flatten_cmd(cmd)) | |||
return subprocess.run(command, cwd=cwd, check=True) |
@@ -14,7 +14,7 @@ from _pytest.tmpdir import TempPathFactory | |||
from _pytest.fixtures import FixtureRequest | |||
from dds_ci import toolchain, paths | |||
from ..dds import DDSWrapper | |||
from ..dds import DDSWrapper, NewDDSWrapper | |||
from ..util import Pathish | |||
tc_mod = toolchain | |||
@@ -72,8 +72,8 @@ class Project: | |||
@property | |||
def project_dir_arg(self) -> str: | |||
"""Argument for --project-dir""" | |||
return f'--project-dir={self.root}' | |||
"""Argument for --project""" | |||
return f'--project={self.root}' | |||
def build(self, *, toolchain: Optional[Pathish] = None) -> None: | |||
""" | |||
@@ -82,9 +82,18 @@ class Project: | |||
with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc: | |||
self.dds.build(root=self.root, build_root=self.build_root, toolchain=tc) | |||
def sdist_create(self) -> None: | |||
def compile_file(self, *paths: Pathish, toolchain: Optional[Pathish] = None) -> None: | |||
with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc: | |||
self.dds.compile_file(paths, toolchain=tc, out=self.build_root, project_dir=self.root) | |||
def sdist_create(self, *, dest: Optional[Pathish] = None) -> None: | |||
self.build_root.mkdir(exist_ok=True, parents=True) | |||
self.dds.run(['sdist', 'create', self.project_dir_arg], cwd=self.build_root) | |||
self.dds.run([ | |||
'sdist', | |||
'create', | |||
self.project_dir_arg, | |||
f'--out={dest}' if dest else (), | |||
], cwd=self.build_root) | |||
def sdist_export(self) -> None: | |||
self.dds.run(['sdist', 'export', self.dds.repo_dir_arg, self.project_dir_arg]) | |||
@@ -170,11 +179,12 @@ def tmp_project(request: FixtureRequest, worker_id: str, project_opener: Project | |||
@pytest.fixture(scope='session') | |||
def dds_2(dds_exe: Path) -> DDSWrapper: | |||
return DDSWrapper(dds_exe) | |||
def dds_2(dds_exe: Path) -> NewDDSWrapper: | |||
wr = NewDDSWrapper(dds_exe) | |||
return wr | |||
@pytest.fixture(scope='session') | |||
def dds_exe(pytestconfig: PyTestConfig) -> Path: | |||
opt = pytestconfig.getoption('--dds-exe') or paths.CUR_BUILT_DDS | |||
opt = pytestconfig.getoption('--dds-exe') or paths.BUILD_DIR / 'dds' | |||
return Path(opt) |
@@ -371,7 +371,7 @@ def http_dl_unpack(url: str) -> Iterator[Path]: | |||
def spec_as_local_tgz(dds_exe: Path, spec: SpecPackage) -> Iterator[Path]: | |||
with spec.remote.make_local_dir(spec.name, spec.version) as clone_dir: | |||
out_tgz = clone_dir / 'sdist.tgz' | |||
check_call([str(dds_exe), 'sdist', 'create', f'--project-dir={clone_dir}', f'--out={out_tgz}']) | |||
check_call([str(dds_exe), 'sdist', 'create', f'--project={clone_dir}', f'--out={out_tgz}']) | |||
yield out_tgz | |||