nix-eval-jobs: add --no-instantiate flag
Closes #987 The patch adds a flag `--no-instantiate` which only performs evaluation without instantiating any derivations. Hence, GC root creation is also skipped. To achieve that, Lix is also put in read-only mode and all operations that require reading a derivation (e.g. constituents or listing input derivations) are disabled fallback values are set. This is a port of an upstream PR[1]. Given the divergence of the codebases (different restructurings on both ends, no more CA derivations) I decided to redo large portions from scratch instead of cherry-picking the patches. Hence, the authorship. Additionally the clean up of casts down to a local store are removed or guarded behind an if, as done in the upstream PR. [1] https://github.com/nix-community/nix-eval-jobs/pull/379 Co-authored-by: Jörg Thalheim <joerg@thalheim.io> Change-Id: Ib84f44e7799bc5577fd2ee98912458f16ebeab81
This commit is contained in:
co-authored by
Jörg Thalheim
parent
812f466e0d
commit
d6b0b8b382
@@ -0,0 +1,10 @@
|
||||
---
|
||||
synopsis: "nix-eval-jobs: support `--no-instantiate` flag"
|
||||
issues: [fj#987]
|
||||
category: Features
|
||||
credits: [mic92,ma27]
|
||||
---
|
||||
|
||||
`nix-eval-jobs` now supports a flag called `--no-instantiate`. With this enabled,
|
||||
no write operations on the eval store are performed. That means, only evaluation is
|
||||
performed, but derivations (and their gcroots) aren't created.
|
||||
@@ -48,12 +48,13 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, nix::DrvInfo &drvInfo,
|
||||
: constituents(constituents) {
|
||||
|
||||
auto localStore = state.ctx.store.try_cast_shared<nix::LocalFSStore>();
|
||||
auto canReadDerivation = localStore && !nix::settings.readOnlyMode;
|
||||
|
||||
try {
|
||||
for (auto &[outputName, optOutputPath] :
|
||||
drvInfo.queryOutputs(state, true)) {
|
||||
assert(optOutputPath);
|
||||
outputs[outputName] = localStore->printStorePath(*optOutputPath);
|
||||
outputs[outputName] = state.ctx.store->printStorePath(*optOutputPath);
|
||||
}
|
||||
} catch (const std::exception &e) { // NOLINT(lix-foreign-exceptions)
|
||||
state.ctx.errors.make<nix::EvalError>(
|
||||
@@ -82,6 +83,8 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, nix::DrvInfo &drvInfo,
|
||||
}
|
||||
meta = meta_;
|
||||
}
|
||||
|
||||
// !canReadDerivation together with checkCacheStatus is rejected in main().
|
||||
if (args.checkCacheStatus) {
|
||||
cacheStatus = queryIsCached(state.aio, *localStore, outputs)
|
||||
? Drv::CacheStatus::Cached
|
||||
@@ -90,8 +93,10 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, nix::DrvInfo &drvInfo,
|
||||
cacheStatus = Drv::CacheStatus::Unknown;
|
||||
}
|
||||
|
||||
drvPath = localStore->printStorePath(drvInfo.requireDrvPath(state));
|
||||
drvPath = state.ctx.store->printStorePath(drvInfo.requireDrvPath(state));
|
||||
name = drvInfo.queryName(state);
|
||||
|
||||
if (canReadDerivation) {
|
||||
auto drv = state.aio.blockOn(localStore->readDerivation(drvInfo.requireDrvPath(state)));
|
||||
for (const auto &[inputDrvPath, inputNode] : drv.inputDrvs) {
|
||||
std::set<std::string> inputDrvOutputs;
|
||||
@@ -100,8 +105,10 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, nix::DrvInfo &drvInfo,
|
||||
}
|
||||
inputDrvs[localStore->printStorePath(inputDrvPath)] = inputDrvOutputs;
|
||||
}
|
||||
name = drvInfo.queryName(state);
|
||||
system = drv.platform;
|
||||
} else {
|
||||
system = drvInfo.querySystem(state);
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nix::JSON &json, const Drv &drv) {
|
||||
@@ -133,15 +140,17 @@ void to_json(nix::JSON &json, const Drv &drv) {
|
||||
|
||||
void register_gc_root(nix::Path &gcRootsDir, std::string &drvPath, const nix::ref<nix::Store> &store,
|
||||
nix::AsyncIoRoot &aio) {
|
||||
if (!gcRootsDir.empty()) {
|
||||
if (!gcRootsDir.empty() && !nix::settings.readOnlyMode) {
|
||||
nix::Path root =
|
||||
gcRootsDir + "/" +
|
||||
std::string(nix::baseNameOf(drvPath));
|
||||
if (!nix::pathExists(root)) {
|
||||
auto localStore = store.try_cast_shared<nix::LocalFSStore>();
|
||||
if (localStore) {
|
||||
auto storePath =
|
||||
localStore->parseStorePath(drvPath);
|
||||
aio.blockOn(localStore->addPermRoot(storePath, root));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,19 @@ MyArgs::MyArgs(nix::AsyncIoRoot & aio) : MixCommonArgs("nix-eval-jobs"), aio_(ai
|
||||
.description = "print out a stack trace in case of evaluation errors",
|
||||
.handler = {&showTrace, true}});
|
||||
|
||||
addFlag({
|
||||
.longName = "no-instantiate",
|
||||
.aliases = {},
|
||||
.shortName = 0,
|
||||
.description =
|
||||
"don't instantiate (write) derivations, only evaluate (faster)",
|
||||
.category = "",
|
||||
.labels = {},
|
||||
.handler = {&noInstantiate, true},
|
||||
.completer = nullptr,
|
||||
.experimentalFeature = std::nullopt,
|
||||
});
|
||||
|
||||
addFlag({.longName = "expr",
|
||||
.shortName = 'E',
|
||||
.description = "treat the argument as a Nix expression",
|
||||
|
||||
@@ -29,6 +29,7 @@ class MyArgs : virtual public nix::MixEvalArgs,
|
||||
bool forceRecurse = false;
|
||||
bool checkCacheStatus = false;
|
||||
bool constituents = false;
|
||||
bool noInstantiate = false;
|
||||
size_t nrWorkers = 1;
|
||||
size_t maxMemorySize = 4096;
|
||||
|
||||
|
||||
@@ -326,6 +326,10 @@ void collector(MyArgs &myArgs, Sync<State> &state_,
|
||||
} else {
|
||||
auto state(state_.lock());
|
||||
state->jobs.insert_or_assign(response["attr"], response);
|
||||
if (nix::settings.readOnlyMode) {
|
||||
response.erase("namedConstituents");
|
||||
response.erase("constituents");
|
||||
}
|
||||
auto named = response.find("namedConstituents");
|
||||
if (named == response.end() || named->empty()) {
|
||||
response.erase("namedConstituents");
|
||||
@@ -367,6 +371,17 @@ int main(int argc, char **argv) {
|
||||
|
||||
myArgs.parseArgs(argv, argc);
|
||||
|
||||
/* Set no-instantiate mode if requested (makes evaluation faster) */
|
||||
if (myArgs.noInstantiate) {
|
||||
nix::settings.readOnlyMode = true;
|
||||
if (myArgs.constituents) {
|
||||
throw UsageError("--no-instantiate and --constituents are mutually exclusive");
|
||||
}
|
||||
if (myArgs.checkCacheStatus) {
|
||||
throw UsageError("--no-instantiate and --check-cache-status are mutually exclusive");
|
||||
}
|
||||
}
|
||||
|
||||
/* When building a flake, use pure evaluation (no access to
|
||||
'getEnv', 'currentSystem' etc. */
|
||||
if (myArgs.impure) {
|
||||
|
||||
@@ -285,3 +285,82 @@ def test_transitivity() -> None:
|
||||
assert aggregate0["attr"] == "aggregate0"
|
||||
|
||||
assert aggregate1["drvPath"] == aggregate0["constituents"][0]
|
||||
|
||||
|
||||
def test_mutually_exclusive_combinations() -> None:
|
||||
with TemporaryDirectory() as tempdir:
|
||||
for flag in ["constituents", "check-cache-status"]:
|
||||
result = subprocess.run(
|
||||
[
|
||||
str(BIN),
|
||||
"--gc-roots-dir",
|
||||
tempdir,
|
||||
"--meta",
|
||||
"--extra-experimental-features",
|
||||
"flakes",
|
||||
"--no-instantiate",
|
||||
f"--{flag}",
|
||||
"--workers",
|
||||
"1",
|
||||
"--flake",
|
||||
".#legacyPackages.x86_64-linux.constituents.success",
|
||||
],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
assert result.returncode == 1
|
||||
assert f"--no-instantiate and --{flag} are mutually exclusive" in result.stderr
|
||||
|
||||
|
||||
def test_no_instantiate_mode() -> None:
|
||||
"""Test that --no-instantiate flag works correctly"""
|
||||
with TemporaryDirectory() as tempdir:
|
||||
path = Path(tempdir)
|
||||
gcroots = path / "gcroots"
|
||||
gcroots.mkdir()
|
||||
results, _ = evaluate(
|
||||
tempdir,
|
||||
0,
|
||||
[
|
||||
"--gc-roots-dir",
|
||||
gcroots,
|
||||
"--eval-store",
|
||||
path / "root",
|
||||
"--meta",
|
||||
"--no-instantiate",
|
||||
"--flake",
|
||||
".#hydraJobs",
|
||||
]
|
||||
)
|
||||
assert len(results) == 4
|
||||
|
||||
# Check that all results have the expected structure
|
||||
for result in results:
|
||||
# In no-instantiate mode, drvPath should still be present (from the attr)
|
||||
assert "drvPath" in result
|
||||
assert result["drvPath"].endswith(".drv")
|
||||
|
||||
assert not (path / "root" / result["drvPath"][1:]).exists()
|
||||
|
||||
# System should still be present (from querySystem fallback)
|
||||
assert "system" in result
|
||||
assert result["system"] != ""
|
||||
|
||||
# Name should still be present
|
||||
assert "name" in result
|
||||
|
||||
# Outputs should still be present but may be empty
|
||||
assert "outputs" in result
|
||||
|
||||
# Cache status should not be present (it's Unknown and not included)
|
||||
assert "cacheStatus" not in result
|
||||
assert "neededBuilds" not in result
|
||||
assert "neededSubstitutes" not in result
|
||||
|
||||
# Input drvs should not be present (requires reading derivation from store)
|
||||
assert not result["inputDrvs"]
|
||||
|
||||
# No GC roots should be created in no-instantiate mode
|
||||
assert len(list(Path(gcroots).iterdir())) == 0
|
||||
|
||||
Reference in New Issue
Block a user