Files
cargoxx/CHANGELOG.md

17 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.
  • 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).