Browse Source

Merge branch 'feature/rm-ver-ranges' into develop

default_compile_flags
vector-of-bool 3 years ago
parent
commit
1a1b087bbc
38 changed files with 587 additions and 99 deletions
  1. +9
    -7
      src/dds/build/builder.cpp
  2. +2
    -2
      src/dds/build/plan/library.cpp
  3. +1
    -1
      src/dds/build/plan/template.cpp
  4. +5
    -1
      src/dds/cli/cmd/build.cpp
  5. +15
    -0
      src/dds/cli/cmd/build_common.cpp
  6. +2
    -0
      src/dds/cli/cmd/build_common.hpp
  7. +7
    -1
      src/dds/cli/cmd/build_deps.cpp
  8. +1
    -1
      src/dds/cli/cmd/pkg_ls.cpp
  9. +63
    -7
      src/dds/cli/error_handler.cpp
  10. +1
    -1
      src/dds/db/database.cpp
  11. +20
    -7
      src/dds/deps.cpp
  12. +6
    -1
      src/dds/deps.hpp
  13. +4
    -5
      src/dds/deps.test.cpp
  14. +15
    -0
      src/dds/error/handle.cpp
  15. +26
    -0
      src/dds/error/handle.hpp
  16. +2
    -0
      src/dds/error/on_error.hpp
  17. +4
    -0
      src/dds/error/result.hpp
  18. +55
    -15
      src/dds/pkg/cache.cpp
  19. +12
    -8
      src/dds/pkg/db.cpp
  20. +4
    -4
      src/dds/pkg/db.test.cpp
  21. +1
    -1
      src/dds/pkg/get/dds_http.cpp
  22. +1
    -1
      src/dds/pkg/get/dds_http.test.cpp
  23. +6
    -4
      src/dds/pkg/id.cpp
  24. +4
    -2
      src/dds/pkg/id.hpp
  25. +1
    -1
      src/dds/pkg/id.test.cpp
  26. +73
    -0
      src/dds/pkg/name.cpp
  27. +36
    -0
      src/dds/pkg/name.hpp
  28. +72
    -0
      src/dds/pkg/name.test.cpp
  29. +7
    -8
      src/dds/repoman/repoman.cpp
  30. +7
    -2
      src/dds/sdist/library/manifest.cpp
  31. +6
    -1
      src/dds/sdist/library/manifest.hpp
  32. +9
    -2
      src/dds/sdist/library/root.cpp
  33. +12
    -2
      src/dds/sdist/package.cpp
  34. +14
    -2
      src/dds/sdist/package.hpp
  35. +4
    -4
      src/dds/solve/solve.cpp
  36. +0
    -4
      src/dds/util/result.hpp
  37. +51
    -4
      tests/test_basics.py
  38. +29
    -0
      tests/test_pkg_db.py

+ 9
- 7
src/dds/build/builder.cpp View File

} }
} }
} }
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,

+ 2
- 2
src/dds/build/plan/library.cpp View File

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));

+ 1
- 1
src/dds/build/plan/template.cpp View File

{ {
"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()},
}, },
}, },

+ 5
- 1
src/dds/cli/cmd/build.cpp View File



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

+ 15
- 0
src/dds/cli/cmd/build_common.cpp View File

#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;
});
}

+ 2
- 0
src/dds/cli/cmd/build_common.hpp View File



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

+ 7
- 1
src/dds/cli/cmd/build_deps.cpp View File

#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

+ 1
- 1
src/dds/cli/cmd/pkg_ls.cpp View File

}); });


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());
} }

+ 63
- 7
src/dds/cli/error_handler.cpp View File



#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: {}",

+ 1
- 1
src/dds/db/database.cpp View File

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};

+ 20
- 7
src/dds/deps.cpp View File

#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();

+ 6
- 1
src/dds/deps.hpp View File

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

+ 4
- 5
src/dds/deps.test.cpp View File

}; };


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));

+ 15
- 0
src/dds/error/handle.cpp View File

#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);
}

+ 26
- 0
src/dds/error/handle.hpp View File

#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

+ 2
- 0
src/dds/error/on_error.hpp View File

#pragma once #pragma once


#include <neo/pp.hpp>

#include <boost/leaf/on_error.hpp> #include <boost/leaf/on_error.hpp>


/** /**

+ 4
- 0
src/dds/error/result.hpp View File



using boost::leaf::new_error; using boost::leaf::new_error;


struct e_human_message {
std::string value;
};

} // namespace dds } // namespace dds

+ 55
- 15
src/dds/pkg/cache.cpp View File

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

+ 12
- 8
src/dds/pkg/db.cpp View File

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) {

+ 4
- 4
src/dds/pkg/db.test.cpp View File



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");
} }

+ 1
- 1
src/dds/pkg/get/dds_http.cpp View File



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;

+ 1
- 1
src/dds/pkg/get/dds_http.test.cpp View File

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");
} }

+ 6
- 4
src/dds/pkg/id.cpp View File

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());
}

+ 4
- 2
src/dds/pkg/id.hpp View File

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



+ 1
- 1
src/dds/pkg/id.test.cpp View File



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);
} }



+ 73
- 0
src/dds/pkg/name.cpp View File

#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)};
}

+ 36
- 0
src/dds/pkg/name.hpp View File

#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

+ 72
- 0
src/dds/pkg/name.test.cpp View File

#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);
}

+ 7
- 8
src/dds/repoman/repoman.cpp View File

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};

+ 7
- 2
src/dds/sdist/library/manifest.cpp View File



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

+ 6
- 1
src/dds/sdist/library/manifest.hpp View File

#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

+ 9
- 2
src/dds/sdist/library/root.cpp View File



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

+ 12
- 2
src/dds/sdist/package.cpp View File

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);

+ 14
- 2
src/dds/sdist/package.hpp View File

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.

+ 4
- 4
src/dds/solve/solve.cpp View File

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);

+ 0
- 4
src/dds/util/result.hpp View File

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;
}; };

+ 51
- 4
tests/test_basics.py View File

""" """
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',

+ 29
- 0
tests/test_pkg_db.py View File

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()

Loading…
Cancel
Save