[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

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