Files
cargoxx/CHANGELOG.md

501 lines
30 KiB
Markdown

# 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 add`s 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-default`s `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.dockerImage``docker 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:
```toml
[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.