Files
cargoxx/CHANGELOG.md

30 KiB

Changelog

All notable changes to cargoxx will be documented in this file.

[Unreleased]

Added

  • M0 repo skeleton: hand-written CMakeLists.txt, bootstrap flake.nix, empty C++23 module units for every component listed in TECH_SPEC.md §1 (util, exec, manifest, lockfile, layout, linkdb, resolver, codegen, cli), root module cargoxx, and a stub main.cpp that builds an empty cargoxx binary.
  • .clang-format (LLVM, 100-column) and .gitignore.
  • SPEC.md, TECH_SPEC.md.
  • M1 foundation in cargoxx.util: ErrorCode (numbers per TECH_SPEC.md §4), Error, Result<T> = std::expected<T, Error>, and format(Error) rendering error[Ennnn]: ... with optional --> path:line:col and hint: lines. format lives in src/util/error.cpp as a module implementation unit.
  • Catch2 v3 wired through flake.nix (libc++-built override) and registered in CMake; tests/util_error.cpp covers six format cases via catch_discover_tests.
  • cargoxx.manifest public types (Package, Dependency, BuildSettings, Edition, Manifest) and parse(path) returning Result<Manifest>, backed by toml++ vendored as third_party/toml.hpp. Unknown keys in [package] / [build] and unknown top-level keys are rejected; reserved fields (description, repository, [dev-dependencies], [features], [workspace]) are accepted. tests/manifest_parse.cpp covers 17 cases.
  • manifest::write(m, path) serializes a Manifest as TOML using toml++. Dependencies are emitted alphabetically (matches Cargo). Round-trip property is exercised by tests/manifest_write.cpp (9 cases). Defaulted operator== on the manifest structs supports comparison.
  • cargoxx.layout public types (Target, TargetKind, DiscoveredLayout) and discover(project_root, package_name). Walks src/ recursively for the library (excluding the src/bin/ subtree), enumerates src/main.cpp, src/bin/*.cpp, tests/*.cpp, examples/*.cpp flat, sorts results for deterministic output, and returns LayoutNoTarget when neither a library nor any binary is present. tests/layout_discovery.cpp covers 12 cases.
  • cargoxx new <name> and cargoxx new --lib <name> scaffold a project directory with Cargoxx.toml, src/main.cpp or src/lib.cppm, and a minimal .gitignore. Hyphens in the package name are mapped to underscores when generating the module/namespace identifier. Codegen of flake.nix / CMakeLists.txt is intentionally deferred to M3. CLI11 v2.6.2 vendored at third_party/CLI11.hpp; entry point is now cargoxx::cli::run(argc, argv). tests/cmd_new.cpp covers 9 cases.
  • util::satisfies(version, range) — minimal semver range matcher supporting *, ==/>=/<=/>/< operators, and comma-separated AND clauses. Versions are zero-padded to three components. tests/semver_satisfies.cpp covers 7 cases.
  • cargoxx.linkdb: Recipe, Database::open(), Database::resolve(...) for curated lookups. The curated database ships at data/linkdb.json with all 25 packages from SPEC.md §11. Component-substitution helpers expand {{components}} (space-joined) in find_package and fan out {{component}} per-target. Path is injected via the CARGOXX_LINKDB_DEFAULT_PATH compile definition. nlohmann/json 3.12.0 vendored at third_party/json.hpp. SQLite overlay and add_manual are deferred to the M2 follow-up commit. tests/linkdb_lookup.cpp covers 13 cases including a smoke test that resolves all 25 curated packages.
  • cargoxx.lockfile: Lockfile, LockfilePackage types and parse(path) / write(lock, path) matching the format in SPEC.md §5. Also Lockfile::nixpkgs_rev() returns the shared revision (codegen will consume this in M3). tests/lockfile_round_trip.cpp covers 9 cases.
  • cargoxx.codegen: GenerateInputs plus the pure function flake_nix(in) -> std::string. Substitutes the package name, the resolved nixpkgs revision (defaulting to nixos-unstable when the lockfile pins none), and a deduplicated list of dep nixpkgs_attr entries into buildInputs. Output is byte-deterministic. tests/codegen_flake.cpp covers 7 cases. Note: SPEC §7's template did not show buildInputs; we add one between nativeBuildInputs and the env.NIX_CFLAGS_COMPILE block as the natural slot for the deps that TECH_SPEC §10 says we splice in.
  • codegen::cmake_lists(in) -> std::string per SPEC.md §8 and TECH_SPEC.md §9: find_package per dep, optional library target with module units + private impl sources, primary binary <pkg>_bin linked against the library, additional binaries from src/bin/, tests with enable_testing() + add_test, examples, and [build] honoring warnings_as_errors and sanitizers. Source paths emitted relative to build/ (i.e. prefixed with ../). Output is deterministic. tests/codegen_cmake.cpp covers 11 cases.
  • cargoxx.resolver::devbox_resolve(name, version) and pure parser parse_devbox_resolve(json) — port of devbox's internal/searcher/client.go Resolve method. Hits https://search.devbox.sh/v1/resolve?name=<n>&version=<v> via curl and pulls out name, version, commit_hash, attr_paths. Falls back to the per-system systems.<plat>.commit_hash when the top-level field is empty (older response shapes). 404 → ResolutionUnknownPackage, missing commit_hash → ResolutionVersionNotFound, transport / parse errors → ResolutionNetworkError. tests/devbox_resolve_parse.cpp covers 6 cases against fixtures derived from a real fmt 10.2.1 response; tests/devbox_resolve_live.cpp (gated by CARGOXX_NETWORK_TESTS=1) hits the live API.
  • Fix: nix_cmake_scan would silently fail when the dev output had not yet been realized on the local machine — nixpkgs_probe's nix eval only computes a store path, so packages first-time encountered (libclang, anything not previously built) returned a path that didn't exist on disk. Discovery now realizes the candidate output via nix build --no-link --print-out-paths nixpkgs#<attr>.dev (or .<empty>) before scanning. New free function cargoxx::resolver::realize_path(flake_attr) wraps the call. Live verified: cargoxx add libclang now reaches the verify-link step (the subsequent find_dependency(LLVM) failure inside Clang's CMake config is the pre-existing cross-package transitive-dep limit — workaround is to cargoxx add libllvm first).
  • Fix: nix_cmake_scan walked only the package's default output. For multi-output Nix packages — boost, openssl, libllvm, libclang, glib, … — CMake configs live in the dev output (<store>/lib/cmake/llvm/LLVMConfig.cmake), not under the default output, so cargoxx add libllvm (or any other multi-output dep) failed with "Conan/vcpkg/nix-scan all empty". NixpkgsInfo now carries an optional dev_path; the nix eval apply expression reports if p ? dev then p.dev.outPath else "". discover scans the dev output preferentially and falls back to out. Live verified: cargoxx add libllvm now reaches the verify-link step (the linker-time libstdc++/libc++ ABI mismatch is a separate documented limit and applies to abseil-cpp too).
  • Fix: pinned-dep buildInputs line used the curated linkdb's nixpkgs_attr (e.g. fmt_10) regardless of the pinned rev, so cargoxx add fmt@12.1.0 emitted pkgs_nixpkgs_fmt_12_1_0.fmt_10 even though that rev only exposes fmt. resolve_version now returns ResolvedVersion { nixpkgs_rev, nixpkgs_attr } (the attr comes from devbox's attr_paths for the resolved rev, with the package name as a best-effort fallback for the git path). cmd_add writes the attr into the lockfile and cmd_build's merge_lockfile now also preserves it. Codegen prefers the lockfile's attr over the linkdb recipe's whenever it's set, so pinned deps reach the right attribute on their per-dep nixpkgs. Live verified: cargoxx add fmt@12.1.0 && cargoxx build now emits pkgs_nixpkgs_fmt_12_1_0.fmt and the binary builds and runs.
  • flake.nix codegen rewritten for per-dep nixpkgs revisions. The shared nixpkgs input always tracks nixos-unstable and provides the toolchain. Each pinned manifest dep (<pkg>@<concrete-version> whose lockfile entry has a non-null nixpkgs_rev) gets its own nixpkgs_<sanitized> input, a matching outputs-lambda parameter, a pkgs_nixpkgs_<sanitized> let binding, and a pkgs_nixpkgs_<sanitized>.<attr> line in buildInputs. Wildcard / unpinned deps stay on the shared pkgs.<attr>. Sanitization replaces every char outside [a-zA-Z0-9_] with _, giving a single identifier-safe form for all three Nix contexts. Two pinned deps that share the same (name, version) are deduplicated. Live verified: cargoxx new app && cargoxx add fmt@10.2.1 && cargoxx add zlib && cargoxx build produces a binary that calls fmt 10.2.1 + zlib from the toolchain nixpkgs; a second cargoxx build regenerates byte-identical flake.nix + Cargoxx.lock.
  • SPEC.md §7 (flake template) and §10 (version resolution) amended to describe the per-dep model. The previous single-shared-rev whole-project SAT was retired (user-directed; documented ABI trade-off).
  • cargoxx build now merges rather than overwrites Cargoxx.lock: when an existing entry's (name, version) still matches the manifest, its nixpkgs_rev is preserved. New deps and version bumps still get a null rev (track nixos-unstable). Means pins written by cargoxx add <pkg>@<ver> survive arbitrary rebuilds; cmd_build is now idempotent w.r.t. the lockfile. synthesize_lockfile was renamed merge_lockfile.
  • cargoxx.resolver::resolve_version(name, version) orchestrator chains devbox_resolve (HTTP, primary) → nixpkgs_git_resolve (offline, fallback). Returns a 40-char nixpkgs SHA. Wildcards (*, empty) are rejected with ResolutionVersionNotFound since they aren't concrete pins.
  • cargoxx add <pkg>@<version> now resolves the pin to a nixpkgs rev before writing the manifest. Failure to resolve aborts the add cleanly (no manifest mutation, no lockfile mutation). On success the rev is persisted into Cargoxx.lock's nixpkgs_rev for that dep. Wildcards (cargoxx add fmt) skip resolution and track nixos-unstable as before. Tests opt out of the network resolver via CARGOXX_NO_AUTORESOLVE=1.
  • cargoxx.resolver::nixpkgs_git_resolve(name, version, repo_path?) — fallback for when search.devbox.sh is unreachable. Lazily clones https://github.com/NixOS/nixpkgs.git into $XDG_CACHE_HOME/cargoxx/nixpkgs/ (or $HOME/.cache/...) on first use, then runs git -C <repo> log --all -S 'version = "<v>"' --pretty='%H %ct' -- pkgs/ and returns the youngest matching commit. Pure helper pick_youngest_commit(text) parses the %H %ct lines. The unit test builds a tiny throwaway git fixture that mirrors the pkgs/development/libraries/<pkg>/default.nix layout (avoiding the real multi-GB clone) and verifies introducing-commit detection plus the not-found path; 5 cases.
  • cargoxx add <pkg> now auto-resolves packages outside the curated linkdb. On LinkdbUnknownPackage, cmd_add invokes resolver::discover which: probes nixpkgs#<pkg> to confirm the attribute exists and capture its store path; tries Conan, vcpkg, and the nix-store CMake scan in order; for each candidate runs a real cargoxx build against an empty int main() {} project via verify_link; on first success persists a confirmed overlay row. Subsequent cargoxx add <pkg> for the same package is an overlay-cache hit (~ms). End-to-end live: a fresh cargoxx add simdjson (not in our curated db) takes ~11 s including the verifying nix+cmake build, then 0.002 s on the second invocation. linkdb::default_overlay_path() is now exported as a public helper so the resolver and CLI agree on the overlay file when no path is explicitly passed. Tests opt out of the slow chain via CARGOXX_NO_AUTORESOLVE=1.
  • cargoxx.linkdb::Database::insert_provisional, confirm_provisional, and abort_provisional — three-step lifecycle for non-manual overlay rows: insert with verified_at = 0, run a build, then either bump verified_at = now on success or DELETE the row on failure.
  • cargoxx.resolver::verify_link(req, build_fn) — scaffolds a tiny Cargoxx.toml + int main() {} project under a scratch dir, writes a provisional overlay row pointing at the candidate Recipe, runs the caller-supplied BuildFn (typically cli::cmd_build injected to avoid a resolver-on-cli dep cycle), and on success/failure confirms or aborts the provisional row. Cleans the scratch project via RAII regardless of outcome. tests/verify_link_unit.cpp exercises both success and failure paths against a mock BuildFn, verifying that the overlay row's lifecycle matches the build outcome.
  • cargoxx.resolver::vcpkg_probe(name) — fetches https://raw.githubusercontent.com/microsoft/vcpkg/master/ports/<name>/usage and feeds it through parse_vcpkg_usage. The pure parser extracts the first find_package(...) arg block, adds REQUIRED if absent, and gathers target_link_libraries targets that contain :: (skips generator expressions and bare names). tests/vcpkg_probe_parse.cpp covers 6 cases; tests/vcpkg_probe_live.cpp (gated by CARGOXX_NETWORK_TESTS=1) verifies fmt + a 404 path against real microsoft/vcpkg ports.
  • cargoxx.resolver::conan_probe(name) — fetches https://raw.githubusercontent.com/conan-io/conan-center-index/master/recipes/<name>/all/conanfile.py via curl (text-only — never executes Python, per SPEC.md §14) and feeds it through parse_conanfile. Pure parser handles both the modern set_property("cmake_target_name", "...") form and the legacy names["cmake_find_package"] = "..." form, derives the missing field from the other when only one is set, normalizes bare names into the canonical <file>::<target> shape, and substitutes Python f-string {...} placeholders with the cmake_file_name so recipes like fmt's f"fmt::{target}" parse correctly. tests/conan_probe_parse.cpp covers 6 cases; tests/conan_probe_live.cpp (gated by CARGOXX_NETWORK_TESTS=1) verifies fmt and a 404 path against the real conan-center-index.
  • cargoxx.resolver::nix_cmake_scan(store_path, package_name) — walks <store_path>/lib/cmake/** for *Config.cmake / *-config.cmake files, scans them and their sibling .cmake files (e.g. the <pkg>-targets.cmake that real packages like fmt put their add_library(... IMPORTED) calls in) for IMPORTED + ALIAS targets, and returns the NixCmakeCandidate whose derived stem best matches package_name (case-insensitive equality > prefix > first non-empty). Pure helpers scan_imported_targets(text) and config_stem_to_package(filename) are exported for unit testing. tests/nix_cmake_scan_parse.cpp covers 11 cases including a fixture that mirrors fmt's <pkg>-config.cmake → <pkg>-targets.cmake layout. tests/nix_cmake_scan_live.cpp (gated by CARGOXX_NETWORK_TESTS=1) realizes nixpkgs#fmt.dev via nix build and verifies fmt::fmt is discovered end-to-end.
  • cargoxx.resolver::nixpkgs_probe(attr) — runs nix eval nixpkgs#<attr> --json --apply 'p: { version, path }' via exec::run and returns a NixpkgsInfo { attr, version, out_path }. Distinguishes missing attributes (ResolutionUnknownPackage) from evaluator/network errors (ResolutionNetworkError). The path field name avoids nix's outPath-driven derivation coercion in --json mode (which would otherwise serialize the result as a bare path string instead of a JSON object). Pure parser parse_nix_eval_json(attr, text) exposed for unit tests; live tests in tests/nixpkgs_probe_live.cpp are gated behind CARGOXX_NETWORK_TESTS=1 (verified locally against hello).
  • cargoxx add <pkg>[@<version>] [--components a,b] edits Cargoxx.toml, validates the new dep against the curated linkdb (so unknown packages and missing components fail before any disk write), and rejects already-declared deps. Version is now optional; an omitted version is stored as "*" and resolves against the linkdb's first matching recipe. Generated flake.nix continues to track the nixos-unstable branch when the lockfile carries no rev.
  • util::satisfies treats version == "*" as a match against any range, mirroring the existing range == "*" shortcut.
  • scripts/verify-curated-db.sh plus per-package using-snippets at scripts/curated-snippets/<pkg>.cpp. For each curated package the script scaffolds a fresh project, cargoxx adds the dep, drops in a snippet that exercises the library's API, and runs cargoxx build, asserting the binary is produced. 21/25 packages pass against nixos-unstable; the remaining 4 are documented as skipped: catch2/gtest (their default linkdb targets ship their own main() which collides with the snippet), grpc (needs protobuf declared as a transitive dep — cross-package transitives are out of v0.1 scope), abseil-cpp (nixpkgs builds it against libstdc++ while our flake uses libc++ → ABI mismatch on the linker step).
  • data/linkdb.json: boost recipe is now header-only — find_package(Boost REQUIRED) + Boost::headers. Boost 1.89's BoostConfig.cmake searches for separately-installed boost_<comp>Config.cmake files that nixpkgs doesn't ship, so the previous COMPONENTS form failed at configure time. Linkdb-component tests now use abseil-cpp for component coverage.
  • cargoxx remove <pkg> drops a declared dep from Cargoxx.toml, errors when the dep is not declared. Other deps are preserved.
  • End-to-end verified on a freshly-scaffolded project. tests/cmd_add.cpp and tests/cmd_remove.cpp cover 9 cases. Known cosmetic issue: toml++ writes top-level sections alphabetically, so [package] may end up after [dependencies] post-mutation; logged for M6 polish.
  • cargoxx run [--release] [--bin <name>] [-- <args>...], cargoxx test [--release], and cargoxx clean. run builds first, picks the binary (errors with the available list when the project has multiple bins and --bin is omitted), and execs it with the process exit code propagated. test invokes nix develop -c ctest --test-dir build/<profile> --output-on-failure. clean removes build/ while leaving Cargoxx.lock and flake.lock intact. End-to-end verified against a freshly-scaffolded project. tests/cmd_run.cpp and tests/cmd_clean.cpp cover 6 cases.
  • cargoxx build (without --no-build) now drives the full build: nix develop -c cmake -B build/<profile> -S build -G Ninja -DCMAKE_BUILD_TYPE=<Profile> then nix develop -c cmake --build with optional --target, both with inherit_stdio = true so the user sees compilation output live. Non-zero cmake exit returns BuildCmakeFailed. End-to-end verified: cargoxx new myapp && cargoxx build produces a working build/debug/myapp that prints Hello from myapp!.
  • Generated build/CMakeLists.txt now opts into the experimental import std; UUID + CMAKE_CXX_MODULE_STD ON, and sets CMAKE_CXX_EXTENSIONS ON (deviation from SPEC §8 — required for libc++'s std module to load without module-file-config-mismatch on clang 21). Without these the templates from cargoxx new fail to compile.
  • cargoxx.exec: ExecResult, ExecOptions, and run(program, args, opts) — argv-only subprocess wrapper around reproc 14.2.4. Captures stdout/stderr (or inherits stdio when opts.inherit_stdio is set), supports cwd, env_overrides, and a timeout (enforced via reproc_options.deadline so drains return REPROC_ETIMEDOUT instead of blocking on long-lived streams). On destruction reproc terminates and then kills any still-running child. Returns ExecToolNotFound for ENOENT and ExecCommandFailed for other failures including timeouts. tests/exec_run.cpp covers 9 cases.
  • cargoxx build --no-build end-to-end. Reads Cargoxx.toml, discovers the layout, opens the curated linkdb, resolves a Recipe per manifest dep, synthesizes a fresh Cargoxx.lock, and writes flake.nix, build/CMakeLists.txt, and Cargoxx.lock. With no resolver yet (M5), nixpkgs_rev is left null and the generated flake falls back to the nixos-unstable branch. Without --no-build, the command still generates files but returns a NotImplemented error pointing at M4. tests/cmd_build.cpp covers 8 cases.
  • SQLite overlay: Database::open(overlay_path) now opens (and creates, if missing) a per-user linkdb.sqlite cache, applying the schema from SPEC.md §9 idempotently. Default path is $XDG_CACHE_HOME/cargoxx/linkdb.sqlite (falling back to $HOME/.cache/cargoxx/...); tests pass an explicit temp path so they never touch the user cache. Database::add_manual(pkg, range, recipe) inserts a row with source = 'manual' and verified_at = now(); resolve() consults the overlay first and falls back to curated when no overlay row matches the requested version range. Manual entries never expire; nix-probe (v0.2) entries respect a 30-day freshness window. tests/linkdb_overlay.cpp covers 7 cases (insert/persist, override-curated, version-range gating, components rejection, move semantics).
  • M7 generated flake.nix moves to build/flake.nix. The project root belongs to the user — any hand-written flake.nix there is never overwritten by cargoxx. cargoxx build always invokes nix develop against path:./build.
  • M7 lockfile pins top-level nixpkgs_rev and flake_utils_rev. The generated flake's inputs.nixpkgs.url / inputs.flake-utils.url now use the pinned revs (falling back to the branch tips during the transitional first build before the lock is written). Per-package schema gains the full recipe (find_package, targets, pkg_config_module, brute_force_libs, brute_force_includes, linkdb_source) so the lockfile is a complete dependency-pinning artifact and cmd_build's recipe_from_lock can short-circuit the linkdb entirely. tests/lockfile_round_trip.cpp extended.
  • M7 codegen::VendorIndex + parse_vendor_toml — new pure parser (src/codegen/vendor.cpp) returns a struct of nixpkgs_store_path, flake_utils_store_path, and a per-dep nixpkgs_attr → store_path map. GenerateInputs gains an optional vendor field; when set, emit_inputs_block emits path: inputs and drops the per-dep github: pins.
  • M7 new helpers in cargoxx.resolver: realize_path_at_rev(rev, attr) realizes github:NixOS/nixpkgs/<rev>#<attr> to a /nix/store/... path (used by cmd_vendor); realize_flake_source(flake_ref) returns the source store path via nix flake prefetch --json (used to pin nixpkgs and flake-utils for offline mode).
  • M7 cargoxx vendor [--output <path>] — new CLI verb. Reads Cargoxx.lock, realizes each locked dep at its pinned (nixpkgs_rev, nixpkgs_attr) into /nix/store, and writes vendor.toml (schema = 1) recording the resolved store paths for every dep plus the nixpkgs and flake-utils flake sources. The output is the input to cargoxx build --offline.
  • M7 cargoxx build --offline [--vendor <path>] — skips every network probe (Conan/vcpkg fuzzy, devbox, nixpkgs_git, linkdb auto-resolve), reads vendor.toml (default ./vendor.toml), and emits build/flake.nix with literal path:/nix/store/... inputs for nixpkgs, flake-utils, and every dep. Offline mode also runs cmake directly (no outer nix develop wrapper) since all paths are already realized in the local store.
  • M7 cargoxx.lib.buildCppPackage — hermetic, sandbox-safe nix builder for downstream flakes. Mirrors rustPlatform.buildRustPackage's ergonomics: a consumer flake passes src and gets a derivation. Reads Cargoxx.lock at outer eval time, resolves each dep's (nixpkgs_rev, nixpkgs_attr) via builtins.getFlake into concrete /nix/store/... paths, and synthesizes a vendor.toml via pkgs.writeText — no network or nested nix invocations inside any build phase. The single derivation runs cargoxx build --release --offline --vendor <store-path>/vendor.toml, which emits a hermetic build/flake.nix with literal path:/nix/store/... inputs and drives cmake directly. Works under the host's default sandbox (sandbox=true, non-trusted user, no __noChroot, no daemon escape). New e2e fixture at tests/e2e/buildCppPackage/ with a run.sh smoke test that scaffolds the fixture in a tmp dir and runs nix build .#default end-to-end. Live verified: Hello from world! from a binary built entirely inside the standard nix sandbox.
  • Fix: -Wparentheses warning in looks_like_missing_attribute (src/resolver/nixpkgs_probe.cpp:34) — explicitly parenthesize the && clause inside ||.
  • M7 cargoxx-bin wraps the binary with makeWrapper to lock its runtime CLI dependencies — nix, cmake, ninja, curl, git — onto PATH. Previously cargoxx silently relied on the user's ambient PATH, so it broke whenever invoked outside a nix develop shell. The wrapped binary works under env -i with a minimal PATH.
  • M7 the wrapper also --set-defaults NIX_CONFIG to experimental-features = nix-command flakes and build-users-group =. Without this, the bundled pkgs.nix defaults to the multi-user daemon model and fails on non-NixOS hosts (error: the group 'nixbld' specified in 'build-users-group' does not exist). --set-default lets a user with a properly- configured nix daemon override by exporting NIX_CONFIG=... before invoking cargoxx. Verified end-to-end in archlinux:latest via docker run: install the .pkg.tar.zst, cargoxx new demo && cargoxx add fmt runs the resolver chain through verify-link cmake/ninja and writes the dep without error.
  • M7 packaging functions exposed as a stable to* library and as ready-to-build flake packages, mirroring the github:NixOS/bundlers naming. Available as both packages.<format> (for nix build .#<format>) and lib.to<Format> (for downstream flakes to package their own derivations):
    • toAppImage / packages.appimage — Linux AppImage (~207 MB)
    • toDockerImage / packages.dockerImagedocker load-able tar.gz (~213 MB)
    • toDEB / packages.deb — Debian .deb (~211 MB)
    • toRPM / packages.rpm — Red Hat .rpm (~212 MB)
    • toArchPkg / packages.archpkg — Arch .pkg.tar.zst (~196 MB), implemented locally because no NixOS bundler exists. Builds a closure via pkgs.closureInfo, lays it under /nix/store, drops a /usr/bin/<mainProgram> symlink, writes .PKGINFO from drv.pname/drv.version/drv.meta, and packs with bsdtar --zstd. Generic — works for any derivation with a bin/<mainProgram> and the usual pname/version attrs. Added inputs.bundlers.url = "github:NixOS/bundlers" (with inputs.nixpkgs.follows = "nixpkgs") to keep the closure aligned with the project's pinned nixpkgs.
  • M8 multiple binary build artifacts (Cargo src/bin/ parity). Codegen now routes every executable (add_executable) to ${CMAKE_BINARY_DIR}/bin via set_target_properties(... RUNTIME_OUTPUT_DIRECTORY ...). buildCppPackage and cargoxx run --bin <name> follow suit, and layout discovery picks up Cargo's src/bin/<sub>/main.cpp form (subdir name becomes the binary name). End-to-end fixture tests/e2e/buildCppPackage/src/bin/extra.cpp proves a second binary lands in $out/bin/ alongside the primary.
  • M8 reusable library install layout. cargoxx-built libraries now ship a CMake-idiomatic $out tree: lib/lib<name>.a, lib/cmake/<name>/{<name>Config.cmake,<name>ConfigVersion.cmake, <name>Targets.cmake}, lib/pkgconfig/<name>.pc, and modules under include/<name>/ (via install(TARGETS ... FILE_SET CXX_MODULES ...)). Codegen emits the install rules + a Config.cmake.in template (inline file(WRITE …)) consumed by configure_package_config_file and write_basic_package_version_file, plus a basic .pc.in template. The exported library target carries target_compile_features(... PUBLIC cxx_std_<NN>) so consumers find_package-ing it get the right standard for module BMI regeneration. buildCppPackage.installPhase switched from cp -a build/release/bin/. to cmake --install build/release --prefix $out — bins, libs, headers, config, and pc files all land via one invocation. project(... VERSION <pkg.version> ...) is now part of the generated header so <name>ConfigVersion.cmake reflects the manifest's version.
  • M8 cargoxx-path dependencies (Cargo's { path = "..." }). The manifest gains a discriminated dep table form:
    [dependencies]
    greeter = { path = "./greeter" }
    
    manifest::Dependency carries DepSource source + optional<string> path. The parser branches on path; unknown dep-table keys still rejected for tables that have neither version nor path. Round-tripped by the writer. Lockfile schema adds per-package source_kind + source_path so the consumer's Cargoxx.lock records "this dep is built from a cargoxx source tree at ./greeter" (not nixpkgs). cmd_build::resolve_path_dep reads <path>/Cargoxx.toml to verify the dep's name matches, then synthesizes a Recipe (find_package = "<name> CONFIG REQUIRED", targets = ["<name>::<name>"], source = "cargoxx-path"). Codegen needs no special case — the synthesized recipe flows through the existing find_package emission path. buildCppPackage's recursion: evalDep branches on source_kind == "cargoxx-path" and recurses with src = src + "/" + source_path. The resulting derivation joins buildInputs; CMake's CMAKE_PREFIX_PATH picks it up so the consumer's find_package(<dep> CONFIG REQUIRED) resolves against the producer's installed Config.cmake. Path deps must be subdirectories of the consumer's source tree (no sibling form in v1 — sibling deps will land with git/registry sources in 1c/1d). New e2e fixture tests/e2e/pathDep/: a consumer project with [dependencies] greeter = { path = "./greeter" }. run.sh generates locks in both, then nix build .#default produces a binary that prints "Hello from greeter, world!".
  • M8 design doc at docs/library-reuse-and-publish.md covers the full two-part roadmap: library reuse (path → git → registry deps) and Gitea-hosted public registry with cargoxx publish + bot validation + binary-cache substitution. Phase 1a (install rules) and Phase 1b (path deps) shipped in this commit; phases 1c, 1d, and 2 remain to be built.