[M3] add cargoxx build --no-build
This commit is contained in:
@@ -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
139
src/cli/cmd_build.cpp
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user