17 KiB
17 KiB
Changelog
All notable changes to cargoxx will be documented in this file.
[Unreleased]
Added
- M0 repo skeleton: hand-written
CMakeLists.txt, bootstrapflake.nix, empty C++23 module units for every component listed inTECH_SPEC.md§1 (util,exec,manifest,lockfile,layout,linkdb,resolver,codegen,cli), root modulecargoxx, and a stubmain.cppthat builds an emptycargoxxbinary. .clang-format(LLVM, 100-column) and.gitignore.SPEC.md,TECH_SPEC.md.- M1 foundation in
cargoxx.util:ErrorCode(numbers perTECH_SPEC.md§4),Error,Result<T> = std::expected<T, Error>, andformat(Error)renderingerror[Ennnn]: ...with optional--> path:line:colandhint:lines.formatlives insrc/util/error.cppas a module implementation unit. Catch2 v3wired throughflake.nix(libc++-built override) and registered in CMake;tests/util_error.cppcovers six format cases viacatch_discover_tests.cargoxx.manifestpublic types (Package,Dependency,BuildSettings,Edition,Manifest) andparse(path)returningResult<Manifest>, backed by toml++ vendored asthird_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.cppcovers 17 cases.manifest::write(m, path)serializes aManifestas TOML using toml++. Dependencies are emitted alphabetically (matches Cargo). Round-trip property is exercised bytests/manifest_write.cpp(9 cases). Defaultedoperator==on the manifest structs supports comparison.cargoxx.layoutpublic types (Target,TargetKind,DiscoveredLayout) anddiscover(project_root, package_name). Walkssrc/recursively for the library (excluding thesrc/bin/subtree), enumeratessrc/main.cpp,src/bin/*.cpp,tests/*.cpp,examples/*.cppflat, sorts results for deterministic output, and returnsLayoutNoTargetwhen neither a library nor any binary is present.tests/layout_discovery.cppcovers 12 cases.cargoxx new <name>andcargoxx new --lib <name>scaffold a project directory withCargoxx.toml,src/main.cpporsrc/lib.cppm, and a minimal.gitignore. Hyphens in the package name are mapped to underscores when generating the module/namespace identifier. Codegen offlake.nix/CMakeLists.txtis intentionally deferred to M3. CLI11 v2.6.2 vendored atthird_party/CLI11.hpp; entry point is nowcargoxx::cli::run(argc, argv).tests/cmd_new.cppcovers 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.cppcovers 7 cases.cargoxx.linkdb:Recipe,Database::open(),Database::resolve(...)for curated lookups. The curated database ships atdata/linkdb.jsonwith all 25 packages fromSPEC.md§11. Component-substitution helpers expand{{components}}(space-joined) infind_packageand fan out{{component}}per-target. Path is injected via theCARGOXX_LINKDB_DEFAULT_PATHcompile definition. nlohmann/json 3.12.0 vendored atthird_party/json.hpp. SQLite overlay andadd_manualare deferred to the M2 follow-up commit.tests/linkdb_lookup.cppcovers 13 cases including a smoke test that resolves all 25 curated packages.cargoxx.lockfile:Lockfile,LockfilePackagetypes andparse(path)/write(lock, path)matching the format inSPEC.md§5. AlsoLockfile::nixpkgs_rev()returns the shared revision (codegen will consume this in M3).tests/lockfile_round_trip.cppcovers 9 cases.cargoxx.codegen:GenerateInputsplus the pure functionflake_nix(in) -> std::string. Substitutes the package name, the resolved nixpkgs revision (defaulting tonixos-unstablewhen the lockfile pins none), and a deduplicated list of depnixpkgs_attrentries intobuildInputs. Output is byte-deterministic.tests/codegen_flake.cppcovers 7 cases. Note: SPEC §7's template did not showbuildInputs; we add one betweennativeBuildInputsand theenv.NIX_CFLAGS_COMPILEblock as the natural slot for the deps that TECH_SPEC §10 says we splice in.codegen::cmake_lists(in) -> std::stringperSPEC.md§8 andTECH_SPEC.md§9:find_packageper dep, optional library target with module units + private impl sources, primary binary<pkg>_binlinked against the library, additional binaries fromsrc/bin/, tests withenable_testing()+add_test, examples, and[build]honoringwarnings_as_errorsandsanitizers. Source paths emitted relative tobuild/(i.e. prefixed with../). Output is deterministic.tests/codegen_cmake.cppcovers 11 cases.cargoxx.resolver::devbox_resolve(name, version)and pure parserparse_devbox_resolve(json)— port of devbox'sinternal/searcher/client.goResolve method. Hitshttps://search.devbox.sh/v1/resolve?name=<n>&version=<v>via curl and pulls outname,version,commit_hash,attr_paths. Falls back to the per-systemsystems.<plat>.commit_hashwhen the top-level field is empty (older response shapes). 404 →ResolutionUnknownPackage, missing commit_hash →ResolutionVersionNotFound, transport / parse errors →ResolutionNetworkError.tests/devbox_resolve_parse.cppcovers 6 cases against fixtures derived from a real fmt 10.2.1 response;tests/devbox_resolve_live.cpp(gated byCARGOXX_NETWORK_TESTS=1) hits the live API.cargoxx.resolver::resolve_version(name, version)orchestrator chainsdevbox_resolve(HTTP, primary) →nixpkgs_git_resolve(offline, fallback). Returns a 40-char nixpkgs SHA. Wildcards (*, empty) are rejected withResolutionVersionNotFoundsince 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 intoCargoxx.lock'snixpkgs_revfor that dep. Wildcards (cargoxx add fmt) skip resolution and tracknixos-unstableas before. Tests opt out of the network resolver viaCARGOXX_NO_AUTORESOLVE=1.cargoxx.resolver::nixpkgs_git_resolve(name, version, repo_path?)— fallback for when search.devbox.sh is unreachable. Lazily cloneshttps://github.com/NixOS/nixpkgs.gitinto$XDG_CACHE_HOME/cargoxx/nixpkgs/(or$HOME/.cache/...) on first use, then runsgit -C <repo> log --all -S 'version = "<v>"' --pretty='%H %ct' -- pkgs/and returns the youngest matching commit. Pure helperpick_youngest_commit(text)parses the%H %ctlines. The unit test builds a tiny throwaway git fixture that mirrors thepkgs/development/libraries/<pkg>/default.nixlayout (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. OnLinkdbUnknownPackage,cmd_addinvokesresolver::discoverwhich: probesnixpkgs#<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 realcargoxx buildagainst an emptyint main() {}project viaverify_link; on first success persists a confirmed overlay row. Subsequentcargoxx add <pkg>for the same package is an overlay-cache hit (~ms). End-to-end live: a freshcargoxx 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 viaCARGOXX_NO_AUTORESOLVE=1.cargoxx.linkdb::Database::insert_provisional,confirm_provisional, andabort_provisional— three-step lifecycle for non-manualoverlay rows: insert withverified_at = 0, run a build, then either bumpverified_at = nowon success orDELETEthe row on failure.cargoxx.resolver::verify_link(req, build_fn)— scaffolds a tinyCargoxx.toml+int main() {}project under a scratch dir, writes a provisional overlay row pointing at the candidateRecipe, runs the caller-suppliedBuildFn(typicallycli::cmd_buildinjected 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.cppexercises both success and failure paths against a mockBuildFn, verifying that the overlay row's lifecycle matches the build outcome.cargoxx.resolver::vcpkg_probe(name)— fetcheshttps://raw.githubusercontent.com/microsoft/vcpkg/master/ports/<name>/usageand feeds it throughparse_vcpkg_usage. The pure parser extracts the firstfind_package(...)arg block, addsREQUIREDif absent, and gatherstarget_link_librariestargets that contain::(skips generator expressions and bare names).tests/vcpkg_probe_parse.cppcovers 6 cases;tests/vcpkg_probe_live.cpp(gated byCARGOXX_NETWORK_TESTS=1) verifies fmt + a 404 path against real microsoft/vcpkg ports.cargoxx.resolver::conan_probe(name)— fetcheshttps://raw.githubusercontent.com/conan-io/conan-center-index/master/recipes/<name>/all/conanfile.pyviacurl(text-only — never executes Python, perSPEC.md§14) and feeds it throughparse_conanfile. Pure parser handles both the modernset_property("cmake_target_name", "...")form and the legacynames["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'sf"fmt::{target}"parse correctly.tests/conan_probe_parse.cppcovers 6 cases;tests/conan_probe_live.cpp(gated byCARGOXX_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.cmakefiles, scans them and their sibling.cmakefiles (e.g. the<pkg>-targets.cmakethat real packages like fmt put theiradd_library(... IMPORTED)calls in) for IMPORTED + ALIAS targets, and returns theNixCmakeCandidatewhose derived stem best matchespackage_name(case-insensitive equality > prefix > first non-empty). Pure helpersscan_imported_targets(text)andconfig_stem_to_package(filename)are exported for unit testing.tests/nix_cmake_scan_parse.cppcovers 11 cases including a fixture that mirrors fmt's<pkg>-config.cmake → <pkg>-targets.cmakelayout.tests/nix_cmake_scan_live.cpp(gated byCARGOXX_NETWORK_TESTS=1) realizesnixpkgs#fmt.devvianix buildand verifiesfmt::fmtis discovered end-to-end.cargoxx.resolver::nixpkgs_probe(attr)— runsnix eval nixpkgs#<attr> --json --apply 'p: { version, path }'viaexec::runand returns aNixpkgsInfo { attr, version, out_path }. Distinguishes missing attributes (ResolutionUnknownPackage) from evaluator/network errors (ResolutionNetworkError). Thepathfield name avoids nix'soutPath-driven derivation coercion in--jsonmode (which would otherwise serialize the result as a bare path string instead of a JSON object). Pure parserparse_nix_eval_json(attr, text)exposed for unit tests; live tests intests/nixpkgs_probe_live.cppare gated behindCARGOXX_NETWORK_TESTS=1(verified locally againsthello).cargoxx add <pkg>[@<version>] [--components a,b]editsCargoxx.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. Generatedflake.nixcontinues to track thenixos-unstablebranch when the lockfile carries no rev.util::satisfiestreatsversion == "*"as a match against any range, mirroring the existingrange == "*"shortcut.scripts/verify-curated-db.shplus per-package using-snippets atscripts/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 runscargoxx build, asserting the binary is produced. 21/25 packages pass againstnixos-unstable; the remaining 4 are documented as skipped:catch2/gtest(their default linkdb targets ship their ownmain()which collides with the snippet),grpc(needsprotobufdeclared 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:boostrecipe is now header-only —find_package(Boost REQUIRED)+Boost::headers. Boost 1.89'sBoostConfig.cmakesearches for separately-installedboost_<comp>Config.cmakefiles that nixpkgs doesn't ship, so the previousCOMPONENTSform failed at configure time. Linkdb-component tests now useabseil-cppfor component coverage.cargoxx remove <pkg>drops a declared dep fromCargoxx.toml, errors when the dep is not declared. Other deps are preserved.- End-to-end verified on a freshly-scaffolded project.
tests/cmd_add.cppandtests/cmd_remove.cppcover 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], andcargoxx clean.runbuilds first, picks the binary (errors with the available list when the project has multiple bins and--binis omitted), and execs it with the process exit code propagated.testinvokesnix develop -c ctest --test-dir build/<profile> --output-on-failure.cleanremovesbuild/while leavingCargoxx.lockandflake.lockintact. End-to-end verified against a freshly-scaffolded project.tests/cmd_run.cppandtests/cmd_clean.cppcover 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>thennix develop -c cmake --buildwith optional--target, both withinherit_stdio = trueso the user sees compilation output live. Non-zero cmake exit returnsBuildCmakeFailed. End-to-end verified:cargoxx new myapp && cargoxx buildproduces a workingbuild/debug/myappthat printsHello from myapp!.- Generated
build/CMakeLists.txtnow opts into the experimentalimport std;UUID +CMAKE_CXX_MODULE_STD ON, and setsCMAKE_CXX_EXTENSIONS ON(deviation from SPEC §8 — required for libc++'s std module to load withoutmodule-file-config-mismatchon clang 21). Without these the templates fromcargoxx newfail to compile. cargoxx.exec:ExecResult,ExecOptions, andrun(program, args, opts)— argv-only subprocess wrapper around reproc 14.2.4. Captures stdout/stderr (or inherits stdio whenopts.inherit_stdiois set), supportscwd,env_overrides, and atimeout(enforced viareproc_options.deadlineso drains returnREPROC_ETIMEDOUTinstead of blocking on long-lived streams). On destruction reproc terminates and then kills any still-running child. ReturnsExecToolNotFoundforENOENTandExecCommandFailedfor other failures including timeouts.tests/exec_run.cppcovers 9 cases.cargoxx build --no-buildend-to-end. ReadsCargoxx.toml, discovers the layout, opens the curated linkdb, resolves aRecipeper manifest dep, synthesizes a freshCargoxx.lock, and writesflake.nix,build/CMakeLists.txt, andCargoxx.lock. With no resolver yet (M5),nixpkgs_revis left null and the generated flake falls back to thenixos-unstablebranch. Without--no-build, the command still generates files but returns aNotImplementederror pointing at M4.tests/cmd_build.cppcovers 8 cases.- SQLite overlay:
Database::open(overlay_path)now opens (and creates, if missing) a per-userlinkdb.sqlitecache, applying the schema fromSPEC.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 withsource = 'manual'andverified_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.cppcovers 7 cases (insert/persist, override-curated, version-range gating, components rejection, move semantics).