} | } | ||||
} | } | ||||
} | } | ||||
return library_plan::create(lib, std::move(lp), pkg_man.namespace_ + "/" + lib.manifest().name); | |||||
return library_plan::create(lib, | |||||
std::move(lp), | |||||
pkg_man.namespace_.str + "/" + lib.manifest().name.str); | |||||
} | } | ||||
package_plan prepare_one(state& st, const sdist_target& sd) { | package_plan prepare_one(state& st, const sdist_target& sd) { | ||||
package_plan pkg{sd.sd.manifest.id.name, sd.sd.manifest.namespace_}; | |||||
package_plan pkg{sd.sd.manifest.id.name.str, sd.sd.manifest.namespace_.str}; | |||||
auto libs = collect_libraries(sd.sd.path); | auto libs = collect_libraries(sd.sd.path); | ||||
for (const auto& lib : libs) { | for (const auto& lib : libs) { | ||||
pkg.add_library(prepare_library(st, sd, lib, sd.sd.manifest)); | pkg.add_library(prepare_library(st, sd, lib, sd.sd.manifest)); | ||||
usage_requirement_map ureqs; | usage_requirement_map ureqs; | ||||
for (const auto& pkg : plan.packages()) { | for (const auto& pkg : plan.packages()) { | ||||
for (const auto& lib : pkg.libraries()) { | for (const auto& lib : pkg.libraries()) { | ||||
auto& lib_reqs = ureqs.add(pkg.namespace_(), lib.name()); | |||||
auto& lib_reqs = ureqs.add(pkg.namespace_(), lib.name().str); | |||||
lib_reqs.include_paths.push_back(lib.library_().public_include_dir()); | lib_reqs.include_paths.push_back(lib.library_().public_include_dir()); | ||||
lib_reqs.uses = lib.library_().manifest().uses; | lib_reqs.uses = lib.library_().manifest().uses; | ||||
lib_reqs.links = lib.library_().manifest().links; | lib_reqs.links = lib.library_().manifest().links; | ||||
fs::create_directories(lml_path.parent_path()); | fs::create_directories(lml_path.parent_path()); | ||||
auto out = open(lml_path, std::ios::binary | std::ios::out); | auto out = open(lml_path, std::ios::binary | std::ios::out); | ||||
out << "Type: Library\n" | out << "Type: Library\n" | ||||
<< "Name: " << lib.name() << '\n' | |||||
<< "Name: " << lib.name().str << '\n' | |||||
<< "Include-Path: " << lib.library_().public_include_dir().generic_string() << '\n'; | << "Include-Path: " << lib.library_().public_include_dir().generic_string() << '\n'; | ||||
for (auto&& use : lib.uses()) { | for (auto&& use : lib.uses()) { | ||||
out << "Uses: " << use.namespace_ << "/" << use.name << '\n'; | out << "Uses: " << use.namespace_ << "/" << use.name << '\n'; | ||||
<< "Name: " << pkg.name() << '\n' | << "Name: " << pkg.name() << '\n' | ||||
<< "Namespace: " << pkg.namespace_() << '\n'; | << "Namespace: " << pkg.namespace_() << '\n'; | ||||
for (const auto& lib : pkg.libraries()) { | for (const auto& lib : pkg.libraries()) { | ||||
auto lml_path = lmp_path.parent_path() / pkg.namespace_() / (lib.name() + ".lml"); | |||||
auto lml_path = lmp_path.parent_path() / pkg.namespace_() / (lib.name().str + ".lml"); | |||||
write_lml(env, lib, lml_path); | write_lml(env, lib, lml_path); | ||||
out << "Library: " << lml_path.generic_string() << '\n'; | out << "Library: " << lml_path.generic_string() << '\n'; | ||||
} | } | ||||
std::ostream& out, | std::ostream& out, | ||||
const package_plan& pkg, | const package_plan& pkg, | ||||
const library_plan& lib) { | const library_plan& lib) { | ||||
fmt::print(out, "# Library {}/{}\n", pkg.namespace_(), lib.name()); | |||||
auto cmake_name = fmt::format("{}::{}", pkg.namespace_(), lib.name()); | |||||
fmt::print(out, "# Library {}/{}\n", pkg.namespace_(), lib.name().str); | |||||
auto cmake_name = fmt::format("{}::{}", pkg.namespace_(), lib.name().str); | |||||
auto cm_kind = lib.archive_plan().has_value() ? "STATIC" : "INTERFACE"; | auto cm_kind = lib.archive_plan().has_value() ? "STATIC" : "INTERFACE"; | ||||
fmt::print( | fmt::print( | ||||
out, | out, |
std::vector<source_file> lib_sources; | std::vector<source_file> lib_sources; | ||||
std::vector<source_file> template_sources; | std::vector<source_file> template_sources; | ||||
auto qual_name = std::string(qual_name_.value_or(lib.manifest().name)); | |||||
auto qual_name = std::string(qual_name_.value_or(lib.manifest().name.str)); | |||||
// Collect the source for this library. This will look for any compilable sources in the | // Collect the source for this library. This will look for any compilable sources in the | ||||
// `src/` subdirectory of the library. | // `src/` subdirectory of the library. | ||||
std::optional<create_archive_plan> archive_plan; | std::optional<create_archive_plan> archive_plan; | ||||
if (!lib_compile_files.empty()) { | if (!lib_compile_files.empty()) { | ||||
dds_log(debug, "Generating an archive library for {}", qual_name); | dds_log(debug, "Generating an archive library for {}", qual_name); | ||||
archive_plan.emplace(lib.manifest().name, | |||||
archive_plan.emplace(lib.manifest().name.str, | |||||
qual_name, | qual_name, | ||||
params.out_subdir, | params.out_subdir, | ||||
std::move(lib_compile_files)); | std::move(lib_compile_files)); |
{ | { | ||||
"lib", | "lib", | ||||
json_data::mapping_type{ | json_data::mapping_type{ | ||||
{"name", lib.manifest().name}, | |||||
{"name", lib.manifest().name.str}, | |||||
{"root", lib.path().string()}, | {"root", lib.path().string()}, | ||||
}, | }, | ||||
}, | }, |
namespace dds::cli::cmd { | namespace dds::cli::cmd { | ||||
int build(const options& opts) { | |||||
static int _build(const options& opts) { | |||||
if (!opts.build.add_repos.empty()) { | if (!opts.build.add_repos.empty()) { | ||||
auto cat = opts.open_pkg_db(); | auto cat = opts.open_pkg_db(); | ||||
for (auto& str : opts.build.add_repos) { | for (auto& str : opts.build.add_repos) { | ||||
return 0; | return 0; | ||||
} | } | ||||
int build(const options& opts) { | |||||
return handle_build_error([&] { return _build(opts); }); | |||||
} | |||||
} // namespace dds::cli::cmd | } // namespace dds::cli::cmd |
#include "./build_common.hpp" | #include "./build_common.hpp" | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/pkg/cache.hpp> | #include <dds/pkg/cache.hpp> | ||||
#include <dds/pkg/db.hpp> | #include <dds/pkg/db.hpp> | ||||
#include <dds/pkg/get/get.hpp> | #include <dds/pkg/get/get.hpp> | ||||
#include <boost/leaf/handle_exception.hpp> | |||||
using namespace dds; | using namespace dds; | ||||
builder dds::cli::create_project_builder(const dds::cli::options& opts) { | builder dds::cli::create_project_builder(const dds::cli::options& opts) { | ||||
builder.add(sdist{std::move(man), opts.project_dir}, main_params); | builder.add(sdist{std::move(man), opts.project_dir}, main_params); | ||||
return builder; | return builder; | ||||
} | } | ||||
int dds::cli::handle_build_error(std::function<int()> fn) { | |||||
return boost::leaf::try_catch( // | |||||
fn, | |||||
[](user_error<errc::test_failure> exc) { | |||||
write_error_marker("build-failed-test-failed"); | |||||
dds_log(error, "{}", exc.what()); | |||||
dds_log(error, "{}", exc.explanation()); | |||||
dds_log(error, "Refer: {}", exc.error_reference()); | |||||
return 1; | |||||
}); | |||||
} |
dds::builder create_project_builder(const options& opts); | dds::builder create_project_builder(const options& opts); | ||||
int handle_build_error(std::function<int()>); | |||||
} // namespace dds::cli | } // namespace dds::cli |
#include "../options.hpp" | #include "../options.hpp" | ||||
#include "./build_common.hpp" | |||||
#include <dds/build/builder.hpp> | #include <dds/build/builder.hpp> | ||||
#include <dds/build/params.hpp> | #include <dds/build/params.hpp> | ||||
#include <dds/pkg/cache.hpp> | #include <dds/pkg/cache.hpp> | ||||
namespace dds::cli::cmd { | namespace dds::cli::cmd { | ||||
int build_deps(const options& opts) { | |||||
static int _build_deps(const options& opts) { | |||||
dds::build_params params{ | dds::build_params params{ | ||||
.out_root = opts.out_path.value_or(fs::current_path() / "_deps"), | .out_root = opts.out_path.value_or(fs::current_path() / "_deps"), | ||||
.existing_lm_index = {}, | .existing_lm_index = {}, | ||||
return 0; | return 0; | ||||
} | } | ||||
int build_deps(const options& opts) { | |||||
return handle_build_error([&] { return _build_deps(opts); }); | |||||
} | |||||
} // namespace dds::cli::cmd | } // namespace dds::cli::cmd |
}); | }); | ||||
for (const auto& [name, grp] : grp_by_name) { | for (const auto& [name, grp] : grp_by_name) { | ||||
dds_log(info, "{}:", name); | |||||
dds_log(info, "{}:", name.str); | |||||
for (const dds::sdist& sd : grp) { | for (const dds::sdist& sd : grp) { | ||||
dds_log(info, " - {}", sd.manifest.id.version.to_string()); | dds_log(info, " - {}", sd.manifest.id.version.to_string()); | ||||
} | } |
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/toolchain.hpp> | #include <dds/error/toolchain.hpp> | ||||
#include <dds/sdist/library/manifest.hpp> | |||||
#include <dds/sdist/package.hpp> | |||||
#include <dds/util/http/pool.hpp> | #include <dds/util/http/pool.hpp> | ||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <dds/util/result.hpp> | #include <dds/util/result.hpp> | ||||
write_error_marker("package-json5-parse-error"); | write_error_marker("package-json5-parse-error"); | ||||
return 1; | return 1; | ||||
}, | }, | ||||
[](user_error<errc::test_failure> exc, matchv<cli::subcommand::build>) { | |||||
write_error_marker("build-failed-test-failed"); | |||||
dds_log(error, "{}", exc.what()); | |||||
dds_log(error, "{}", exc.explanation()); | |||||
dds_log(error, "Refer: {}", exc.error_reference()); | |||||
return 1; | |||||
}, | |||||
[](boost::leaf::catch_<error_base> exc) { | [](boost::leaf::catch_<error_base> exc) { | ||||
dds_log(error, "{}", exc.value().what()); | dds_log(error, "{}", exc.value().what()); | ||||
dds_log(error, "{}", exc.value().explanation()); | dds_log(error, "{}", exc.value().explanation()); | ||||
} | } | ||||
return 1; | return 1; | ||||
}, | }, | ||||
[](e_name_str badname, | |||||
invalid_name_reason why, | |||||
e_dependency_string depstr, | |||||
e_package_manifest_path* pkman_path) { | |||||
dds_log( | |||||
error, | |||||
"Invalid package name '.bold.red[{}]' in dependency string '.br.red[{}]': .br.yellow[{}]"_styled, | |||||
badname.value, | |||||
depstr.value, | |||||
invalid_name_reason_str(why)); | |||||
if (pkman_path) { | |||||
dds_log(error, | |||||
" (While reading package manifest from [.bold.yellow[{}]])"_styled, | |||||
pkman_path->value); | |||||
} | |||||
write_error_marker("invalid-pkg-dep-name"); | |||||
return 1; | |||||
}, | |||||
[](e_pkg_name_str, | |||||
e_name_str badname, | |||||
invalid_name_reason why, | |||||
e_package_manifest_path* pkman_path) { | |||||
dds_log(error, | |||||
"Invalid package name '.bold.red[{}]': .br.yellow[{}]"_styled, | |||||
badname.value, | |||||
invalid_name_reason_str(why)); | |||||
if (pkman_path) { | |||||
dds_log(error, | |||||
" (While reading package manifest from [.bold.yellow[{}]])"_styled, | |||||
pkman_path->value); | |||||
} | |||||
write_error_marker("invalid-pkg-name"); | |||||
return 1; | |||||
}, | |||||
[](e_pkg_namespace_str, | |||||
e_name_str badname, | |||||
invalid_name_reason why, | |||||
e_package_manifest_path* pkman_path) { | |||||
dds_log(error, | |||||
"Invalid package namespace '.bold.red[{}]': .br.yellow[{}]"_styled, | |||||
badname.value, | |||||
invalid_name_reason_str(why)); | |||||
if (pkman_path) { | |||||
dds_log(error, | |||||
" (While reading package manifest from [.bold.yellow[{}]])"_styled, | |||||
pkman_path->value); | |||||
} | |||||
write_error_marker("invalid-pkg-namespace-name"); | |||||
return 1; | |||||
}, | |||||
[](e_library_manifest_path libpath, invalid_name_reason why, e_name_str badname) { | |||||
dds_log(error, | |||||
"Invalid library name '.bold.red[{}]': .br.yellow[{}]"_styled, | |||||
badname.value, | |||||
invalid_name_reason_str(why)); | |||||
dds_log(error, | |||||
" (While reading library manifest from [.bold.yellow[{}]]"_styled, | |||||
libpath.value); | |||||
write_error_marker("invalid-lib-name"); | |||||
return 1; | |||||
}, | |||||
[](e_system_error_exc exc, boost::leaf::verbose_diagnostic_info const& diag) { | [](e_system_error_exc exc, boost::leaf::verbose_diagnostic_info const& diag) { | ||||
dds_log(critical, | dds_log(critical, | ||||
"An unhandled std::system_error arose. THIS IS A DDS BUG! Info: {}", | "An unhandled std::system_error arose. THIS IS A DDS BUG! Info: {}", |
PRAGMA foreign_keys = 1; | PRAGMA foreign_keys = 1; | ||||
DROP TABLE IF EXISTS dds_meta; | DROP TABLE IF EXISTS dds_meta; | ||||
CREATE TABLE IF NOT EXISTS dds_meta_1 AS | CREATE TABLE IF NOT EXISTS dds_meta_1 AS | ||||
WITH init (version) AS (VALUES ('eggs')) | |||||
WITH init (version) AS (VALUES ('')) | |||||
SELECT * FROM init; | SELECT * FROM init; | ||||
)"); | )"); | ||||
nsql::transaction_guard tr{db}; | nsql::transaction_guard tr{db}; |
#include "./deps.hpp" | #include "./deps.hpp" | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/on_error.hpp> | |||||
#include <dds/error/result.hpp> | |||||
#include <dds/util/log.hpp> | |||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <boost/leaf/exception.hpp> | |||||
#include <ctre.hpp> | |||||
#include <json5/parse_data.hpp> | #include <json5/parse_data.hpp> | ||||
#include <semester/walk.hpp> | #include <semester/walk.hpp> | ||||
using namespace dds; | using namespace dds; | ||||
dependency dependency::parse_depends_string(std::string_view str) { | dependency dependency::parse_depends_string(std::string_view str) { | ||||
DDS_E_SCOPE(e_dependency_string{std::string(str)}); | |||||
auto sep_pos = str.find_first_of("=@^~+"); | auto sep_pos = str.find_first_of("=@^~+"); | ||||
if (sep_pos == str.npos) { | if (sep_pos == str.npos) { | ||||
throw_user_error<errc::invalid_version_range_string>("Invalid dependency string '{}'", str); | throw_user_error<errc::invalid_version_range_string>("Invalid dependency string '{}'", str); | ||||
} | } | ||||
auto name = str.substr(0, sep_pos); | |||||
auto name = *dds::name::from_string(str.substr(0, sep_pos)); | |||||
if (str[sep_pos] == '@') { | |||||
++sep_pos; | |||||
if (str[sep_pos] != '@') { | |||||
static bool did_warn = false; | |||||
if (!did_warn) { | |||||
dds_log(warn, | |||||
"Dependency version ranges are deprecated. All are treated as " | |||||
"same-major-version. (Parsing dependency '{}')", | |||||
str); | |||||
} | |||||
did_warn = true; | |||||
} | } | ||||
auto range_str = str.substr(sep_pos); | |||||
auto range_str = "^" + std::string(str.substr(sep_pos + 1)); | |||||
try { | try { | ||||
auto rng = semver::range::parse_restricted(range_str); | auto rng = semver::range::parse_restricted(range_str); | ||||
return dependency{std::string(name), {rng.low(), rng.high()}}; | |||||
return dependency{name, {rng.low(), rng.high()}}; | |||||
} catch (const semver::invalid_range&) { | } catch (const semver::invalid_range&) { | ||||
throw_user_error<errc::invalid_version_range_string>( | throw_user_error<errc::invalid_version_range_string>( | ||||
"Invalid version range string '{}' in dependency string '{}'", range_str, str); | "Invalid version range string '{}' in dependency string '{}'", range_str, str); | ||||
std::string dependency::to_string() const noexcept { | std::string dependency::to_string() const noexcept { | ||||
std::stringstream strm; | std::stringstream strm; | ||||
strm << name << "@"; | |||||
strm << name.str << "@"; | |||||
if (versions.num_intervals() == 1) { | if (versions.num_intervals() == 1) { | ||||
auto iv = *versions.iter_intervals().begin(); | auto iv = *versions.iter_intervals().begin(); | ||||
if (iv.high == iv.low.next_after()) { | if (iv.high == iv.low.next_after()) { | ||||
return strm.str(); | return strm.str(); | ||||
} | } | ||||
if (iv.low == semver::version() && iv.high == semver::version::max_version()) { | if (iv.low == semver::version() && iv.high == semver::version::max_version()) { | ||||
return name; | |||||
return name.str; | |||||
} | } | ||||
strm << "[" << iv_string(iv) << "]"; | strm << "[" << iv_string(iv) << "]"; | ||||
return strm.str(); | return strm.str(); |
#pragma once | #pragma once | ||||
#include <dds/pkg/name.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <pubgrub/interval.hpp> | #include <pubgrub/interval.hpp> | ||||
using version_range_set = pubgrub::interval_set<semver::version>; | using version_range_set = pubgrub::interval_set<semver::version>; | ||||
struct e_dependency_string { | |||||
std::string value; | |||||
}; | |||||
struct dependency { | struct dependency { | ||||
std::string name; | |||||
dds::name name; | |||||
version_range_set versions; | version_range_set versions; | ||||
static dependency parse_depends_string(std::string_view str); | static dependency parse_depends_string(std::string_view str); |
}; | }; | ||||
auto cur = GENERATE(Catch::Generators::values<case_>({ | auto cur = GENERATE(Catch::Generators::values<case_>({ | ||||
{"foo@1.2.3", "foo", "1.2.3", "1.2.4"}, | |||||
{"foo=1.2.3", "foo", "1.2.3", "1.2.4"}, | |||||
{"foo@1.2.3", "foo", "1.2.3", "2.0.0"}, | |||||
{"foo=1.2.3", "foo", "1.2.3", "2.0.0"}, | |||||
{"foo^1.2.3", "foo", "1.2.3", "2.0.0"}, | {"foo^1.2.3", "foo", "1.2.3", "2.0.0"}, | ||||
{"foo~1.2.3", "foo", "1.2.3", "1.3.0"}, | |||||
{"foo+1.2.3", "foo", "1.2.3", semver::version::max_version().to_string()}, | |||||
{"foo~1.2.3", "foo", "1.2.3", "2.0.0"}, | |||||
})); | })); | ||||
auto dep = dds::dependency::parse_depends_string(cur.depstr); | auto dep = dds::dependency::parse_depends_string(cur.depstr); | ||||
CHECK(dep.name == cur.name); | |||||
CHECK(dep.name.str == cur.name); | |||||
CHECK(dep.versions.num_intervals() == 1); | CHECK(dep.versions.num_intervals() == 1); | ||||
auto ver_iv = *dep.versions.iter_intervals().begin(); | auto ver_iv = *dep.versions.iter_intervals().begin(); | ||||
CHECK(ver_iv.low == semver::version::parse(cur.low)); | CHECK(ver_iv.low == semver::version::parse(cur.low)); |
#include "./handle.hpp" | |||||
#include <dds/util/log.hpp> | |||||
#include <boost/leaf/handle_exception.hpp> | |||||
#include <boost/leaf/result.hpp> | |||||
#include <fmt/ostream.h> | |||||
using namespace dds; | |||||
void dds::leaf_handle_unknown_void(std::string_view message, | |||||
const boost::leaf::verbose_diagnostic_info& info) { | |||||
dds_log(warn, message); | |||||
dds_log(warn, "An unhandled error occurred:\n{}", info); | |||||
} |
#pragma once | |||||
#include <neo/fwd.hpp> | |||||
#include <string_view> | |||||
namespace boost::leaf { | |||||
class verbose_diagnostic_info; | |||||
} // namespace boost::leaf | |||||
namespace dds { | |||||
void leaf_handle_unknown_void(std::string_view message, | |||||
const boost::leaf::verbose_diagnostic_info&); | |||||
template <typename T> | |||||
auto leaf_handle_unknown(std::string_view message, T&& val) { | |||||
return [val = NEO_FWD(val), message](const boost::leaf::verbose_diagnostic_info& info) { | |||||
leaf_handle_unknown_void(message, info); | |||||
return val; | |||||
}; | |||||
} | |||||
} // namespace dds |
#pragma once | #pragma once | ||||
#include <neo/pp.hpp> | |||||
#include <boost/leaf/on_error.hpp> | #include <boost/leaf/on_error.hpp> | ||||
/** | /** |
using boost::leaf::new_error; | using boost::leaf::new_error; | ||||
struct e_human_message { | |||||
std::string value; | |||||
}; | |||||
} // namespace dds | } // namespace dds |
#include "./cache.hpp" | #include "./cache.hpp" | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/handle.hpp> | |||||
#include <dds/pkg/db.hpp> | #include <dds/pkg/db.hpp> | ||||
#include <dds/sdist/dist.hpp> | #include <dds/sdist/dist.hpp> | ||||
#include <dds/sdist/library/manifest.hpp> | |||||
#include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <boost/leaf/handle_exception.hpp> | |||||
#include <fansi/styled.hpp> | |||||
#include <neo/ref.hpp> | #include <neo/ref.hpp> | ||||
#include <range/v3/action/sort.hpp> | #include <range/v3/action/sort.hpp> | ||||
#include <range/v3/action/unique.hpp> | #include <range/v3/action/unique.hpp> | ||||
#include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
using namespace dds; | using namespace dds; | ||||
using namespace fansi::literals; | |||||
using namespace ranges; | using namespace ranges; | ||||
void pkg_cache::_log_blocking(path_ref dirpath) noexcept { | void pkg_cache::_log_blocking(path_ref dirpath) noexcept { | ||||
fs::path pkg_cache::default_local_path() noexcept { return dds_data_dir() / "pkg"; } | fs::path pkg_cache::default_local_path() noexcept { return dds_data_dir() / "pkg"; } | ||||
pkg_cache pkg_cache::_open_for_directory(bool writeable, path_ref dirpath) { | |||||
auto try_read_sdist = [](path_ref p) -> std::optional<sdist> { | |||||
if (starts_with(p.filename().string(), ".")) { | |||||
return std::nullopt; | |||||
} | |||||
try { | |||||
return sdist::from_directory(p); | |||||
} catch (const std::runtime_error& e) { | |||||
dds_log(error, | |||||
namespace { | |||||
std::optional<sdist> try_open_sdist_for_directory(path_ref p) noexcept { | |||||
if (starts_with(p.filename().string(), ".")) { | |||||
return std::nullopt; | |||||
} | |||||
return boost::leaf::try_catch( // | |||||
[&] { return std::make_optional(sdist::from_directory(p)); }, | |||||
[&](boost::leaf::catch_<std::runtime_error> exc) { | |||||
dds_log(warn, | |||||
"Failed to load source distribution from directory '{}': {}", | "Failed to load source distribution from directory '{}': {}", | ||||
p.string(), | p.string(), | ||||
e.what()); | |||||
exc.value().what()); | |||||
return std::nullopt; | return std::nullopt; | ||||
} | |||||
}; | |||||
}, | |||||
[&](e_package_manifest_path*, | |||||
e_library_manifest_path* lman_path, | |||||
e_pkg_namespace_str* is_namespace, | |||||
e_pkg_name_str* is_pkgname, | |||||
e_name_str bad_name, | |||||
invalid_name_reason why) { | |||||
dds_log( | |||||
warn, | |||||
"Failed to load a source distribution contained in the package cache directory"); | |||||
dds_log(warn, | |||||
"The invalid source distribution is in [.bold.yellow[{}]]"_styled, | |||||
p.string()); | |||||
if (is_namespace) { | |||||
dds_log(warn, | |||||
"Invalid package namespace '.bold.yellow[{}]'"_styled, | |||||
bad_name.value); | |||||
} else if (is_pkgname) { | |||||
dds_log(warn, "Invalid package name '.bold.yellow[{}]'"_styled, bad_name.value); | |||||
} else if (lman_path) { | |||||
dds_log( | |||||
warn, | |||||
"Invalid library name '.bold.yellow[{}]' (Defined in [.bold.yellow[{}]])"_styled, | |||||
bad_name.value, | |||||
lman_path->value); | |||||
} | |||||
dds_log(warn, " (.bold.yellow[{}])"_styled, invalid_name_reason_str(why)); | |||||
dds_log(warn, "We will ignore this directory and not load it as an available package"); | |||||
return std::nullopt; | |||||
}, | |||||
leaf_handle_unknown(fmt::format("Failed to load source distribution from directory [{}]", | |||||
p.string()), | |||||
std::nullopt)); | |||||
} | |||||
} // namespace | |||||
pkg_cache pkg_cache::_open_for_directory(bool writeable, path_ref dirpath) { | |||||
auto entries = | auto entries = | ||||
// Get the top-level `name-version` dirs | // Get the top-level `name-version` dirs | ||||
fs::directory_iterator(dirpath) // | fs::directory_iterator(dirpath) // | ||||
| neo::lref // | | neo::lref // | ||||
// Convert each dir into an `sdist` object | // Convert each dir into an `sdist` object | ||||
| ranges::views::transform(try_read_sdist) // | |||||
| ranges::views::transform(try_open_sdist_for_directory) // | |||||
// Drop items that failed to load | // Drop items that failed to load | ||||
| ranges::views::filter([](auto&& opt) { return opt.has_value(); }) // | | ranges::views::filter([](auto&& opt) { return opt.has_value(); }) // | ||||
| ranges::views::transform([](auto&& opt) { return *opt; }) // | | ranges::views::transform([](auto&& opt) { return *opt; }) // | ||||
[&](std::string_view name) -> std::vector<pkg_id> { | [&](std::string_view name) -> std::vector<pkg_id> { | ||||
auto mine = ranges::views::all(_sdists) // | auto mine = ranges::views::all(_sdists) // | ||||
| ranges::views::filter( | | ranges::views::filter( | ||||
[&](const sdist& sd) { return sd.manifest.id.name == name; }) | |||||
[&](const sdist& sd) { return sd.manifest.id.name.str == name; }) | |||||
| ranges::views::transform([](const sdist& sd) { return sd.manifest.id; }); | | ranges::views::transform([](const sdist& sd) { return sd.manifest.id; }); | ||||
auto avail = ctlg.by_name(name); | auto avail = ctlg.by_name(name); | ||||
auto all = ranges::views::concat(mine, avail) | ranges::to_vector; | auto all = ranges::views::concat(mine, avail) | ranges::to_vector; |
void do_store_pkg(neo::sqlite3::database& db, | void do_store_pkg(neo::sqlite3::database& db, | ||||
neo::sqlite3::statement_cache& st_cache, | neo::sqlite3::statement_cache& st_cache, | ||||
const pkg_listing& pkg) { | const pkg_listing& pkg) { | ||||
dds_log(debug, "Recording package {}@{}", pkg.ident.name, pkg.ident.version.to_string()); | |||||
dds_log(debug, "Recording package {}@{}", pkg.ident.name.str, pkg.ident.version.to_string()); | |||||
auto& store_pkg_st = st_cache(R"( | auto& store_pkg_st = st_cache(R"( | ||||
INSERT OR REPLACE INTO dds_pkgs | INSERT OR REPLACE INTO dds_pkgs | ||||
(name, version, remote_url, description) | (name, version, remote_url, description) | ||||
(?, ?, ?, ?) | (?, ?, ?, ?) | ||||
)"_sql); | )"_sql); | ||||
nsql::exec(store_pkg_st, | nsql::exec(store_pkg_st, | ||||
pkg.ident.name, | |||||
pkg.ident.name.str, | |||||
pkg.ident.version.to_string(), | pkg.ident.version.to_string(), | ||||
pkg.remote_pkg.to_url_string(), | pkg.remote_pkg.to_url_string(), | ||||
pkg.description); | pkg.description); | ||||
assert(dep.versions.num_intervals() == 1); | assert(dep.versions.num_intervals() == 1); | ||||
auto iv_1 = *dep.versions.iter_intervals().begin(); | auto iv_1 = *dep.versions.iter_intervals().begin(); | ||||
dds_log(trace, " Depends on: {}", dep.to_string()); | dds_log(trace, " Depends on: {}", dep.to_string()); | ||||
nsql::exec(new_dep_st, db_pkg_id, dep.name, iv_1.low.to_string(), iv_1.high.to_string()); | |||||
nsql::exec(new_dep_st, | |||||
db_pkg_id, | |||||
dep.name.str, | |||||
iv_1.low.to_string(), | |||||
iv_1.high.to_string()); | |||||
} | } | ||||
} | } | ||||
result<pkg_listing> pkg_db::get(const pkg_id& pk_id) const noexcept { | result<pkg_listing> pkg_db::get(const pkg_id& pk_id) const noexcept { | ||||
auto ver_str = pk_id.version.to_string(); | auto ver_str = pk_id.version.to_string(); | ||||
dds_log(trace, "Lookup package {}@{}", pk_id.name, ver_str); | |||||
dds_log(trace, "Lookup package {}@{}", pk_id.name.str, ver_str); | |||||
auto& st = _stmt_cache(R"( | auto& st = _stmt_cache(R"( | ||||
SELECT | SELECT | ||||
pkg_id, | pkg_id, | ||||
ORDER BY pkg_id DESC | ORDER BY pkg_id DESC | ||||
)"_sql); | )"_sql); | ||||
st.reset(); | st.reset(); | ||||
st.bindings() = std::forward_as_tuple(pk_id.name, ver_str); | |||||
st.bindings() = std::forward_as_tuple(pk_id.name.str, ver_str); | |||||
auto ec = st.step(std::nothrow); | auto ec = st.step(std::nothrow); | ||||
if (ec == nsql::errc::done) { | if (ec == nsql::errc::done) { | ||||
return new_error([&] { | return new_error([&] { | ||||
} | } | ||||
neo_assert(invariant, | neo_assert(invariant, | ||||
pk_id.name == name && pk_id.version == semver::version::parse(version), | |||||
pk_id.name.str == name && pk_id.version == semver::version::parse(version), | |||||
"Package metadata does not match", | "Package metadata does not match", | ||||
pk_id.to_string(), | pk_id.to_string(), | ||||
name, | name, | ||||
} | } | ||||
std::vector<dependency> pkg_db::dependencies_of(const pkg_id& pkg) const noexcept { | std::vector<dependency> pkg_db::dependencies_of(const pkg_id& pkg) const noexcept { | ||||
dds_log(trace, "Lookup dependencies of {}@{}", pkg.name, pkg.version.to_string()); | |||||
dds_log(trace, "Lookup dependencies of {}", pkg.to_string()); | |||||
return nsql::exec_tuples<std::string, | return nsql::exec_tuples<std::string, | ||||
std::string, | std::string, | ||||
std::string>( // | std::string>( // | ||||
WHERE pkg_id IN this_pkg_id | WHERE pkg_id IN this_pkg_id | ||||
ORDER BY dep_name | ORDER BY dep_name | ||||
)"_sql), | )"_sql), | ||||
pkg.name, | |||||
pkg.name.str, | |||||
pkg.version.to_string()) // | pkg.version.to_string()) // | ||||
| neo::lref // | | neo::lref // | ||||
| ranges::views::transform([](auto&& pair) { | | ranges::views::transform([](auto&& pair) { |
auto pkgs = db.by_name("foo"); | auto pkgs = db.by_name("foo"); | ||||
REQUIRE(pkgs.size() == 1); | REQUIRE(pkgs.size() == 1); | ||||
CHECK(pkgs[0].name == "foo"); | |||||
CHECK(pkgs[0].name.str == "foo"); | |||||
CHECK(pkgs[0].version == semver::version::parse("1.2.3")); | CHECK(pkgs[0].version == semver::version::parse("1.2.3")); | ||||
auto info = db.get(pkgs[0]); | auto info = db.get(pkgs[0]); | ||||
REQUIRE(info); | REQUIRE(info); | ||||
}); | }); | ||||
auto pkgs = db.by_name("foo"); | auto pkgs = db.by_name("foo"); | ||||
REQUIRE(pkgs.size() == 1); | REQUIRE(pkgs.size() == 1); | ||||
CHECK(pkgs[0].name == "foo"); | |||||
CHECK(pkgs[0].name.str == "foo"); | |||||
auto deps = db.dependencies_of(pkgs[0]); | auto deps = db.dependencies_of(pkgs[0]); | ||||
CHECK(deps.size() == 2); | CHECK(deps.size() == 2); | ||||
CHECK(deps[0].name == "bar"); | |||||
CHECK(deps[1].name == "baz"); | |||||
CHECK(deps[0].name.str == "bar"); | |||||
CHECK(deps[1].name.str == "baz"); | |||||
} | } |
void dds_http_remote_pkg::do_get_raw(path_ref dest) const { | void dds_http_remote_pkg::do_get_raw(path_ref dest) const { | ||||
auto http_url = repo_url; | auto http_url = repo_url; | ||||
fs::path path = fs::path(repo_url.path) / "pkg" / pkg_id.name / pkg_id.version.to_string() | |||||
fs::path path = fs::path(repo_url.path) / "pkg" / pkg_id.name.str / pkg_id.version.to_string() | |||||
/ "sdist.tar.gz"; | / "sdist.tar.gz"; | ||||
http_url.path = path.lexically_normal().generic_string(); | http_url.path = path.lexically_normal().generic_string(); | ||||
http_remote_pkg http; | http_remote_pkg http; |
auto pkg = dds::dds_http_remote_pkg::from_url( | auto pkg = dds::dds_http_remote_pkg::from_url( | ||||
neo::url::parse("dds+http://foo.bar/repo-dir/egg@1.2.3")); | neo::url::parse("dds+http://foo.bar/repo-dir/egg@1.2.3")); | ||||
CHECK(pkg.repo_url.to_string() == "http://foo.bar/repo-dir"); | CHECK(pkg.repo_url.to_string() == "http://foo.bar/repo-dir"); | ||||
CHECK(pkg.pkg_id.name == "egg"); | |||||
CHECK(pkg.pkg_id.name.str == "egg"); | |||||
CHECK(pkg.pkg_id.version.to_string() == "1.2.3"); | CHECK(pkg.pkg_id.version.to_string() == "1.2.3"); | ||||
CHECK(pkg.to_url_string() == "dds+http://foo.bar/repo-dir/egg@1.2.3"); | CHECK(pkg.to_url_string() == "dds+http://foo.bar/repo-dir/egg@1.2.3"); | ||||
} | } |
using namespace dds; | using namespace dds; | ||||
pkg_id pkg_id::parse(const std::string_view s) { | pkg_id pkg_id::parse(const std::string_view s) { | ||||
DDS_E_SCOPE(e_invalid_pkg_id_str{std::string(s)}); | |||||
DDS_E_SCOPE(e_pkg_id_str{std::string(s)}); | |||||
auto at_pos = s.find('@'); | auto at_pos = s.find('@'); | ||||
if (at_pos == s.npos) { | if (at_pos == s.npos) { | ||||
BOOST_LEAF_THROW_EXCEPTION( | BOOST_LEAF_THROW_EXCEPTION( | ||||
make_user_error<errc::invalid_pkg_id>("Package ID must contain an '@' symbol")); | make_user_error<errc::invalid_pkg_id>("Package ID must contain an '@' symbol")); | ||||
} | } | ||||
auto name = s.substr(0, at_pos); | |||||
auto name = *dds::name::from_string(s.substr(0, at_pos)); | |||||
auto ver_str = s.substr(at_pos + 1); | auto ver_str = s.substr(at_pos + 1); | ||||
try { | try { | ||||
return {std::string(name), semver::version::parse(ver_str)}; | |||||
return {name, semver::version::parse(ver_str)}; | |||||
} catch (const semver::invalid_version& err) { | } catch (const semver::invalid_version& err) { | ||||
BOOST_LEAF_THROW_EXCEPTION(make_user_error<errc::invalid_pkg_id>(), err); | BOOST_LEAF_THROW_EXCEPTION(make_user_error<errc::invalid_pkg_id>(), err); | ||||
} | } | ||||
} | } | ||||
std::string pkg_id::to_string() const noexcept { return name + "@" + version.to_string(); } | |||||
std::string pkg_id::to_string() const noexcept { | |||||
return fmt::format("{}@{}", name.str, version.to_string()); | |||||
} |
#pragma once | #pragma once | ||||
#include "./name.hpp" | |||||
#include <semver/version.hpp> | #include <semver/version.hpp> | ||||
#include <string> | #include <string> | ||||
namespace dds { | namespace dds { | ||||
struct e_invalid_pkg_id_str { | |||||
struct e_pkg_id_str { | |||||
std::string value; | std::string value; | ||||
}; | }; | ||||
*/ | */ | ||||
struct pkg_id { | struct pkg_id { | ||||
/// The name of the package | /// The name of the package | ||||
std::string name; | |||||
dds::name name; | |||||
/// The version of the package | /// The version of the package | ||||
semver::version version; | semver::version version; | ||||
auto pk_id = dds::pkg_id::parse(id_str); | auto pk_id = dds::pkg_id::parse(id_str); | ||||
CHECK(pk_id.to_string() == id_str); | CHECK(pk_id.to_string() == id_str); | ||||
CHECK(pk_id.name == exp_name); | |||||
CHECK(pk_id.name.str == exp_name); | |||||
CHECK(pk_id.version.to_string() == exp_ver); | CHECK(pk_id.version.to_string() == exp_ver); | ||||
} | } | ||||
#include "./name.hpp" | |||||
#include <dds/error/on_error.hpp> | |||||
#include <dds/error/result.hpp> | |||||
#include <neo/assert.hpp> | |||||
#include <ctre.hpp> | |||||
using namespace dds; | |||||
using err_reason = invalid_name_reason; | |||||
static err_reason calc_invalid_name_reason(std::string_view str) noexcept { | |||||
constexpr ctll::fixed_string capital_re = "[A-Z]"; | |||||
constexpr ctll::fixed_string double_punct = "[._\\-]{2}"; | |||||
constexpr ctll::fixed_string end_punct = "[._\\-]$"; | |||||
constexpr ctll::fixed_string ws = "\\s"; | |||||
constexpr ctll::fixed_string invalid_chars = "[^a-z0-9._\\-]"; | |||||
if (str.empty()) { | |||||
return err_reason::empty; | |||||
} else if (ctre::search<capital_re>(str)) { | |||||
return err_reason::capital; | |||||
} else if (ctre::search<double_punct>(str)) { | |||||
return err_reason::double_punct; | |||||
} else if (str[0] < 'a' || str[0] > 'z') { | |||||
return err_reason::initial_not_alpha; | |||||
} else if (ctre::search<end_punct>(str)) { | |||||
return err_reason::end_punct; | |||||
} else if (ctre::search<ws>(str)) { | |||||
return err_reason::whitespace; | |||||
} else if (ctre::search<invalid_chars>(str)) { | |||||
return err_reason::invalid_char; | |||||
} else { | |||||
neo_assert(invariant, | |||||
false, | |||||
"Expected to be able to determine an error-reason for the given invalid name", | |||||
str); | |||||
} | |||||
} | |||||
std::string_view dds::invalid_name_reason_str(err_reason e) noexcept { | |||||
switch (e) { | |||||
case err_reason::capital: | |||||
return "Uppercase letters are not valid in package names"; | |||||
case err_reason::double_punct: | |||||
return "Adjacent punctuation characters are not valid in package names"; | |||||
case err_reason::end_punct: | |||||
return "Names must not end with a punctuation character"; | |||||
case err_reason::whitespace: | |||||
return "Names must not contain whitespace"; | |||||
case err_reason::invalid_char: | |||||
return "Name contains an invalid character"; | |||||
case err_reason::initial_not_alpha: | |||||
return "Name must begin with a lowercase alphabetic character"; | |||||
case err_reason::empty: | |||||
return "Name cannot be empty"; | |||||
} | |||||
neo::unreachable(); | |||||
} | |||||
result<name> name::from_string(std::string_view str) noexcept { | |||||
constexpr ctll::fixed_string name_re = "^([a-z][a-z0-9]*)([._\\-][a-z0-9]+)*$"; | |||||
auto mat = ctre::match<name_re>(str); | |||||
if (!mat) { | |||||
return new_error(DDS_E_ARG(e_name_str{std::string(str)}), | |||||
DDS_E_ARG(calc_invalid_name_reason(str))); | |||||
} | |||||
return name{std::string(str)}; | |||||
} |
#pragma once | |||||
#include <dds/error/result_fwd.hpp> | |||||
#include <string> | |||||
namespace dds { | |||||
enum class invalid_name_reason { | |||||
empty, | |||||
capital, | |||||
initial_not_alpha, | |||||
double_punct, | |||||
end_punct, | |||||
invalid_char, | |||||
whitespace, | |||||
}; | |||||
struct e_name_str { | |||||
std::string value; | |||||
}; | |||||
std::string_view invalid_name_reason_str(invalid_name_reason) noexcept; | |||||
struct name { | |||||
std::string str; | |||||
// Parse a package name, ensuring it is a valid package name string | |||||
static result<name> from_string(std::string_view str) noexcept; | |||||
bool operator==(const name& o) const noexcept { return str == o.str; } | |||||
bool operator!=(const name& o) const noexcept { return str != o.str; } | |||||
bool operator<(const name& o) const noexcept { return str < o.str; } | |||||
}; | |||||
} // namespace dds |
#include "./name.hpp" | |||||
#include <dds/error/result.hpp> | |||||
#include <boost/leaf/handle_error.hpp> | |||||
#include <catch2/catch.hpp> | |||||
TEST_CASE("Try some invalid names") { | |||||
using reason = dds::invalid_name_reason; | |||||
struct case_ { | |||||
std::string_view invalid_name; | |||||
dds::invalid_name_reason error; | |||||
}; | |||||
auto given = GENERATE(Catch::Generators::values<case_>({ | |||||
{"", reason::empty}, | |||||
{"H", reason::capital}, | |||||
{"heLlo", reason::capital}, | |||||
{"eGGG", reason::capital}, | |||||
{"e0131F-gg", reason::capital}, | |||||
{"-foo", reason::initial_not_alpha}, | |||||
{"123", reason::initial_not_alpha}, | |||||
{"123-bar", reason::initial_not_alpha}, | |||||
{" fooo", reason::initial_not_alpha}, | |||||
{"foo..bar", reason::double_punct}, | |||||
{"foo-.bar", reason::double_punct}, | |||||
{"foo-_bar", reason::double_punct}, | |||||
{"foo__bar", reason::double_punct}, | |||||
{"foo_.bar", reason::double_punct}, | |||||
{"foo.", reason::end_punct}, | |||||
{"foo.bar_", reason::end_punct}, | |||||
{"foo.bar-", reason::end_punct}, | |||||
{"foo ", reason::whitespace}, | |||||
{"foo bar", reason::whitespace}, | |||||
{"foo\nbar", reason::whitespace}, | |||||
{"foo&bar", reason::invalid_char}, | |||||
{"foo+baz", reason::invalid_char}, | |||||
})); | |||||
boost::leaf::context<reason> err_ctx; | |||||
err_ctx.activate(); | |||||
CAPTURE(given.invalid_name); | |||||
auto res = dds::name::from_string(given.invalid_name); | |||||
err_ctx.deactivate(); | |||||
CHECKED_IF(!res) { | |||||
err_ctx.handle_error<void>( | |||||
res.error(), | |||||
[&](reason r) { CHECK(r == given.error); }, | |||||
[] { FAIL_CHECK("No error reason was given"); }); | |||||
} | |||||
} | |||||
TEST_CASE("Try some valid names") { | |||||
auto given = GENERATE(Catch::Generators::values<std::string_view>({ | |||||
"hi", | |||||
"dog", | |||||
"foo.bar", | |||||
"foo-bar_bark", | |||||
"foo-bar-baz", | |||||
"foo-bar.quz", | |||||
"q", | |||||
})); | |||||
auto res = dds::name::from_string(given); | |||||
CHECK(res->str == given); | |||||
} |
DDS_E_SCOPE(man->id); | DDS_E_SCOPE(man->id); | ||||
neo::sqlite3::transaction_guard tr{_db}; | neo::sqlite3::transaction_guard tr{_db}; | ||||
dds_log(debug, "Recording package {}@{}", man->id.name, man->id.version.to_string()); | |||||
dds_log(debug, "Recording package {}", man->id.to_string()); | |||||
dds::pkg_listing info{.ident = man->id, | dds::pkg_listing info{.ident = man->id, | ||||
.deps = man->dependencies, | .deps = man->dependencies, | ||||
.description = "[No description]", | .description = "[No description]", | ||||
auto rel_url = fmt::format("dds:{}", man->id.to_string()); | auto rel_url = fmt::format("dds:{}", man->id.to_string()); | ||||
add_pkg(info, rel_url); | add_pkg(info, rel_url); | ||||
auto dest_path = pkg_dir() / man->id.name / man->id.version.to_string() / "sdist.tar.gz"; | |||||
auto dest_path = pkg_dir() / man->id.name.str / man->id.version.to_string() / "sdist.tar.gz"; | |||||
fs::create_directories(dest_path.parent_path()); | fs::create_directories(dest_path.parent_path()); | ||||
fs::copy(tgz_file, dest_path); | fs::copy(tgz_file, dest_path); | ||||
WHERE name = ? | WHERE name = ? | ||||
AND version = ? | AND version = ? | ||||
)"_sql), | )"_sql), | ||||
pkg_id.name, | |||||
pkg_id.name.str, | |||||
pkg_id.version.to_string()); | pkg_id.version.to_string()); | ||||
/// XXX: Verify with _db.changes() that we actually deleted one row | /// XXX: Verify with _db.changes() that we actually deleted one row | ||||
auto name_dir = pkg_dir() / pkg_id.name; | |||||
auto name_dir = pkg_dir() / pkg_id.name.str; | |||||
auto ver_dir = name_dir / pkg_id.version.to_string(); | auto ver_dir = name_dir / pkg_id.version.to_string(); | ||||
DDS_E_SCOPE(e_repo_delete_path{ver_dir}); | DDS_E_SCOPE(e_repo_delete_path{ver_dir}); | ||||
INSERT INTO dds_repo_packages (name, version, description, url) | INSERT INTO dds_repo_packages (name, version, description, url) | ||||
VALUES (?, ?, ?, ?) | VALUES (?, ?, ?, ?) | ||||
)"_sql), | )"_sql), | ||||
info.ident.name, | |||||
info.ident.name.str, | |||||
info.ident.version.to_string(), | info.ident.version.to_string(), | ||||
info.description, | info.description, | ||||
url); | url); | ||||
dds_log(trace, " Depends on: {}", dep.to_string()); | dds_log(trace, " Depends on: {}", dep.to_string()); | ||||
nsql::exec(insert_dep_st, | nsql::exec(insert_dep_st, | ||||
package_rowid, | package_rowid, | ||||
dep.name, | |||||
dep.name.str, | |||||
iv_1.low.to_string(), | iv_1.low.to_string(), | ||||
iv_1.high.to_string()); | iv_1.high.to_string()); | ||||
} | } | ||||
auto dest_dir = pkg_dir() / info.ident.name / info.ident.version.to_string(); | |||||
auto dest_dir = pkg_dir() / info.ident.name.str / info.ident.version.to_string(); | |||||
auto stamp_path = dest_dir / "url.txt"; | auto stamp_path = dest_dir / "url.txt"; | ||||
fs::create_directories(dest_dir); | fs::create_directories(dest_dir); | ||||
std::ofstream stamp_file{stamp_path, std::ios::binary}; | std::ofstream stamp_file{stamp_path, std::ios::binary}; |
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/on_error.hpp> | |||||
#include <dds/error/result.hpp> | |||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <json5/parse_data.hpp> | #include <json5/parse_data.hpp> | ||||
using namespace dds; | using namespace dds; | ||||
library_manifest library_manifest::load_from_file(path_ref fpath) { | library_manifest library_manifest::load_from_file(path_ref fpath) { | ||||
DDS_E_SCOPE(e_library_manifest_path{fpath.string()}); | |||||
auto content = slurp_file(fpath); | auto content = slurp_file(fpath); | ||||
auto data = json5::parse_data(content); | auto data = json5::parse_data(content); | ||||
mapping{ | mapping{ | ||||
if_key{"name", | if_key{"name", | ||||
require_type<std::string>{"`name` must be a string"}, | require_type<std::string>{"`name` must be a string"}, | ||||
put_into{lib.name}}, | |||||
put_into{lib.name.str}}, | |||||
if_key{"uses", | if_key{"uses", | ||||
require_type<json5::data::array_type>{ | require_type<json5::data::array_type>{ | ||||
"`uses` must be an array of usage requirements"}, | "`uses` must be an array of usage requirements"}, | ||||
throw_user_error<errc::invalid_lib_manifest>(rej->message); | throw_user_error<errc::invalid_lib_manifest>(rej->message); | ||||
} | } | ||||
if (lib.name.empty()) { | |||||
if (lib.name.str.empty()) { | |||||
throw_user_error<errc::invalid_lib_manifest>( | throw_user_error<errc::invalid_lib_manifest>( | ||||
"The 'name' field is required (Reading library manifest [{}])", fpath.string()); | "The 'name' field is required (Reading library manifest [{}])", fpath.string()); | ||||
} | } | ||||
lib.name = *dds::name::from_string(lib.name.str); | |||||
return lib; | return lib; | ||||
} | } |
#pragma once | #pragma once | ||||
#include <dds/pkg/name.hpp> | |||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <libman/library.hpp> | #include <libman/library.hpp> | ||||
namespace dds { | namespace dds { | ||||
struct e_library_manifest_path { | |||||
std::string value; | |||||
}; | |||||
/** | /** | ||||
* Represents the contents of a `library.json5`. This is somewhat a stripped-down | * Represents the contents of a `library.json5`. This is somewhat a stripped-down | ||||
* version of lm::library, to only represent exactly the parts that we want to | * version of lm::library, to only represent exactly the parts that we want to | ||||
*/ | */ | ||||
struct library_manifest { | struct library_manifest { | ||||
/// The name of the library | /// The name of the library | ||||
std::string name; | |||||
dds::name name; | |||||
/// The libraries that the owning library "uses" | /// The libraries that the owning library "uses" | ||||
std::vector<lm::usage> uses; | std::vector<lm::usage> uses; | ||||
/// The libraries that the owning library must be linked with | /// The libraries that the owning library must be linked with |
#include <dds/build/plan/compile_file.hpp> | #include <dds/build/plan/compile_file.hpp> | ||||
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/result.hpp> | |||||
#include <dds/sdist/root.hpp> | #include <dds/sdist/root.hpp> | ||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
auto sources = collect_pf_sources(lib_dir); | auto sources = collect_pf_sources(lib_dir); | ||||
library_manifest man; | library_manifest man; | ||||
man.name = lib_dir.filename().string(); | |||||
auto found = library_manifest::find_in_directory(lib_dir); | |||||
auto found = library_manifest::find_in_directory(lib_dir); | |||||
if (found) { | if (found) { | ||||
man = library_manifest::load_from_file(*found); | man = library_manifest::load_from_file(*found); | ||||
} else { | |||||
auto name_from_dir = dds::name::from_string(lib_dir.filename().string()); | |||||
if (!name_from_dir) { | |||||
man.name.str = "unnamed"; | |||||
} else { | |||||
man.name = *name_from_dir; | |||||
} | |||||
} | } | ||||
auto lib = library_root(lib_dir, std::move(sources), std::move(man)); | auto lib = library_root(lib_dir, std::move(sources), std::move(man)); |
required_key{"name", | required_key{"name", | ||||
"A string 'name' is required", | "A string 'name' is required", | ||||
require_str{"'name' must be a string"}, | require_str{"'name' must be a string"}, | ||||
put_into{ret.id.name}}, | |||||
put_into{ret.id.name, | |||||
[](std::string s) { | |||||
DDS_E_SCOPE(e_pkg_name_str{s}); | |||||
return *dds::name::from_string(s); | |||||
}}}, | |||||
required_key{"namespace", | required_key{"namespace", | ||||
"A string 'namespace' is a required ", | "A string 'namespace' is a required ", | ||||
require_str{"'namespace' must be a string"}, | require_str{"'namespace' must be a string"}, | ||||
put_into{ret.namespace_}}, | |||||
put_into{ret.namespace_, | |||||
[](std::string s) { | |||||
DDS_E_SCOPE(e_pkg_namespace_str{s}); | |||||
return *dds::name::from_string(s); | |||||
}}}, | |||||
required_key{"version", | required_key{"version", | ||||
"A 'version' string is requried", | "A 'version' string is requried", | ||||
require_str{"'version' must be a string"}, | require_str{"'version' must be a string"}, | ||||
} // namespace | } // namespace | ||||
package_manifest package_manifest::load_from_file(const fs::path& fpath) { | package_manifest package_manifest::load_from_file(const fs::path& fpath) { | ||||
DDS_E_SCOPE(e_package_manifest_path{fpath.string()}); | |||||
auto content = slurp_file(fpath); | auto content = slurp_file(fpath); | ||||
return load_from_json5_str(content, fpath.string()); | return load_from_json5_str(content, fpath.string()); | ||||
} | } | ||||
package_manifest package_manifest::load_from_json5_str(std::string_view content, | package_manifest package_manifest::load_from_json5_str(std::string_view content, | ||||
std::string_view input_name) { | std::string_view input_name) { | ||||
DDS_E_SCOPE(e_package_manifest_path{std::string(input_name)}); | |||||
try { | try { | ||||
auto data = json5::parse_data(content); | auto data = json5::parse_data(content); | ||||
return parse_json(data, input_name); | return parse_json(data, input_name); |
catch_main, | catch_main, | ||||
}; | }; | ||||
struct e_package_manifest_path { | |||||
std::string value; | |||||
}; | |||||
struct e_pkg_name_str { | |||||
std::string value; | |||||
}; | |||||
struct e_pkg_namespace_str { | |||||
std::string value; | |||||
}; | |||||
/** | /** | ||||
* Struct representing the contents of a `packaeg.dds` file. | |||||
* Struct representing the contents of a `package.json5` file. | |||||
*/ | */ | ||||
struct package_manifest { | struct package_manifest { | ||||
/// The package ID, as determined by `Name` and `Version` together | /// The package ID, as determined by `Name` and `Version` together | ||||
dds::pkg_id id; | dds::pkg_id id; | ||||
/// The declared `Namespace` of the package. This directly corresponds with the libman Namespace | /// The declared `Namespace` of the package. This directly corresponds with the libman Namespace | ||||
std::string namespace_; | |||||
name namespace_; | |||||
/// The `test_driver` that this package declares, or `nullopt` if absent. | /// The `test_driver` that this package declares, or `nullopt` if absent. | ||||
std::optional<test_lib> test_driver; | std::optional<test_lib> test_driver; | ||||
/// The dependencies declared with the `Depends` fields, if any. | /// The dependencies declared with the `Depends` fields, if any. |
auto as_pkg_id(const req_type& req) { | auto as_pkg_id(const req_type& req) { | ||||
const version_range_set& versions = req.dep.versions; | const version_range_set& versions = req.dep.versions; | ||||
assert(versions.num_intervals() == 1); | assert(versions.num_intervals() == 1); | ||||
return pkg_id{req.dep.name, (*versions.iter_intervals().begin()).low}; | |||||
return pkg_id{req.dep.name.str, (*versions.iter_intervals().begin()).low}; | |||||
} | } | ||||
struct solver_provider { | struct solver_provider { | ||||
std::optional<req_type> best_candidate(const req_type& req) const { | std::optional<req_type> best_candidate(const req_type& req) const { | ||||
dds_log(debug, "Find best candidate of {}", req.dep.to_string()); | dds_log(debug, "Find best candidate of {}", req.dep.to_string()); | ||||
// Look up in the cachce for the packages we have with the given name | // Look up in the cachce for the packages we have with the given name | ||||
auto found = pkgs_by_name.find(req.dep.name); | |||||
auto found = pkgs_by_name.find(req.dep.name.str); | |||||
if (found == pkgs_by_name.end()) { | if (found == pkgs_by_name.end()) { | ||||
// If it isn't there, insert an entry in the cache | // If it isn't there, insert an entry in the cache | ||||
found = pkgs_by_name.emplace(req.dep.name, pkgs_for_name(req.dep.name)).first; | |||||
found = pkgs_by_name.emplace(req.dep.name.str, pkgs_for_name(req.dep.name.str)).first; | |||||
} | } | ||||
// Find the first package with the version contained by the ranges in the requirement | // Find the first package with the version contained by the ranges in the requirement | ||||
auto& for_name = found->second; | auto& for_name = found->second; | ||||
std::vector<req_type> requirements_of(const req_type& req) const { | std::vector<req_type> requirements_of(const req_type& req) const { | ||||
dds_log(trace, | dds_log(trace, | ||||
"Lookup requirements of {}@{}", | "Lookup requirements of {}@{}", | ||||
req.key(), | |||||
req.key().str, | |||||
(*req.dep.versions.iter_intervals().begin()).low.to_string()); | (*req.dep.versions.iter_intervals().begin()).low.to_string()); | ||||
auto pk_id = as_pkg_id(req); | auto pk_id = as_pkg_id(req); | ||||
auto deps = deps_for_pkg(pk_id); | auto deps = deps_for_pkg(pk_id); |
std::string value; | std::string value; | ||||
}; | }; | ||||
struct e_human_message { | |||||
std::string value; | |||||
}; | |||||
struct e_missing_file { | struct e_missing_file { | ||||
std::filesystem::path path; | std::filesystem::path path; | ||||
}; | }; |
""" | """ | ||||
tmp_project.write('src/foo.cpp', 'int the_answer() { return 42; }') | tmp_project.write('src/foo.cpp', 'int the_answer() { return 42; }') | ||||
tmp_project.package_json = { | tmp_project.package_json = { | ||||
'name': 'TestProject', | |||||
'name': 'test-project', | |||||
'version': '0.0.0', | 'version': '0.0.0', | ||||
'namespace': 'test', | 'namespace': 'test', | ||||
} | } | ||||
tmp_project.library_json = {'name': 'TestLibrary'} | |||||
tmp_project.library_json = {'name': 'test-library'} | |||||
tmp_project.build() | tmp_project.build() | ||||
assert (tmp_project.build_root / 'compile_commands.json').is_file() | |||||
assert list(tmp_project.build_root.glob('libTestLibrary.*')) != [] | |||||
assert (tmp_project.build_root / 'compile_commands.json').is_file(), 'compdb was not created' | |||||
assert list(tmp_project.build_root.glob('libtest-library.*')) != [], 'No archive was created' | |||||
def test_lib_with_just_test(tmp_project: Project) -> None: | def test_lib_with_just_test(tmp_project: Project) -> None: | ||||
tmp_project.build() | tmp_project.build() | ||||
def test_invalid_names(tmp_project: Project) -> None: | |||||
tmp_project.package_json = { | |||||
'name': 'test', | |||||
'version': '1.2.3', | |||||
'namespace': 'test', | |||||
'depends': ['invalid name@1.2.3'] | |||||
} | |||||
with expect_error_marker('invalid-pkg-dep-name'): | |||||
tmp_project.build() | |||||
with expect_error_marker('invalid-pkg-dep-name'): | |||||
tmp_project.pkg_create() | |||||
with expect_error_marker('invalid-pkg-dep-name'): | |||||
tmp_project.dds.build_deps(['invalid name@1.2.3']) | |||||
tmp_project.package_json = { | |||||
**tmp_project.package_json, | |||||
'name': 'invalid name', | |||||
'depends': [], | |||||
} | |||||
with expect_error_marker('invalid-pkg-name'): | |||||
tmp_project.build() | |||||
with expect_error_marker('invalid-pkg-name'): | |||||
tmp_project.pkg_create() | |||||
tmp_project.package_json = { | |||||
**tmp_project.package_json, | |||||
'name': 'simple_name', | |||||
'namespace': 'invalid namespace', | |||||
} | |||||
with expect_error_marker('invalid-pkg-namespace-name'): | |||||
tmp_project.build() | |||||
with expect_error_marker('invalid-pkg-namespace-name'): | |||||
tmp_project.pkg_create() | |||||
tmp_project.package_json = { | |||||
'name': 'test', | |||||
'version': '1.2.3', | |||||
'namespace': 'test', | |||||
'depends': [], | |||||
} | |||||
tmp_project.library_json = {'name': 'invalid name'} | |||||
# Need a source directory for dds to load the lib manifest | |||||
tmp_project.write('src/empty.hpp', '') | |||||
with expect_error_marker('invalid-lib-name'): | |||||
tmp_project.build() | |||||
TEST_PACKAGE: PackageJSON = { | TEST_PACKAGE: PackageJSON = { | ||||
'name': 'test-pkg', | 'name': 'test-pkg', | ||||
'version': '0.2.2', | 'version': '0.2.2', |
import json | |||||
from dds_ci.dds import DDSWrapper | from dds_ci.dds import DDSWrapper | ||||
from dds_ci.testing import Project, RepoServer, PackageJSON | from dds_ci.testing import Project, RepoServer, PackageJSON | ||||
from dds_ci.testing.error import expect_error_marker | from dds_ci.testing.error import expect_error_marker | ||||
dds.run(['pkg', dds.pkg_db_path_arg, 'search', 'neo-*']) | dds.run(['pkg', dds.pkg_db_path_arg, 'search', 'neo-*']) | ||||
with expect_error_marker('pkg-search-no-result'): | with expect_error_marker('pkg-search-no-result'): | ||||
dds.run(['pkg', dds.pkg_db_path_arg, 'search', 'nonexistent']) | dds.run(['pkg', dds.pkg_db_path_arg, 'search', 'nonexistent']) | ||||
def test_pkg_cache_invalid_nofail(tmp_project: Project) -> None: | |||||
""" | |||||
Check that dds will not fail a build just because the package cache has an invalid | |||||
object within. | |||||
""" | |||||
sdist_dir = tmp_project.dds.repo_dir / 'bad@1.2.3' | |||||
sdist_dir.mkdir(parents=True) | |||||
tmp_project.build() | |||||
# Write an invalid source distribution | |||||
pkman_path = sdist_dir / 'package.json5' | |||||
pkman_path.write_text(json.dumps({})) | |||||
tmp_project.build() | |||||
pkman_path.write_text('lol') # Inavlid JSON | |||||
tmp_project.build() | |||||
pkman_path.write_text(''' | |||||
{ | |||||
name: 'invalid name', | |||||
namespace: 'test', | |||||
version: '1.2.3' | |||||
} | |||||
''') | |||||
tmp_project.build() |