export module cargoxx.resolver; import std; import cargoxx.util; import cargoxx.exec; import cargoxx.linkdb; import cargoxx.manifest; export namespace cargoxx::resolver { // What `nix eval nixpkgs#` reports for a package: a confirmation that // the attribute exists, a best-effort version string, and the realized // nix-store path(s) so later probes can scan its installed CMake configs. // // Multi-output packages (boost, openssl, llvm, ...) expose CMake configs // in their `dev` output, not in the default `out`. When the package has // a separate dev output its store path is captured here so callers can // scan it preferentially. struct NixpkgsInfo { std::string attr; // the queried name, e.g. "simdjson" std::string version; // empty when the derivation has no version std::string out_path; // default output's absolute /nix/store/... path std::string dev_path; // dev output's path; empty when no dev output }; // Pure parser exposed for unit testing. Accepts the raw JSON returned by // `nix eval --json --apply 'p: { ... }'` and extracts NixpkgsInfo. auto parse_nix_eval_json(std::string_view attr, std::string_view json) -> util::Result; // Runs `nix eval nixpkgs# --json --apply ...` via exec::run. Returns // `ResolutionUnknownPackage` when the attribute is missing, // `ResolutionNetworkError` on timeout or evaluator errors. auto nixpkgs_probe(const std::string& attr) -> util::Result; // Materializes a flake attribute on disk by shelling out to // nix build --no-link --print-out-paths nixpkgs# // and returns the absolute store path. The eval-only `nixpkgs_probe` // computes the path the output *would* have, but it isn't actually // present on disk until it's been built / fetched from the binary // cache; nix_cmake_scan needs it on disk to walk lib/cmake. Use // `realize_path("libclang.dev")` to scan a dev output, etc. // // `ResolutionUnknownPackage` for unknown attrs, `ResolutionNetworkError` // for build / network errors. auto realize_path(const std::string& flake_attr) -> util::Result; // One CMake config-file's IMPORTED targets together with the find_package // expression derived from its filename stem. struct NixCmakeCandidate { std::string find_package; // e.g. "fmt CONFIG REQUIRED" std::vector targets; // e.g. ["fmt::fmt"] std::filesystem::path config_file; // the *Config.cmake we scraped }; // Pure: scan a single CMake config text for `add_library(... IMPORTED)` // target names. ALIAS targets are also collected so canonical // `::` forms get picked up. auto scan_imported_targets(std::string_view config_text) -> std::vector; // Pure: pick the public-API subset out of a freshly scanned target // list. See nix_cmake_scan.cpp for the rule (namespaced > stem-named // umbrella > pass-through). Exported so unit tests can drive it // without scaffolding an on-disk store. auto filter_public_targets(std::vector targets, std::string_view stem) -> std::vector; // Pure: turn a CMake config filename into the find_package name. // `fmtConfig.cmake` / `fmt-config.cmake` -> `fmt`. auto config_stem_to_package(std::string_view filename) -> std::string; // Walks /lib/cmake/** for *Config.cmake / *-config.cmake files. // Picks the candidate whose derived package name best matches `package_name` // (exact case-insensitive equality > prefix > first non-empty target list). // Returns ResolutionUnknownPackage when nothing usable is found. auto nix_cmake_scan(const std::filesystem::path& store_path, const std::string& package_name) -> util::Result; // A pkg-config-shaped recipe: the package ships a `.pc` file rather // than a CMake config. Consumed via `find_package(PkgConfig REQUIRED)` // + `pkg_check_modules( REQUIRED IMPORTED_TARGET )`, // linking against the generated `PkgConfig::` target. struct PcCandidate { std::string pc_module; // file stem, e.g. "sqlite3" std::filesystem::path pc_file; // path to the .pc on disk }; // Walks /lib/pkgconfig/*.pc and picks the best match for // `package_name`. Returns ResolutionUnknownPackage when no `.pc` file // is present or none scores acceptably. auto pc_scan(const std::filesystem::path& store_path, const std::string& package_name) -> util::Result; // Output of a conan-center-index recipe scrape. struct ConanRecipe { std::string find_package; // e.g. "fmt CONFIG REQUIRED" std::vector targets; // e.g. ["fmt::fmt"] }; // Pure: scrapes a conanfile.py text for `cmake_target_name` and // `cmake_file_name`. Handles both the modern // `cpp_info.set_property("cmake_target_name", "...")` form and the // legacy `cpp_info.names["cmake_find_package"] = "..."` form. Returns // ResolutionUnknownPackage when no recognizable recipe is found. auto parse_conanfile(std::string_view conanfile_text, const std::string& fallback_name) -> util::Result; // Fetches https://raw.githubusercontent.com/conan-io/conan-center-index/ // master/recipes//all/conanfile.py via `curl` and feeds it through // parse_conanfile. 404 → ResolutionUnknownPackage; transport errors → // ResolutionNetworkError. auto conan_probe(const std::string& name) -> util::Result; // Output of a microsoft/vcpkg port usage-file scrape. struct VcpkgRecipe { std::string find_package; // e.g. "fmt CONFIG REQUIRED" std::vector targets; // e.g. ["fmt::fmt"] }; // Pure: scrape a vcpkg port `usage` file (plain CMake) for the first // find_package(...) arguments and the targets named in the corresponding // target_link_libraries(...) call. Returns ResolutionUnknownPackage when // no find_package directive appears. auto parse_vcpkg_usage(std::string_view usage_text) -> util::Result; // Fetches https://raw.githubusercontent.com/microsoft/vcpkg/master/ports//usage // via `curl` and feeds it through parse_vcpkg_usage. 404 → // ResolutionUnknownPackage; transport errors → ResolutionNetworkError. auto vcpkg_probe(const std::string& name) -> util::Result; // Caller-supplied closure that runs `cargoxx build` (or any equivalent // build) on a project rooted at the given path. Injected so the resolver // stays decoupled from `cargoxx.cli`. using BuildFn = std::function(const std::filesystem::path& project_root)>; struct VerifyLinkRequest { linkdb::Recipe candidate; // recipe under test std::string source; // "conan" | "vcpkg" | "nix-probe" std::string package_name; std::string version_spec; // user-supplied spec (e.g. "*", "1.2") std::vector components; std::filesystem::path overlay_path; // sqlite file we read/write std::filesystem::path scratch_root; // parent dir for the tmp project }; // Scaffolds a tiny Cargoxx project under `req.scratch_root`, writes a // provisional overlay row pointing at `req.candidate`, runs `build_fn` on // the project (typically `cli::cmd_build`), and depending on the build // result either confirms the provisional row (verified_at = now) or // deletes it. Cleans the tmp project regardless. Returns success only // when the build itself succeeded. auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn) -> util::Result; // What discover() returns: the verified Recipe and a tag identifying // which probe yielded it ("conan", "vcpkg", or "nix-probe"). struct Discovered { linkdb::Recipe recipe; std::string source; }; // Walks the full auto-resolution chain for a package not present in the // curated linkdb or the user's overlay: // 1. nixpkgs_probe(name) — confirms the attribute exists, captures // version + out_path // 2. for each of conan_probe, vcpkg_probe, nix_cmake_scan(out_path,…): // build a candidate linkdb::Recipe, run verify_link on it, return // on first success // 3. all candidates failed → ResolutionUnsatisfiable auto discover(const std::string& name, const std::string& version_spec, const std::vector& components, const std::filesystem::path& overlay_path, const std::filesystem::path& scratch_root, const BuildFn& build_fn) -> util::Result; // Output of devbox's /v1/resolve API. We capture only the fields cargoxx // uses; the response carries far more metadata (license, summary, per- // system store hashes) that we deliberately ignore. struct DevboxResolution { std::string name; std::string version; std::string commit_hash; std::vector attr_paths; }; // Pure: parse the JSON body of GET https://search.devbox.sh/v1/resolve. // `commit_hash` must be present and non-empty; other fields tolerate // absence by leaving themselves blank. auto parse_devbox_resolve(std::string_view json) -> util::Result; // Calls GET https://search.devbox.sh/v1/resolve?name=&version= // via curl. 404 → ResolutionUnknownPackage; transport / parse errors → // ResolutionNetworkError. auto devbox_resolve(const std::string& name, const std::string& version) -> util::Result; // Pure: parse the output of // git log --all -S 'version = ""' --pretty='%H %ct' -- pkgs/ // (one commit per line: "<40-char-sha> ") and // return the youngest matching SHA, or nullopt on empty input. auto pick_youngest_commit(std::string_view git_log_output) -> std::optional; // Searches a local nixpkgs clone for the youngest commit that // introduced `version = ""` under pkgs/. `repo_path` // defaults to ~/.cache/cargoxx/nixpkgs/ — when the directory doesn't // exist the repo is cloned lazily (multi-GB, only on first miss). // 404-equivalent: ResolutionVersionNotFound. auto nixpkgs_git_resolve(const std::string& name, const std::string& version, std::optional repo_path = std::nullopt) -> util::Result; // What `resolve_version` returns: a nixpkgs commit and the attribute // path under which the package lives at that commit. The attr is // authoritative *for that rev* — the cargoxx-curated linkdb attrs // (e.g. `fmt_10`) only apply to the unpinned `nixos-unstable` set. // When devbox supplies multiple attr paths the first is canonical. struct ResolvedVersion { std::string nixpkgs_rev; std::string nixpkgs_attr; }; // Top-level orchestrator: try `devbox_resolve` first, then // `nixpkgs_git_resolve` as fallback. Returns the rev + attr or // `ResolutionVersionNotFound` when both probes come back empty. // // Use this from `cargoxx add @` to capture the pin // that lockfile/codegen will use. Wildcards (`*`, empty) should NOT // be passed — they are not concrete versions and the resolver // returns `ResolutionVersionNotFound` for them by design. auto resolve_version(const std::string& name, const std::string& version) -> util::Result; } // namespace cargoxx::resolver