diff options
Diffstat (limited to 'src/legacy')
-rw-r--r-- | src/legacy/build-remote.cc | 396 | ||||
-rw-r--r-- | src/legacy/build-remote.hh | 8 | ||||
-rw-r--r-- | src/legacy/buildenv.nix | 27 | ||||
-rw-r--r-- | src/legacy/dotgraph.cc | 70 | ||||
-rw-r--r-- | src/legacy/dotgraph.hh | 10 | ||||
-rw-r--r-- | src/legacy/graphml.cc | 87 | ||||
-rw-r--r-- | src/legacy/graphml.hh | 10 | ||||
-rw-r--r-- | src/legacy/meson.build | 35 | ||||
-rw-r--r-- | src/legacy/nix-build.cc | 623 | ||||
-rw-r--r-- | src/legacy/nix-build.hh | 8 | ||||
-rw-r--r-- | src/legacy/nix-channel.cc | 272 | ||||
-rw-r--r-- | src/legacy/nix-channel.hh | 8 | ||||
-rw-r--r-- | src/legacy/nix-collect-garbage.cc | 118 | ||||
-rw-r--r-- | src/legacy/nix-collect-garbage.hh | 8 | ||||
-rw-r--r-- | src/legacy/nix-copy-closure.cc | 68 | ||||
-rw-r--r-- | src/legacy/nix-copy-closure.hh | 8 | ||||
-rw-r--r-- | src/legacy/nix-env.cc | 1553 | ||||
-rw-r--r-- | src/legacy/nix-env.hh | 8 | ||||
-rw-r--r-- | src/legacy/nix-instantiate.cc | 203 | ||||
-rw-r--r-- | src/legacy/nix-instantiate.hh | 8 | ||||
-rw-r--r-- | src/legacy/nix-store.cc | 1183 | ||||
-rw-r--r-- | src/legacy/nix-store.hh | 8 | ||||
-rw-r--r-- | src/legacy/unpack-channel.nix | 16 | ||||
-rw-r--r-- | src/legacy/user-env.cc | 155 | ||||
-rw-r--r-- | src/legacy/user-env.hh | 14 |
25 files changed, 4904 insertions, 0 deletions
diff --git a/src/legacy/build-remote.cc b/src/legacy/build-remote.cc new file mode 100644 index 000000000..62ceef283 --- /dev/null +++ b/src/legacy/build-remote.cc @@ -0,0 +1,396 @@ +#include <algorithm> +#include <set> +#include <memory> +#include <tuple> +#if __APPLE__ +#include <sys/time.h> +#endif + +#include "machines.hh" +#include "shared.hh" +#include "pathlocks.hh" +#include "globals.hh" +#include "serialise.hh" +#include "build-result.hh" +#include "store-api.hh" +#include "derivations.hh" +#include "strings.hh" +#include "local-store.hh" +#include "legacy.hh" +#include "experimental-features.hh" +#include "hash.hh" +#include "build-remote.hh" + +namespace nix { + +static void handleAlarm(int sig) { +} + +std::string escapeUri(std::string uri) +{ + std::replace(uri.begin(), uri.end(), '/', '_'); + return uri; +} + +static std::string currentLoad; + +static std::string makeLockFilename(const std::string & storeUri) { + // We include 48 bytes of escaped URI to give an idea of what the lock + // is on, then 16 bytes of hash to disambiguate. + // This avoids issues with the escaped URI being very long and causing + // path too long errors, while also avoiding any possibility of collision + // caused by simple truncation. + auto hash = hashString(HashType::SHA256, storeUri).to_string(Base::Base32, false); + return escapeUri(storeUri).substr(0, 48) + "-" + hash.substr(0, 16); +} + +static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot) +{ + return openLockFile(fmt("%s/%s-%d", currentLoad, makeLockFilename(m.storeUri), slot), true); +} + +static bool allSupportedLocally(Store & store, const std::set<std::string>& requiredFeatures) { + for (auto & feature : requiredFeatures) + if (!store.systemFeatures.get().count(feature)) return false; + return true; +} + +static int main_build_remote(int argc, char * * argv) +{ + { + logger = makeJSONLogger(*logger); + + /* Ensure we don't get any SSH passphrase or host key popups. */ + unsetenv("DISPLAY"); + unsetenv("SSH_ASKPASS"); + + /* If we ever use the common args framework, make sure to + remove initPlugins below and initialize settings first. + */ + if (argc != 2) + throw UsageError("called without required arguments"); + + verbosity = (Verbosity) std::stoll(argv[1]); + + FdSource source(STDIN_FILENO); + + /* Read the parent's settings. */ + while (readInt(source)) { + auto name = readString(source); + auto value = readString(source); + settings.set(name, value); + } + + auto maxBuildJobs = settings.maxBuildJobs; + settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work + + initPlugins(); + + auto store = openStore(); + + /* It would be more appropriate to use $XDG_RUNTIME_DIR, since + that gets cleared on reboot, but it wouldn't work on macOS. */ + auto currentLoadName = "/current-load"; + if (auto localStore = store.dynamic_pointer_cast<LocalFSStore>()) + currentLoad = std::string { localStore->stateDir } + currentLoadName; + else + currentLoad = settings.nixStateDir + currentLoadName; + + std::shared_ptr<Store> sshStore; + AutoCloseFD bestSlotLock; + + auto machines = getMachines(); + debug("got %d remote builders", machines.size()); + + if (machines.empty()) { + std::cerr << "# decline-permanently\n"; + return 0; + } + + std::optional<StorePath> drvPath; + std::string storeUri; + + while (true) { + + try { + auto s = readString(source); + if (s != "try") return 0; + } catch (EndOfFile &) { return 0; } + + auto amWilling = readInt(source); + auto neededSystem = readString(source); + drvPath = store->parseStorePath(readString(source)); + auto requiredFeatures = readStrings<std::set<std::string>>(source); + + /* It would be possible to build locally after some builds clear out, + so don't show the warning now: */ + bool couldBuildLocally = maxBuildJobs > 0 + && ( neededSystem == settings.thisSystem + || settings.extraPlatforms.get().count(neededSystem) > 0) + && allSupportedLocally(*store, requiredFeatures); + /* It's possible to build this locally right now: */ + bool canBuildLocally = amWilling && couldBuildLocally; + + /* Error ignored here, will be caught later */ + mkdir(currentLoad.c_str(), 0777); + + while (true) { + bestSlotLock.reset(); + AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true); + lockFile(lock.get(), ltWrite, true); + + bool rightType = false; + + Machine * bestMachine = nullptr; + uint64_t bestLoad = 0; + for (auto & m : machines) { + debug("considering building on remote machine '%s'", m.storeUri); + + if (m.enabled && + m.systemSupported(neededSystem) && + m.allSupported(requiredFeatures) && + m.mandatoryMet(requiredFeatures)) + { + rightType = true; + AutoCloseFD free; + uint64_t load = 0; + for (uint64_t slot = 0; slot < m.maxJobs; ++slot) { + auto slotLock = openSlotLock(m, slot); + if (lockFile(slotLock.get(), ltWrite, false)) { + if (!free) { + free = std::move(slotLock); + } + } else { + ++load; + } + } + if (!free) { + continue; + } + bool best = false; + if (!bestSlotLock) { + best = true; + } else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) { + best = true; + } else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) { + if (m.speedFactor > bestMachine->speedFactor) { + best = true; + } else if (m.speedFactor == bestMachine->speedFactor) { + if (load < bestLoad) { + best = true; + } + } + } + if (best) { + bestLoad = load; + bestSlotLock = std::move(free); + bestMachine = &m; + } + } + } + + if (!bestSlotLock) { + if (rightType && !canBuildLocally) + std::cerr << "# postpone\n"; + else + { + // add the template values. + std::string drvstr; + if (drvPath.has_value()) + drvstr = drvPath->to_string(); + else + drvstr = "<unknown>"; + + std::string machinesFormatted; + + for (auto & m : machines) { + machinesFormatted += HintFmt( + "\n([%s], %s, [%s], [%s])", + concatStringsSep<StringSet>(", ", m.systemTypes), + m.maxJobs, + concatStringsSep<StringSet>(", ", m.supportedFeatures), + concatStringsSep<StringSet>(", ", m.mandatoryFeatures) + ).str(); + } + + auto error = HintFmt( + "Failed to find a machine for remote build!\n" + "derivation: %s\n" + "required (system, features): (%s, [%s])\n" + "%s available machines:\n" + "(systems, maxjobs, supportedFeatures, mandatoryFeatures)%s", + drvstr, + neededSystem, + concatStringsSep<StringSet>(", ", requiredFeatures), + machines.size(), + Uncolored(machinesFormatted) + ); + + printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str()); + + std::cerr << "# decline\n"; + } + break; + } + +#if __APPLE__ + futimes(bestSlotLock.get(), nullptr); +#else + futimens(bestSlotLock.get(), nullptr); +#endif + + lock.reset(); + + try { + + Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri)); + + sshStore = bestMachine->openStore(); + sshStore->connect(); + storeUri = bestMachine->storeUri; + + } catch (std::exception & e) { + auto msg = chomp(drainFD(5, false)); + printError("cannot build on '%s': %s%s", + bestMachine->storeUri, e.what(), + msg.empty() ? "" : ": " + msg); + bestMachine->enabled = false; + continue; + } + + goto connected; + } + } + +connected: + close(5); + + assert(sshStore); + + std::cerr << "# accept\n" << storeUri << "\n"; + + auto inputs = readStrings<PathSet>(source); + auto wantedOutputs = readStrings<StringSet>(source); + + auto lockFileName = currentLoad + "/" + makeLockFilename(storeUri) + ".upload-lock"; + + AutoCloseFD uploadLock = openLockFile(lockFileName, true); + + { + Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri)); + + auto old = signal(SIGALRM, handleAlarm); + alarm(15 * 60); + if (!lockFile(uploadLock.get(), ltWrite, true)) + printError("somebody is hogging the upload lock for '%s', continuing..."); + alarm(0); + signal(SIGALRM, old); + } + + auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute; + + { + Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri)); + copyPaths(*store, *sshStore, store->parseStorePathSet(inputs), NoRepair, NoCheckSigs, substitute); + } + + uploadLock.reset(); + + auto drv = store->readDerivation(*drvPath); + + std::optional<BuildResult> optResult; + + // If we don't know whether we are trusted (e.g. `ssh://` + // stores), we assume we are. This is necessary for backwards + // compat. + bool trustedOrLegacy = ({ + std::optional trusted = sshStore->isTrustedClient(); + !trusted || *trusted; + }); + + // See the very large comment in `case WorkerProto::Op::BuildDerivation:` in + // `src/libstore/daemon.cc` that explains the trust model here. + // + // This condition mirrors that: that code enforces the "rules" outlined there; + // we do the best we can given those "rules". + if (trustedOrLegacy || drv.type().isCA()) { + // Hijack the inputs paths of the derivation to include all + // the paths that come from the `inputDrvs` set. We don’t do + // that for the derivations whose `inputDrvs` is empty + // because: + // + // 1. It’s not needed + // + // 2. Changing the `inputSrcs` set changes the associated + // output ids, which break CA derivations + if (!drv.inputDrvs.map.empty()) + drv.inputSrcs = store->parseStorePathSet(inputs); + optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv); + auto & result = *optResult; + if (!result.success()) + throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg); + } else { + copyClosure(*store, *sshStore, StorePathSet {*drvPath}, NoRepair, NoCheckSigs, substitute); + auto res = sshStore->buildPathsWithResults({ + DerivedPath::Built { + .drvPath = makeConstantStorePathRef(*drvPath), + .outputs = OutputsSpec::All {}, + } + }); + // One path to build should produce exactly one build result + assert(res.size() == 1); + optResult = std::move(res[0]); + } + + + auto outputHashes = staticOutputHashes(*store, drv); + std::set<Realisation> missingRealisations; + StorePathSet missingPaths; + if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) { + for (auto & outputName : wantedOutputs) { + auto thisOutputHash = outputHashes.at(outputName); + auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; + if (!store->queryRealisation(thisOutputId)) { + debug("missing output %s", outputName); + assert(optResult); + auto & result = *optResult; + auto i = result.builtOutputs.find(outputName); + assert(i != result.builtOutputs.end()); + auto & newRealisation = i->second; + missingRealisations.insert(newRealisation); + missingPaths.insert(newRealisation.outPath); + } + } + } else { + auto outputPaths = drv.outputsAndOptPaths(*store); + for (auto & [outputName, hopefullyOutputPath] : outputPaths) { + assert(hopefullyOutputPath.second); + if (!store->isValidPath(*hopefullyOutputPath.second)) + missingPaths.insert(*hopefullyOutputPath.second); + } + } + + if (!missingPaths.empty()) { + Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri)); + if (auto localStore = store.dynamic_pointer_cast<LocalStore>()) + for (auto & path : missingPaths) + localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */ + copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute); + } + // XXX: Should be done as part of `copyPaths` + for (auto & realisation : missingRealisations) { + // Should hold, because if the feature isn't enabled the set + // of missing realisations should be empty + experimentalFeatureSettings.require(Xp::CaDerivations); + store->registerDrvOutput(realisation); + } + + return 0; + } +} + +void registerBuildRemote() { + LegacyCommands::add("build-remote", main_build_remote); +} + +} diff --git a/src/legacy/build-remote.hh b/src/legacy/build-remote.hh new file mode 100644 index 000000000..c4a35f706 --- /dev/null +++ b/src/legacy/build-remote.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerBuildRemote(); + +} diff --git a/src/legacy/buildenv.nix b/src/legacy/buildenv.nix new file mode 100644 index 000000000..c8955a94e --- /dev/null +++ b/src/legacy/buildenv.nix @@ -0,0 +1,27 @@ +{ derivations, manifest }: + +derivation { + name = "user-environment"; + system = "builtin"; + builder = "builtin:buildenv"; + + inherit manifest; + + # !!! grmbl, need structured data for passing this in a clean way. + derivations = map ( + d: + [ + (d.meta.active or "true") + (d.meta.priority or 5) + (builtins.length d.outputs) + ] + ++ map (output: builtins.getAttr output d) d.outputs + ) derivations; + + # Building user environments remotely just causes huge amounts of + # network traffic, so don't do that. + preferLocalBuild = true; + + # Also don't bother substituting. + allowSubstitutes = false; +} diff --git a/src/legacy/dotgraph.cc b/src/legacy/dotgraph.cc new file mode 100644 index 000000000..2c530999b --- /dev/null +++ b/src/legacy/dotgraph.cc @@ -0,0 +1,70 @@ +#include "dotgraph.hh" +#include "store-api.hh" + +#include <iostream> + + +using std::cout; + +namespace nix { + + +static std::string dotQuote(std::string_view s) +{ + return "\"" + std::string(s) + "\""; +} + + +static const std::string & nextColour() +{ + static int n = 0; + static std::vector<std::string> colours + { "black", "red", "green", "blue" + , "magenta", "burlywood" }; + return colours[n++ % colours.size()]; +} + + +static std::string makeEdge(std::string_view src, std::string_view dst) +{ + return fmt("%1% -> %2% [color = %3%];\n", + dotQuote(src), dotQuote(dst), dotQuote(nextColour())); +} + + +static std::string makeNode(std::string_view id, std::string_view label, + std::string_view colour) +{ + return fmt("%1% [label = %2%, shape = box, " + "style = filled, fillcolor = %3%];\n", + dotQuote(id), dotQuote(label), dotQuote(colour)); +} + + +void printDotGraph(ref<Store> store, StorePathSet && roots) +{ + StorePathSet workList(std::move(roots)); + StorePathSet doneSet; + + cout << "digraph G {\n"; + + while (!workList.empty()) { + auto path = std::move(workList.extract(workList.begin()).value()); + + if (!doneSet.insert(path).second) continue; + + cout << makeNode(std::string(path.to_string()), path.name(), "#ff0000"); + + for (auto & p : store->queryPathInfo(path)->references) { + if (p != path) { + workList.insert(p); + cout << makeEdge(std::string(p.to_string()), std::string(path.to_string())); + } + } + } + + cout << "}\n"; +} + + +} diff --git a/src/legacy/dotgraph.hh b/src/legacy/dotgraph.hh new file mode 100644 index 000000000..4fd944080 --- /dev/null +++ b/src/legacy/dotgraph.hh @@ -0,0 +1,10 @@ +#pragma once +///@file + +#include "store-api.hh" + +namespace nix { + +void printDotGraph(ref<Store> store, StorePathSet && roots); + +} diff --git a/src/legacy/graphml.cc b/src/legacy/graphml.cc new file mode 100644 index 000000000..3e789a2d8 --- /dev/null +++ b/src/legacy/graphml.cc @@ -0,0 +1,87 @@ +#include "graphml.hh" +#include "store-api.hh" +#include "derivations.hh" + +#include <iostream> + + +using std::cout; + +namespace nix { + + +static inline std::string_view xmlQuote(std::string_view s) +{ + // Luckily, store paths shouldn't contain any character that needs to be + // quoted. + return s; +} + + +static std::string symbolicName(std::string_view p) +{ + return std::string(p.substr(0, p.find('-') + 1)); +} + + +static std::string makeEdge(std::string_view src, std::string_view dst) +{ + return fmt(" <edge source=\"%1%\" target=\"%2%\"/>\n", + xmlQuote(src), xmlQuote(dst)); +} + + +static std::string makeNode(const ValidPathInfo & info) +{ + return fmt( + " <node id=\"%1%\">\n" + " <data key=\"narSize\">%2%</data>\n" + " <data key=\"name\">%3%</data>\n" + " <data key=\"type\">%4%</data>\n" + " </node>\n", + info.path.to_string(), + info.narSize, + symbolicName(std::string(info.path.name())), + (info.path.isDerivation() ? "derivation" : "output-path")); +} + + +void printGraphML(ref<Store> store, StorePathSet && roots) +{ + StorePathSet workList(std::move(roots)); + StorePathSet doneSet; + std::pair<StorePathSet::iterator, bool> ret; + + cout << "<?xml version='1.0' encoding='utf-8'?>\n" + << "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n" + << " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n" + << " xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n" + << "<key id='narSize' for='node' attr.name='narSize' attr.type='long'/>" + << "<key id='name' for='node' attr.name='name' attr.type='string'/>" + << "<key id='type' for='node' attr.name='type' attr.type='string'/>" + << "<graph id='G' edgedefault='directed'>\n"; + + while (!workList.empty()) { + auto path = std::move(workList.extract(workList.begin()).value()); + + ret = doneSet.insert(path); + if (ret.second == false) continue; + + auto info = store->queryPathInfo(path); + cout << makeNode(*info); + + for (auto & p : info->references) { + if (p != path) { + workList.insert(p); + cout << makeEdge(path.to_string(), p.to_string()); + } + } + + } + + cout << "</graph>\n"; + cout << "</graphml>\n"; +} + + +} diff --git a/src/legacy/graphml.hh b/src/legacy/graphml.hh new file mode 100644 index 000000000..bd3a4a37c --- /dev/null +++ b/src/legacy/graphml.hh @@ -0,0 +1,10 @@ +#pragma once +///@file + +#include "store-api.hh" + +namespace nix { + +void printGraphML(ref<Store> store, StorePathSet && roots); + +} diff --git a/src/legacy/meson.build b/src/legacy/meson.build new file mode 100644 index 000000000..13b90314c --- /dev/null +++ b/src/legacy/meson.build @@ -0,0 +1,35 @@ +legacy_include_directories = include_directories('.') + +legacy_sources = files( + # `build-remote` is not really legacy (it powers all remote builds), but it's + # not a `nix3` command. + 'build-remote.cc', + 'dotgraph.cc', + 'graphml.cc', + 'nix-build.cc', + 'nix-channel.cc', + 'nix-collect-garbage.cc', + 'nix-copy-closure.cc', + 'nix-env.cc', + 'nix-env.hh', + 'nix-instantiate.cc', + 'nix-store.cc', + 'user-env.cc', +) + +legacy_headers = files( + 'build-remote.hh', + 'nix-build.hh', + 'nix-channel.hh', + 'nix-collect-garbage.hh', + 'nix-copy-closure.hh', + 'nix-instantiate.hh', + 'nix-store.hh', +) + +legacy_generated_headers = [ + gen_header.process('buildenv.nix', preserve_path_from: meson.current_source_dir()), + gen_header.process('unpack-channel.nix', preserve_path_from: meson.current_source_dir()), +] + +fs.copyfile('unpack-channel.nix') diff --git a/src/legacy/nix-build.cc b/src/legacy/nix-build.cc new file mode 100644 index 000000000..eb9b6cd8d --- /dev/null +++ b/src/legacy/nix-build.cc @@ -0,0 +1,623 @@ +#include <cstring> +#include <fstream> +#include <iostream> +#include <filesystem> +#include <regex> +#include <sstream> +#include <vector> +#include <map> + +#include <nlohmann/json.hpp> + +#include "parsed-derivations.hh" +#include "store-api.hh" +#include "local-fs-store.hh" +#include "globals.hh" +#include "current-process.hh" +#include "derivations.hh" +#include "shared.hh" +#include "path-with-outputs.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "get-drvs.hh" +#include "common-eval-args.hh" +#include "attr-path.hh" +#include "legacy.hh" +#include "shlex.hh" +#include "nix-build.hh" + +extern char * * environ __attribute__((weak)); // Man what even is this + +namespace nix { + +using namespace std::string_literals; + +static void main_nix_build(int argc, char * * argv) +{ + auto dryRun = false; + auto runEnv = std::regex_search(argv[0], std::regex("nix-shell$")); + auto pure = false; + auto fromArgs = false; + auto packages = false; + // Same condition as bash uses for interactive shells + auto interactive = isatty(STDIN_FILENO) && isatty(STDERR_FILENO); + Strings attrPaths; + Strings left; + BuildMode buildMode = bmNormal; + bool readStdin = false; + + std::string envCommand; // interactive shell + Strings envExclude; + + auto myName = runEnv ? "nix-shell" : "nix-build"; + + auto inShebang = false; + std::string script; + std::vector<std::string> savedArgs; + + AutoDelete tmpDir(createTempDir("", myName)); + + std::string outLink = "./result"; + + // List of environment variables kept for --pure + std::set<std::string> keepVars{ + "HOME", "XDG_RUNTIME_DIR", "USER", "LOGNAME", "DISPLAY", + "WAYLAND_DISPLAY", "WAYLAND_SOCKET", "PATH", "TERM", "IN_NIX_SHELL", + "NIX_SHELL_PRESERVE_PROMPT", "TZ", "PAGER", "NIX_BUILD_SHELL", "SHLVL", + "http_proxy", "https_proxy", "ftp_proxy", "all_proxy", "no_proxy" + }; + + Strings args; + for (int i = 1; i < argc; ++i) + args.push_back(argv[i]); + + // Heuristic to see if we're invoked as a shebang script, namely, + // if we have at least one argument, it's the name of an + // executable file, and it starts with "#!". + if (runEnv && argc > 1) { + script = argv[1]; + try { + auto lines = tokenizeString<Strings>(readFile(script), "\n"); + if (std::regex_search(lines.front(), std::regex("^#!"))) { + lines.pop_front(); + inShebang = true; + for (int i = 2; i < argc; ++i) + savedArgs.push_back(argv[i]); + args.clear(); + for (auto line : lines) { + line = chomp(line); + std::smatch match; + if (std::regex_match(line, match, std::regex("^#!\\s*nix-shell\\s+(.*)$"))) + for (const auto & word : shell_split(match[1].str())) + args.push_back(word); + } + } + } catch (SysError &) { } + } + + struct MyArgs : LegacyArgs, MixEvalArgs + { + using LegacyArgs::LegacyArgs; + }; + + MyArgs myArgs(myName, [&](Strings::iterator & arg, const Strings::iterator & end) { + if (*arg == "--help") { + deletePath(tmpDir); + showManPage(myName); + } + + else if (*arg == "--version") + printVersion(myName); + + else if (*arg == "--add-drv-link" || *arg == "--indirect") + ; // obsolete + + else if (*arg == "--no-out-link" || *arg == "--no-link") + outLink = (Path) tmpDir + "/result"; + + else if (*arg == "--attr" || *arg == "-A") + attrPaths.push_back(getArg(*arg, arg, end)); + + else if (*arg == "--drv-link") + getArg(*arg, arg, end); // obsolete + + else if (*arg == "--out-link" || *arg == "-o") + outLink = getArg(*arg, arg, end); + + else if (*arg == "--dry-run") + dryRun = true; + + else if (*arg == "--run-env") // obsolete + runEnv = true; + + else if (runEnv && (*arg == "--command" || *arg == "--run")) { + if (*arg == "--run") + interactive = false; + envCommand = getArg(*arg, arg, end) + "\nexit"; + } + + else if (*arg == "--check") + buildMode = bmCheck; + + else if (*arg == "--exclude") + envExclude.push_back(getArg(*arg, arg, end)); + + else if (*arg == "--expr" || *arg == "-E") + fromArgs = true; + + else if (*arg == "--pure") pure = true; + else if (*arg == "--impure") pure = false; + + else if (runEnv && (*arg == "--packages" || *arg == "-p")) + packages = true; + + else if (inShebang && *arg == "-i") { + auto interpreter = getArg(*arg, arg, end); + interactive = false; + auto execArgs = ""; + + // Überhack to support Perl. Perl examines the shebang and + // executes it unless it contains the string "perl" or "indir", + // or (undocumented) argv[0] does not contain "perl". Exploit + // the latter by doing "exec -a". + if (std::regex_search(interpreter, std::regex("perl"))) + execArgs = "-a PERL"; + + std::ostringstream joined; + for (const auto & i : savedArgs) + joined << shellEscape(i) << ' '; + + if (std::regex_search(interpreter, std::regex("ruby"))) { + // Hack for Ruby. Ruby also examines the shebang. It tries to + // read the shebang to understand which packages to read from. Since + // this is handled via nix-shell -p, we wrap our ruby script execution + // in ruby -e 'load' which ignores the shebangs. + envCommand = fmt("exec %1% %2% -e 'load(ARGV.shift)' -- %3% %4%", execArgs, interpreter, shellEscape(script), joined.str()); + } else { + envCommand = fmt("exec %1% %2% %3% %4%", execArgs, interpreter, shellEscape(script), joined.str()); + } + } + + else if (*arg == "--keep") + keepVars.insert(getArg(*arg, arg, end)); + + else if (*arg == "-") + readStdin = true; + + else if (*arg != "" && arg->at(0) == '-') + return false; + + else + left.push_back(*arg); + + return true; + }); + + myArgs.parseCmdline(args); + + if (packages && fromArgs) + throw UsageError("'-p' and '-E' are mutually exclusive"); + + auto store = openStore(); + auto evalStore = myArgs.evalStoreUrl ? openStore(*myArgs.evalStoreUrl) : store; + + auto state = std::make_unique<EvalState>(myArgs.searchPath, evalStore, store); + state->repair = myArgs.repair; + if (myArgs.repair) buildMode = bmRepair; + + auto autoArgs = myArgs.getAutoArgs(*state); + + auto autoArgsWithInNixShell = autoArgs; + if (runEnv) { + auto newArgs = state->buildBindings(autoArgsWithInNixShell->size() + 1); + newArgs.alloc("inNixShell").mkBool(true); + for (auto & i : *autoArgs) newArgs.insert(i); + autoArgsWithInNixShell = newArgs.finish(); + } + + if (packages) { + std::ostringstream joined; + joined << "{...}@args: with import <nixpkgs> args; (pkgs.runCommandCC or pkgs.runCommand) \"shell\" { buildInputs = [ "; + for (const auto & i : left) + joined << '(' << i << ") "; + joined << "]; } \"\""; + fromArgs = true; + left = {joined.str()}; + } else if (!fromArgs) { + if (left.empty() && runEnv && pathExists("shell.nix")) + left = {"shell.nix"}; + if (left.empty()) + left = {"default.nix"}; + } + + if (runEnv) + setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1); + + DrvInfos drvs; + + /* Parse the expressions. */ + std::vector<std::reference_wrapper<Expr>> exprs; + + if (readStdin) + exprs = {state->parseStdin()}; + else + for (auto i : left) { + if (fromArgs) + exprs.push_back(state->parseExprFromString(std::move(i), state->rootPath(CanonPath::fromCwd()))); + else { + auto absolute = i; + try { + absolute = canonPath(absPath(i), true); + } catch (Error & e) {}; + auto [path, outputNames] = parsePathWithOutputs(absolute); + if (evalStore->isStorePath(path) && path.ends_with(".drv")) + drvs.push_back(DrvInfo(*state, evalStore, absolute)); + else + /* If we're in a #! script, interpret filenames + relative to the script. */ + exprs.push_back(state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, + inShebang && !packages ? absPath(i, absPath(dirOf(script))) : i))))); + } + } + + /* Evaluate them into derivations. */ + if (attrPaths.empty()) attrPaths = {""}; + + for (auto e : exprs) { + Value vRoot; + state->eval(e, vRoot); + + std::function<bool(const Value & v)> takesNixShellAttr; + takesNixShellAttr = [&](const Value & v) { + if (!runEnv) { + return false; + } + bool add = false; + if (v.type() == nFunction && v.lambda.fun->hasFormals()) { + for (auto & i : v.lambda.fun->formals->formals) { + if (state->symbols[i.name] == "inNixShell") { + add = true; + break; + } + } + } + return add; + }; + + for (auto & i : attrPaths) { + Value & v(*findAlongAttrPath( + *state, + i, + takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs, + vRoot + ).first); + state->forceValue(v, v.determinePos(noPos)); + getDerivations( + *state, + v, + "", + takesNixShellAttr(v) ? *autoArgsWithInNixShell : *autoArgs, + drvs, + false + ); + } + } + + state->maybePrintStats(); + + auto buildPaths = [&](const std::vector<DerivedPath> & paths) { + /* Note: we do this even when !printMissing to efficiently + fetch binary cache data. */ + uint64_t downloadSize, narSize; + StorePathSet willBuild, willSubstitute, unknown; + store->queryMissing(paths, + willBuild, willSubstitute, unknown, downloadSize, narSize); + + if (settings.printMissing) + printMissing(ref<Store>(store), willBuild, willSubstitute, unknown, downloadSize, narSize); + + if (!dryRun) + store->buildPaths(paths, buildMode, evalStore); + }; + + if (runEnv) { + if (drvs.size() != 1) + throw UsageError("nix-shell requires a single derivation"); + + auto & drvInfo = drvs.front(); + auto drv = evalStore->derivationFromPath(drvInfo.requireDrvPath()); + + std::vector<DerivedPath> pathsToBuild; + RealisedPath::Set pathsToCopy; + + /* Figure out what bash shell to use. If $NIX_BUILD_SHELL + is not set, then build bashInteractive from + <nixpkgs>. */ + auto shell = getEnv("NIX_BUILD_SHELL"); + std::optional<StorePath> shellDrv; + + if (!shell) { + + try { + auto & expr = state->parseExprFromString( + "(import <nixpkgs> {}).bashInteractive", + state->rootPath(CanonPath::fromCwd())); + + Value v; + state->eval(expr, v); + + auto drv = getDerivation(*state, v, false); + if (!drv) + throw Error("the 'bashInteractive' attribute in <nixpkgs> did not evaluate to a derivation"); + + auto bashDrv = drv->requireDrvPath(); + pathsToBuild.push_back(DerivedPath::Built { + .drvPath = makeConstantStorePathRef(bashDrv), + .outputs = OutputsSpec::Names {"out"}, + }); + pathsToCopy.insert(bashDrv); + shellDrv = bashDrv; + + } catch (Error & e) { + logError(e.info()); + notice("will use bash from your environment"); + shell = "bash"; + } + } + + std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> accumDerivedPath; + + accumDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) { + if (!inputNode.value.empty()) + pathsToBuild.push_back(DerivedPath::Built { + .drvPath = inputDrv, + .outputs = OutputsSpec::Names { inputNode.value }, + }); + for (const auto & [outputName, childNode] : inputNode.childMap) + accumDerivedPath( + make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }), + childNode); + }; + + // Build or fetch all dependencies of the derivation. + for (const auto & [inputDrv0, inputNode] : drv.inputDrvs.map) { + // To get around lambda capturing restrictions in the + // standard. + const auto & inputDrv = inputDrv0; + if (std::all_of(envExclude.cbegin(), envExclude.cend(), + [&](const std::string & exclude) { + return !std::regex_search(store->printStorePath(inputDrv), std::regex(exclude)); + })) + { + accumDerivedPath(makeConstantStorePathRef(inputDrv), inputNode); + pathsToCopy.insert(inputDrv); + } + } + for (const auto & src : drv.inputSrcs) { + pathsToBuild.emplace_back(DerivedPath::Opaque{src}); + pathsToCopy.insert(src); + } + + buildPaths(pathsToBuild); + + if (dryRun) return; + + if (shellDrv) { + auto shellDrvOutputs = store->queryPartialDerivationOutputMap(shellDrv.value(), &*evalStore); + shell = store->printStorePath(shellDrvOutputs.at("out").value()) + "/bin/bash"; + } + + if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) { + auto resolvedDrv = drv.tryResolve(*store); + assert(resolvedDrv && "Successfully resolved the derivation"); + drv = *resolvedDrv; + } + + // Set the environment. + auto env = getEnv(); + + auto tmp = defaultTempDir(); + + if (pure) { + decltype(env) newEnv; + for (auto & i : env) + if (keepVars.count(i.first)) + newEnv.emplace(i); + env = newEnv; + // NixOS hack: prevent /etc/bashrc from sourcing /etc/profile. + env["__ETC_PROFILE_SOURCED"] = "1"; + } + + env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmp; + env["NIX_STORE"] = store->storeDir; + env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores); + + auto passAsFile = tokenizeString<StringSet>(getOr(drv.env, "passAsFile", "")); + + bool keepTmp = false; + int fileNr = 0; + + for (auto & var : drv.env) + if (passAsFile.count(var.first)) { + keepTmp = true; + auto fn = ".attr-" + std::to_string(fileNr++); + Path p = (Path) tmpDir + "/" + fn; + writeFile(p, var.second); + env[var.first + "Path"] = p; + } else + env[var.first] = var.second; + + std::string structuredAttrsRC; + + if (env.count("__json")) { + StorePathSet inputs; + + std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputClosure; + + accumInputClosure = [&](const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) { + auto outputs = store->queryPartialDerivationOutputMap(inputDrv, &*evalStore); + for (auto & i : inputNode.value) { + auto o = outputs.at(i); + store->computeFSClosure(*o, inputs); + } + for (const auto & [outputName, childNode] : inputNode.childMap) + accumInputClosure(*outputs.at(outputName), childNode); + }; + + for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map) + accumInputClosure(inputDrv, inputNode); + + ParsedDerivation parsedDrv(drvInfo.requireDrvPath(), drv); + + if (auto structAttrs = parsedDrv.prepareStructuredAttrs(*store, inputs)) { + auto json = structAttrs.value(); + structuredAttrsRC = writeStructuredAttrsShell(json); + + auto attrsJSON = (Path) tmpDir + "/.attrs.json"; + writeFile(attrsJSON, json.dump()); + + auto attrsSH = (Path) tmpDir + "/.attrs.sh"; + writeFile(attrsSH, structuredAttrsRC); + + env["NIX_ATTRS_SH_FILE"] = attrsSH; + env["NIX_ATTRS_JSON_FILE"] = attrsJSON; + keepTmp = true; + } + } + + /* Run a shell using the derivation's environment. For + convenience, source $stdenv/setup to setup additional + environment variables and shell functions. Also don't + lose the current $PATH directories. */ + auto rcfile = (Path) tmpDir + "/rc"; + std::string rc = fmt( + R"(_nix_shell_clean_tmpdir() { command rm -rf %1%; }; )"s + + (keepTmp ? + "trap _nix_shell_clean_tmpdir EXIT; " + "exitHooks+=(_nix_shell_clean_tmpdir); " + "failureHooks+=(_nix_shell_clean_tmpdir); ": + "_nix_shell_clean_tmpdir; ") + + (pure ? "" : "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;") + + "%2%" + // always clear PATH. + // when nix-shell is run impure, we rehydrate it with the `p=$PATH` above + "unset PATH;" + "dontAddDisableDepTrack=1;\n" + + structuredAttrsRC + + "\n[ -e $stdenv/setup ] && source $stdenv/setup; " + "%3%" + "PATH=%4%:\"$PATH\"; " + "SHELL=%5%; " + "BASH=%5%; " + "set +e; " + R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && )s" + + (getuid() == 0 ? R"s(PS1='\n\[\033[1;31m\][nix-shell:\w]\$\[\033[0m\] '; )s" + : R"s(PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s") + + "if [ \"$(type -t runHook)\" = function ]; then runHook shellHook; fi; " + "unset NIX_ENFORCE_PURITY; " + "shopt -u nullglob; " + "unset TZ; %6%" + "shopt -s execfail;" + "%7%", + shellEscape(tmpDir), + (pure ? "" : "p=$PATH; "), + (pure ? "" : "PATH=$PATH:$p; unset p; "), + shellEscape(dirOf(*shell)), + shellEscape(*shell), + (getenv("TZ") ? (std::string("export TZ=") + shellEscape(getenv("TZ")) + "; ") : ""), + envCommand); + vomit("Sourcing nix-shell with file %s and contents:\n%s", rcfile, rc); + writeFile(rcfile, rc); + + Strings envStrs; + for (auto & i : env) + envStrs.push_back(i.first + "=" + i.second); + + auto args = interactive + ? Strings{"bash", "--rcfile", rcfile} + : Strings{"bash", rcfile}; + + auto envPtrs = stringsToCharPtrs(envStrs); + + environ = envPtrs.data(); + + auto argPtrs = stringsToCharPtrs(args); + + restoreProcessContext(); + + logger->pause(); + + execvp(shell->c_str(), argPtrs.data()); + + throw SysError("executing shell '%s'", *shell); + } + + else { + + std::vector<DerivedPath> pathsToBuild; + std::vector<std::pair<StorePath, std::string>> pathsToBuildOrdered; + RealisedPath::Set drvsToCopy; + + std::map<StorePath, std::pair<size_t, StringSet>> drvMap; + + for (auto & drvInfo : drvs) { + auto drvPath = drvInfo.requireDrvPath(); + + auto outputName = drvInfo.queryOutputName(); + if (outputName == "") + throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath)); + + pathsToBuild.push_back(DerivedPath::Built{ + .drvPath = makeConstantStorePathRef(drvPath), + .outputs = OutputsSpec::Names{outputName}, + }); + pathsToBuildOrdered.push_back({drvPath, {outputName}}); + drvsToCopy.insert(drvPath); + + auto i = drvMap.find(drvPath); + if (i != drvMap.end()) + i->second.second.insert(outputName); + else + drvMap[drvPath] = {drvMap.size(), {outputName}}; + } + + buildPaths(pathsToBuild); + + if (dryRun) return; + + std::vector<StorePath> outPaths; + + for (auto & [drvPath, outputName] : pathsToBuildOrdered) { + auto & [counter, _wantedOutputs] = drvMap.at({drvPath}); + std::string drvPrefix = outLink; + if (counter) + drvPrefix += fmt("-%d", counter + 1); + + auto builtOutputs = store->queryPartialDerivationOutputMap(drvPath, &*evalStore); + + auto maybeOutputPath = builtOutputs.at(outputName); + assert(maybeOutputPath); + auto outputPath = *maybeOutputPath; + + if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) { + std::string symlink = drvPrefix; + if (outputName != "out") symlink += "-" + outputName; + store2->addPermRoot(outputPath, absPath(symlink)); + } + + outPaths.push_back(outputPath); + } + + logger->pause(); + + for (auto & path : outPaths) + std::cout << store->printStorePath(path) << '\n'; + } +} + +void registerNixBuildAndNixShell() { + LegacyCommands::add("nix-build", main_nix_build); + LegacyCommands::add("nix-shell", main_nix_build); +} + +} diff --git a/src/legacy/nix-build.hh b/src/legacy/nix-build.hh new file mode 100644 index 000000000..945ac06e2 --- /dev/null +++ b/src/legacy/nix-build.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixBuildAndNixShell(); + +} diff --git a/src/legacy/nix-channel.cc b/src/legacy/nix-channel.cc new file mode 100644 index 000000000..2f79919dd --- /dev/null +++ b/src/legacy/nix-channel.cc @@ -0,0 +1,272 @@ +#include "profiles.hh" +#include "shared.hh" +#include "globals.hh" +#include "filetransfer.hh" +#include "store-api.hh" +#include "legacy.hh" +#include "fetchers.hh" +#include "eval-settings.hh" // for defexpr +#include "users.hh" +#include "nix-channel.hh" + +#include <fcntl.h> +#include <regex> +#include <pwd.h> + +namespace nix { + +typedef std::map<std::string, std::string> Channels; + +static Channels channels; +static Path channelsList; + +// Reads the list of channels. +static void readChannels() +{ + if (!pathExists(channelsList)) return; + auto channelsFile = readFile(channelsList); + + for (const auto & line : tokenizeString<std::vector<std::string>>(channelsFile, "\n")) { + chomp(line); + if (std::regex_search(line, std::regex("^\\s*\\#"))) + continue; + auto split = tokenizeString<std::vector<std::string>>(line, " "); + auto url = std::regex_replace(split[0], std::regex("/*$"), ""); + auto name = split.size() > 1 ? split[1] : std::string(baseNameOf(url)); + channels[name] = url; + } +} + +// Writes the list of channels. +static void writeChannels() +{ + auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)}; + if (!channelsFD) + throw SysError("opening '%1%' for writing", channelsList); + for (const auto & channel : channels) + writeFull(channelsFD.get(), channel.second + " " + channel.first + "\n"); +} + +// Adds a channel. +static void addChannel(const std::string & url, const std::string & name) +{ + if (!regex_search(url, std::regex("^(file|http|https)://"))) + throw Error("invalid channel URL '%1%'", url); + if (!regex_search(name, std::regex("^[a-zA-Z0-9_][a-zA-Z0-9_\\.-]*$"))) + throw Error("invalid channel identifier '%1%'", name); + readChannels(); + channels[name] = url; + writeChannels(); +} + +static Path profile; + +// Remove a channel. +static void removeChannel(const std::string & name) +{ + readChannels(); + channels.erase(name); + writeChannels(); + + runProgram(settings.nixBinDir + "/nix-env", true, { "--profile", profile, "--uninstall", name }); +} + +static Path nixDefExpr; + +// Fetch Nix expressions and binary cache URLs from the subscribed channels. +static void update(const StringSet & channelNames) +{ + readChannels(); + + auto store = openStore(); + + auto [fd, unpackChannelPath] = createTempFile(); + writeFull(fd.get(), + #include "unpack-channel.nix.gen.hh" + ); + fd.reset(); + AutoDelete del(unpackChannelPath, false); + + // Download each channel. + Strings exprs; + for (const auto & channel : channels) { + auto name = channel.first; + auto url = channel.second; + + // If the URL contains a version number, append it to the name + // attribute (so that "nix-env -q" on the channels profile + // shows something useful). + auto cname = name; + std::smatch match; + auto urlBase = std::string(baseNameOf(url)); + if (std::regex_search(urlBase, match, std::regex("(-\\d.*)$"))) + cname = cname + match.str(1); + + std::string extraAttrs; + + if (!(channelNames.empty() || channelNames.count(name))) { + // no need to update this channel, reuse the existing store path + Path symlink = profile + "/" + name; + Path storepath = dirOf(readLink(symlink)); + exprs.push_back("f: rec { name = \"" + cname + "\"; type = \"derivation\"; outputs = [\"out\"]; system = \"builtin\"; outPath = builtins.storePath \"" + storepath + "\"; out = { inherit outPath; };}"); + } else { + // We want to download the url to a file to see if it's a tarball while also checking if we + // got redirected in the process, so that we can grab the various parts of a nix channel + // definition from a consistent location if the redirect changes mid-download. + auto result = fetchers::downloadFile(store, url, std::string(baseNameOf(url)), false); + auto filename = store->toRealPath(result.storePath); + url = result.effectiveUrl; + + bool unpacked = false; + if (std::regex_search(filename, std::regex("\\.tar\\.(gz|bz2|xz)$"))) { + runProgram(settings.nixBinDir + "/nix-build", false, { "--no-out-link", "--expr", "import " + unpackChannelPath + + "{ name = \"" + cname + "\"; channelName = \"" + name + "\"; src = builtins.storePath \"" + filename + "\"; }" }); + unpacked = true; + } + + if (!unpacked) { + // Download the channel tarball. + try { + filename = store->toRealPath(fetchers::downloadFile(store, url + "/nixexprs.tar.xz", "nixexprs.tar.xz", false).storePath); + } catch (FileTransferError & e) { + filename = store->toRealPath(fetchers::downloadFile(store, url + "/nixexprs.tar.bz2", "nixexprs.tar.bz2", false).storePath); + } + } + // Regardless of where it came from, add the expression representing this channel to accumulated expression + exprs.push_back("f: f { name = \"" + cname + "\"; channelName = \"" + name + "\"; src = builtins.storePath \"" + filename + "\"; " + extraAttrs + " }"); + } + } + + // Unpack the channel tarballs into the Nix store and install them + // into the channels profile. + std::cerr << "unpacking " << exprs.size() << " channels...\n"; + Strings envArgs{ "--profile", profile, "--file", unpackChannelPath, "--install", "--remove-all", "--from-expression" }; + for (auto & expr : exprs) + envArgs.push_back(std::move(expr)); + envArgs.push_back("--quiet"); + runProgram(settings.nixBinDir + "/nix-env", false, envArgs); + + // Make the channels appear in nix-env. + struct stat st; + if (lstat(nixDefExpr.c_str(), &st) == 0) { + if (S_ISLNK(st.st_mode)) + // old-skool ~/.nix-defexpr + if (unlink(nixDefExpr.c_str()) == -1) + throw SysError("unlinking %1%", nixDefExpr); + } else if (errno != ENOENT) { + throw SysError("getting status of %1%", nixDefExpr); + } + createDirs(nixDefExpr); + auto channelLink = nixDefExpr + "/channels"; + replaceSymlink(profile, channelLink); +} + +static int main_nix_channel(int argc, char ** argv) +{ + { + // Figure out the name of the `.nix-channels' file to use + auto home = getHome(); + channelsList = settings.useXDGBaseDirectories ? createNixStateDir() + "/channels" : home + "/.nix-channels"; + nixDefExpr = getNixDefExpr(); + + // Figure out the name of the channels profile. + profile = profilesDir() + "/channels"; + createDirs(dirOf(profile)); + + enum { + cNone, + cAdd, + cRemove, + cList, + cUpdate, + cListGenerations, + cRollback + } cmd = cNone; + std::vector<std::string> args; + parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { + if (*arg == "--help") { + showManPage("nix-channel"); + } else if (*arg == "--version") { + printVersion("nix-channel"); + } else if (*arg == "--add") { + cmd = cAdd; + } else if (*arg == "--remove") { + cmd = cRemove; + } else if (*arg == "--list") { + cmd = cList; + } else if (*arg == "--update") { + cmd = cUpdate; + } else if (*arg == "--list-generations") { + cmd = cListGenerations; + } else if (*arg == "--rollback") { + cmd = cRollback; + } else { + if ((*arg).starts_with("-")) + throw UsageError("unsupported argument '%s'", *arg); + args.push_back(std::move(*arg)); + } + return true; + }); + + switch (cmd) { + case cNone: + throw UsageError("no command specified"); + case cAdd: + if (args.size() < 1 || args.size() > 2) + throw UsageError("'--add' requires one or two arguments"); + { + auto url = args[0]; + std::string name; + if (args.size() == 2) { + name = args[1]; + } else { + name = baseNameOf(url); + name = std::regex_replace(name, std::regex("-unstable$"), ""); + name = std::regex_replace(name, std::regex("-stable$"), ""); + } + addChannel(url, name); + } + break; + case cRemove: + if (args.size() != 1) + throw UsageError("'--remove' requires one argument"); + removeChannel(args[0]); + break; + case cList: + if (!args.empty()) + throw UsageError("'--list' expects no arguments"); + readChannels(); + for (const auto & channel : channels) + std::cout << channel.first << ' ' << channel.second << '\n'; + break; + case cUpdate: + update(StringSet(args.begin(), args.end())); + break; + case cListGenerations: + if (!args.empty()) + throw UsageError("'--list-generations' expects no arguments"); + std::cout << runProgram(settings.nixBinDir + "/nix-env", false, {"--profile", profile, "--list-generations"}) << std::flush; + break; + case cRollback: + if (args.size() > 1) + throw UsageError("'--rollback' has at most one argument"); + Strings envArgs{"--profile", profile}; + if (args.size() == 1) { + envArgs.push_back("--switch-generation"); + envArgs.push_back(args[0]); + } else { + envArgs.push_back("--rollback"); + } + runProgram(settings.nixBinDir + "/nix-env", false, envArgs); + break; + } + + return 0; + } +} + +void registerNixChannel() { + LegacyCommands::add("nix-channel", main_nix_channel); +} + +} diff --git a/src/legacy/nix-channel.hh b/src/legacy/nix-channel.hh new file mode 100644 index 000000000..f1583767f --- /dev/null +++ b/src/legacy/nix-channel.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixChannel(); + +} diff --git a/src/legacy/nix-collect-garbage.cc b/src/legacy/nix-collect-garbage.cc new file mode 100644 index 000000000..7640100a0 --- /dev/null +++ b/src/legacy/nix-collect-garbage.cc @@ -0,0 +1,118 @@ +#include "file-system.hh" +#include "store-api.hh" +#include "store-cast.hh" +#include "gc-store.hh" +#include "profiles.hh" +#include "shared.hh" +#include "globals.hh" +#include "legacy.hh" +#include "signals.hh" +#include "nix-collect-garbage.hh" + +#include <iostream> +#include <cerrno> + +namespace nix { + +std::string deleteOlderThan; +bool dryRun = false; + + +/* If `-d' was specified, remove all old generations of all profiles. + * Of course, this makes rollbacks to before this point in time + * impossible. */ + +void removeOldGenerations(std::string dir) +{ + if (access(dir.c_str(), R_OK) != 0) return; + + bool canWrite = access(dir.c_str(), W_OK) == 0; + + for (auto & i : readDirectory(dir)) { + checkInterrupt(); + + auto path = dir + "/" + i.name; + auto type = i.type == DT_UNKNOWN ? getFileType(path) : i.type; + + if (type == DT_LNK && canWrite) { + std::string link; + try { + link = readLink(path); + } catch (SysError & e) { + if (e.errNo == ENOENT) continue; + throw; + } + if (link.find("link") != std::string::npos) { + printInfo("removing old generations of profile %s", path); + if (deleteOlderThan != "") { + auto t = parseOlderThanTimeSpec(deleteOlderThan); + deleteGenerationsOlderThan(path, t, dryRun); + } else + deleteOldGenerations(path, dryRun); + } + } else if (type == DT_DIR) { + removeOldGenerations(path); + } + } +} + +static int main_nix_collect_garbage(int argc, char * * argv) +{ + { + bool removeOld = false; + + GCOptions options; + + parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { + if (*arg == "--help") + showManPage("nix-collect-garbage"); + else if (*arg == "--version") + printVersion("nix-collect-garbage"); + else if (*arg == "--delete-old" || *arg == "-d") removeOld = true; + else if (*arg == "--delete-older-than") { + removeOld = true; + deleteOlderThan = getArg(*arg, arg, end); + } + else if (*arg == "--dry-run") dryRun = true; + else if (*arg == "--max-freed") + options.maxFreed = std::max(getIntArg<int64_t>(*arg, arg, end, true), (int64_t) 0); + else + return false; + return true; + }); + + if (removeOld) { + std::set<Path> dirsToClean = { + profilesDir(), settings.nixStateDir + "/profiles", dirOf(getDefaultProfile())}; + for (auto & dir : dirsToClean) + removeOldGenerations(dir); + } + + // Run the actual garbage collector. + if (!dryRun) { + options.action = GCOptions::gcDeleteDead; + } else { + options.action = GCOptions::gcReturnDead; + } + auto store = openStore(); + auto & gcStore = require<GcStore>(*store); + GCResults results; + PrintFreed freed(true, results); + gcStore.collectGarbage(options, results); + + if (dryRun) { + // Only print results for dry run; when !dryRun, paths will be printed as they're deleted. + for (auto & i : results.paths) { + printInfo("%s", i); + } + } + + return 0; + } +} + +void registerNixCollectGarbage() { + LegacyCommands::add("nix-collect-garbage", main_nix_collect_garbage); +} + +} diff --git a/src/legacy/nix-collect-garbage.hh b/src/legacy/nix-collect-garbage.hh new file mode 100644 index 000000000..68515b537 --- /dev/null +++ b/src/legacy/nix-collect-garbage.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixCollectGarbage(); + +} diff --git a/src/legacy/nix-copy-closure.cc b/src/legacy/nix-copy-closure.cc new file mode 100644 index 000000000..1e61e3c48 --- /dev/null +++ b/src/legacy/nix-copy-closure.cc @@ -0,0 +1,68 @@ +#include "shared.hh" +#include "store-api.hh" +#include "legacy.hh" +#include "nix-copy-closure.hh" + +namespace nix { + +static int main_nix_copy_closure(int argc, char ** argv) +{ + { + auto gzip = false; + auto toMode = true; + auto includeOutputs = false; + auto dryRun = false; + auto useSubstitutes = NoSubstitute; + std::string sshHost; + PathSet storePaths; + + parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { + if (*arg == "--help") + showManPage("nix-copy-closure"); + else if (*arg == "--version") + printVersion("nix-copy-closure"); + else if (*arg == "--gzip" || *arg == "--bzip2" || *arg == "--xz") { + if (*arg != "--gzip") + warn("'%1%' is not implemented, falling back to gzip", *arg); + gzip = true; + } else if (*arg == "--from") + toMode = false; + else if (*arg == "--to") + toMode = true; + else if (*arg == "--include-outputs") + includeOutputs = true; + else if (*arg == "--show-progress") + printMsg(lvlError, "Warning: '--show-progress' is not implemented"); + else if (*arg == "--dry-run") + dryRun = true; + else if (*arg == "--use-substitutes" || *arg == "-s") + useSubstitutes = Substitute; + else if (sshHost.empty()) + sshHost = *arg; + else + storePaths.insert(*arg); + return true; + }); + + if (sshHost.empty()) + throw UsageError("no host name specified"); + + auto remoteUri = "ssh://" + sshHost + (gzip ? "?compress=true" : ""); + auto to = toMode ? openStore(remoteUri) : openStore(); + auto from = toMode ? openStore() : openStore(remoteUri); + + RealisedPath::Set storePaths2; + for (auto & path : storePaths) + storePaths2.insert(from->followLinksToStorePath(path)); + + copyClosure(*from, *to, storePaths2, NoRepair, NoCheckSigs, useSubstitutes); + + return 0; + } +} + +void registerNixCopyClosure() { + LegacyCommands::add("nix-copy-closure", main_nix_copy_closure); +} + +} diff --git a/src/legacy/nix-copy-closure.hh b/src/legacy/nix-copy-closure.hh new file mode 100644 index 000000000..fb5d0fc6e --- /dev/null +++ b/src/legacy/nix-copy-closure.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixCopyClosure(); + +} diff --git a/src/legacy/nix-env.cc b/src/legacy/nix-env.cc new file mode 100644 index 000000000..4674fd783 --- /dev/null +++ b/src/legacy/nix-env.cc @@ -0,0 +1,1553 @@ +#include "attr-path.hh" +#include "common-eval-args.hh" +#include "derivations.hh" +#include "terminal.hh" +#include "eval.hh" +#include "get-drvs.hh" +#include "globals.hh" +#include "names.hh" +#include "profiles.hh" +#include "path-with-outputs.hh" +#include "shared.hh" +#include "store-api.hh" +#include "local-fs-store.hh" +#include "user-env.hh" +#include "users.hh" +#include "value-to-json.hh" +#include "xml-writer.hh" +#include "legacy.hh" +#include "eval-settings.hh" // for defexpr +#include "nix-env.hh" + +#include <ctime> +#include <algorithm> +#include <iostream> +#include <sstream> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <nlohmann/json.hpp> + +using std::cout; + +namespace nix { + + +typedef enum { + srcNixExprDrvs, + srcNixExprs, + srcStorePaths, + srcProfile, + srcAttrPath, + srcUnknown +} InstallSourceType; + + +struct InstallSourceInfo +{ + InstallSourceType type; + std::shared_ptr<SourcePath> nixExprPath; /* for srcNixExprDrvs, srcNixExprs */ + Path profile; /* for srcProfile */ + std::string systemFilter; /* for srcNixExprDrvs */ + Bindings * autoArgs; +}; + + +struct Globals +{ + InstallSourceInfo instSource; + Path profile; + std::shared_ptr<EvalState> state; + bool dryRun; + bool preserveInstalled; + bool removeAll; + std::string forceName; + bool prebuiltOnly; +}; + + +typedef void (* Operation) (Globals & globals, + Strings opFlags, Strings opArgs); + + +static std::string needArg(Strings::iterator & i, + Strings & args, const std::string & arg) +{ + if (i == args.end()) throw UsageError("'%1%' requires an argument", arg); + return *i++; +} + + +static bool parseInstallSourceOptions(Globals & globals, + Strings::iterator & i, Strings & args, const std::string & arg) +{ + if (arg == "--from-expression" || arg == "-E") + globals.instSource.type = srcNixExprs; + else if (arg == "--from-profile") { + globals.instSource.type = srcProfile; + globals.instSource.profile = needArg(i, args, arg); + } + else if (arg == "--attr" || arg == "-A") + globals.instSource.type = srcAttrPath; + else return false; + return true; +} + + +static bool isNixExpr(const SourcePath & path, struct InputAccessor::Stat & st) +{ + return + st.type == InputAccessor::tRegular + || (st.type == InputAccessor::tDirectory && (path + "default.nix").pathExists()); +} + + +static constexpr size_t maxAttrs = 1024; + + +static void getAllExprs(EvalState & state, + const SourcePath & path, StringSet & seen, BindingsBuilder & attrs) +{ + StringSet namesSorted; + for (auto & [name, _] : path.readDirectory()) namesSorted.insert(name); + + for (auto & i : namesSorted) { + /* Ignore the manifest.nix used by profiles. This is + necessary to prevent it from showing up in channels (which + are implemented using profiles). */ + if (i == "manifest.nix") continue; + + SourcePath path2 = path + i; + + InputAccessor::Stat st; + try { + st = path2.resolveSymlinks().lstat(); + } catch (Error &) { + continue; // ignore dangling symlinks in ~/.nix-defexpr + } + + if (isNixExpr(path2, st) && (st.type != InputAccessor::tRegular || path2.baseName().ends_with(".nix"))) { + /* Strip off the `.nix' filename suffix (if applicable), + otherwise the attribute cannot be selected with the + `-A' option. Useful if you want to stick a Nix + expression directly in ~/.nix-defexpr. */ + std::string attrName = i; + if (attrName.ends_with(".nix")) + attrName = std::string(attrName, 0, attrName.size() - 4); + if (!seen.insert(attrName).second) { + std::string suggestionMessage = ""; + if (path2.path.abs().find("channels") != std::string::npos && path.path.abs().find("channels") != std::string::npos) + suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName); + printError("warning: name collision in input Nix expressions, skipping '%1%'" + "%2%", path2, suggestionMessage); + continue; + } + /* Load the expression on demand. */ + auto vArg = state.allocValue(); + vArg->mkString(path2.path.abs()); + if (seen.size() == maxAttrs) + throw Error("too many Nix expressions in directory '%1%'", path); + attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg); + } + else if (st.type == InputAccessor::tDirectory) + /* `path2' is a directory (with no default.nix in it); + recurse into it. */ + getAllExprs(state, path2, seen, attrs); + } +} + + + +static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v) +{ + auto st = path.resolveSymlinks().lstat(); + + if (isNixExpr(path, st)) + state.evalFile(path, v); + + /* The path is a directory. Put the Nix expressions in the + directory in a set, with the file name of each expression as + the attribute name. Recurse into subdirectories (but keep the + set flat, not nested, to make it easier for a user to have a + ~/.nix-defexpr directory that includes some system-wide + directory). */ + else if (st.type == InputAccessor::tDirectory) { + auto attrs = state.buildBindings(maxAttrs); + attrs.alloc("_combineChannels").mkList(0); + StringSet seen; + getAllExprs(state, path, seen, attrs); + v.mkAttrs(attrs); + } + + else throw Error("path '%s' is not a directory or a Nix expression", path); +} + + +static void loadDerivations(EvalState & state, const SourcePath & nixExprPath, + std::string systemFilter, Bindings & autoArgs, + const std::string & pathPrefix, DrvInfos & elems) +{ + Value vRoot; + loadSourceExpr(state, nixExprPath, vRoot); + + Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first); + + getDerivations(state, v, pathPrefix, autoArgs, elems, true); + + /* Filter out all derivations not applicable to the current + system. */ + for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) { + j = i; j++; + if (systemFilter != "*" && i->querySystem() != systemFilter) + elems.erase(i); + } +} + + +static NixInt getPriority(EvalState & state, DrvInfo & drv) +{ + return drv.queryMetaInt("priority", NixInt(0)); +} + + +static std::strong_ordering comparePriorities(EvalState & state, DrvInfo & drv1, DrvInfo & drv2) +{ + return getPriority(state, drv2) <=> getPriority(state, drv1); +} + + +// FIXME: this function is rather slow since it checks a single path +// at a time. +static bool isPrebuilt(EvalState & state, DrvInfo & elem) +{ + auto path = elem.queryOutPath(); + if (state.store->isValidPath(path)) return true; + return state.store->querySubstitutablePaths({path}).count(path); +} + + +static void checkSelectorUse(DrvNames & selectors) +{ + /* Check that all selectors have been used. */ + for (auto & i : selectors) + if (i.hits == 0 && i.fullName != "*") + throw Error("selector '%1%' matches no derivations", i.fullName); +} + + +namespace { + +std::set<std::string> searchByPrefix(DrvInfos & allElems, std::string_view prefix) { + constexpr std::size_t maxResults = 3; + std::set<std::string> result; + for (auto & drvInfo : allElems) { + const auto drvName = DrvName { drvInfo.queryName() }; + if (drvName.name.starts_with(prefix)) { + result.emplace(drvName.name); + + if (result.size() >= maxResults) { + break; + } + } + } + return result; +} + +struct Match +{ + DrvInfo drvInfo; + std::size_t index; + + Match(DrvInfo drvInfo_, std::size_t index_) + : drvInfo{std::move(drvInfo_)} + , index{index_} + {} +}; + +/* If a selector matches multiple derivations + with the same name, pick the one matching the current + system. If there are still multiple derivations, pick the + one with the highest priority. If there are still multiple + derivations, pick the one with the highest version. + Finally, if there are still multiple derivations, + arbitrarily pick the first one. */ +std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches) { + /* Map from package names to derivations. */ + std::map<std::string, Match> newest; + StringSet multiple; + + for (auto & match : matches) { + auto & oneDrv = match.drvInfo; + + const auto drvName = DrvName { oneDrv.queryName() }; + std::strong_ordering comparison = std::strong_ordering::greater; + + const auto itOther = newest.find(drvName.name); + + if (itOther != newest.end()) { + auto & newestDrv = itOther->second.drvInfo; + + comparison = + oneDrv.querySystem() == newestDrv.querySystem() ? std::strong_ordering::equal : + oneDrv.querySystem() == settings.thisSystem ? std::strong_ordering::greater : + newestDrv.querySystem() == settings.thisSystem ? std::strong_ordering::less : std::strong_ordering::equal; + if (comparison == 0) + comparison = comparePriorities(state, oneDrv, newestDrv); + if (comparison == 0) + comparison = compareVersions(drvName.version, DrvName { newestDrv.queryName() }.version); + } + + if (comparison > 0) { + newest.erase(drvName.name); + newest.emplace(drvName.name, match); + multiple.erase(drvName.fullName); + } else if (comparison == 0) { + multiple.insert(drvName.fullName); + } + } + + matches.clear(); + for (auto & [name, match] : newest) { + if (multiple.find(name) != multiple.end()) + warn( + "there are multiple derivations named '%1%'; using the first one", + name); + matches.push_back(match); + } + + return matches; +} + +} // end namespace + +static DrvInfos filterBySelector(EvalState & state, DrvInfos & allElems, + const Strings & args, bool newestOnly) +{ + DrvNames selectors = drvNamesFromArgs(args); + if (selectors.empty()) + selectors.emplace_back("*"); + + DrvInfos elems; + std::set<std::size_t> done; + + for (auto & selector : selectors) { + std::vector<Match> matches; + for (auto && [index, drvInfo] : enumerate(allElems)) { + const auto drvName = DrvName { drvInfo.queryName() }; + if (selector.matches(drvName)) { + ++selector.hits; + matches.emplace_back(drvInfo, index); + } + } + + if (newestOnly) { + matches = pickNewestOnly(state, std::move(matches)); + } + + /* Insert only those elements in the final list that we + haven't inserted before. */ + for (auto & match : matches) + if (done.insert(match.index).second) + elems.push_back(match.drvInfo); + + if (selector.hits == 0 && selector.fullName != "*") { + const auto prefixHits = searchByPrefix(allElems, selector.name); + + if (prefixHits.empty()) { + throw Error("selector '%1%' matches no derivations", selector.fullName); + } else { + std::string suggestionMessage = ", maybe you meant:"; + for (const auto & drvName : prefixHits) { + suggestionMessage += fmt("\n%s", drvName); + } + throw Error("selector '%1%' matches no derivations" + suggestionMessage, selector.fullName); + } + } + } + + return elems; +} + + +static bool isPath(std::string_view s) +{ + return s.find('/') != std::string_view::npos; +} + + +static void queryInstSources(EvalState & state, + InstallSourceInfo & instSource, const Strings & args, + DrvInfos & elems, bool newestOnly) +{ + InstallSourceType type = instSource.type; + if (type == srcUnknown && args.size() > 0 && isPath(args.front())) + type = srcStorePaths; + + switch (type) { + + /* Get the available user environment elements from the + derivations specified in a Nix expression, including only + those with names matching any of the names in `args'. */ + case srcUnknown: + case srcNixExprDrvs: { + + /* Load the derivations from the (default or specified) + Nix expression. */ + DrvInfos allElems; + loadDerivations(state, *instSource.nixExprPath, + instSource.systemFilter, *instSource.autoArgs, "", allElems); + + elems = filterBySelector(state, allElems, args, newestOnly); + + break; + } + + /* Get the available user environment elements from the Nix + expressions specified on the command line; these should be + functions that take the default Nix expression file as + argument, e.g., if the file is `./foo.nix', then the + argument `x: x.bar' is equivalent to `(x: x.bar) + (import ./foo.nix)' = `(import ./foo.nix).bar'. */ + case srcNixExprs: { + + Value vArg; + loadSourceExpr(state, *instSource.nixExprPath, vArg); + + for (auto & i : args) { + Expr & eFun = state.parseExprFromString(i, state.rootPath(CanonPath::fromCwd())); + Value vFun, vTmp; + state.eval(eFun, vFun); + vTmp.mkApp(&vFun, &vArg); + getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true); + } + + break; + } + + /* The available user environment elements are specified as a + list of store paths (which may or may not be + derivations). */ + case srcStorePaths: { + + for (auto & i : args) { + auto path = state.store->followLinksToStorePath(i); + + std::string name(path.name()); + + DrvInfo elem(state, "", nullptr); + elem.setName(name); + + if (path.isDerivation()) { + elem.setDrvPath(path); + auto outputs = state.store->queryDerivationOutputMap(path); + elem.setOutPath(outputs.at("out")); + if (name.size() >= drvExtension.size() && + std::string(name, name.size() - drvExtension.size()) == drvExtension) + name = name.substr(0, name.size() - drvExtension.size()); + } + else + elem.setOutPath(path); + + elems.push_back(elem); + } + + break; + } + + /* Get the available user environment elements from another + user environment. These are then filtered as in the + `srcNixExprDrvs' case. */ + case srcProfile: { + auto installed = queryInstalled(state, instSource.profile); + elems = filterBySelector( + state, + installed, + args, newestOnly + ); + break; + } + + case srcAttrPath: { + Value vRoot; + loadSourceExpr(state, *instSource.nixExprPath, vRoot); + for (auto & i : args) { + Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first); + getDerivations(state, v, "", *instSource.autoArgs, elems, true); + } + break; + } + } +} + + +static void printMissing(EvalState & state, DrvInfos & elems) +{ + std::vector<DerivedPath> targets; + for (auto & i : elems) + if (auto drvPath = i.queryDrvPath()) + targets.emplace_back(DerivedPath::Built{ + .drvPath = makeConstantStorePathRef(*drvPath), + .outputs = OutputsSpec::All { }, + }); + else + targets.emplace_back(DerivedPath::Opaque{ + .path = i.queryOutPath(), + }); + + printMissing(state.store, targets); +} + + +static bool keep(DrvInfo & drv) +{ + return drv.queryMetaBool("keep", false); +} + + +static void installDerivations(Globals & globals, + const Strings & args, const Path & profile) +{ + debug("installing derivations"); + + /* Get the set of user environment elements to be installed. */ + DrvInfos newElems, newElemsTmp; + queryInstSources(*globals.state, globals.instSource, args, newElemsTmp, true); + + /* If --prebuilt-only is given, filter out source-only packages. */ + for (auto & i : newElemsTmp) + if (!globals.prebuiltOnly || isPrebuilt(*globals.state, i)) + newElems.push_back(i); + + StringSet newNames; + for (auto & i : newElems) { + /* `forceName' is a hack to get package names right in some + one-click installs, namely those where the name used in the + path is not the one we want (e.g., `java-front' versus + `java-front-0.9pre15899'). */ + if (globals.forceName != "") + i.setName(globals.forceName); + newNames.insert(DrvName(i.queryName()).name); + } + + + while (true) { + auto lockToken = optimisticLockProfile(profile); + + DrvInfos allElems(newElems); + + /* Add in the already installed derivations, unless they have + the same name as a to-be-installed element. */ + if (!globals.removeAll) { + DrvInfos installedElems = queryInstalled(*globals.state, profile); + + for (auto & i : installedElems) { + DrvName drvName(i.queryName()); + if (!globals.preserveInstalled && + newNames.find(drvName.name) != newNames.end() && + !keep(i)) + printInfo("replacing old '%s'", i.queryName()); + else + allElems.push_back(i); + } + + for (auto & i : newElems) + printInfo("installing '%s'", i.queryName()); + } + + printMissing(*globals.state, newElems); + + if (globals.dryRun) return; + + if (createUserEnv(*globals.state, allElems, + profile, settings.envKeepDerivations, lockToken)) break; + } +} + + +static void opInstall(Globals & globals, Strings opFlags, Strings opArgs) +{ + for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) { + auto arg = *i++; + if (parseInstallSourceOptions(globals, i, opFlags, arg)) ; + else if (arg == "--preserve-installed" || arg == "-P") + globals.preserveInstalled = true; + else if (arg == "--remove-all" || arg == "-r") + globals.removeAll = true; + else throw UsageError("unknown flag '%1%'", arg); + } + + installDerivations(globals, opArgs, globals.profile); +} + + +typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType; + + +static void upgradeDerivations(Globals & globals, + const Strings & args, UpgradeType upgradeType) +{ + debug("upgrading derivations"); + + /* Upgrade works as follows: we take all currently installed + derivations, and for any derivation matching any selector, look + for a derivation in the input Nix expression that has the same + name and a higher version number. */ + + while (true) { + auto lockToken = optimisticLockProfile(globals.profile); + + DrvInfos installedElems = queryInstalled(*globals.state, globals.profile); + + /* Fetch all derivations from the input file. */ + DrvInfos availElems; + queryInstSources(*globals.state, globals.instSource, args, availElems, false); + + /* Go through all installed derivations. */ + DrvInfos newElems; + for (auto & i : installedElems) { + DrvName drvName(i.queryName()); + + try { + + if (keep(i)) { + newElems.push_back(i); + continue; + } + + /* Find the derivation in the input Nix expression + with the same name that satisfies the version + constraints specified by upgradeType. If there are + multiple matches, take the one with the highest + priority. If there are still multiple matches, + take the one with the highest version. + Do not upgrade if it would decrease the priority. */ + DrvInfos::iterator bestElem = availElems.end(); + std::string bestVersion; + for (auto j = availElems.begin(); j != availElems.end(); ++j) { + if (comparePriorities(*globals.state, i, *j) > 0) + continue; + DrvName newName(j->queryName()); + if (newName.name == drvName.name) { + std::strong_ordering d = compareVersions(drvName.version, newName.version); + if ((upgradeType == utLt && d < 0) || + (upgradeType == utLeq && d <= 0) || + (upgradeType == utEq && d == 0) || + upgradeType == utAlways) + { + std::strong_ordering d2 = std::strong_ordering::less; + if (bestElem != availElems.end()) { + d2 = comparePriorities(*globals.state, *bestElem, *j); + if (d2 == 0) d2 = compareVersions(bestVersion, newName.version); + } + if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*globals.state, *j))) { + bestElem = j; + bestVersion = newName.version; + } + } + } + } + + if (bestElem != availElems.end() && + i.queryOutPath() != + bestElem->queryOutPath()) + { + const char * action = compareVersions(drvName.version, bestVersion) <= 0 + ? "upgrading" : "downgrading"; + printInfo("%1% '%2%' to '%3%'", + action, i.queryName(), bestElem->queryName()); + newElems.push_back(*bestElem); + } else newElems.push_back(i); + + } catch (Error & e) { + e.addTrace(nullptr, "while trying to find an upgrade for '%s'", i.queryName()); + throw; + } + } + + printMissing(*globals.state, newElems); + + if (globals.dryRun) return; + + if (createUserEnv(*globals.state, newElems, + globals.profile, settings.envKeepDerivations, lockToken)) break; + } +} + + +static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs) +{ + UpgradeType upgradeType = utLt; + for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) { + std::string arg = *i++; + if (parseInstallSourceOptions(globals, i, opFlags, arg)) ; + else if (arg == "--lt") upgradeType = utLt; + else if (arg == "--leq") upgradeType = utLeq; + else if (arg == "--eq") upgradeType = utEq; + else if (arg == "--always") upgradeType = utAlways; + else throw UsageError("unknown flag '%1%'", arg); + } + + upgradeDerivations(globals, opArgs, upgradeType); +} + + +static void setMetaFlag(EvalState & state, DrvInfo & drv, + const std::string & name, const std::string & value) +{ + auto v = state.allocValue(); + v->mkString(value); + drv.setMeta(name, v); +} + + +static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + if (opArgs.size() < 2) + throw UsageError("not enough arguments to '--set-flag'"); + + Strings::iterator arg = opArgs.begin(); + std::string flagName = *arg++; + std::string flagValue = *arg++; + DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end())); + + while (true) { + std::string lockToken = optimisticLockProfile(globals.profile); + + DrvInfos installedElems = queryInstalled(*globals.state, globals.profile); + + /* Update all matching derivations. */ + for (auto & i : installedElems) { + DrvName drvName(i.queryName()); + for (auto & j : selectors) + if (j.matches(drvName)) { + printInfo("setting flag on '%1%'", i.queryName()); + j.hits++; + setMetaFlag(*globals.state, i, flagName, flagValue); + break; + } + } + + checkSelectorUse(selectors); + + /* Write the new user environment. */ + if (createUserEnv(*globals.state, installedElems, + globals.profile, settings.envKeepDerivations, lockToken)) break; + } +} + + +static void opSet(Globals & globals, Strings opFlags, Strings opArgs) +{ + auto store2 = globals.state->store.dynamic_pointer_cast<LocalFSStore>(); + if (!store2) throw Error("--set is not supported for this Nix store"); + + for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) { + std::string arg = *i++; + if (parseInstallSourceOptions(globals, i, opFlags, arg)) ; + else throw UsageError("unknown flag '%1%'", arg); + } + + DrvInfos elems; + queryInstSources(*globals.state, globals.instSource, opArgs, elems, true); + + if (elems.size() != 1) + throw Error("--set requires exactly one derivation"); + + DrvInfo & drv(elems.front()); + + if (globals.forceName != "") + drv.setName(globals.forceName); + + auto drvPath = drv.queryDrvPath(); + std::vector<DerivedPath> paths { + drvPath + ? (DerivedPath) (DerivedPath::Built { + .drvPath = makeConstantStorePathRef(*drvPath), + .outputs = OutputsSpec::All { }, + }) + : (DerivedPath) (DerivedPath::Opaque { + .path = drv.queryOutPath(), + }), + }; + printMissing(globals.state->store, paths); + if (globals.dryRun) return; + globals.state->store->buildPaths(paths, globals.state->repair ? bmRepair : bmNormal); + + debug("switching to new user environment"); + Path generation = createGeneration( + *store2, + globals.profile, + drv.queryOutPath()); + switchLink(globals.profile, generation); +} + + +static void uninstallDerivations(Globals & globals, Strings & selectors, + Path & profile) +{ + while (true) { + auto lockToken = optimisticLockProfile(profile); + + DrvInfos workingElems = queryInstalled(*globals.state, profile); + + for (auto & selector : selectors) { + DrvInfos::iterator split = workingElems.begin(); + if (isPath(selector)) { + StorePath selectorStorePath = globals.state->store->followLinksToStorePath(selector); + split = std::partition( + workingElems.begin(), workingElems.end(), + [&selectorStorePath, globals](auto &elem) { + return selectorStorePath != elem.queryOutPath(); + } + ); + } else { + DrvName selectorName(selector); + split = std::partition( + workingElems.begin(), workingElems.end(), + [&selectorName](auto &elem){ + DrvName elemName(elem.queryName()); + return !selectorName.matches(elemName); + } + ); + } + if (split == workingElems.end()) + warn("selector '%s' matched no installed derivations", selector); + for (auto removedElem = split; removedElem != workingElems.end(); removedElem++) { + printInfo("uninstalling '%s'", removedElem->queryName()); + } + workingElems.erase(split, workingElems.end()); + } + + if (globals.dryRun) return; + + if (createUserEnv(*globals.state, workingElems, + profile, settings.envKeepDerivations, lockToken)) break; + } +} + + +static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + uninstallDerivations(globals, opArgs, globals.profile); +} + + +static bool cmpChars(char a, char b) +{ + return toupper(a) < toupper(b); +} + + +static bool cmpElemByName(DrvInfo & a, DrvInfo & b) +{ + auto a_name = a.queryName(); + auto b_name = b.queryName(); + return lexicographical_compare( + a_name.begin(), a_name.end(), + b_name.begin(), b_name.end(), cmpChars); +} + + +typedef std::list<Strings> Table; + + +void printTable(Table & table) +{ + auto nrColumns = table.size() > 0 ? table.front().size() : 0; + + std::vector<size_t> widths; + widths.resize(nrColumns); + + for (auto & i : table) { + assert(i.size() == nrColumns); + Strings::iterator j; + size_t column; + for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) + if (j->size() > widths[column]) widths[column] = j->size(); + } + + for (auto & i : table) { + Strings::iterator j; + size_t column; + for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) { + std::string s = *j; + replace(s.begin(), s.end(), '\n', ' '); + cout << s; + if (column < nrColumns - 1) + cout << std::string(widths[column] - s.size() + 2, ' '); + } + cout << std::endl; + } +} + + +/* This function compares the version of an element against the + versions in the given set of elements. `cvLess' means that only + lower versions are in the set, `cvEqual' means that at most an + equal version is in the set, and `cvGreater' means that there is at + least one element with a higher version in the set. `cvUnavail' + means that there are no elements with the same name in the set. */ + +typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff; + +static VersionDiff compareVersionAgainstSet( + DrvInfo & elem, DrvInfos & elems, std::string & version) +{ + DrvName name(elem.queryName()); + + VersionDiff diff = cvUnavail; + version = "?"; + + for (auto & i : elems) { + DrvName name2(i.queryName()); + if (name.name == name2.name) { + std::strong_ordering d = compareVersions(name.version, name2.version); + if (d < 0) { + diff = cvGreater; + version = name2.version; + } + else if (diff != cvGreater && d == 0) { + diff = cvEqual; + version = name2.version; + } + else if (diff != cvGreater && diff != cvEqual && d > 0) { + diff = cvLess; + if (version == "" || compareVersions(version, name2.version) < 0) + version = name2.version; + } + } + } + + return diff; +} + + +static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool printOutPath, bool printDrvPath, bool printMeta) +{ + using nlohmann::json; + json topObj = json::object(); + for (auto & i : elems) { + try { + if (i.hasFailed()) continue; + + + auto drvName = DrvName(i.queryName()); + json &pkgObj = topObj[i.attrPath]; + pkgObj = { + {"name", drvName.fullName}, + {"pname", drvName.name}, + {"version", drvName.version}, + {"system", i.querySystem()}, + {"outputName", i.queryOutputName()}, + }; + + { + DrvInfo::Outputs outputs = i.queryOutputs(printOutPath); + json &outputObj = pkgObj["outputs"]; + outputObj = json::object(); + for (auto & j : outputs) { + if (j.second) + outputObj[j.first] = globals.state->store->printStorePath(*j.second); + else + outputObj[j.first] = nullptr; + } + } + + if (printDrvPath) { + auto drvPath = i.queryDrvPath(); + if (drvPath) pkgObj["drvPath"] = globals.state->store->printStorePath(*drvPath); + } + + if (printMeta) { + json &metaObj = pkgObj["meta"]; + metaObj = json::object(); + StringSet metaNames = i.queryMetaNames(); + for (auto & j : metaNames) { + Value * v = i.queryMeta(j); + if (!v) { + printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j); + metaObj[j] = nullptr; + } else { + NixStringContext context; + metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context); + } + } + } + } catch (AssertionError & e) { + printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName()); + } catch (Error & e) { + e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName()); + throw; + } + } + std::cout << topObj.dump(2); +} + + +static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) +{ + auto & store { *globals.state->store }; + + Strings remaining; + std::string attrPath; + + bool printStatus = false; + bool printName = true; + bool printAttrPath = false; + bool printSystem = false; + bool printDrvPath = false; + bool printOutPath = false; + bool printDescription = false; + bool printMeta = false; + bool compareVersions = false; + bool xmlOutput = false; + bool jsonOutput = false; + + enum { sInstalled, sAvailable } source = sInstalled; + + settings.readOnlyMode = true; /* makes evaluation a bit faster */ + + for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) { + auto arg = *i++; + if (arg == "--status" || arg == "-s") printStatus = true; + else if (arg == "--no-name") printName = false; + else if (arg == "--system") printSystem = true; + else if (arg == "--description") printDescription = true; + else if (arg == "--compare-versions" || arg == "-c") compareVersions = true; + else if (arg == "--drv-path") printDrvPath = true; + else if (arg == "--out-path") printOutPath = true; + else if (arg == "--meta") printMeta = true; + else if (arg == "--installed") source = sInstalled; + else if (arg == "--available" || arg == "-a") source = sAvailable; + else if (arg == "--xml") xmlOutput = true; + else if (arg == "--json") jsonOutput = true; + else if (arg == "--attr-path" || arg == "-P") printAttrPath = true; + else if (arg == "--attr" || arg == "-A") + attrPath = needArg(i, opFlags, arg); + else + throw UsageError("unknown flag '%1%'", arg); + } + + if (printAttrPath && source != sAvailable) + throw UsageError("--attr-path(-P) only works with --available"); + + /* Obtain derivation information from the specified source. */ + DrvInfos availElems, installedElems; + + if (source == sInstalled || compareVersions || printStatus) + installedElems = queryInstalled(*globals.state, globals.profile); + + if (source == sAvailable || compareVersions) + loadDerivations(*globals.state, *globals.instSource.nixExprPath, + globals.instSource.systemFilter, *globals.instSource.autoArgs, + attrPath, availElems); + + DrvInfos elems_ = filterBySelector(*globals.state, + source == sInstalled ? installedElems : availElems, + opArgs, false); + + DrvInfos & otherElems(source == sInstalled ? availElems : installedElems); + + + /* Sort them by name. */ + /* !!! */ + std::vector<DrvInfo> elems; + for (auto & i : elems_) elems.push_back(i); + sort(elems.begin(), elems.end(), cmpElemByName); + + + /* We only need to know the installed paths when we are querying + the status of the derivation. */ + StorePathSet installed; /* installed paths */ + + if (printStatus) + for (auto & i : installedElems) + installed.insert(i.queryOutPath()); + + + /* Query which paths have substitutes. */ + StorePathSet validPaths; + StorePathSet substitutablePaths; + if (printStatus || globals.prebuiltOnly) { + StorePathSet paths; + for (auto & i : elems) + try { + paths.insert(i.queryOutPath()); + } catch (AssertionError & e) { + printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName()); + i.setFailed(); + } + validPaths = store.queryValidPaths(paths); + substitutablePaths = store.querySubstitutablePaths(paths); + } + + + /* Print the desired columns, or XML output. */ + if (jsonOutput) { + queryJSON(globals, elems, printOutPath, printDrvPath, printMeta); + cout << '\n'; + return; + } + + RunPager pager; + + Table table; + std::ostringstream dummy; + XMLWriter xml(true, *(xmlOutput ? &cout : &dummy)); + XMLOpenElement xmlRoot(xml, "items"); + + for (auto & i : elems) { + try { + if (i.hasFailed()) continue; + + //Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath); + + if (globals.prebuiltOnly && + !validPaths.count(i.queryOutPath()) && + !substitutablePaths.count(i.queryOutPath())) + continue; + + /* For table output. */ + Strings columns; + + /* For XML output. */ + XMLAttrs attrs; + + if (printStatus) { + auto outPath = i.queryOutPath(); + bool hasSubs = substitutablePaths.count(outPath); + bool isInstalled = installed.count(outPath); + bool isValid = validPaths.count(outPath); + if (xmlOutput) { + attrs["installed"] = isInstalled ? "1" : "0"; + attrs["valid"] = isValid ? "1" : "0"; + attrs["substitutable"] = hasSubs ? "1" : "0"; + } else + columns.push_back( + (std::string) (isInstalled ? "I" : "-") + + (isValid ? "P" : "-") + + (hasSubs ? "S" : "-")); + } + + if (xmlOutput) + attrs["attrPath"] = i.attrPath; + else if (printAttrPath) + columns.push_back(i.attrPath); + + if (xmlOutput) { + auto drvName = DrvName(i.queryName()); + attrs["name"] = drvName.fullName; + attrs["pname"] = drvName.name; + attrs["version"] = drvName.version; + } else if (printName) { + columns.push_back(i.queryName()); + } + + if (compareVersions) { + /* Compare this element against the versions of the + same named packages in either the set of available + elements, or the set of installed elements. !!! + This is O(N * M), should be O(N * lg M). */ + std::string version; + VersionDiff diff = compareVersionAgainstSet(i, otherElems, version); + + char ch; + switch (diff) { + case cvLess: ch = '>'; break; + case cvEqual: ch = '='; break; + case cvGreater: ch = '<'; break; + case cvUnavail: ch = '-'; break; + default: abort(); + } + + if (xmlOutput) { + if (diff != cvUnavail) { + attrs["versionDiff"] = ch; + attrs["maxComparedVersion"] = version; + } + } else { + auto column = (std::string) "" + ch + " " + version; + if (diff == cvGreater && shouldANSI(StandardOutputStream::Stdout)) + column = ANSI_RED + column + ANSI_NORMAL; + columns.push_back(column); + } + } + + if (xmlOutput) { + if (i.querySystem() != "") attrs["system"] = i.querySystem(); + } + else if (printSystem) + columns.push_back(i.querySystem()); + + if (printDrvPath) { + auto drvPath = i.queryDrvPath(); + if (xmlOutput) { + if (drvPath) attrs["drvPath"] = store.printStorePath(*drvPath); + } else + columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-"); + } + + if (xmlOutput) + attrs["outputName"] = i.queryOutputName(); + + if (printOutPath && !xmlOutput) { + DrvInfo::Outputs outputs = i.queryOutputs(); + std::string s; + for (auto & j : outputs) { + if (!s.empty()) s += ';'; + if (j.first != "out") { s += j.first; s += "="; } + s += store.printStorePath(*j.second); + } + columns.push_back(s); + } + + if (printDescription) { + auto descr = i.queryMetaString("description"); + if (xmlOutput) { + if (descr != "") attrs["description"] = descr; + } else + columns.push_back(descr); + } + + if (xmlOutput) { + XMLOpenElement item(xml, "item", attrs); + DrvInfo::Outputs outputs = i.queryOutputs(printOutPath); + for (auto & j : outputs) { + XMLAttrs attrs2; + attrs2["name"] = j.first; + if (j.second) + attrs2["path"] = store.printStorePath(*j.second); + xml.writeEmptyElement("output", attrs2); + } + if (printMeta) { + StringSet metaNames = i.queryMetaNames(); + for (auto & j : metaNames) { + XMLAttrs attrs2; + attrs2["name"] = j; + Value * v = i.queryMeta(j); + if (!v) + printError( + "derivation '%s' has invalid meta attribute '%s'", + i.queryName(), j); + else { + if (v->type() == nString) { + attrs2["type"] = "string"; + attrs2["value"] = v->string.s; + xml.writeEmptyElement("meta", attrs2); + } else if (v->type() == nInt) { + attrs2["type"] = "int"; + attrs2["value"] = fmt("%1%", v->integer); + xml.writeEmptyElement("meta", attrs2); + } else if (v->type() == nFloat) { + attrs2["type"] = "float"; + attrs2["value"] = fmt("%1%", v->fpoint); + xml.writeEmptyElement("meta", attrs2); + } else if (v->type() == nBool) { + attrs2["type"] = "bool"; + attrs2["value"] = v->boolean ? "true" : "false"; + xml.writeEmptyElement("meta", attrs2); + } else if (v->type() == nList) { + attrs2["type"] = "strings"; + XMLOpenElement m(xml, "meta", attrs2); + for (auto elem : v->listItems()) { + if (elem->type() != nString) continue; + XMLAttrs attrs3; + attrs3["value"] = elem->string.s; + xml.writeEmptyElement("string", attrs3); + } + } else if (v->type() == nAttrs) { + attrs2["type"] = "strings"; + XMLOpenElement m(xml, "meta", attrs2); + Bindings & attrs = *v->attrs; + for (auto &i : attrs) { + Attr & a(*attrs.find(i.name)); + if(a.value->type() != nString) continue; + XMLAttrs attrs3; + attrs3["type"] = globals.state->symbols[i.name]; + attrs3["value"] = a.value->string.s; + xml.writeEmptyElement("string", attrs3); + } + } + } + } + } + } else + table.push_back(columns); + + cout.flush(); + + } catch (AssertionError & e) { + printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName()); + } catch (Error & e) { + e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName()); + throw; + } + } + + if (!xmlOutput) printTable(table); +} + + +static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + if (opArgs.size() != 1) + throw UsageError("exactly one argument expected"); + + Path profile = absPath(opArgs.front()); + Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; + + switchLink(profileLink, profile); +} + + +static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + if (opArgs.size() != 1) + throw UsageError("exactly one argument expected"); + + if (auto dstGen = string2Int<GenerationNumber>(opArgs.front())) + switchGeneration(globals.profile, *dstGen, globals.dryRun); + else + throw UsageError("expected a generation number"); +} + + +static void opRollback(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + if (opArgs.size() != 0) + throw UsageError("no arguments expected"); + + switchGeneration(globals.profile, {}, globals.dryRun); +} + + +static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + if (opArgs.size() != 0) + throw UsageError("no arguments expected"); + + PathLocks lock; + lockProfile(lock, globals.profile); + + auto [gens, curGen] = findGenerations(globals.profile); + + RunPager pager; + + for (auto & i : gens) { + tm t; + if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time"); + logger->cout("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||", + i.number, + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec, + i.number == curGen ? "(current)" : ""); + } +} + + +static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError("unknown flag '%1%'", opFlags.front()); + + if (opArgs.size() == 1 && opArgs.front() == "old") { + deleteOldGenerations(globals.profile, globals.dryRun); + } else if (opArgs.size() == 1 && opArgs.front().find('d') != std::string::npos) { + auto t = parseOlderThanTimeSpec(opArgs.front()); + deleteGenerationsOlderThan(globals.profile, t, globals.dryRun); + } else if (opArgs.size() == 1 && opArgs.front().find('+') != std::string::npos) { + if (opArgs.front().size() < 2) + throw Error("invalid number of generations '%1%'", opArgs.front()); + auto str_max = opArgs.front().substr(1); + auto max = string2Int<GenerationNumber>(str_max); + if (!max) + throw Error("invalid number of generations to keep '%1%'", opArgs.front()); + deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun); + } else { + std::set<GenerationNumber> gens; + for (auto & i : opArgs) { + if (auto n = string2Int<GenerationNumber>(i)) + gens.insert(*n); + else + throw UsageError("invalid generation number '%1%'", i); + } + deleteGenerations(globals.profile, gens, globals.dryRun); + } +} + + +static void opVersion(Globals & globals, Strings opFlags, Strings opArgs) +{ + printVersion("nix-env"); +} + + +static int main_nix_env(int argc, char * * argv) +{ + { + Strings opFlags, opArgs; + Operation op = 0; + std::string opName; + bool showHelp = false; + std::string file; + + Globals globals; + + globals.instSource.type = srcUnknown; + globals.instSource.systemFilter = "*"; + + Path nixExprPath = getNixDefExpr(); + + if (!pathExists(nixExprPath)) { + try { + createDirs(nixExprPath); + replaceSymlink( + defaultChannelsDir(), + nixExprPath + "/channels"); + if (getuid() != 0) + replaceSymlink( + rootChannelsDir(), + nixExprPath + "/channels_root"); + } catch (Error &) { } + } + + globals.dryRun = false; + globals.preserveInstalled = false; + globals.removeAll = false; + globals.prebuiltOnly = false; + + struct MyArgs : LegacyArgs, MixEvalArgs + { + using LegacyArgs::LegacyArgs; + }; + + MyArgs myArgs(std::string(baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) { + Operation oldOp = op; + + if (*arg == "--help") + showHelp = true; + else if (*arg == "--version") + op = opVersion; + else if (*arg == "--install" || *arg == "-i") { + op = opInstall; + opName = "-install"; + } + else if (*arg == "--force-name") // undocumented flag for nix-install-package + globals.forceName = getArg(*arg, arg, end); + else if (*arg == "--uninstall" || *arg == "-e") { + op = opUninstall; + opName = "-uninstall"; + } + else if (*arg == "--upgrade" || *arg == "-u") { + op = opUpgrade; + opName = "-upgrade"; + } + else if (*arg == "--set-flag") { + op = opSetFlag; + opName = arg->substr(1); + } + else if (*arg == "--set") { + op = opSet; + opName = arg->substr(1); + } + else if (*arg == "--query" || *arg == "-q") { + op = opQuery; + opName = "-query"; + } + else if (*arg == "--profile" || *arg == "-p") + globals.profile = absPath(getArg(*arg, arg, end)); + else if (*arg == "--file" || *arg == "-f") + file = getArg(*arg, arg, end); + else if (*arg == "--switch-profile" || *arg == "-S") { + op = opSwitchProfile; + opName = "-switch-profile"; + } + else if (*arg == "--switch-generation" || *arg == "-G") { + op = opSwitchGeneration; + opName = "-switch-generation"; + } + else if (*arg == "--rollback") { + op = opRollback; + opName = arg->substr(1); + } + else if (*arg == "--list-generations") { + op = opListGenerations; + opName = arg->substr(1); + } + else if (*arg == "--delete-generations") { + op = opDeleteGenerations; + opName = arg->substr(1); + } + else if (*arg == "--dry-run") { + printInfo("(dry run; not doing anything)"); + globals.dryRun = true; + } + else if (*arg == "--system-filter") + globals.instSource.systemFilter = getArg(*arg, arg, end); + else if (*arg == "--prebuilt-only" || *arg == "-b") + globals.prebuiltOnly = true; + else if (*arg != "" && arg->at(0) == '-') { + opFlags.push_back(*arg); + /* FIXME: hacky */ + if (*arg == "--from-profile" || + (op == opQuery && (*arg == "--attr" || *arg == "-A"))) + opFlags.push_back(getArg(*arg, arg, end)); + } + else + opArgs.push_back(*arg); + + if (oldOp && oldOp != op) + throw UsageError("only one operation may be specified"); + + return true; + }); + + myArgs.parseCmdline(argvToStrings(argc, argv)); + + if (showHelp) showManPage("nix-env" + opName); + if (!op) throw UsageError("no operation specified"); + + auto store = openStore(); + + globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store)); + globals.state->repair = myArgs.repair; + + globals.instSource.nixExprPath = std::make_shared<SourcePath>( + file != "" + ? lookupFileArg(*globals.state, file) + : globals.state->rootPath(CanonPath(nixExprPath))); + + globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state); + + if (globals.profile == "") + globals.profile = getEnv("NIX_PROFILE").value_or(""); + + if (globals.profile == "") + globals.profile = getDefaultProfile(); + + op(globals, std::move(opFlags), std::move(opArgs)); + + globals.state->maybePrintStats(); + + return 0; + } +} + +void registerNixEnv() { + LegacyCommands::add("nix-env", main_nix_env); +} + +} diff --git a/src/legacy/nix-env.hh b/src/legacy/nix-env.hh new file mode 100644 index 000000000..47d62b8e6 --- /dev/null +++ b/src/legacy/nix-env.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixEnv(); + +} diff --git a/src/legacy/nix-instantiate.cc b/src/legacy/nix-instantiate.cc new file mode 100644 index 000000000..5d1da0d70 --- /dev/null +++ b/src/legacy/nix-instantiate.cc @@ -0,0 +1,203 @@ +#include "globals.hh" +#include "print-ambiguous.hh" +#include "shared.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "get-drvs.hh" +#include "attr-path.hh" +#include "value-to-xml.hh" +#include "value-to-json.hh" +#include "store-api.hh" +#include "local-fs-store.hh" +#include "common-eval-args.hh" +#include "legacy.hh" +#include "nix-instantiate.hh" + +#include <map> +#include <iostream> + + +namespace nix { + + +static Path gcRoot; +static int rootNr = 0; + + +enum OutputKind { okPlain, okXML, okJSON }; + +void processExpr(EvalState & state, const Strings & attrPaths, + bool parseOnly, bool strict, Bindings & autoArgs, + bool evalOnly, OutputKind output, bool location, Expr & e) +{ + if (parseOnly) { + e.show(state.symbols, std::cout); + std::cout << "\n"; + return; + } + + Value vRoot; + state.eval(e, vRoot); + + for (auto & i : attrPaths) { + Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first); + state.forceValue(v, v.determinePos(noPos)); + + NixStringContext context; + if (evalOnly) { + Value vRes; + if (autoArgs.empty()) + vRes = v; + else + state.autoCallFunction(autoArgs, v, vRes); + if (output == okXML) + printValueAsXML(state, strict, location, vRes, std::cout, context, noPos); + else if (output == okJSON) { + printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context); + std::cout << std::endl; + } else { + if (strict) state.forceValueDeep(vRes); + std::set<const void *> seen; + printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits<int>::max()); + std::cout << std::endl; + } + } else { + DrvInfos drvs; + getDerivations(state, v, "", autoArgs, drvs, false); + for (auto & i : drvs) { + auto drvPath = i.requireDrvPath(); + auto drvPathS = state.store->printStorePath(drvPath); + + /* What output do we want? */ + std::string outputName = i.queryOutputName(); + if (outputName == "") + throw Error("derivation '%1%' lacks an 'outputName' attribute", drvPathS); + + if (gcRoot == "") + printGCWarning(); + else { + Path rootName = absPath(gcRoot); + if (++rootNr > 1) rootName += "-" + std::to_string(rootNr); + auto store2 = state.store.dynamic_pointer_cast<LocalFSStore>(); + if (store2) + drvPathS = store2->addPermRoot(drvPath, rootName); + } + std::cout << fmt("%s%s\n", drvPathS, (outputName != "out" ? "!" + outputName : "")); + } + } + } +} + + +static int main_nix_instantiate(int argc, char * * argv) +{ + { + Strings files; + bool readStdin = false; + bool fromArgs = false; + bool findFile = false; + bool evalOnly = false; + bool parseOnly = false; + OutputKind outputKind = okPlain; + bool xmlOutputSourceLocation = true; + bool strict = false; + Strings attrPaths; + bool wantsReadWrite = false; + + struct MyArgs : LegacyArgs, MixEvalArgs + { + using LegacyArgs::LegacyArgs; + }; + + MyArgs myArgs(std::string(baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) { + if (*arg == "--help") + showManPage("nix-instantiate"); + else if (*arg == "--version") + printVersion("nix-instantiate"); + else if (*arg == "-") + readStdin = true; + else if (*arg == "--expr" || *arg == "-E") + fromArgs = true; + else if (*arg == "--eval" || *arg == "--eval-only") + evalOnly = true; + else if (*arg == "--read-write-mode") + wantsReadWrite = true; + else if (*arg == "--parse" || *arg == "--parse-only") + parseOnly = evalOnly = true; + else if (*arg == "--find-file") + findFile = true; + else if (*arg == "--attr" || *arg == "-A") + attrPaths.push_back(getArg(*arg, arg, end)); + else if (*arg == "--add-root") + gcRoot = getArg(*arg, arg, end); + else if (*arg == "--indirect") + ; + else if (*arg == "--xml") + outputKind = okXML; + else if (*arg == "--json") + outputKind = okJSON; + else if (*arg == "--no-location") + xmlOutputSourceLocation = false; + else if (*arg == "--strict") + strict = true; + else if (*arg == "--dry-run") + settings.readOnlyMode = true; + else if (*arg != "" && arg->at(0) == '-') + return false; + else + files.push_back(*arg); + return true; + }); + + myArgs.parseCmdline(argvToStrings(argc, argv)); + + if (evalOnly && !wantsReadWrite) + settings.readOnlyMode = true; + + auto store = openStore(); + auto evalStore = myArgs.evalStoreUrl ? openStore(*myArgs.evalStoreUrl) : store; + + auto state = std::make_unique<EvalState>(myArgs.searchPath, evalStore, store); + state->repair = myArgs.repair; + + Bindings & autoArgs = *myArgs.getAutoArgs(*state); + + if (attrPaths.empty()) attrPaths = {""}; + + if (findFile) { + for (auto & i : files) { + auto p = state->findFile(i); + if (auto fn = p.getPhysicalPath()) + std::cout << fn->abs() << std::endl; + else + throw Error("'%s' has no physical path", p); + } + return 0; + } + + if (readStdin) { + Expr & e = state->parseStdin(); + processExpr(*state, attrPaths, parseOnly, strict, autoArgs, + evalOnly, outputKind, xmlOutputSourceLocation, e); + } else if (files.empty() && !fromArgs) + files.push_back("./default.nix"); + + for (auto & i : files) { + Expr & e = fromArgs + ? state->parseExprFromString(i, state->rootPath(CanonPath::fromCwd())) + : state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, i)))); + processExpr(*state, attrPaths, parseOnly, strict, autoArgs, + evalOnly, outputKind, xmlOutputSourceLocation, e); + } + + state->maybePrintStats(); + + return 0; + } +} + +void registerNixInstantiate() { + LegacyCommands::add("nix-instantiate", main_nix_instantiate); +} + +} diff --git a/src/legacy/nix-instantiate.hh b/src/legacy/nix-instantiate.hh new file mode 100644 index 000000000..f4c35a6b5 --- /dev/null +++ b/src/legacy/nix-instantiate.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixInstantiate(); + +} diff --git a/src/legacy/nix-store.cc b/src/legacy/nix-store.cc new file mode 100644 index 000000000..e42aa4065 --- /dev/null +++ b/src/legacy/nix-store.cc @@ -0,0 +1,1183 @@ +#include "archive.hh" +#include "derivations.hh" +#include "dotgraph.hh" +#include "exit.hh" +#include "globals.hh" +#include "build-result.hh" +#include "store-cast.hh" +#include "gc-store.hh" +#include "log-store.hh" +#include "local-store.hh" +#include "monitor-fd.hh" +#include "serve-protocol.hh" +#include "serve-protocol-impl.hh" +#include "shared.hh" +#include "graphml.hh" +#include "legacy.hh" +#include "path-with-outputs.hh" +#include "nix-store.hh" + +#include <iostream> +#include <algorithm> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + +namespace nix { + + +using std::cin; +using std::cout; + + +typedef void (* Operation) (Strings opFlags, Strings opArgs); + + +static Path gcRoot; +static int rootNr = 0; +static bool noOutput = false; +static std::shared_ptr<Store> store; + + +ref<LocalStore> ensureLocalStore() +{ + auto store2 = std::dynamic_pointer_cast<LocalStore>(store); + if (!store2) throw Error("you don't have sufficient rights to use this command"); + return ref<LocalStore>(store2); +} + + +static StorePath useDeriver(const StorePath & path) +{ + if (path.isDerivation()) return path; + auto info = store->queryPathInfo(path); + if (!info->deriver) + throw Error("deriver of path '%s' is not known", store->printStorePath(path)); + return *info->deriver; +} + + +/* Realise the given path. For a derivation that means build it; for + other paths it means ensure their validity. */ +static PathSet realisePath(StorePathWithOutputs path, bool build = true) +{ + auto store2 = std::dynamic_pointer_cast<LocalFSStore>(store); + + if (path.path.isDerivation()) { + if (build) store->buildPaths({path.toDerivedPath()}); + auto outputPaths = store->queryDerivationOutputMap(path.path); + Derivation drv = store->derivationFromPath(path.path); + rootNr++; + + /* FIXME: Encode this empty special case explicitly in the type. */ + if (path.outputs.empty()) + for (auto & i : drv.outputs) path.outputs.insert(i.first); + + PathSet outputs; + for (auto & j : path.outputs) { + /* Match outputs of a store path with outputs of the derivation that produces it. */ + DerivationOutputs::iterator i = drv.outputs.find(j); + if (i == drv.outputs.end()) + throw Error("derivation '%s' does not have an output named '%s'", + store2->printStorePath(path.path), j); + auto outPath = outputPaths.at(i->first); + auto retPath = store->printStorePath(outPath); + if (store2) { + if (gcRoot == "") + printGCWarning(); + else { + Path rootName = gcRoot; + if (rootNr > 1) rootName += "-" + std::to_string(rootNr); + if (i->first != "out") rootName += "-" + i->first; + retPath = store2->addPermRoot(outPath, rootName); + } + } + outputs.insert(retPath); + } + return outputs; + } + + else { + if (build) store->ensurePath(path.path); + else if (!store->isValidPath(path.path)) + throw Error("path '%s' does not exist and cannot be created", store->printStorePath(path.path)); + if (store2) { + if (gcRoot == "") + printGCWarning(); + else { + Path rootName = gcRoot; + rootNr++; + if (rootNr > 1) rootName += "-" + std::to_string(rootNr); + return {store2->addPermRoot(path.path, rootName)}; + } + } + return {store->printStorePath(path.path)}; + } +} + + +/* Realise the given paths. */ +static void opRealise(Strings opFlags, Strings opArgs) +{ + bool dryRun = false; + BuildMode buildMode = bmNormal; + bool ignoreUnknown = false; + + for (auto & i : opFlags) + if (i == "--dry-run") dryRun = true; + else if (i == "--repair") buildMode = bmRepair; + else if (i == "--check") buildMode = bmCheck; + else if (i == "--ignore-unknown") ignoreUnknown = true; + else throw UsageError("unknown flag '%1%'", i); + + std::vector<StorePathWithOutputs> paths; + for (auto & i : opArgs) + paths.push_back(followLinksToStorePathWithOutputs(*store, i)); + + uint64_t downloadSize, narSize; + StorePathSet willBuild, willSubstitute, unknown; + store->queryMissing( + toDerivedPaths(paths), + willBuild, willSubstitute, unknown, downloadSize, narSize); + + /* Filter out unknown paths from `paths`. */ + if (ignoreUnknown) { + std::vector<StorePathWithOutputs> paths2; + for (auto & i : paths) + if (!unknown.count(i.path)) paths2.push_back(i); + paths = std::move(paths2); + unknown = StorePathSet(); + } + + if (settings.printMissing) + printMissing(ref<Store>(store), willBuild, willSubstitute, unknown, downloadSize, narSize); + + if (dryRun) return; + + /* Build all paths at the same time to exploit parallelism. */ + store->buildPaths(toDerivedPaths(paths), buildMode); + + if (!ignoreUnknown) + for (auto & i : paths) { + auto paths2 = realisePath(i, false); + if (!noOutput) + for (auto & j : paths2) + cout << fmt("%1%\n", j); + } +} + + +/* Add files to the Nix store and print the resulting paths. */ +static void opAdd(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (auto & i : opArgs) + cout << fmt("%s\n", store->printStorePath(store->addToStore(std::string(baseNameOf(i)), i))); +} + + +/* Preload the output of a fixed-output derivation into the Nix + store. */ +static void opAddFixed(Strings opFlags, Strings opArgs) +{ + auto method = FileIngestionMethod::Flat; + + for (auto & i : opFlags) + if (i == "--recursive") method = FileIngestionMethod::Recursive; + else throw UsageError("unknown flag '%1%'", i); + + if (opArgs.empty()) + throw UsageError("first argument must be hash algorithm"); + + HashType hashAlgo = parseHashType(opArgs.front()); + opArgs.pop_front(); + + for (auto & i : opArgs) + std::cout << fmt("%s\n", store->printStorePath(store->addToStoreSlow(baseNameOf(i), i, method, hashAlgo).path)); +} + + +/* Hack to support caching in `nix-prefetch-url'. */ +static void opPrintFixedPath(Strings opFlags, Strings opArgs) +{ + auto method = FileIngestionMethod::Flat; + + for (auto i : opFlags) + if (i == "--recursive") method = FileIngestionMethod::Recursive; + else throw UsageError("unknown flag '%1%'", i); + + if (opArgs.size() != 3) + throw UsageError("'--print-fixed-path' requires three arguments"); + + Strings::iterator i = opArgs.begin(); + HashType hashAlgo = parseHashType(*i++); + std::string hash = *i++; + std::string name = *i++; + + cout << fmt("%s\n", store->printStorePath(store->makeFixedOutputPath(name, FixedOutputInfo { + .method = method, + .hash = Hash::parseAny(hash, hashAlgo), + .references = {}, + }))); +} + + +static StorePathSet maybeUseOutputs(const StorePath & storePath, bool useOutput, bool forceRealise) +{ + if (forceRealise) realisePath({storePath}); + if (useOutput && storePath.isDerivation()) { + auto drv = store->derivationFromPath(storePath); + StorePathSet outputs; + if (forceRealise) + return store->queryDerivationOutputs(storePath); + for (auto & i : drv.outputsAndOptPaths(*store)) { + if (!i.second.second) + throw UsageError("Cannot use output path of floating content-addressed derivation until we know what it is (e.g. by building it)"); + outputs.insert(*i.second.second); + } + return outputs; + } + else return {storePath}; +} + + +/* Some code to print a tree representation of a derivation dependency + graph. Topological sorting is used to keep the tree relatively + flat. */ +static void printTree(const StorePath & path, + const std::string & firstPad, const std::string & tailPad, StorePathSet & done) +{ + if (!done.insert(path).second) { + cout << fmt("%s%s [...]\n", firstPad, store->printStorePath(path)); + return; + } + + cout << fmt("%s%s\n", firstPad, store->printStorePath(path)); + + auto info = store->queryPathInfo(path); + + /* Topologically sort under the relation A < B iff A \in + closure(B). That is, if derivation A is an (possibly indirect) + input of B, then A is printed first. This has the effect of + flattening the tree, preventing deeply nested structures. */ + auto sorted = store->topoSortPaths(info->references); + reverse(sorted.begin(), sorted.end()); + + for (const auto &[n, i] : enumerate(sorted)) { + bool last = n + 1 == sorted.size(); + printTree(i, + tailPad + (last ? treeLast : treeConn), + tailPad + (last ? treeNull : treeLine), + done); + } +} + + +/* Perform various sorts of queries. */ +static void opQuery(Strings opFlags, Strings opArgs) +{ + enum QueryType + { qOutputs, qRequisites, qReferences, qReferrers + , qReferrersClosure, qDeriver, qValidDerivers, qBinding, qHash, qSize + , qTree, qGraph, qGraphML, qResolve, qRoots }; + std::optional<QueryType> query; + bool useOutput = false; + bool includeOutputs = false; + bool forceRealise = false; + std::string bindingName; + + for (auto & i : opFlags) { + std::optional<QueryType> prev = query; + if (i == "--outputs") query = qOutputs; + else if (i == "--requisites" || i == "-R") query = qRequisites; + else if (i == "--references") query = qReferences; + else if (i == "--referrers" || i == "--referers") query = qReferrers; + else if (i == "--referrers-closure" || i == "--referers-closure") query = qReferrersClosure; + else if (i == "--deriver" || i == "-d") query = qDeriver; + else if (i == "--valid-derivers") query = qValidDerivers; + else if (i == "--binding" || i == "-b") { + if (opArgs.size() == 0) + throw UsageError("expected binding name"); + bindingName = opArgs.front(); + opArgs.pop_front(); + query = qBinding; + } + else if (i == "--hash") query = qHash; + else if (i == "--size") query = qSize; + else if (i == "--tree") query = qTree; + else if (i == "--graph") query = qGraph; + else if (i == "--graphml") query = qGraphML; + else if (i == "--resolve") query = qResolve; + else if (i == "--roots") query = qRoots; + else if (i == "--use-output" || i == "-u") useOutput = true; + else if (i == "--force-realise" || i == "--force-realize" || i == "-f") forceRealise = true; + else if (i == "--include-outputs") includeOutputs = true; + else throw UsageError("unknown flag '%1%'", i); + if (prev && prev != query) + throw UsageError("query type '%1%' conflicts with earlier flag", i); + } + + if (!query) query = qOutputs; + + RunPager pager; + + switch (*query) { + + case qOutputs: { + for (auto & i : opArgs) { + auto outputs = maybeUseOutputs(store->followLinksToStorePath(i), true, forceRealise); + for (auto & outputPath : outputs) + cout << fmt("%1%\n", store->printStorePath(outputPath)); + } + break; + } + + case qRequisites: + case qReferences: + case qReferrers: + case qReferrersClosure: { + StorePathSet paths; + for (auto & i : opArgs) { + auto ps = maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise); + for (auto & j : ps) { + if (query == qRequisites) store->computeFSClosure(j, paths, false, includeOutputs); + else if (query == qReferences) { + for (auto & p : store->queryPathInfo(j)->references) + paths.insert(p); + } + else if (query == qReferrers) { + StorePathSet tmp; + store->queryReferrers(j, tmp); + for (auto & i : tmp) + paths.insert(i); + } + else if (query == qReferrersClosure) store->computeFSClosure(j, paths, true); + } + } + auto sorted = store->topoSortPaths(paths); + for (StorePaths::reverse_iterator i = sorted.rbegin(); + i != sorted.rend(); ++i) + cout << fmt("%s\n", store->printStorePath(*i)); + break; + } + + case qDeriver: + for (auto & i : opArgs) { + auto info = store->queryPathInfo(store->followLinksToStorePath(i)); + cout << fmt("%s\n", info->deriver ? store->printStorePath(*info->deriver) : "unknown-deriver"); + } + break; + + case qValidDerivers: { + StorePathSet result; + for (auto & i : opArgs) { + auto derivers = store->queryValidDerivers(store->followLinksToStorePath(i)); + for (const auto &i: derivers) { + result.insert(i); + } + } + auto sorted = store->topoSortPaths(result); + for (StorePaths::reverse_iterator i = sorted.rbegin(); + i != sorted.rend(); ++i) + cout << fmt("%s\n", store->printStorePath(*i)); + break; + } + + case qBinding: + for (auto & i : opArgs) { + auto path = useDeriver(store->followLinksToStorePath(i)); + Derivation drv = store->derivationFromPath(path); + StringPairs::iterator j = drv.env.find(bindingName); + if (j == drv.env.end()) + throw Error("derivation '%s' has no environment binding named '%s'", + store->printStorePath(path), bindingName); + cout << fmt("%s\n", j->second); + } + break; + + case qHash: + case qSize: + for (auto & i : opArgs) { + for (auto & j : maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)) { + auto info = store->queryPathInfo(j); + if (query == qHash) { + assert(info->narHash.type == HashType::SHA256); + cout << fmt("%s\n", info->narHash.to_string(Base::Base32, true)); + } else if (query == qSize) + cout << fmt("%d\n", info->narSize); + } + } + break; + + case qTree: { + StorePathSet done; + for (auto & i : opArgs) + printTree(store->followLinksToStorePath(i), "", "", done); + break; + } + + case qGraph: { + StorePathSet roots; + for (auto & i : opArgs) + for (auto & j : maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)) + roots.insert(j); + printDotGraph(ref<Store>(store), std::move(roots)); + break; + } + + case qGraphML: { + StorePathSet roots; + for (auto & i : opArgs) + for (auto & j : maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)) + roots.insert(j); + printGraphML(ref<Store>(store), std::move(roots)); + break; + } + + case qResolve: { + for (auto & i : opArgs) + cout << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i))); + break; + } + + case qRoots: { + StorePathSet args; + for (auto & i : opArgs) + for (auto & p : maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)) + args.insert(p); + + StorePathSet referrers; + store->computeFSClosure( + args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations); + + auto & gcStore = require<GcStore>(*store); + Roots roots = gcStore.findRoots(false); + for (auto & [target, links] : roots) + if (referrers.find(target) != referrers.end()) + for (auto & link : links) + cout << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target)); + break; + } + + default: + abort(); + } +} + + +static void opPrintEnv(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("'--print-env' requires one derivation store path"); + + Path drvPath = opArgs.front(); + Derivation drv = store->derivationFromPath(store->parseStorePath(drvPath)); + + /* Print each environment variable in the derivation in a format + * that can be sourced by the shell. */ + for (auto & i : drv.env) + logger->cout("export %1%; %1%=%2%\n", i.first, shellEscape(i.second)); + + /* Also output the arguments. This doesn't preserve whitespace in + arguments. */ + cout << "export _args; _args='"; + bool first = true; + for (auto & i : drv.args) { + if (!first) cout << ' '; + first = false; + cout << shellEscape(i); + } + cout << "'\n"; +} + + +static void opReadLog(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + auto & logStore = require<LogStore>(*store); + + RunPager pager; + + for (auto & i : opArgs) { + auto path = logStore.followLinksToStorePath(i); + auto log = logStore.getBuildLog(path); + if (!log) + throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path)); + std::cout << *log; + } +} + + +static void opDumpDB(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) { + for (auto & i : opArgs) + cout << store->makeValidityRegistration({store->followLinksToStorePath(i)}, true, true); + } else { + for (auto & i : store->queryAllValidPaths()) + cout << store->makeValidityRegistration({i}, true, true); + } +} + + +static void registerValidity(bool reregister, bool hashGiven, bool canonicalise) +{ + ValidPathInfos infos; + + while (1) { + // We use a dummy value because we'll set it below. FIXME be correct by + // construction and avoid dummy value. + auto hashResultOpt = !hashGiven ? std::optional<HashResult> { {Hash::dummy, -1} } : std::nullopt; + auto info = decodeValidPathInfo(*store, cin, hashResultOpt); + if (!info) break; + if (!store->isValidPath(info->path) || reregister) { + /* !!! races */ + if (canonicalise) + canonicalisePathMetaData(store->printStorePath(info->path), {}); + if (!hashGiven) { + HashResult hash = hashPath(HashType::SHA256, store->printStorePath(info->path)); + info->narHash = hash.first; + info->narSize = hash.second; + } + infos.insert_or_assign(info->path, *info); + } + } + + ensureLocalStore()->registerValidPaths(infos); +} + + +static void opLoadDB(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) + throw UsageError("no arguments expected"); + registerValidity(true, true, false); +} + + +static void opRegisterValidity(Strings opFlags, Strings opArgs) +{ + bool reregister = false; // !!! maybe this should be the default + bool hashGiven = false; + + for (auto & i : opFlags) + if (i == "--reregister") reregister = true; + else if (i == "--hash-given") hashGiven = true; + else throw UsageError("unknown flag '%1%'", i); + + if (!opArgs.empty()) throw UsageError("no arguments expected"); + + registerValidity(reregister, hashGiven, true); +} + + +static void opCheckValidity(Strings opFlags, Strings opArgs) +{ + bool printInvalid = false; + + for (auto & i : opFlags) + if (i == "--print-invalid") printInvalid = true; + else throw UsageError("unknown flag '%1%'", i); + + for (auto & i : opArgs) { + auto path = store->followLinksToStorePath(i); + if (!store->isValidPath(path)) { + if (printInvalid) + cout << fmt("%s\n", store->printStorePath(path)); + else + throw Error("path '%s' is not valid", store->printStorePath(path)); + } + } +} + + +static void opGC(Strings opFlags, Strings opArgs) +{ + bool printRoots = false; + GCOptions options; + options.action = GCOptions::gcDeleteDead; + + GCResults results; + + /* Do what? */ + for (auto i = opFlags.begin(); i != opFlags.end(); ++i) + if (*i == "--print-roots") printRoots = true; + else if (*i == "--print-live") options.action = GCOptions::gcReturnLive; + else if (*i == "--print-dead") options.action = GCOptions::gcReturnDead; + else if (*i == "--max-freed") + options.maxFreed = std::max(getIntArg<int64_t>(*i, i, opFlags.end(), true), (int64_t) 0); + else throw UsageError("bad sub-operation '%1%' in GC", *i); + + if (!opArgs.empty()) throw UsageError("no arguments expected"); + + auto & gcStore = require<GcStore>(*store); + + if (printRoots) { + Roots roots = gcStore.findRoots(false); + std::set<std::pair<Path, StorePath>> roots2; + // Transpose and sort the roots. + for (auto & [target, links] : roots) + for (auto & link : links) + roots2.emplace(link, target); + for (auto & [link, target] : roots2) + std::cout << link << " -> " << gcStore.printStorePath(target) << "\n"; + } + + else { + PrintFreed freed(options.action == GCOptions::gcDeleteDead, results); + gcStore.collectGarbage(options, results); + + if (options.action != GCOptions::gcDeleteDead) + for (auto & i : results.paths) + cout << i << std::endl; + } +} + + +/* Remove paths from the Nix store if possible (i.e., if they do not + have any remaining referrers and are not reachable from any GC + roots). */ +static void opDelete(Strings opFlags, Strings opArgs) +{ + GCOptions options; + options.action = GCOptions::gcDeleteSpecific; + + for (auto & i : opFlags) + if (i == "--ignore-liveness") options.ignoreLiveness = true; + else throw UsageError("unknown flag '%1%'", i); + + for (auto & i : opArgs) + options.pathsToDelete.insert(store->followLinksToStorePath(i)); + + auto & gcStore = require<GcStore>(*store); + + GCResults results; + PrintFreed freed(true, results); + gcStore.collectGarbage(options, results); +} + + +/* Dump a path as a Nix archive. The archive is written to stdout */ +static void opDump(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("only one argument allowed"); + + FdSink sink(STDOUT_FILENO); + std::string path = *opArgs.begin(); + sink << dumpPath(path); + sink.flush(); +} + + +/* Restore a value from a Nix archive. The archive is read from stdin. */ +static void opRestore(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("only one argument allowed"); + + FdSource source(STDIN_FILENO); + restorePath(*opArgs.begin(), source); +} + + +static void opExport(Strings opFlags, Strings opArgs) +{ + for (auto & i : opFlags) + throw UsageError("unknown flag '%1%'", i); + + StorePathSet paths; + + for (auto & i : opArgs) + paths.insert(store->followLinksToStorePath(i)); + + FdSink sink(STDOUT_FILENO); + store->exportPaths(paths, sink); + sink.flush(); +} + + +static void opImport(Strings opFlags, Strings opArgs) +{ + for (auto & i : opFlags) + throw UsageError("unknown flag '%1%'", i); + + if (!opArgs.empty()) throw UsageError("no arguments expected"); + + FdSource source(STDIN_FILENO); + auto paths = store->importPaths(source, NoCheckSigs); + + for (auto & i : paths) + cout << fmt("%s\n", store->printStorePath(i)) << std::flush; +} + + +/* Initialise the Nix databases. */ +static void opInit(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) + throw UsageError("no arguments expected"); + /* Doesn't do anything right now; database tables are initialised + automatically. */ +} + + +/* Verify the consistency of the Nix environment. */ +static void opVerify(Strings opFlags, Strings opArgs) +{ + if (!opArgs.empty()) + throw UsageError("no arguments expected"); + + bool checkContents = false; + RepairFlag repair = NoRepair; + + for (auto & i : opFlags) + if (i == "--check-contents") checkContents = true; + else if (i == "--repair") repair = Repair; + else throw UsageError("unknown flag '%1%'", i); + + if (store->verifyStore(checkContents, repair)) { + warn("not all store errors were fixed"); + throw Exit(1); + } +} + + +/* Verify whether the contents of the given store path have not changed. */ +static void opVerifyPath(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) + throw UsageError("no flags expected"); + + int status = 0; + + for (auto & i : opArgs) { + auto path = store->followLinksToStorePath(i); + printMsg(lvlTalkative, "checking path '%s'...", store->printStorePath(path)); + auto info = store->queryPathInfo(path); + HashSink sink(info->narHash.type); + sink << store->narFromPath(path); + auto current = sink.finish(); + if (current.first != info->narHash) { + printError("path '%s' was modified! expected hash '%s', got '%s'", + store->printStorePath(path), + info->narHash.to_string(Base::Base32, true), + current.first.to_string(Base::Base32, true)); + status = 1; + } + } + + throw Exit(status); +} + + +/* Repair the contents of the given path by redownloading it using a + substituter (if available). */ +static void opRepairPath(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) + throw UsageError("no flags expected"); + + for (auto & i : opArgs) + store->repairPath(store->followLinksToStorePath(i)); +} + +/* Optimise the disk space usage of the Nix store by hard-linking + files with the same contents. */ +static void opOptimise(Strings opFlags, Strings opArgs) +{ + if (!opArgs.empty() || !opFlags.empty()) + throw UsageError("no arguments expected"); + + store->optimiseStore(); +} + +/* Serve the nix store in a way usable by a restricted ssh user. */ +static void opServe(Strings opFlags, Strings opArgs) +{ + bool writeAllowed = false; + for (auto & i : opFlags) + if (i == "--write") writeAllowed = true; + else throw UsageError("unknown flag '%1%'", i); + + if (!opArgs.empty()) throw UsageError("no arguments expected"); + + FdSource in(STDIN_FILENO); + FdSink out(STDOUT_FILENO); + + /* Exchange the greeting. */ + unsigned int magic = readInt(in); + if (magic != SERVE_MAGIC_1) throw Error("protocol mismatch"); + out << SERVE_MAGIC_2 << SERVE_PROTOCOL_VERSION; + out.flush(); + ServeProto::Version clientVersion = readInt(in); + + ServeProto::ReadConn rconn { + .from = in, + .version = clientVersion, + }; + ServeProto::WriteConn wconn { + .version = clientVersion, + }; + + auto getBuildSettings = [&]() { + // FIXME: changing options here doesn't work if we're + // building through the daemon. + verbosity = lvlError; + settings.keepLog = false; + settings.useSubstitutes = false; + settings.maxSilentTime = readInt(in); + settings.buildTimeout = readInt(in); + if (GET_PROTOCOL_MINOR(clientVersion) >= 2) + settings.maxLogSize = readNum<unsigned long>(in); + if (GET_PROTOCOL_MINOR(clientVersion) >= 3) { + auto nrRepeats = readInt(in); + if (nrRepeats != 0) { + throw Error("client requested repeating builds, but this is not currently implemented"); + } + // Ignore 'enforceDeterminism'. It used to be true by + // default, but also only never had any effect when + // `nrRepeats == 0`. We have already asserted that + // `nrRepeats` in fact is 0, so we can safely ignore this + // without doing something other than what the client + // asked for. + readInt(in); + + settings.runDiffHook = true; + } + if (GET_PROTOCOL_MINOR(clientVersion) >= 7) { + settings.keepFailed = (bool) readInt(in); + } + }; + + while (true) { + ServeProto::Command cmd; + try { + cmd = (ServeProto::Command) readInt(in); + } catch (EndOfFile & e) { + break; + } + + switch (cmd) { + + case ServeProto::Command::QueryValidPaths: { + bool lock = readInt(in); + bool substitute = readInt(in); + auto paths = ServeProto::Serialise<StorePathSet>::read(*store, rconn); + if (lock && writeAllowed) + for (auto & path : paths) + store->addTempRoot(path); + + if (substitute && writeAllowed) { + store->substitutePaths(paths); + } + + auto valid = store->queryValidPaths(paths); + out << ServeProto::write(*store, wconn, valid); + break; + } + + case ServeProto::Command::QueryPathInfos: { + auto paths = ServeProto::Serialise<StorePathSet>::read(*store, rconn); + // !!! Maybe we want a queryPathInfos? + for (auto & i : paths) { + try { + auto info = store->queryPathInfo(i); + out << store->printStorePath(info->path); + out << ServeProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info)); + } catch (InvalidPath &) { + } + } + out << ""; + break; + } + + case ServeProto::Command::DumpStorePath: + out << store->narFromPath(store->parseStorePath(readString(in))); + break; + + case ServeProto::Command::ImportPaths: { + if (!writeAllowed) throw Error("importing paths is not allowed"); + store->importPaths(in, NoCheckSigs); // FIXME: should we skip sig checking? + out << 1; // indicate success + break; + } + + case ServeProto::Command::ExportPaths: { + readInt(in); // obsolete + store->exportPaths(ServeProto::Serialise<StorePathSet>::read(*store, rconn), out); + break; + } + + case ServeProto::Command::BuildPaths: { + + if (!writeAllowed) throw Error("building paths is not allowed"); + + std::vector<StorePathWithOutputs> paths; + for (auto & s : readStrings<Strings>(in)) + paths.push_back(parsePathWithOutputs(*store, s)); + + getBuildSettings(); + + try { + MonitorFdHup monitor(in.fd); + store->buildPaths(toDerivedPaths(paths)); + out << 0; + } catch (Error & e) { + assert(e.info().status); + out << e.info().status << e.msg(); + } + break; + } + + case ServeProto::Command::BuildDerivation: { /* Used by hydra-queue-runner. */ + + if (!writeAllowed) throw Error("building paths is not allowed"); + + auto drvPath = store->parseStorePath(readString(in)); + BasicDerivation drv; + readDerivation(in, *store, drv, Derivation::nameFromPath(drvPath)); + + getBuildSettings(); + + MonitorFdHup monitor(in.fd); + auto status = store->buildDerivation(drvPath, drv); + + out << ServeProto::write(*store, wconn, status); + break; + } + + case ServeProto::Command::QueryClosure: { + bool includeOutputs = readInt(in); + StorePathSet closure; + store->computeFSClosure(ServeProto::Serialise<StorePathSet>::read(*store, rconn), + closure, false, includeOutputs); + out << ServeProto::write(*store, wconn, closure); + break; + } + + case ServeProto::Command::AddToStoreNar: { + if (!writeAllowed) throw Error("importing paths is not allowed"); + + auto path = readString(in); + auto deriver = readString(in); + ValidPathInfo info { + store->parseStorePath(path), + Hash::parseAny(readString(in), HashType::SHA256), + }; + if (deriver != "") + info.deriver = store->parseStorePath(deriver); + info.references = ServeProto::Serialise<StorePathSet>::read(*store, rconn); + in >> info.registrationTime >> info.narSize >> info.ultimate; + info.sigs = readStrings<StringSet>(in); + info.ca = ContentAddress::parseOpt(readString(in)); + + if (info.narSize == 0) + throw Error("narInfo is too old and missing the narSize field"); + + SizedSource sizedSource(in, info.narSize); + + store->addToStore(info, sizedSource, NoRepair, NoCheckSigs); + + // consume all the data that has been sent before continuing. + sizedSource.drainAll(); + + out << 1; // indicate success + + break; + } + + default: + throw Error("unknown serve command %1%", cmd); + } + + out.flush(); + } +} + + +static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs) +{ + for (auto & i : opFlags) + throw UsageError("unknown flag '%1%'", i); + + if (opArgs.size() != 3) throw UsageError("three arguments expected"); + auto i = opArgs.begin(); + std::string keyName = *i++; + std::string secretKeyFile = *i++; + std::string publicKeyFile = *i++; + + auto secretKey = SecretKey::generate(keyName); + + writeFile(publicKeyFile, secretKey.toPublicKey().to_string()); + umask(0077); + writeFile(secretKeyFile, secretKey.to_string()); +} + + +static void opVersion(Strings opFlags, Strings opArgs) +{ + printVersion("nix-store"); +} + + +/* Scan the arguments; find the operation, set global flags, put all + other flags in a list, and put all other arguments in another + list. */ +static int main_nix_store(int argc, char * * argv) +{ + { + Strings opFlags, opArgs; + Operation op = 0; + bool readFromStdIn = false; + std::string opName; + bool showHelp = false; + + parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { + Operation oldOp = op; + + if (*arg == "--help") + showHelp = true; + else if (*arg == "--version") + op = opVersion; + else if (*arg == "--realise" || *arg == "--realize" || *arg == "-r") { + op = opRealise; + opName = "-realise"; + } + else if (*arg == "--add" || *arg == "-A"){ + op = opAdd; + opName = "-add"; + } + else if (*arg == "--add-fixed") { + op = opAddFixed; + opName = arg->substr(1); + } + else if (*arg == "--print-fixed-path") + op = opPrintFixedPath; + else if (*arg == "--delete") { + op = opDelete; + opName = arg->substr(1); + } + else if (*arg == "--query" || *arg == "-q") { + op = opQuery; + opName = "-query"; + } + else if (*arg == "--print-env") { + op = opPrintEnv; + opName = arg->substr(1); + } + else if (*arg == "--read-log" || *arg == "-l") { + op = opReadLog; + opName = "-read-log"; + } + else if (*arg == "--dump-db") { + op = opDumpDB; + opName = arg->substr(1); + } + else if (*arg == "--load-db") { + op = opLoadDB; + opName = arg->substr(1); + } + else if (*arg == "--register-validity") + op = opRegisterValidity; + else if (*arg == "--check-validity") + op = opCheckValidity; + else if (*arg == "--gc") { + op = opGC; + opName = arg->substr(1); + } + else if (*arg == "--dump") { + op = opDump; + opName = arg->substr(1); + } + else if (*arg == "--restore") { + op = opRestore; + opName = arg->substr(1); + } + else if (*arg == "--export") { + op = opExport; + opName = arg->substr(1); + } + else if (*arg == "--import") { + op = opImport; + opName = arg->substr(1); + } + else if (*arg == "--init") + op = opInit; + else if (*arg == "--verify") { + op = opVerify; + opName = arg->substr(1); + } + else if (*arg == "--verify-path") { + op = opVerifyPath; + opName = arg->substr(1); + } + else if (*arg == "--repair-path") { + op = opRepairPath; + opName = arg->substr(1); + } + else if (*arg == "--optimise" || *arg == "--optimize") { + op = opOptimise; + opName = "-optimise"; + } + else if (*arg == "--serve") { + op = opServe; + opName = arg->substr(1); + } + else if (*arg == "--generate-binary-cache-key") { + op = opGenerateBinaryCacheKey; + opName = arg->substr(1); + } + else if (*arg == "--add-root") + gcRoot = absPath(getArg(*arg, arg, end)); + else if (*arg == "--stdin" && !isatty(STDIN_FILENO)) + readFromStdIn = true; + else if (*arg == "--indirect") + ; + else if (*arg == "--no-output") + noOutput = true; + else if (*arg != "" && arg->at(0) == '-') { + opFlags.push_back(*arg); + if (*arg == "--max-freed" || *arg == "--max-links" || *arg == "--max-atime") /* !!! hack */ + opFlags.push_back(getArg(*arg, arg, end)); + } + else + opArgs.push_back(*arg); + + if (readFromStdIn && op != opImport && op != opRestore && op != opServe) { + std::string word; + while (std::cin >> word) { + opArgs.emplace_back(std::move(word)); + }; + } + + if (oldOp && oldOp != op) + throw UsageError("only one operation may be specified"); + + return true; + }); + + if (showHelp) showManPage("nix-store" + opName); + if (!op) throw UsageError("no operation specified"); + + if (op != opDump && op != opRestore) /* !!! hack */ + store = openStore(); + + op(std::move(opFlags), std::move(opArgs)); + + return 0; + } +} + +void registerNixStore() { + LegacyCommands::add("nix-store", main_nix_store); +} + +} diff --git a/src/legacy/nix-store.hh b/src/legacy/nix-store.hh new file mode 100644 index 000000000..b010e7b19 --- /dev/null +++ b/src/legacy/nix-store.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixStore(); + +} diff --git a/src/legacy/unpack-channel.nix b/src/legacy/unpack-channel.nix new file mode 100644 index 000000000..84e324a4d --- /dev/null +++ b/src/legacy/unpack-channel.nix @@ -0,0 +1,16 @@ +{ + name, + channelName, + src, +}: + +derivation { + builder = "builtin:unpack-channel"; + + system = "builtin"; + + inherit name channelName src; + + # No point in doing this remotely. + preferLocalBuild = true; +} diff --git a/src/legacy/user-env.cc b/src/legacy/user-env.cc new file mode 100644 index 000000000..f5dbd06ca --- /dev/null +++ b/src/legacy/user-env.cc @@ -0,0 +1,155 @@ +#include "user-env.hh" +#include "derivations.hh" +#include "store-api.hh" +#include "path-with-outputs.hh" +#include "local-fs-store.hh" +#include "globals.hh" +#include "shared.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "profiles.hh" +#include "print-ambiguous.hh" + +#include <limits> +#include <sstream> + +namespace nix { + + +bool createUserEnv(EvalState & state, DrvInfos & elems, + const Path & profile, bool keepDerivations, + const std::string & lockToken) +{ + /* Build the components in the user environment, if they don't + exist already. */ + std::vector<StorePathWithOutputs> drvsToBuild; + for (auto & i : elems) + if (auto drvPath = i.queryDrvPath()) + drvsToBuild.push_back({*drvPath}); + + debug("building user environment dependencies"); + state.store->buildPaths( + toDerivedPaths(drvsToBuild), + state.repair ? bmRepair : bmNormal); + + /* Construct the whole top level derivation. */ + StorePathSet references; + Value manifest; + state.mkList(manifest, elems.size()); + size_t n = 0; + for (auto & i : elems) { + /* Create a pseudo-derivation containing the name, system, + output paths, and optionally the derivation path, as well + as the meta attributes. */ + std::optional<StorePath> drvPath = keepDerivations ? i.queryDrvPath() : std::nullopt; + DrvInfo::Outputs outputs = i.queryOutputs(true, true); + StringSet metaNames = i.queryMetaNames(); + + auto attrs = state.buildBindings(7 + outputs.size()); + + attrs.alloc(state.sType).mkString("derivation"); + attrs.alloc(state.sName).mkString(i.queryName()); + auto system = i.querySystem(); + if (!system.empty()) + attrs.alloc(state.sSystem).mkString(system); + attrs.alloc(state.sOutPath).mkString(state.store->printStorePath(i.queryOutPath())); + if (drvPath) + attrs.alloc(state.sDrvPath).mkString(state.store->printStorePath(*drvPath)); + + // Copy each output meant for installation. + auto & vOutputs = attrs.alloc(state.sOutputs); + state.mkList(vOutputs, outputs.size()); + for (const auto & [m, j] : enumerate(outputs)) { + (vOutputs.listElems()[m] = state.allocValue())->mkString(j.first); + auto outputAttrs = state.buildBindings(2); + outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(*j.second)); + attrs.alloc(j.first).mkAttrs(outputAttrs); + + /* This is only necessary when installing store paths, e.g., + `nix-env -i /nix/store/abcd...-foo'. */ + state.store->addTempRoot(*j.second); + state.store->ensurePath(*j.second); + + references.insert(*j.second); + } + + // Copy the meta attributes. + auto meta = state.buildBindings(metaNames.size()); + for (auto & j : metaNames) { + Value * v = i.queryMeta(j); + if (!v) continue; + meta.insert(state.symbols.create(j), v); + } + + attrs.alloc(state.sMeta).mkAttrs(meta); + + (manifest.listElems()[n++] = state.allocValue())->mkAttrs(attrs); + + if (drvPath) references.insert(*drvPath); + } + + /* Also write a copy of the list of user environment elements to + the store; we need it for future modifications of the + environment. */ + std::ostringstream str; + printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits<int>::max()); + auto manifestFile = state.store->addTextToStore("env-manifest.nix", + str.str(), references); + + /* Get the environment builder expression. */ + Value envBuilder; + state.eval(state.parseExprFromString( + #include "buildenv.nix.gen.hh" + , state.rootPath(CanonPath::root)), envBuilder); + + /* Construct a Nix expression that calls the user environment + builder with the manifest as argument. */ + auto attrs = state.buildBindings(3); + state.mkStorePathString(manifestFile, attrs.alloc("manifest")); + attrs.insert(state.symbols.create("derivations"), &manifest); + Value args; + args.mkAttrs(attrs); + + Value topLevel; + topLevel.mkApp(&envBuilder, &args); + + /* Evaluate it. */ + debug("evaluating user environment builder"); + state.forceValue(topLevel, topLevel.determinePos(noPos)); + NixStringContext context; + Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); + auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, ""); + Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); + auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, ""); + + /* Realise the resulting store expression. */ + debug("building user environment"); + std::vector<StorePathWithOutputs> topLevelDrvs; + topLevelDrvs.push_back({topLevelDrv}); + state.store->buildPaths( + toDerivedPaths(topLevelDrvs), + state.repair ? bmRepair : bmNormal); + + /* Switch the current user environment to the output path. */ + auto store2 = state.store.dynamic_pointer_cast<LocalFSStore>(); + + if (store2) { + PathLocks lock; + lockProfile(lock, profile); + + Path lockTokenCur = optimisticLockProfile(profile); + if (lockToken != lockTokenCur) { + printInfo("profile '%1%' changed while we were busy; restarting", profile); + return false; + } + + debug("switching to new user environment"); + Path generation = createGeneration(*store2, profile, topLevelOut); + switchLink(profile, generation); + } + + return true; +} + + +} diff --git a/src/legacy/user-env.hh b/src/legacy/user-env.hh new file mode 100644 index 000000000..af45d2d85 --- /dev/null +++ b/src/legacy/user-env.hh @@ -0,0 +1,14 @@ +#pragma once +///@file + +#include "get-drvs.hh" + +namespace nix { + +DrvInfos queryInstalled(EvalState & state, const Path & userEnv); + +bool createUserEnv(EvalState & state, DrvInfos & elems, + const Path & profile, bool keepDerivations, + const std::string & lockToken); + +} |