diff options
Diffstat (limited to 'src/nix/installables.cc')
-rw-r--r-- | src/nix/installables.cc | 452 |
1 files changed, 336 insertions, 116 deletions
diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 0e8bba39d..671cf513a 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -1,3 +1,4 @@ +#include "installables.hh" #include "command.hh" #include "attr-path.hh" #include "common-eval-args.hh" @@ -7,75 +8,76 @@ #include "get-drvs.hh" #include "store-api.hh" #include "shared.hh" +#include "flake/flake.hh" +#include "flake/eval-cache.hh" #include <regex> +#include <queue> namespace nix { +MixFlakeOptions::MixFlakeOptions() +{ + mkFlag() + .longName("recreate-lock-file") + .description("recreate lock file from scratch") + .set(&recreateLockFile, true); + + mkFlag() + .longName("no-save-lock-file") + .description("do not save the newly generated lock file") + .set(&saveLockFile, false); + + mkFlag() + .longName("no-registries") + .description("don't use flake registries") + .set(&useRegistries, false); +} + +flake::HandleLockFile MixFlakeOptions::getLockFileMode() +{ + using namespace flake; + return + useRegistries + ? recreateLockFile + ? (saveLockFile ? RecreateLockFile : UseNewLockFile) + : (saveLockFile ? UpdateLockFile : UseUpdatedLockFile) + : AllPure; +} + SourceExprCommand::SourceExprCommand() { mkFlag() .shortName('f') .longName("file") .label("file") - .description("evaluate FILE rather than the default") + .description("evaluate a set of attributes from FILE (deprecated)") .dest(&file); } -Value * SourceExprCommand::getSourceExpr(EvalState & state) +Strings SourceExprCommand::getDefaultFlakeAttrPaths() { - if (vSourceExpr) return vSourceExpr; - - auto sToplevel = state.symbols.create("_toplevel"); - - vSourceExpr = state.allocValue(); - - if (file != "") - state.evalFile(lookupFileArg(state, file), *vSourceExpr); - - else { - - /* Construct the installation source from $NIX_PATH. */ - - auto searchPath = state.getSearchPath(); - - state.mkAttrs(*vSourceExpr, 1024); - - mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true); - - std::unordered_set<std::string> seen; - - auto addEntry = [&](const std::string & name) { - if (name == "") return; - if (!seen.insert(name).second) return; - Value * v1 = state.allocValue(); - mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath")); - Value * v2 = state.allocValue(); - mkApp(*v2, *v1, mkString(*state.allocValue(), name)); - mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)), - state.getBuiltin("import"), *v2); - }; - - for (auto & i : searchPath) - /* Hack to handle channels. */ - if (i.first.empty() && pathExists(i.second + "/manifest.nix")) { - for (auto & j : readDirectory(i.second)) - if (j.name != "manifest.nix" - && pathExists(fmt("%s/%s/default.nix", i.second, j.name))) - addEntry(j.name); - } else - addEntry(i.first); - - vSourceExpr->attrs->sort(); - } + return {"defaultPackage." + settings.thisSystem.get()}; +} - return vSourceExpr; +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() + "." + }; } -ref<EvalState> SourceExprCommand::getEvalState() +ref<EvalState> EvalCommand::getEvalState() { - if (!evalState) + if (!evalState) { evalState = std::make_shared<EvalState>(searchPath, getStore()); + evalState->addRegistryOverrides(registryOverrides); + } return ref<EvalState>(evalState); } @@ -87,6 +89,27 @@ Buildable Installable::toBuildable() return std::move(buildables[0]); } +App::App(EvalState & state, Value & vApp) +{ + state.forceAttrs(vApp); + + auto aType = vApp.attrs->need(state.sType); + if (state.forceStringNoCtx(*aType.value, *aType.pos) != "app") + throw Error("value does not have type 'app', at %s", *aType.pos); + + auto aProgram = vApp.attrs->need(state.symbols.create("program")); + program = state.forceString(*aProgram.value, context, *aProgram.pos); + + // FIXME: check that 'program' is in the closure of 'context'. + if (!state.store->isInStore(program)) + throw Error("app program '%s' is not in the Nix store", program); +} + +App Installable::toApp(EvalState & state) +{ + return App(state, *toValue(state)); +} + struct InstallableStorePath : Installable { Path storePath; @@ -99,53 +122,65 @@ struct InstallableStorePath : Installable { return {{isDerivation(storePath) ? storePath : "", {{"out", storePath}}}}; } + + std::optional<Path> getStorePath() override + { + return storePath; + } }; -struct InstallableValue : Installable +std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations() { - SourceExprCommand & cmd; - - InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { } + auto state = cmd.getEvalState(); - Buildables toBuildables() override - { - auto state = cmd.getEvalState(); + auto v = toValue(*state); - auto v = toValue(*state); + Bindings & autoArgs = *cmd.getAutoArgs(*state); - Bindings & autoArgs = *cmd.getAutoArgs(*state); + DrvInfos drvInfos; + getDerivations(*state, *v, "", autoArgs, drvInfos, false); - DrvInfos drvs; - getDerivations(*state, *v, "", autoArgs, drvs, false); + std::vector<flake::EvalCache::Derivation> res; + for (auto & drvInfo : drvInfos) { + res.push_back({ + drvInfo.queryDrvPath(), + drvInfo.queryOutPath(), + drvInfo.queryOutputName() + }); + } - Buildables res; + return res; +} - PathSet drvPaths; +Buildables InstallableValue::toBuildables() +{ + Buildables res; - for (auto & drv : drvs) { - Buildable b{drv.queryDrvPath()}; - drvPaths.insert(b.drvPath); + PathSet drvPaths; - auto outputName = drv.queryOutputName(); - if (outputName == "") - throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath); + for (auto & drv : toDerivations()) { + Buildable b{drv.drvPath}; + drvPaths.insert(b.drvPath); - b.outputs.emplace(outputName, drv.queryOutPath()); + auto outputName = drv.outputName; + if (outputName == "") + throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath); - res.push_back(std::move(b)); - } + b.outputs.emplace(outputName, drv.outPath); - // Hack to recognize .all: if all drvs have the same drvPath, - // merge the buildables. - if (drvPaths.size() == 1) { - Buildable b{*drvPaths.begin()}; - for (auto & b2 : res) - b.outputs.insert(b2.outputs.begin(), b2.outputs.end()); - return {b}; - } else - return res; + res.push_back(std::move(b)); } -}; + + // Hack to recognize .all: if all drvs have the same drvPath, + // merge the buildables. + if (drvPaths.size() == 1) { + Buildable b{*drvPaths.begin()}; + for (auto & b2 : res) + b.outputs.insert(b2.outputs.begin(), b2.outputs.end()); + return {b}; + } else + return res; +} struct InstallableExpr : InstallableValue { @@ -166,70 +201,251 @@ struct InstallableExpr : InstallableValue struct InstallableAttrPath : InstallableValue { + Value * v; std::string attrPath; - InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath) - : InstallableValue(cmd), attrPath(attrPath) + InstallableAttrPath(SourceExprCommand & cmd, Value * v, const std::string & attrPath) + : InstallableValue(cmd), v(v), attrPath(attrPath) { } std::string what() override { return attrPath; } Value * toValue(EvalState & state) override { - auto source = cmd.getSourceExpr(state); + auto vRes = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v); + state.forceValue(*vRes); + return vRes; + } +}; + +void makeFlakeClosureGCRoot(Store & store, + const FlakeRef & origFlakeRef, + const flake::ResolvedFlake & resFlake) +{ + if (std::get_if<FlakeRef::IsPath>(&origFlakeRef.data)) return; + + /* Get the store paths of all non-local flakes. */ + PathSet closure; + + assert(store.isValidPath(resFlake.flake.sourceInfo.storePath)); + closure.insert(resFlake.flake.sourceInfo.storePath); + + std::queue<std::reference_wrapper<const flake::LockedInputs>> queue; + queue.push(resFlake.lockFile); + + while (!queue.empty()) { + const flake::LockedInputs & flake = queue.front(); + queue.pop(); + /* Note: due to lazy fetching, these paths might not exist + yet. */ + for (auto & dep : flake.inputs) { + auto path = dep.second.computeStorePath(store); + if (store.isValidPath(path)) + closure.insert(path); + queue.push(dep.second); + } + } - Bindings & autoArgs = *cmd.getAutoArgs(state); + if (closure.empty()) return; - Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source); - state.forceValue(*v); + /* Write the closure to a file in the store. */ + auto closurePath = store.addTextToStore("flake-closure", concatStringsSep(" ", closure), closure); - return v; + Path cacheDir = getCacheDir() + "/nix/flake-closures"; + createDirs(cacheDir); + + auto s = origFlakeRef.to_string(); + assert(s[0] != '.'); + s = replaceStrings(s, "%", "%25"); + s = replaceStrings(s, "/", "%2f"); + s = replaceStrings(s, ":", "%3a"); + Path symlink = cacheDir + "/" + s; + debug("writing GC root '%s' for flake closure of '%s'", symlink, origFlakeRef); + replaceSymlink(closurePath, symlink); + store.addIndirectRoot(symlink); +} + +std::vector<std::string> InstallableFlake::getActualAttrPaths() +{ + std::vector<std::string> 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::ResolvedFlake & resFlake) +{ + auto vFlake = state.allocValue(); + + callFlake(state, resFlake, *vFlake); + + makeFlakeClosureGCRoot(*state.store, flakeRef, resFlake); + + auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); + assert(aOutputs); + + state.forceValue(*(*aOutputs)->value); + + return (*aOutputs)->value; +} + +std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake::toDerivation() +{ + auto state = cmd.getEvalState(); + + auto resFlake = resolveFlake(*state, flakeRef, cmd.getLockFileMode()); + + Value * vOutputs = nullptr; + + auto emptyArgs = state->allocBindings(0); + + auto & evalCache = flake::EvalCache::singleton(); + + auto fingerprint = resFlake.getFingerprint(); + + for (auto & attrPath : getActualAttrPaths()) { + auto drv = evalCache.getDerivation(fingerprint, attrPath); + if (drv) { + if (state->store->isValidPath(drv->drvPath)) + return {attrPath, resFlake.flake.sourceInfo.resolvedRef, *drv}; + } + + if (!vOutputs) + vOutputs = getFlakeOutputs(*state, resFlake); + + try { + auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs); + state->forceValue(*v); + + auto drvInfo = getDerivation(*state, *v, false); + if (!drvInfo) + throw Error("flake output attribute '%s' is not a derivation", attrPath); + + auto drv = flake::EvalCache::Derivation{ + drvInfo->queryDrvPath(), + drvInfo->queryOutPath(), + drvInfo->queryOutputName() + }; + + evalCache.addDerivation(fingerprint, attrPath, drv); + + return {attrPath, resFlake.flake.sourceInfo.resolvedRef, drv}; + } catch (AttrPathNotFound & e) { + } } -}; + + throw Error("flake '%s' does not provide attribute %s", + flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); +} + +std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations() +{ + return {std::get<2>(toDerivation())}; +} + +Value * InstallableFlake::toValue(EvalState & state) +{ + auto resFlake = resolveFlake(state, flakeRef, cmd.getLockFileMode()); + + auto vOutputs = getFlakeOutputs(state, resFlake); + + auto emptyArgs = state.allocBindings(0); + + for (auto & attrPath : getActualAttrPaths()) { + try { + auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); + state.forceValue(*v); + return v; + } catch (AttrPathNotFound & e) { + } + } + + throw Error("flake '%s' does not provide attribute %s", + flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); +} // FIXME: extend std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)"; static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex)); -static std::vector<std::shared_ptr<Installable>> parseInstallables( - SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables) +std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( + ref<Store> store, std::vector<std::string> ss) { std::vector<std::shared_ptr<Installable>> result; - if (ss.empty() && useDefaultInstallables) { - if (cmd.file == "") - cmd.file = "."; - ss = {""}; - } - - for (auto & s : ss) { + if (file) { + // FIXME: backward compatibility hack + evalSettings.pureEval = false; - if (s.compare(0, 1, "(") == 0) - result.push_back(std::make_shared<InstallableExpr>(cmd, s)); + auto state = getEvalState(); + auto vFile = state->allocValue(); + state->evalFile(lookupFileArg(*state, *file), *vFile); - else if (s.find("/") != std::string::npos) { + if (ss.empty()) + ss = {""}; - auto path = store->toStorePath(store->followLinksToStore(s)); + for (auto & s : ss) + result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s)); - if (store->isStorePath(path)) - result.push_back(std::make_shared<InstallableStorePath>(path)); - } + } else { - else if (s == "" || std::regex_match(s, attrPathRegex)) - result.push_back(std::make_shared<InstallableAttrPath>(cmd, s)); + auto follow = [&](const std::string & s) -> std::optional<Path> { + try { + return store->followLinksToStorePath(s); + } catch (NotInStore &) { + return {}; + } + }; - else - throw UsageError("don't know what to do with argument '%s'", s); + for (auto & s : ss) { + + size_t hash; + std::optional<Path> storePath; + + if (s.compare(0, 1, "(") == 0) + result.push_back(std::make_shared<InstallableExpr>(*this, s)); + + else if (hasPrefix(s, "nixpkgs.")) { + bool static warned; + warnOnce(warned, "the syntax 'nixpkgs.<attr>' is deprecated; use 'nixpkgs:<attr>' instead"); + result.push_back(std::make_shared<InstallableFlake>(*this, FlakeRef("nixpkgs"), + Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)})); + } + + else if ((hash = s.rfind('#')) != std::string::npos) + result.push_back(std::make_shared<InstallableFlake>( + *this, + FlakeRef(std::string(s, 0, hash), true), + std::string(s, hash + 1), + getDefaultFlakeAttrPathPrefixes())); + + else { + try { + auto flakeRef = FlakeRef(s, true); + result.push_back(std::make_shared<InstallableFlake>( + *this, std::move(flakeRef), getDefaultFlakeAttrPaths())); + } catch (...) { + if (s.find('/') != std::string::npos && (storePath = follow(s))) + result.push_back(std::make_shared<InstallableStorePath>(*storePath)); + else + throw; + } + } + } } return result; } -std::shared_ptr<Installable> parseInstallable( - SourceExprCommand & cmd, ref<Store> store, const std::string & installable, - bool useDefaultInstallables) +std::shared_ptr<Installable> SourceExprCommand::parseInstallable( + ref<Store> store, const std::string & installable) { - auto installables = parseInstallables(cmd, store, {installable}, false); + auto installables = parseInstallables(store, {installable}); assert(installables.size() == 1); return installables.front(); } @@ -285,7 +501,7 @@ Path toStorePath(ref<Store> store, RealiseMode mode, auto paths = toStorePaths(store, mode, {installable}); if (paths.size() != 1) - throw Error("argument '%s' should evaluate to one store path", installable->what()); + throw Error("argument '%s' should evaluate to one store path", installable->what()); return *paths.begin(); } @@ -316,12 +532,16 @@ PathSet toDerivations(ref<Store> store, void InstallablesCommand::prepare() { - installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables()); + if (_installables.empty() && !file && useDefaultInstallables()) + // FIXME: commands like "nix install" should not have a + // default, probably. + _installables.push_back("."); + installables = parseInstallables(getStore(), _installables); } void InstallableCommand::prepare() { - installable = parseInstallable(*this, getStore(), _installable, false); + installable = parseInstallable(getStore(), _installable); } } |