Files
cargoxx/src/linkdb/linkdb.cppm

156 lines
5.9 KiB
C++

module;
#include <sqlite3.h>
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<std::string> 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(<UPPER> REQUIRED IMPORTED_TARGET <pkg_config_module>)
// and the recipe's `targets` list holds the synthesized
// PkgConfig::<UPPER> entries. `find_package` for these recipes is
// the literal string "PkgConfig REQUIRED" — kept consistent so
// overlay rows don't need a sentinel.
std::optional<std::string> 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<std::string> targets;
std::string source;
std::int64_t verified_at = 0;
std::optional<std::string> 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<OverlayState>, 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<std::unique_ptr<OverlayState>>;
auto overlay_insert_manual(OverlayState& state, const std::string& package,
const std::string& version_range,
const cargoxx::linkdb::Recipe& r)
-> cargoxx::util::Result<void>;
// 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<void>;
auto overlay_confirm_provisional(OverlayState& state, const std::string& package,
const std::string& version_range,
const std::string& source)
-> cargoxx::util::Result<void>;
auto overlay_delete_recipe(OverlayState& state, const std::string& package,
const std::string& version_range,
const std::string& source)
-> cargoxx::util::Result<void>;
// 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<void>;
auto overlay_query(OverlayState& state, const std::string& package)
-> cargoxx::util::Result<std::vector<OverlayRow>>;
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<std::filesystem::path> overlay_path = std::nullopt)
-> util::Result<Database>;
auto resolve(const std::string& package, const std::string& version,
const std::vector<std::string>& components = {})
-> util::Result<Recipe>;
auto add_manual(const std::string& package, const std::string& version_range,
const Recipe& r) -> util::Result<void>;
// 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<void>;
auto confirm_provisional(const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void>;
auto abort_provisional(const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void>;
// 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<void>;
private:
Database() = default;
std::unique_ptr<detail::OverlayState> 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)
// <cwd>/.cargoxx-linkdb.sqlite (final fallback)
auto default_overlay_path() -> std::filesystem::path;
} // namespace cargoxx::linkdb