[M3] add cargoxx build --no-build

This commit is contained in:
2026-05-08 12:56:17 +00:00
parent 4380c5181c
commit 219254a1dd
7 changed files with 363 additions and 7 deletions

View File

@@ -3,12 +3,24 @@ export module cargoxx.cli;
import std;
import cargoxx.util;
import cargoxx.manifest;
import cargoxx.layout;
import cargoxx.linkdb;
import cargoxx.lockfile;
import cargoxx.codegen;
export namespace cargoxx::cli {
auto cmd_new(const std::string& name, bool lib_only,
const std::filesystem::path& parent_dir) -> util::Result<void>;
// Generates flake.nix, build/CMakeLists.txt, and Cargoxx.lock for the project
// rooted at `project_root`. With `no_build = true`, only generates files; the
// nix/cmake invocation lands at M4. `overlay_path` lets tests redirect the
// linkdb overlay away from ~/.cache.
auto cmd_build(const std::filesystem::path& project_root, bool no_build, bool release,
std::optional<std::filesystem::path> overlay_path = std::nullopt)
-> util::Result<void>;
auto run(int argc, char** argv) -> int;
} // namespace cargoxx::cli

139
src/cli/cmd_build.cpp Normal file
View File

@@ -0,0 +1,139 @@
module cargoxx.cli;
import std;
import cargoxx.util;
import cargoxx.manifest;
import cargoxx.layout;
import cargoxx.linkdb;
import cargoxx.lockfile;
import cargoxx.codegen;
namespace cargoxx::cli {
namespace {
namespace fs = std::filesystem;
auto io_error(std::string msg, fs::path path) -> util::Error {
return util::Error{
util::ErrorCode::Internal, std::move(msg), "", std::move(path), std::nullopt,
};
}
auto write_text(const fs::path& path, std::string_view content) -> util::Result<void> {
std::ofstream out{path};
if (!out) {
return std::unexpected(
io_error(std::format("cannot open for writing: {}", path.string()), path));
}
out << content;
if (!out) {
return std::unexpected(io_error(std::format("write failed: {}", path.string()), path));
}
return {};
}
// Synthesizes a fresh lockfile from the manifest + resolved recipes. Without a
// resolver (M5), `nixpkgs_rev` is left null — flake codegen falls back to the
// `nixos-unstable` branch and the user gets a working but non-reproducible
// build. M5 will populate the rev.
auto synthesize_lockfile(const manifest::Manifest& m,
const std::vector<linkdb::Recipe>& recipes) -> lockfile::Lockfile {
lockfile::Lockfile lock;
lock.version = 1;
lockfile::LockfilePackage root{
.name = m.package.name,
.version = m.package.version,
.dependencies = {},
.nixpkgs_attr = std::nullopt,
.nixpkgs_rev = std::nullopt,
.linkdb_source = std::nullopt,
};
for (const auto& dep : m.dependencies) {
root.dependencies.push_back(std::format("{} {}", dep.name, dep.version_spec));
}
lock.packages.push_back(std::move(root));
for (std::size_t i = 0; i < m.dependencies.size(); ++i) {
const auto& dep = m.dependencies[i];
const auto& rec = recipes[i];
lock.packages.push_back(lockfile::LockfilePackage{
.name = dep.name,
.version = dep.version_spec,
.dependencies = {},
.nixpkgs_attr = rec.nixpkgs_attr,
.nixpkgs_rev = std::nullopt,
.linkdb_source = rec.source,
});
}
return lock;
}
} // namespace
auto cmd_build(const fs::path& project_root, bool no_build, bool /*release*/,
std::optional<fs::path> overlay_path) -> util::Result<void> {
auto manifest_path = project_root / "Cargoxx.toml";
auto m = manifest::parse(manifest_path);
if (!m) {
return std::unexpected(m.error());
}
auto layout_result = layout::discover(project_root, m->package.name);
if (!layout_result) {
return std::unexpected(layout_result.error());
}
auto db = linkdb::Database::open(std::move(overlay_path));
if (!db) {
return std::unexpected(db.error());
}
std::vector<linkdb::Recipe> recipes;
recipes.reserve(m->dependencies.size());
for (const auto& dep : m->dependencies) {
auto r = db->resolve(dep.name, dep.version_spec, dep.components);
if (!r) {
return std::unexpected(r.error());
}
recipes.push_back(std::move(*r));
}
auto lock = synthesize_lockfile(*m, recipes);
codegen::GenerateInputs in{*m, *layout_result, lock, recipes, project_root};
auto flake_text = codegen::flake_nix(in);
auto cmake_text = codegen::cmake_lists(in);
std::error_code ec;
fs::create_directories(project_root / "build", ec);
if (ec) {
return std::unexpected(io_error(
std::format("cannot create build directory: {}", ec.message()),
project_root / "build"));
}
if (auto r = write_text(project_root / "flake.nix", flake_text); !r) {
return std::unexpected(r.error());
}
if (auto r = write_text(project_root / "build" / "CMakeLists.txt", cmake_text); !r) {
return std::unexpected(r.error());
}
if (auto r = lockfile::write(lock, project_root / "Cargoxx.lock"); !r) {
return std::unexpected(r.error());
}
if (!no_build) {
return std::unexpected(util::Error{
util::ErrorCode::NotImplemented,
"cargoxx build invocation is not implemented in this milestone",
"rerun with --no-build to generate files only (full build lands at M4)",
std::nullopt, std::nullopt,
});
}
return {};
}
} // namespace cargoxx::cli

View File

@@ -19,20 +19,29 @@ auto run(int argc, char** argv) -> int {
new_cmd->add_option("name", new_name, "Project name")->required();
new_cmd->add_flag("--lib", new_lib, "Create a library project");
auto* build_cmd = app.add_subcommand(
"build", "Generate flake.nix and build/CMakeLists.txt; build with nix+cmake");
bool build_no_build = false;
bool build_release = false;
build_cmd->add_flag("--no-build", build_no_build,
"Generate files only; do not invoke nix/cmake");
build_cmd->add_flag("--release", build_release, "Build the release profile");
try {
app.parse(argc, argv);
} catch (const CLI::ParseError& e) {
return app.exit(e);
}
std::error_code ec;
auto cwd = std::filesystem::current_path(ec);
if (ec) {
std::cerr << std::format("error: cannot determine current working directory: {}\n",
ec.message());
return 1;
}
if (*new_cmd) {
std::error_code ec;
auto cwd = std::filesystem::current_path(ec);
if (ec) {
std::cerr << std::format("error: cannot determine current working directory: {}\n",
ec.message());
return 1;
}
auto r = cmd_new(new_name, new_lib, cwd);
if (!r) {
std::cerr << util::format(r.error());
@@ -42,6 +51,16 @@ auto run(int argc, char** argv) -> int {
return 0;
}
if (*build_cmd) {
auto r = cmd_build(cwd, build_no_build, build_release);
if (!r) {
std::cerr << util::format(r.error());
return 1;
}
std::cout << " Generated flake.nix, build/CMakeLists.txt, Cargoxx.lock\n";
return 0;
}
return 0;
}