119 lines
3.9 KiB
C++
119 lines
3.9 KiB
C++
module;
|
|
|
|
#include <json.hpp>
|
|
|
|
module cargoxx.resolver;
|
|
|
|
import std;
|
|
import cargoxx.util;
|
|
import cargoxx.exec;
|
|
|
|
namespace cargoxx::resolver {
|
|
|
|
namespace {
|
|
|
|
// `outPath` is a magic attribute name that triggers nix's derivation
|
|
// coercion in --json mode (the attrset gets serialized as a bare path
|
|
// string). Renaming the field to `path` keeps it a proper JSON object.
|
|
//
|
|
// `dev_path` is captured for multi-output packages (boost, openssl,
|
|
// llvm, …) where CMake configs live under the `dev` output rather
|
|
// than `out`. `p ? dev` is false for single-output packages, leaving
|
|
// the field as the empty string.
|
|
constexpr std::string_view APPLY_FN =
|
|
"p: { version = p.version or \"\"; path = p.outPath; "
|
|
"dev_path = if p ? dev then p.dev.outPath else \"\"; }";
|
|
|
|
auto make_error(util::ErrorCode code, std::string msg) -> util::Error {
|
|
return util::Error{code, std::move(msg), "", std::nullopt, std::nullopt};
|
|
}
|
|
|
|
// nix eval emits these markers when an attribute is missing on the flake.
|
|
auto looks_like_missing_attribute(std::string_view stderr_text) -> bool {
|
|
return stderr_text.find("does not provide attribute") != std::string_view::npos ||
|
|
stderr_text.find("attribute '") != std::string_view::npos &&
|
|
stderr_text.find("missing") != std::string_view::npos;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto parse_nix_eval_json(std::string_view attr, std::string_view json)
|
|
-> util::Result<NixpkgsInfo> {
|
|
nlohmann::json j;
|
|
try {
|
|
j = nlohmann::json::parse(json);
|
|
} catch (const nlohmann::json::parse_error& e) {
|
|
return std::unexpected(make_error(
|
|
util::ErrorCode::ResolutionNetworkError,
|
|
std::format("nix eval emitted unparseable JSON: {}", e.what())));
|
|
}
|
|
|
|
if (!j.is_object()) {
|
|
return std::unexpected(make_error(
|
|
util::ErrorCode::ResolutionNetworkError,
|
|
"nix eval JSON is not an object"));
|
|
}
|
|
|
|
NixpkgsInfo info{
|
|
.attr = std::string{attr}, .version = {}, .out_path = {}, .dev_path = {},
|
|
};
|
|
|
|
if (j.contains("path") && j["path"].is_string()) {
|
|
info.out_path = j["path"].get<std::string>();
|
|
} else {
|
|
return std::unexpected(make_error(
|
|
util::ErrorCode::ResolutionNetworkError,
|
|
"nix eval JSON lacks 'path'"));
|
|
}
|
|
|
|
if (j.contains("version") && j["version"].is_string()) {
|
|
info.version = j["version"].get<std::string>();
|
|
}
|
|
|
|
if (j.contains("dev_path") && j["dev_path"].is_string()) {
|
|
info.dev_path = j["dev_path"].get<std::string>();
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
auto nixpkgs_probe(const std::string& attr) -> util::Result<NixpkgsInfo> {
|
|
if (attr.empty()) {
|
|
return std::unexpected(make_error(util::ErrorCode::ResolutionUnknownPackage,
|
|
"package name is empty"));
|
|
}
|
|
|
|
std::vector<std::string> args{
|
|
"--extra-experimental-features", "nix-command flakes",
|
|
"eval", std::format("nixpkgs#{}", attr), "--json", "--apply",
|
|
std::string{APPLY_FN},
|
|
};
|
|
|
|
auto r = exec::run("nix", args,
|
|
exec::ExecOptions{
|
|
.cwd = {},
|
|
.env_overrides = {},
|
|
.timeout = std::chrono::seconds{60},
|
|
.inherit_stdio = false,
|
|
});
|
|
if (!r) {
|
|
return std::unexpected(r.error());
|
|
}
|
|
|
|
if (r->exit_code != 0) {
|
|
if (looks_like_missing_attribute(r->stderr_text)) {
|
|
return std::unexpected(make_error(
|
|
util::ErrorCode::ResolutionUnknownPackage,
|
|
std::format("nixpkgs has no attribute '{}'", attr)));
|
|
}
|
|
return std::unexpected(make_error(
|
|
util::ErrorCode::ResolutionNetworkError,
|
|
std::format("nix eval failed (exit {}): {}", r->exit_code,
|
|
r->stderr_text)));
|
|
}
|
|
|
|
return parse_nix_eval_json(attr, r->stdout_text);
|
|
}
|
|
|
|
} // namespace cargoxx::resolver
|