module; #include export module cargoxx.linkdb; import std; import cargoxx.util; export namespace cargoxx::linkdb { struct Recipe { std::string nixpkgs_attr; std::string find_package; // post-substitution std::vector targets; // post-substitution std::string source; // 'curated' | 'manual' | etc. // When set, the dep is consumed via PkgConfig instead of a normal // find_package. Codegen emits // find_package(PkgConfig REQUIRED) // pkg_check_modules( REQUIRED IMPORTED_TARGET ) // and the recipe's `targets` list holds the synthesized // PkgConfig:: entries. `find_package` for these recipes is // the literal string "PkgConfig REQUIRED" — kept consistent so // overlay rows don't need a sentinel. std::optional pkg_config_module; bool operator==(const Recipe&) const = default; }; } // namespace cargoxx::linkdb namespace cargoxx::linkdb::detail { struct OverlayRow { std::string version_range; std::string nixpkgs_attr; std::string find_package; std::vector targets; std::string source; std::int64_t verified_at = 0; std::optional pkg_config_module; }; // RAII wrapper for an open sqlite3 connection used by the overlay database. // Move-only would be redundant — the holder (Database::overlay_) is a // std::unique_ptr, so we delete copy and move outright. class OverlayState { public: OverlayState() = default; explicit OverlayState(sqlite3* handle) noexcept : db_(handle) {} ~OverlayState() { if (db_) { sqlite3_close(db_); } } OverlayState(const OverlayState&) = delete; OverlayState& operator=(const OverlayState&) = delete; OverlayState(OverlayState&&) = delete; OverlayState& operator=(OverlayState&&) = delete; [[nodiscard]] auto handle() const noexcept -> sqlite3* { return db_; } private: sqlite3* db_ = nullptr; }; auto overlay_open(const std::filesystem::path& path) -> cargoxx::util::Result>; auto overlay_insert_manual(OverlayState& state, const std::string& package, const std::string& version_range, const cargoxx::linkdb::Recipe& r) -> cargoxx::util::Result; // Insert a row from a non-manual probe. `verified_at = 0` flags the row as // provisional; the verify-link step bumps it to the current epoch on // success or deletes the row on failure. auto overlay_insert_provisional(OverlayState& state, const std::string& package, const std::string& version_range, const cargoxx::linkdb::Recipe& r, const std::string& source) -> cargoxx::util::Result; auto overlay_confirm_provisional(OverlayState& state, const std::string& package, const std::string& version_range, const std::string& source) -> cargoxx::util::Result; auto overlay_delete_recipe(OverlayState& state, const std::string& package, const std::string& version_range, const std::string& source) -> cargoxx::util::Result; // Drops every overlay row for `package` whose source is not "manual". // Used by `cargoxx add` to invalidate stale auto-discovered recipes // when the resolver/scanner logic has changed under the user — they // should never have to know `~/.cache/cargoxx/linkdb.sqlite` exists. auto overlay_evict_auto(OverlayState& state, const std::string& package) -> cargoxx::util::Result; auto overlay_query(OverlayState& state, const std::string& package) -> cargoxx::util::Result>; auto overlay_is_fresh(const OverlayRow& row, std::int64_t now_epoch_seconds) -> bool; } // namespace cargoxx::linkdb::detail export namespace cargoxx::linkdb { class Database { public: static auto open(std::optional overlay_path = std::nullopt) -> util::Result; auto resolve(const std::string& package, const std::string& version, const std::vector& components = {}) -> util::Result; auto add_manual(const std::string& package, const std::string& version_range, const Recipe& r) -> util::Result; // Provisional-recipe lifecycle, used by the resolver's verify-link step. // `source` should be one of "conan", "vcpkg", "nix-probe", or any other // probe identifier (NOT "manual"/"curated", which have their own // contracts). auto insert_provisional(const std::string& package, const std::string& version_range, const Recipe& r, const std::string& source) -> util::Result; auto confirm_provisional(const std::string& package, const std::string& version_range, const std::string& source) -> util::Result; auto abort_provisional(const std::string& package, const std::string& version_range, const std::string& source) -> util::Result; // Evict every non-manual overlay row for `package`. Called by // `cargoxx add` so users never have to manually drop stale // auto-discovered recipes when cargoxx's resolver/scanner logic // changes. auto evict_auto_recipes(const std::string& package) -> util::Result; private: Database() = default; std::unique_ptr overlay_; }; // Returns the default overlay-database path: // $XDG_CACHE_HOME/cargoxx/linkdb.sqlite (if XDG_CACHE_HOME is set) // $HOME/.cache/cargoxx/linkdb.sqlite (else if HOME is set) // /.cargoxx-linkdb.sqlite (final fallback) auto default_overlay_path() -> std::filesystem::path; } // namespace cargoxx::linkdb