aboutsummaryrefslogtreecommitdiff
path: root/src/legacy
diff options
context:
space:
mode:
authorjade <lix@jade.fyi>2024-10-09 20:37:58 +0000
committerGerrit Code Review <gerrit@localhost>2024-10-09 20:37:58 +0000
commit9865ebaaa618d82a7b7fdccc636cbaa7dfa42427 (patch)
treeb79db30394a892e7e6b7e0a2f966081b4a366590 /src/legacy
parent7f7a38f2788ba7fa46cf38127f525700cf63e153 (diff)
parentb63d4a0c622fa556695e7666b9b3bde920904920 (diff)
Merge "Remove static initializers for `RegisterLegacyCommand`" into main
Diffstat (limited to 'src/legacy')
-rw-r--r--src/legacy/build-remote.cc396
-rw-r--r--src/legacy/build-remote.hh8
-rw-r--r--src/legacy/buildenv.nix27
-rw-r--r--src/legacy/dotgraph.cc70
-rw-r--r--src/legacy/dotgraph.hh10
-rw-r--r--src/legacy/graphml.cc87
-rw-r--r--src/legacy/graphml.hh10
-rw-r--r--src/legacy/meson.build35
-rw-r--r--src/legacy/nix-build.cc623
-rw-r--r--src/legacy/nix-build.hh8
-rw-r--r--src/legacy/nix-channel.cc272
-rw-r--r--src/legacy/nix-channel.hh8
-rw-r--r--src/legacy/nix-collect-garbage.cc118
-rw-r--r--src/legacy/nix-collect-garbage.hh8
-rw-r--r--src/legacy/nix-copy-closure.cc68
-rw-r--r--src/legacy/nix-copy-closure.hh8
-rw-r--r--src/legacy/nix-env.cc1553
-rw-r--r--src/legacy/nix-env.hh8
-rw-r--r--src/legacy/nix-instantiate.cc203
-rw-r--r--src/legacy/nix-instantiate.hh8
-rw-r--r--src/legacy/nix-store.cc1183
-rw-r--r--src/legacy/nix-store.hh8
-rw-r--r--src/legacy/unpack-channel.nix16
-rw-r--r--src/legacy/user-env.cc155
-rw-r--r--src/legacy/user-env.hh14
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);
+
+}