module; #include module cargoxx.lockfile; import std; import cargoxx.util; namespace cargoxx::lockfile { auto Lockfile::nixpkgs_rev() const -> std::optional { for (const auto& p : packages) { if (p.nixpkgs_rev && !p.nixpkgs_rev->empty()) { return p.nixpkgs_rev; } } return std::nullopt; } namespace { using util::Error; using util::ErrorCode; auto err(ErrorCode code, std::string msg, std::filesystem::path path) -> Error { return Error{code, std::move(msg), "", std::move(path), std::nullopt}; } auto extract_string_array(const toml::array& arr, std::string_view field, const std::filesystem::path& path) -> util::Result> { std::vector out; out.reserve(arr.size()); for (const auto& el : arr) { if (auto s = el.value()) { out.push_back(*s); } else { return std::unexpected(err(ErrorCode::ManifestInvalidField, std::format("'{}' must be an array of strings", field), path)); } } return out; } auto parse_package(const toml::table& tbl, const std::filesystem::path& path) -> util::Result { LockfilePackage pkg; if (auto v = tbl["name"].value()) { pkg.name = *v; } else { return std::unexpected( err(ErrorCode::ManifestInvalidField, "lockfile package missing 'name'", path)); } if (auto v = tbl["version"].value()) { pkg.version = *v; } else { return std::unexpected( err(ErrorCode::ManifestInvalidField, "lockfile package missing 'version'", path)); } if (const auto* deps = tbl["dependencies"].as_array()) { auto r = extract_string_array(*deps, "dependencies", path); if (!r) { return std::unexpected(r.error()); } pkg.dependencies = std::move(*r); } if (auto v = tbl["nixpkgs_attr"].value()) { pkg.nixpkgs_attr = *v; } if (auto v = tbl["nixpkgs_rev"].value()) { pkg.nixpkgs_rev = *v; } if (auto v = tbl["cargoxx_pkgs_attr"].value()) { pkg.cargoxx_pkgs_attr = *v; } if (auto v = tbl["cargoxx_pkgs_rev"].value()) { pkg.cargoxx_pkgs_rev = *v; } if (auto v = tbl["linkdb_source"].value()) { pkg.linkdb_source = *v; } if (auto v = tbl["find_package"].value()) { pkg.find_package = *v; } if (const auto* arr = tbl["targets"].as_array()) { auto r = extract_string_array(*arr, "targets", path); if (!r) { return std::unexpected(r.error()); } pkg.targets = std::move(*r); } if (auto v = tbl["pkg_config_module"].value()) { pkg.pkg_config_module = *v; } if (const auto* arr = tbl["brute_force_libs"].as_array()) { auto r = extract_string_array(*arr, "brute_force_libs", path); if (!r) { return std::unexpected(r.error()); } pkg.brute_force_libs = std::move(*r); } if (const auto* arr = tbl["brute_force_includes"].as_array()) { auto r = extract_string_array(*arr, "brute_force_includes", path); if (!r) { return std::unexpected(r.error()); } pkg.brute_force_includes = std::move(*r); } if (auto v = tbl["source_kind"].value()) { pkg.source_kind = *v; } if (auto v = tbl["source_path"].value()) { pkg.source_path = *v; } if (auto v = tbl["source_git_url"].value()) { pkg.source_git_url = *v; } if (auto v = tbl["source_git_commit"].value()) { pkg.source_git_commit = *v; } if (auto v = tbl["source_git_sha256"].value()) { pkg.source_git_sha256 = *v; } return pkg; } } // namespace auto parse(const std::filesystem::path& path) -> util::Result { std::error_code ec; if (!std::filesystem::exists(path, ec) || ec) { return std::unexpected(err(ErrorCode::ManifestNotFound, std::format("lockfile not found: {}", path.string()), path)); } toml::table root; try { root = toml::parse_file(path.string()); } catch (const toml::parse_error& e) { return std::unexpected(err(ErrorCode::ManifestParseError, std::format("toml parse error: {}", e.description()), path)); } Lockfile lock; if (auto v = root["version"].value()) { lock.version = *v; } if (auto v = root["nixpkgs_rev"].value()) { lock.nixpkgs_rev_pin = *v; } if (auto v = root["flake_utils_rev"].value()) { lock.flake_utils_rev_pin = *v; } if (const auto* arr = root["package"].as_array()) { lock.packages.reserve(arr->size()); for (const auto& el : *arr) { const auto* tbl = el.as_table(); if (!tbl) { return std::unexpected(err(ErrorCode::ManifestInvalidField, "[[package]] entries must be tables", path)); } auto p = parse_package(*tbl, path); if (!p) { return std::unexpected(p.error()); } lock.packages.push_back(std::move(*p)); } } return lock; } auto write(const Lockfile& lock, const std::filesystem::path& path) -> util::Result { toml::table root; root.insert_or_assign("version", lock.version); if (lock.nixpkgs_rev_pin) { root.insert_or_assign("nixpkgs_rev", *lock.nixpkgs_rev_pin); } if (lock.flake_utils_rev_pin) { root.insert_or_assign("flake_utils_rev", *lock.flake_utils_rev_pin); } toml::array packages; for (const auto& p : lock.packages) { toml::table tbl; tbl.insert_or_assign("name", p.name); tbl.insert_or_assign("version", p.version); if (!p.dependencies.empty()) { toml::array deps; for (const auto& d : p.dependencies) { deps.push_back(d); } tbl.insert_or_assign("dependencies", std::move(deps)); } if (p.nixpkgs_attr) { tbl.insert_or_assign("nixpkgs_attr", *p.nixpkgs_attr); } if (p.nixpkgs_rev) { tbl.insert_or_assign("nixpkgs_rev", *p.nixpkgs_rev); } if (p.cargoxx_pkgs_attr) { tbl.insert_or_assign("cargoxx_pkgs_attr", *p.cargoxx_pkgs_attr); } if (p.cargoxx_pkgs_rev) { tbl.insert_or_assign("cargoxx_pkgs_rev", *p.cargoxx_pkgs_rev); } if (p.linkdb_source) { tbl.insert_or_assign("linkdb_source", *p.linkdb_source); } if (p.find_package) { tbl.insert_or_assign("find_package", *p.find_package); } if (!p.targets.empty()) { toml::array arr; for (const auto& t : p.targets) { arr.push_back(t); } tbl.insert_or_assign("targets", std::move(arr)); } if (p.pkg_config_module) { tbl.insert_or_assign("pkg_config_module", *p.pkg_config_module); } if (!p.brute_force_libs.empty()) { toml::array arr; for (const auto& l : p.brute_force_libs) { arr.push_back(l); } tbl.insert_or_assign("brute_force_libs", std::move(arr)); } if (!p.brute_force_includes.empty()) { toml::array arr; for (const auto& i : p.brute_force_includes) { arr.push_back(i); } tbl.insert_or_assign("brute_force_includes", std::move(arr)); } if (p.source_kind) { tbl.insert_or_assign("source_kind", *p.source_kind); } if (p.source_path) { tbl.insert_or_assign("source_path", *p.source_path); } if (p.source_git_url) { tbl.insert_or_assign("source_git_url", *p.source_git_url); } if (p.source_git_commit) { tbl.insert_or_assign("source_git_commit", *p.source_git_commit); } if (p.source_git_sha256) { tbl.insert_or_assign("source_git_sha256", *p.source_git_sha256); } packages.push_back(std::move(tbl)); } root.insert_or_assign("package", std::move(packages)); std::ofstream out{path}; if (!out) { return std::unexpected(util::Error{ util::ErrorCode::Internal, std::format("cannot open lockfile for writing: {}", path.string()), "", path, std::nullopt, }); } out << root << '\n'; if (!out) { return std::unexpected(util::Error{ util::ErrorCode::Internal, std::format("write failed: {}", path.string()), "", path, std::nullopt, }); } return {}; } } // namespace cargoxx::lockfile