319 lines
20 KiB
Markdown
319 lines
20 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: 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).
|