[M6] manifest: add dev_dependencies and [build].include_dirs
This commit is contained in:
@@ -16,6 +16,7 @@ struct Dependency {
|
|||||||
struct BuildSettings {
|
struct BuildSettings {
|
||||||
bool warnings_as_errors = false;
|
bool warnings_as_errors = false;
|
||||||
std::vector<std::string> sanitizers;
|
std::vector<std::string> sanitizers;
|
||||||
|
std::vector<std::string> include_dirs;
|
||||||
|
|
||||||
bool operator==(const BuildSettings&) const = default;
|
bool operator==(const BuildSettings&) const = default;
|
||||||
};
|
};
|
||||||
@@ -35,6 +36,7 @@ struct Package {
|
|||||||
struct Manifest {
|
struct Manifest {
|
||||||
Package package;
|
Package package;
|
||||||
std::vector<Dependency> dependencies;
|
std::vector<Dependency> dependencies;
|
||||||
|
std::vector<Dependency> dev_dependencies;
|
||||||
BuildSettings build;
|
BuildSettings build;
|
||||||
|
|
||||||
bool operator==(const Manifest&) const = default;
|
bool operator==(const Manifest&) const = default;
|
||||||
|
|||||||
@@ -76,13 +76,12 @@ constexpr std::array PACKAGE_KNOWN_KEYS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array BUILD_KNOWN_KEYS = {
|
constexpr std::array BUILD_KNOWN_KEYS = {
|
||||||
"warnings_as_errors", "sanitizers",
|
"warnings_as_errors", "sanitizers", "include_dirs",
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array TOPLEVEL_KNOWN_KEYS = {
|
constexpr std::array TOPLEVEL_KNOWN_KEYS = {
|
||||||
"package", "dependencies", "build",
|
"package", "dependencies", "dev-dependencies", "build",
|
||||||
// reserved (accepted, ignored for v0.1)
|
"features", "workspace",
|
||||||
"dev-dependencies", "features", "workspace",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto parse_package(const toml::table& tbl, const std::filesystem::path& path)
|
auto parse_package(const toml::table& tbl, const std::filesystem::path& path)
|
||||||
@@ -218,6 +217,24 @@ auto parse_build(const toml::table& tbl, const std::filesystem::path& path)
|
|||||||
b.sanitizers = std::move(*r);
|
b.sanitizers = std::move(*r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto* inc = tbl["include_dirs"].as_array()) {
|
||||||
|
auto r = extract_string_array(*inc, "[build].include_dirs", path);
|
||||||
|
if (!r) {
|
||||||
|
return std::unexpected(r.error());
|
||||||
|
}
|
||||||
|
for (const auto& p : *r) {
|
||||||
|
if (p.empty() || p.starts_with('/') || p.find("..") != std::string::npos) {
|
||||||
|
return std::unexpected(err(
|
||||||
|
ErrorCode::ManifestInvalidField,
|
||||||
|
std::format("[build].include_dirs entry '{}' must be a "
|
||||||
|
"relative path that does not contain '..'",
|
||||||
|
p),
|
||||||
|
path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.include_dirs = std::move(*r);
|
||||||
|
}
|
||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +288,14 @@ auto parse(const std::filesystem::path& path) -> util::Result<Manifest> {
|
|||||||
m.dependencies = std::move(*deps);
|
m.dependencies = std::move(*deps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto* dev_tbl = root["dev-dependencies"].as_table()) {
|
||||||
|
auto deps = parse_dependencies(*dev_tbl, path);
|
||||||
|
if (!deps) {
|
||||||
|
return std::unexpected(deps.error());
|
||||||
|
}
|
||||||
|
m.dev_dependencies = std::move(*deps);
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto* build_tbl = root["build"].as_table()) {
|
if (const auto* build_tbl = root["build"].as_table()) {
|
||||||
auto build = parse_build(*build_tbl, path);
|
auto build = parse_build(*build_tbl, path);
|
||||||
if (!build) {
|
if (!build) {
|
||||||
|
|||||||
@@ -46,23 +46,32 @@ auto build_table(const Manifest& m) -> toml::table {
|
|||||||
}
|
}
|
||||||
root.insert_or_assign("package", std::move(package));
|
root.insert_or_assign("package", std::move(package));
|
||||||
|
|
||||||
if (!m.dependencies.empty()) {
|
auto deps_to_table = [](const std::vector<Dependency>& deps) {
|
||||||
toml::table deps;
|
toml::table out;
|
||||||
for (const auto& dep : m.dependencies) {
|
for (const auto& dep : deps) {
|
||||||
if (dep.components.empty()) {
|
if (dep.components.empty()) {
|
||||||
deps.insert_or_assign(dep.name, dep.version_spec);
|
out.insert_or_assign(dep.name, dep.version_spec);
|
||||||
} else {
|
} else {
|
||||||
toml::table dep_tbl;
|
toml::table dep_tbl;
|
||||||
dep_tbl.insert_or_assign("version", dep.version_spec);
|
dep_tbl.insert_or_assign("version", dep.version_spec);
|
||||||
dep_tbl.insert_or_assign("components", to_array(dep.components));
|
dep_tbl.insert_or_assign("components", to_array(dep.components));
|
||||||
dep_tbl.is_inline(true);
|
dep_tbl.is_inline(true);
|
||||||
deps.insert_or_assign(dep.name, std::move(dep_tbl));
|
out.insert_or_assign(dep.name, std::move(dep_tbl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.insert_or_assign("dependencies", std::move(deps));
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!m.dependencies.empty()) {
|
||||||
|
root.insert_or_assign("dependencies", deps_to_table(m.dependencies));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m.build.warnings_as_errors || !m.build.sanitizers.empty()) {
|
if (!m.dev_dependencies.empty()) {
|
||||||
|
root.insert_or_assign("dev-dependencies", deps_to_table(m.dev_dependencies));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.build.warnings_as_errors || !m.build.sanitizers.empty()
|
||||||
|
|| !m.build.include_dirs.empty()) {
|
||||||
toml::table build;
|
toml::table build;
|
||||||
if (m.build.warnings_as_errors) {
|
if (m.build.warnings_as_errors) {
|
||||||
build.insert_or_assign("warnings_as_errors", true);
|
build.insert_or_assign("warnings_as_errors", true);
|
||||||
@@ -70,6 +79,9 @@ auto build_table(const Manifest& m) -> toml::table {
|
|||||||
if (!m.build.sanitizers.empty()) {
|
if (!m.build.sanitizers.empty()) {
|
||||||
build.insert_or_assign("sanitizers", to_array(m.build.sanitizers));
|
build.insert_or_assign("sanitizers", to_array(m.build.sanitizers));
|
||||||
}
|
}
|
||||||
|
if (!m.build.include_dirs.empty()) {
|
||||||
|
build.insert_or_assign("include_dirs", to_array(m.build.include_dirs));
|
||||||
|
}
|
||||||
root.insert_or_assign("build", std::move(build));
|
root.insert_or_assign("build", std::move(build));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,9 +206,8 @@ TEST_CASE("cmake_lists emits find_package per dep", "[codegen][cmake]") {
|
|||||||
|
|
||||||
TEST_CASE("cmake_lists honors warnings_as_errors", "[codegen][cmake]") {
|
TEST_CASE("cmake_lists honors warnings_as_errors", "[codegen][cmake]") {
|
||||||
Manifest m{
|
Manifest m{
|
||||||
pkg("app"),
|
.package = pkg("app"),
|
||||||
{},
|
.build = BuildSettings{.warnings_as_errors = true, .sanitizers = {}},
|
||||||
BuildSettings{.warnings_as_errors = true, .sanitizers = {}},
|
|
||||||
};
|
};
|
||||||
DiscoveredLayout layout{
|
DiscoveredLayout layout{
|
||||||
.library = std::nullopt,
|
.library = std::nullopt,
|
||||||
@@ -226,9 +225,8 @@ TEST_CASE("cmake_lists honors warnings_as_errors", "[codegen][cmake]") {
|
|||||||
|
|
||||||
TEST_CASE("cmake_lists honors sanitizers", "[codegen][cmake]") {
|
TEST_CASE("cmake_lists honors sanitizers", "[codegen][cmake]") {
|
||||||
Manifest m{
|
Manifest m{
|
||||||
pkg("app"),
|
.package = pkg("app"),
|
||||||
{},
|
.build = BuildSettings{.warnings_as_errors = false,
|
||||||
BuildSettings{.warnings_as_errors = false,
|
|
||||||
.sanitizers = {"address", "undefined"}},
|
.sanitizers = {"address", "undefined"}},
|
||||||
};
|
};
|
||||||
DiscoveredLayout layout{
|
DiscoveredLayout layout{
|
||||||
|
|||||||
@@ -199,9 +199,6 @@ TEST_CASE("parse accepts reserved top-level tables", "[manifest][parse]") {
|
|||||||
name = "foo"
|
name = "foo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
gtest = "1.14"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
@@ -212,6 +209,63 @@ members = ["a"]
|
|||||||
REQUIRE(r.has_value());
|
REQUIRE(r.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse extracts dev-dependencies", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
catch2 = "3.5.0"
|
||||||
|
gtest = { version = "1.14", components = ["gmock"] }
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->dev_dependencies.size() == 2);
|
||||||
|
auto find = [&](std::string_view n) {
|
||||||
|
return std::ranges::find_if(r->dev_dependencies,
|
||||||
|
[&](const auto& d) { return d.name == n; });
|
||||||
|
};
|
||||||
|
auto catch2 = find("catch2");
|
||||||
|
REQUIRE(catch2 != r->dev_dependencies.end());
|
||||||
|
REQUIRE(catch2->version_spec == "3.5.0");
|
||||||
|
REQUIRE(catch2->components.empty());
|
||||||
|
auto gtest = find("gtest");
|
||||||
|
REQUIRE(gtest != r->dev_dependencies.end());
|
||||||
|
REQUIRE(gtest->version_spec == "1.14");
|
||||||
|
REQUIRE(gtest->components == std::vector<std::string>{"gmock"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse extracts [build].include_dirs", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
include_dirs = ["third_party", "vendor/spdlog"]
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->build.include_dirs ==
|
||||||
|
std::vector<std::string>{"third_party", "vendor/spdlog"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects [build].include_dirs that escape the project",
|
||||||
|
"[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
include_dirs = ["../escape"]
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == cargoxx::util::ErrorCode::ManifestInvalidField);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("parse rejects invalid name with spaces", "[manifest][parse]") {
|
TEST_CASE("parse rejects invalid name with spaces", "[manifest][parse]") {
|
||||||
auto p = write_manifest(R"(
|
auto p = write_manifest(R"(
|
||||||
[package]
|
[package]
|
||||||
|
|||||||
@@ -93,10 +93,18 @@ TEST_CASE("write sorts dependencies alphabetically (matches Cargo)",
|
|||||||
|
|
||||||
TEST_CASE("write round-trips build settings", "[manifest][write]") {
|
TEST_CASE("write round-trips build settings", "[manifest][write]") {
|
||||||
Manifest m{
|
Manifest m{
|
||||||
pkg("foo", "0.1.0"),
|
.package = pkg("foo", "0.1.0"),
|
||||||
{},
|
.build = BuildSettings{.warnings_as_errors = true,
|
||||||
BuildSettings{.warnings_as_errors = true,
|
.sanitizers = {"address", "undefined"},
|
||||||
.sanitizers = {"address", "undefined"}},
|
.include_dirs = {"third_party"}},
|
||||||
|
};
|
||||||
|
REQUIRE(round_trip(m) == m);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("write round-trips dev-dependencies", "[manifest][write]") {
|
||||||
|
Manifest m{
|
||||||
|
.package = pkg("foo", "0.1.0"),
|
||||||
|
.dev_dependencies = {Dependency{.name = "catch2", .version_spec = "3.5.0"}},
|
||||||
};
|
};
|
||||||
REQUIRE(round_trip(m) == m);
|
REQUIRE(round_trip(m) == m);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user