#include "installables.hh" #include "command.hh" #include "attr-path.hh" #include "common-eval-args.hh" #include "derivations.hh" #include "eval-inline.hh" #include "eval.hh" #include "get-drvs.hh" #include "store-api.hh" #include "shared.hh" #include "flake/flake.hh" #include "eval-cache.hh" #include "url.hh" #include "registry.hh" #include #include #include namespace nix { void completeFlakeInputPath( ref evalState, const FlakeRef & flakeRef, std::string_view prefix) { auto flake = flake::getFlake(*evalState, flakeRef, true); for (auto & input : flake.inputs) if (hasPrefix(input.first, prefix)) completions->add(input.first); } MixFlakeOptions::MixFlakeOptions() { auto category = "Common flake-related options"; addFlag({ .longName = "recreate-lock-file", .description = "Recreate the flake's lock file from scratch.", .category = category, .handler = {&lockFlags.recreateLockFile, true} }); addFlag({ .longName = "no-update-lock-file", .description = "Do not allow any updates to the flake's lock file.", .category = category, .handler = {&lockFlags.updateLockFile, false} }); addFlag({ .longName = "no-write-lock-file", .description = "Do not write the flake's newly generated lock file.", .category = category, .handler = {&lockFlags.writeLockFile, false} }); addFlag({ .longName = "no-registries", .description = "Don't allow lookups in the flake registries.", .category = category, .handler = {&lockFlags.useRegistries, false} }); addFlag({ .longName = "commit-lock-file", .description = "Commit changes to the flake's lock file.", .category = category, .handler = {&lockFlags.commitLockFile, true} }); addFlag({ .longName = "update-input", .description = "Update a specific flake input (ignoring its previous entry in the lock file).", .category = category, .labels = {"input-path"}, .handler = {[&](std::string s) { lockFlags.inputUpdates.insert(flake::parseInputPath(s)); }}, .completer = {[&](size_t, std::string_view prefix) { if (auto flakeRef = getFlakeRefForCompletion()) completeFlakeInputPath(getEvalState(), *flakeRef, prefix); }} }); addFlag({ .longName = "override-input", .description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.", .category = category, .labels = {"input-path", "flake-url"}, .handler = {[&](std::string inputPath, std::string flakeRef) { lockFlags.writeLockFile = false; lockFlags.inputOverrides.insert_or_assign( flake::parseInputPath(inputPath), parseFlakeRef(flakeRef, absPath("."))); }} }); addFlag({ .longName = "inputs-from", .description = "Use the inputs of the specified flake as registry entries.", .category = category, .labels = {"flake-url"}, .handler = {[&](std::string flakeRef) { auto evalState = getEvalState(); auto flake = flake::lockFlake( *evalState, parseFlakeRef(flakeRef, absPath(".")), { .writeLockFile = false }); for (auto & [inputName, input] : flake.lockFile.root->inputs) { auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes if (auto input3 = std::dynamic_pointer_cast(input2)) { overrideRegistry( fetchers::Input::fromAttrs({{"type","indirect"}, {"id", inputName}}), input3->lockedRef.input, {}); } } }}, .completer = {[&](size_t, std::string_view prefix) { completeFlakeRef(getEvalState()->store, prefix); }} }); } SourceExprCommand::SourceExprCommand() { addFlag({ .longName = "file", .shortName = 'f', .description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.", .category = installablesCategory, .labels = {"file"}, .handler = {&file}, .completer = completePath }); addFlag({ .longName = "expr", .description = "Interpret installables as attribute paths relative to the Nix expression *expr*.", .category = installablesCategory, .labels = {"expr"}, .handler = {&expr} }); addFlag({ .longName = "derivation", .description = "Operate on the store derivation rather than its outputs.", .category = installablesCategory, .handler = {&operateOn, OperateOn::Derivation}, }); } Strings SourceExprCommand::getDefaultFlakeAttrPaths() { return {"defaultPackage." + settings.thisSystem.get()}; } Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() { return { // As a convenience, look for the attribute in // 'outputs.packages'. "packages." + settings.thisSystem.get() + ".", // As a temporary hack until Nixpkgs is properly converted // to provide a clean 'packages' set, look in 'legacyPackages'. "legacyPackages." + settings.thisSystem.get() + "." }; } void SourceExprCommand::completeInstallable(std::string_view prefix) { if (file) return; // FIXME completeFlakeRefWithFragment( getEvalState(), lockFlags, getDefaultFlakeAttrPathPrefixes(), getDefaultFlakeAttrPaths(), prefix); } void completeFlakeRefWithFragment( ref evalState, flake::LockFlags lockFlags, Strings attrPathPrefixes, const Strings & defaultFlakeAttrPaths, std::string_view prefix) { /* Look for flake output attributes that match the prefix. */ try { auto hash = prefix.find('#'); if (hash != std::string::npos) { auto fragment = prefix.substr(hash + 1); auto flakeRefS = std::string(prefix.substr(0, hash)); // FIXME: do tilde expansion. auto flakeRef = parseFlakeRef(flakeRefS, absPath(".")); auto evalCache = openEvalCache(*evalState, std::make_shared(lockFlake(*evalState, flakeRef, lockFlags))); auto root = evalCache->getRoot(); /* Complete 'fragment' relative to all the attrpath prefixes as well as the root of the flake. */ attrPathPrefixes.push_back(""); for (auto & attrPathPrefixS : attrPathPrefixes) { auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); auto attrPathS = attrPathPrefixS + std::string(fragment); auto attrPath = parseAttrPath(*evalState, attrPathS); std::string lastAttr; if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { lastAttr = attrPath.back(); attrPath.pop_back(); } auto attr = root->findAlongAttrPath(attrPath); if (!attr) continue; for (auto & attr2 : attr->getAttrs()) { if (hasPrefix(attr2, lastAttr)) { auto attrPath2 = attr->getAttrPath(attr2); /* Strip the attrpath prefix. */ attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2)); } } } /* And add an empty completion for the default attrpaths. */ if (fragment.empty()) { for (auto & attrPath : defaultFlakeAttrPaths) { auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath)); if (!attr) continue; completions->add(flakeRefS + "#"); } } } } catch (Error & e) { warn(e.msg()); } completeFlakeRef(evalState->store, prefix); } /* ref EvalCommand::getEvalState() { if (!evalState) evalState = std::make_shared(searchPath, getStore()); return ref(evalState); } EvalCommand::~EvalCommand() { if (evalState) evalState->printStats(); } */ void completeFlakeRef(ref store, std::string_view prefix) { if (prefix == "") completions->add("."); completeDir(0, prefix); /* Look for registry entries that match the prefix. */ for (auto & registry : fetchers::getRegistries(store)) { for (auto & entry : registry->entries) { auto from = entry.from.to_string(); if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) { std::string from2(from, 6); if (hasPrefix(from2, prefix)) completions->add(from2); } else { if (hasPrefix(from, prefix)) completions->add(from); } } } } DerivedPathWithHints Installable::toDerivedPathWithHints() { auto buildables = toDerivedPathsWithHints(); if (buildables.size() != 1) throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size()); return std::move(buildables[0]); } std::vector, std::string>> Installable::getCursors(EvalState & state) { auto evalCache = std::make_shared(std::nullopt, state, [&]() { return toValue(state).first; }); return {{evalCache->getRoot(), ""}}; } std::pair, std::string> Installable::getCursor(EvalState & state) { auto cursors = getCursors(state); if (cursors.empty()) throw Error("cannot find flake attribute '%s'", what()); return cursors[0]; } struct InstallableStorePath : Installable { ref store; StorePath storePath; InstallableStorePath(ref store, StorePath && storePath) : store(store), storePath(std::move(storePath)) { } std::string what() override { return store->printStorePath(storePath); } DerivedPathsWithHints toDerivedPathsWithHints() override { if (storePath.isDerivation()) { std::map> outputs; auto drv = store->readDerivation(storePath); for (auto & [name, output] : drv.outputsAndOptPaths(*store)) outputs.emplace(name, output.second); return { DerivedPathWithHints::Built { .drvPath = storePath, .outputs = std::move(outputs) } }; } else { return { DerivedPathWithHints::Opaque { .path = storePath, } }; } } std::optional getStorePath() override { return storePath; } }; DerivedPathsWithHints InstallableValue::toDerivedPathsWithHints() { DerivedPathsWithHints res; std::map>> drvsToOutputs; // Group by derivation, helps with .all in particular for (auto & drv : toDerivations()) { auto outputName = drv.outputName; if (outputName == "") throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath)); drvsToOutputs[drv.drvPath].insert_or_assign(outputName, drv.outPath); } for (auto & i : drvsToOutputs) res.push_back(DerivedPathWithHints::Built { i.first, i.second }); return res; } struct InstallableAttrPath : InstallableValue { SourceExprCommand & cmd; RootValue v; std::string attrPath; InstallableAttrPath(ref state, SourceExprCommand & cmd, Value * v, const std::string & attrPath) : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath) { } std::string what() override { return attrPath; } std::pair toValue(EvalState & state) override { auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); state.forceValue(*vRes); return {vRes, pos}; } virtual std::vector toDerivations() override; }; std::vector InstallableAttrPath::toDerivations() { auto v = toValue(*state).first; Bindings & autoArgs = *cmd.getAutoArgs(*state); DrvInfos drvInfos; getDerivations(*state, *v, "", autoArgs, drvInfos, false); std::vector res; for (auto & drvInfo : drvInfos) { res.push_back({ state->store->parseStorePath(drvInfo.queryDrvPath()), state->store->maybeParseStorePath(drvInfo.queryOutPath()), drvInfo.queryOutputName() }); } return res; } std::vector InstallableFlake::getActualAttrPaths() { std::vector res; for (auto & prefix : prefixes) res.push_back(prefix + *attrPaths.begin()); for (auto & s : attrPaths) res.push_back(s); return res; } Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake) { auto vFlake = state.allocValue(); callFlake(state, lockedFlake, *vFlake); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); assert(aOutputs); state.forceValue(*aOutputs->value); return aOutputs->value; } ref openEvalCache( EvalState & state, std::shared_ptr lockedFlake) { auto fingerprint = lockedFlake->getFingerprint(); return make_ref( evalSettings.useEvalCache && evalSettings.pureEval ? std::optional { std::cref(fingerprint) } : std::nullopt, state, [&state, lockedFlake]() { /* For testing whether the evaluation cache is complete. */ if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0") throw Error("not everything is cached, but evaluation is not allowed"); auto vFlake = state.allocValue(); flake::callFlake(state, *lockedFlake, *vFlake); state.forceAttrs(*vFlake); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); assert(aOutputs); return aOutputs->value; }); } static std::string showAttrPaths(const std::vector & paths) { std::string s; for (const auto & [n, i] : enumerate(paths)) { if (n > 0) s += n + 1 == paths.size() ? " or " : ", "; s += '\''; s += i; s += '\''; } return s; } InstallableFlake::InstallableFlake( SourceExprCommand * cmd, ref state, FlakeRef && flakeRef, Strings && attrPaths, Strings && prefixes, const flake::LockFlags & lockFlags) : InstallableValue(state), flakeRef(flakeRef), attrPaths(attrPaths), prefixes(prefixes), lockFlags(lockFlags) { if (cmd && cmd->getAutoArgs(*state)->size()) throw UsageError("'--arg' and '--argstr' are incompatible with flakes"); } std::tuple InstallableFlake::toDerivation() { auto lockedFlake = getLockedFlake(); auto cache = openEvalCache(*state, lockedFlake); auto root = cache->getRoot(); for (auto & attrPath : getActualAttrPaths()) { auto attr = root->findAlongAttrPath( parseAttrPath(*state, attrPath), true ); if (!attr) continue; if (!attr->isDerivation()) throw Error("flake output attribute '%s' is not a derivation", attrPath); auto drvPath = attr->forceDerivation(); auto drvInfo = DerivationInfo{ std::move(drvPath), state->store->maybeParseStorePath(attr->getAttr(state->sOutPath)->getString()), attr->getAttr(state->sOutputName)->getString() }; return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)}; } throw Error("flake '%s' does not provide attribute %s", flakeRef, showAttrPaths(getActualAttrPaths())); } std::vector InstallableFlake::toDerivations() { std::vector res; res.push_back(std::get<2>(toDerivation())); return res; } std::pair InstallableFlake::toValue(EvalState & state) { auto lockedFlake = getLockedFlake(); auto vOutputs = getFlakeOutputs(state, *lockedFlake); auto emptyArgs = state.allocBindings(0); for (auto & attrPath : getActualAttrPaths()) { try { auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); state.forceValue(*v); return {v, pos}; } catch (AttrPathNotFound & e) { } } throw Error("flake '%s' does not provide attribute %s", flakeRef, showAttrPaths(getActualAttrPaths())); } std::vector, std::string>> InstallableFlake::getCursors(EvalState & state) { auto evalCache = openEvalCache(state, std::make_shared(lockFlake(state, flakeRef, lockFlags))); auto root = evalCache->getRoot(); std::vector, std::string>> res; for (auto & attrPath : getActualAttrPaths()) { auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); if (attr) res.push_back({attr, attrPath}); } return res; } std::shared_ptr InstallableFlake::getLockedFlake() const { if (!_lockedFlake) { _lockedFlake = std::make_shared(lockFlake(*state, flakeRef, lockFlags)); _lockedFlake->flake.config.apply(); // FIXME: send new config to the daemon. } return _lockedFlake; } FlakeRef InstallableFlake::nixpkgsFlakeRef() const { auto lockedFlake = getLockedFlake(); if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) { if (auto lockedNode = std::dynamic_pointer_cast(nixpkgsInput)) { debug("using nixpkgs flake '%s'", lockedNode->lockedRef); return std::move(lockedNode->lockedRef); } } return Installable::nixpkgsFlakeRef(); } std::vector> SourceExprCommand::parseInstallables( ref store, std::vector ss) { std::vector> result; if (file || expr) { if (file && expr) throw UsageError("'--file' and '--expr' are exclusive"); // FIXME: backward compatibility hack if (file) evalSettings.pureEval = false; auto state = getEvalState(); auto vFile = state->allocValue(); if (file) state->evalFile(lookupFileArg(*state, *file), *vFile); else { auto e = state->parseExprFromString(*expr, absPath(".")); state->eval(e, *vFile); } for (auto & s : ss) result.push_back(std::make_shared(state, *this, vFile, s == "." ? "" : s)); } else { for (auto & s : ss) { std::exception_ptr ex; try { auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath(".")); result.push_back(std::make_shared( this, getEvalState(), std::move(flakeRef), fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment}, getDefaultFlakeAttrPathPrefixes(), lockFlags)); continue; } catch (...) { ex = std::current_exception(); } if (s.find('/') != std::string::npos) { try { result.push_back(std::make_shared(store, store->followLinksToStorePath(s))); continue; } catch (BadStorePath &) { } catch (...) { if (!ex) ex = std::current_exception(); } } std::rethrow_exception(ex); /* throw Error( pathExists(s) ? "path '%s' is not a flake or a store path" : "don't know how to handle argument '%s'", s); */ } } return result; } std::shared_ptr SourceExprCommand::parseInstallable( ref store, const std::string & installable) { auto installables = parseInstallables(store, {installable}); assert(installables.size() == 1); return installables.front(); } DerivedPathsWithHints build(ref store, Realise mode, std::vector> installables, BuildMode bMode) { if (mode == Realise::Nothing) settings.readOnlyMode = true; DerivedPathsWithHints buildables; std::vector pathsToBuild; for (auto & i : installables) { for (auto & b : i->toDerivedPathsWithHints()) { std::visit(overloaded { [&](DerivedPathWithHints::Opaque bo) { pathsToBuild.push_back(bo); }, [&](DerivedPathWithHints::Built bfd) { StringSet outputNames; for (auto & output : bfd.outputs) outputNames.insert(output.first); pathsToBuild.push_back( DerivedPath::Built{bfd.drvPath, outputNames}); }, }, b.raw()); buildables.push_back(std::move(b)); } } if (mode == Realise::Nothing) printMissing(store, pathsToBuild, lvlError); else if (mode == Realise::Outputs) store->buildPaths(pathsToBuild, bMode); return buildables; } std::set toRealisedPaths( ref store, Realise mode, OperateOn operateOn, std::vector> installables) { std::set res; if (operateOn == OperateOn::Output) { for (auto & b : build(store, mode, installables)) std::visit(overloaded { [&](DerivedPathWithHints::Opaque bo) { res.insert(bo.path); }, [&](DerivedPathWithHints::Built bfd) { auto drv = store->readDerivation(bfd.drvPath); auto outputHashes = staticOutputHashes(*store, drv); for (auto & output : bfd.outputs) { if (settings.isExperimentalFeatureEnabled("ca-derivations")) { if (!outputHashes.count(output.first)) throw Error( "the derivation '%s' doesn't have an output named '%s'", store->printStorePath(bfd.drvPath), output.first); auto outputId = DrvOutput{outputHashes.at(output.first), output.first}; auto realisation = store->queryRealisation(outputId); if (!realisation) throw Error("cannot operate on an output of unbuilt content-addresed derivation '%s'", outputId.to_string()); res.insert(RealisedPath{*realisation}); } else { // If ca-derivations isn't enabled, behave as if // all the paths are opaque to keep the default // behavior assert(output.second); res.insert(*output.second); } } }, }, b.raw()); } else { if (mode == Realise::Nothing) settings.readOnlyMode = true; auto drvPaths = toDerivations(store, installables, true); res.insert(drvPaths.begin(), drvPaths.end()); } return res; } StorePathSet toStorePaths(ref store, Realise mode, OperateOn operateOn, std::vector> installables) { StorePathSet outPaths; for (auto & path : toRealisedPaths(store, mode, operateOn, installables)) outPaths.insert(path.path()); return outPaths; } StorePath toStorePath(ref store, Realise mode, OperateOn operateOn, std::shared_ptr installable) { auto paths = toStorePaths(store, mode, operateOn, {installable}); if (paths.size() != 1) throw Error("argument '%s' should evaluate to one store path", installable->what()); return *paths.begin(); } StorePathSet toDerivations(ref store, std::vector> installables, bool useDeriver) { StorePathSet drvPaths; for (auto & i : installables) for (auto & b : i->toDerivedPathsWithHints()) std::visit(overloaded { [&](DerivedPathWithHints::Opaque bo) { if (!useDeriver) throw Error("argument '%s' did not evaluate to a derivation", i->what()); auto derivers = store->queryValidDerivers(bo.path); if (derivers.empty()) throw Error("'%s' does not have a known deriver", i->what()); // FIXME: use all derivers? drvPaths.insert(*derivers.begin()); }, [&](DerivedPathWithHints::Built bfd) { drvPaths.insert(bfd.drvPath); }, }, b.raw()); return drvPaths; } InstallablesCommand::InstallablesCommand() { expectArgs({ .label = "installables", .handler = {&_installables}, .completer = {[&](size_t, std::string_view prefix) { completeInstallable(prefix); }} }); } void InstallablesCommand::prepare() { if (_installables.empty() && useDefaultInstallables()) // FIXME: commands like "nix install" should not have a // default, probably. _installables.push_back("."); installables = parseInstallables(getStore(), _installables); } std::optional InstallablesCommand::getFlakeRefForCompletion() { if (_installables.empty()) { if (useDefaultInstallables()) return parseFlakeRef(".", absPath(".")); return {}; } return parseFlakeRef(_installables.front(), absPath(".")); } InstallableCommand::InstallableCommand() { expectArgs({ .label = "installable", .optional = true, .handler = {&_installable}, .completer = {[&](size_t, std::string_view prefix) { completeInstallable(prefix); }} }); } void InstallableCommand::prepare() { installable = parseInstallable(getStore(), _installable); } }