From b63d4a0c622fa556695e7666b9b3bde920904920 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Tue, 10 Sep 2024 13:13:19 -0700 Subject: Remove static initializers for `RegisterLegacyCommand` This moves the "legacy"/"nix2" commands under a new `src/legacy/` directory, instead of being scattered around in a bunch of different directories. A new `liblegacy` build target is defined, and the `nix` binary is linked against it. Then, `RegisterLegacyCommand` is replaced with `LegacyCommand::add` calls in functions like `registerNixCollectGarbage()`. These registration functions are called explicitly in `src/nix/main.cc`. See: https://git.lix.systems/lix-project/lix/issues/359 Change-Id: Id450ffc3f793374907599cfcc121863b792aac1a --- src/build-remote/build-remote.cc | 391 ------ src/legacy/build-remote.cc | 396 ++++++ src/legacy/build-remote.hh | 8 + src/legacy/buildenv.nix | 27 + src/legacy/dotgraph.cc | 70 ++ src/legacy/dotgraph.hh | 10 + src/legacy/graphml.cc | 87 ++ src/legacy/graphml.hh | 10 + src/legacy/meson.build | 35 + src/legacy/nix-build.cc | 623 ++++++++++ src/legacy/nix-build.hh | 8 + src/legacy/nix-channel.cc | 272 +++++ src/legacy/nix-channel.hh | 8 + src/legacy/nix-collect-garbage.cc | 118 ++ src/legacy/nix-collect-garbage.hh | 8 + src/legacy/nix-copy-closure.cc | 68 ++ src/legacy/nix-copy-closure.hh | 8 + src/legacy/nix-env.cc | 1553 ++++++++++++++++++++++++ src/legacy/nix-env.hh | 8 + src/legacy/nix-instantiate.cc | 203 ++++ src/legacy/nix-instantiate.hh | 8 + src/legacy/nix-store.cc | 1183 ++++++++++++++++++ src/legacy/nix-store.hh | 8 + src/legacy/unpack-channel.nix | 16 + src/legacy/user-env.cc | 155 +++ src/legacy/user-env.hh | 14 + src/libcmd/legacy.cc | 2 +- src/libcmd/legacy.hh | 4 +- src/meson.build | 49 +- src/nix-build/nix-build.cc | 617 ---------- src/nix-channel/meson.build | 1 - src/nix-channel/nix-channel.cc | 267 ---- src/nix-channel/unpack-channel.nix | 16 - src/nix-collect-garbage/nix-collect-garbage.cc | 113 -- src/nix-copy-closure/nix-copy-closure.cc | 63 - src/nix-env/buildenv.nix | 27 - src/nix-env/nix-env.cc | 1547 ----------------------- src/nix-env/user-env.cc | 155 --- src/nix-env/user-env.hh | 14 - src/nix-instantiate/nix-instantiate.cc | 198 --- src/nix-store/dotgraph.cc | 70 -- src/nix-store/dotgraph.hh | 10 - src/nix-store/graphml.cc | 87 -- src/nix-store/graphml.hh | 10 - src/nix-store/nix-store.cc | 1181 ------------------ src/nix/daemon-command.hh | 8 + src/nix/daemon.cc | 10 +- src/nix/hash-command.hh | 8 + src/nix/hash.cc | 9 +- src/nix/main.cc | 30 +- src/nix/meson.build | 12 +- src/nix/prefetch-command.hh | 8 + src/nix/prefetch.cc | 9 +- src/nix/profile.cc | 2 +- tests/functional/restricted.sh | 4 +- tests/unit/meson.build | 2 +- 56 files changed, 4999 insertions(+), 4829 deletions(-) delete mode 100644 src/build-remote/build-remote.cc create mode 100644 src/legacy/build-remote.cc create mode 100644 src/legacy/build-remote.hh create mode 100644 src/legacy/buildenv.nix create mode 100644 src/legacy/dotgraph.cc create mode 100644 src/legacy/dotgraph.hh create mode 100644 src/legacy/graphml.cc create mode 100644 src/legacy/graphml.hh create mode 100644 src/legacy/meson.build create mode 100644 src/legacy/nix-build.cc create mode 100644 src/legacy/nix-build.hh create mode 100644 src/legacy/nix-channel.cc create mode 100644 src/legacy/nix-channel.hh create mode 100644 src/legacy/nix-collect-garbage.cc create mode 100644 src/legacy/nix-collect-garbage.hh create mode 100644 src/legacy/nix-copy-closure.cc create mode 100644 src/legacy/nix-copy-closure.hh create mode 100644 src/legacy/nix-env.cc create mode 100644 src/legacy/nix-env.hh create mode 100644 src/legacy/nix-instantiate.cc create mode 100644 src/legacy/nix-instantiate.hh create mode 100644 src/legacy/nix-store.cc create mode 100644 src/legacy/nix-store.hh create mode 100644 src/legacy/unpack-channel.nix create mode 100644 src/legacy/user-env.cc create mode 100644 src/legacy/user-env.hh delete mode 100644 src/nix-build/nix-build.cc delete mode 100644 src/nix-channel/meson.build delete mode 100644 src/nix-channel/nix-channel.cc delete mode 100644 src/nix-channel/unpack-channel.nix delete mode 100644 src/nix-collect-garbage/nix-collect-garbage.cc delete mode 100644 src/nix-copy-closure/nix-copy-closure.cc delete mode 100644 src/nix-env/buildenv.nix delete mode 100644 src/nix-env/nix-env.cc delete mode 100644 src/nix-env/user-env.cc delete mode 100644 src/nix-env/user-env.hh delete mode 100644 src/nix-instantiate/nix-instantiate.cc delete mode 100644 src/nix-store/dotgraph.cc delete mode 100644 src/nix-store/dotgraph.hh delete mode 100644 src/nix-store/graphml.cc delete mode 100644 src/nix-store/graphml.hh delete mode 100644 src/nix-store/nix-store.cc create mode 100644 src/nix/daemon-command.hh create mode 100644 src/nix/hash-command.hh create mode 100644 src/nix/prefetch-command.hh diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc deleted file mode 100644 index 3c7af067b..000000000 --- a/src/build-remote/build-remote.cc +++ /dev/null @@ -1,391 +0,0 @@ -#include -#include -#include -#include -#if __APPLE__ -#include -#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" - -using 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& 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()) - currentLoad = std::string { localStore->stateDir } + currentLoadName; - else - currentLoad = settings.nixStateDir + currentLoadName; - - std::shared_ptr 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 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>(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 = ""; - - std::string machinesFormatted; - - for (auto & m : machines) { - machinesFormatted += HintFmt( - "\n([%s], %s, [%s], [%s])", - concatStringsSep(", ", m.systemTypes), - m.maxJobs, - concatStringsSep(", ", m.supportedFeatures), - concatStringsSep(", ", 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(", ", 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(source); - auto wantedOutputs = readStrings(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 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 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()) - 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; - } -} - -static RegisterLegacyCommand r_build_remote("build-remote", main_build_remote); 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 +#include +#include +#include +#if __APPLE__ +#include +#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& 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()) + currentLoad = std::string { localStore->stateDir } + currentLoadName; + else + currentLoad = settings.nixStateDir + currentLoadName; + + std::shared_ptr 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 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>(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 = ""; + + std::string machinesFormatted; + + for (auto & m : machines) { + machinesFormatted += HintFmt( + "\n([%s], %s, [%s], [%s])", + concatStringsSep(", ", m.systemTypes), + m.maxJobs, + concatStringsSep(", ", m.supportedFeatures), + concatStringsSep(", ", 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(", ", 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(source); + auto wantedOutputs = readStrings(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 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 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()) + 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 + + +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 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, 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, 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 + + +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(" \n", + xmlQuote(src), xmlQuote(dst)); +} + + +static std::string makeNode(const ValidPathInfo & info) +{ + return fmt( + " \n" + " %2%\n" + " %3%\n" + " %4%\n" + " \n", + info.path.to_string(), + info.narSize, + symbolicName(std::string(info.path.name())), + (info.path.isDerivation() ? "derivation" : "output-path")); +} + + +void printGraphML(ref store, StorePathSet && roots) +{ + StorePathSet workList(std::move(roots)); + StorePathSet doneSet; + std::pair ret; + + cout << "\n" + << "\n" + << "" + << "" + << "" + << "\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 << "\n"; + cout << "\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, 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 savedArgs; + + AutoDelete tmpDir(createTempDir("", myName)); + + std::string outLink = "./result"; + + // List of environment variables kept for --pure + std::set 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(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(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 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> 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 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 & 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), 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 pathsToBuild; + RealisedPath::Set pathsToCopy; + + /* Figure out what bash shell to use. If $NIX_BUILD_SHELL + is not set, then build bashInteractive from + . */ + auto shell = getEnv("NIX_BUILD_SHELL"); + std::optional shellDrv; + + if (!shell) { + + try { + auto & expr = state->parseExprFromString( + "(import {}).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 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, const DerivedPathMap::ChildNode &)> accumDerivedPath; + + accumDerivedPath = [&](ref inputDrv, const DerivedPathMap::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::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(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::ChildNode &)> accumInputClosure; + + accumInputClosure = [&](const StorePath & inputDrv, const DerivedPathMap::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 pathsToBuild; + std::vector> pathsToBuildOrdered; + RealisedPath::Set drvsToCopy; + + std::map> 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 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()) { + 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 +#include +#include + +namespace nix { + +typedef std::map 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>(channelsFile, "\n")) { + chomp(line); + if (std::regex_search(line, std::regex("^\\s*\\#"))) + continue; + auto split = tokenizeString>(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 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 +#include + +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(*arg, arg, end, true), (int64_t) 0); + else + return false; + return true; + }); + + if (removeOld) { + std::set 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(*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 +#include +#include +#include + +#include +#include +#include +#include + +using std::cout; + +namespace nix { + + +typedef enum { + srcNixExprDrvs, + srcNixExprs, + srcStorePaths, + srcProfile, + srcAttrPath, + srcUnknown +} InstallSourceType; + + +struct InstallSourceInfo +{ + InstallSourceType type; + std::shared_ptr nixExprPath; /* for srcNixExprDrvs, srcNixExprs */ + Path profile; /* for srcProfile */ + std::string systemFilter; /* for srcNixExprDrvs */ + Bindings * autoArgs; +}; + + +struct Globals +{ + InstallSourceInfo instSource; + Path profile; + std::shared_ptr 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 searchByPrefix(DrvInfos & allElems, std::string_view prefix) { + constexpr std::size_t maxResults = 3; + std::set 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 pickNewestOnly(EvalState & state, std::vector matches) { + /* Map from package names to derivations. */ + std::map 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 done; + + for (auto & selector : selectors) { + std::vector 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 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(); + 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 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 Table; + + +void printTable(Table & table) +{ + auto nrColumns = table.size() > 0 ? table.front().size() : 0; + + std::vector 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 & 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 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(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(str_max); + if (!max) + throw Error("invalid number of generations to keep '%1%'", opArgs.front()); + deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun); + } else { + std::set gens; + for (auto & i : opArgs) { + if (auto n = string2Int(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(new EvalState(myArgs.searchPath, store)); + globals.state->repair = myArgs.repair; + + globals.instSource.nixExprPath = std::make_shared( + 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 +#include + + +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 seen; + printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits::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(); + 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(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 +#include + +#include +#include +#include + + +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; + + +ref ensureLocalStore() +{ + auto store2 = std::dynamic_pointer_cast(store); + if (!store2) throw Error("you don't have sufficient rights to use this command"); + return ref(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(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 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 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), 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 query; + bool useOutput = false; + bool includeOutputs = false; + bool forceRealise = false; + std::string bindingName; + + for (auto & i : opFlags) { + std::optional 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), 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), 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(*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(*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 { {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(*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(*store); + + if (printRoots) { + Roots roots = gcStore.findRoots(false); + std::set> 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(*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(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::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::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(*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::read(*store, rconn), out); + break; + } + + case ServeProto::Command::BuildPaths: { + + if (!writeAllowed) throw Error("building paths is not allowed"); + + std::vector paths; + for (auto & s : readStrings(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::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::read(*store, rconn); + in >> info.registrationTime >> info.narSize >> info.ultimate; + info.sigs = readStrings(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 +#include + +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 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 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::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 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(); + + 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); + +} diff --git a/src/libcmd/legacy.cc b/src/libcmd/legacy.cc index 6df09ee37..8bbe9b031 100644 --- a/src/libcmd/legacy.cc +++ b/src/libcmd/legacy.cc @@ -2,6 +2,6 @@ namespace nix { -RegisterLegacyCommand::Commands * RegisterLegacyCommand::commands = 0; +LegacyCommands::Commands * LegacyCommands::commands = 0; } diff --git a/src/libcmd/legacy.hh b/src/libcmd/legacy.hh index 357500a4d..45a231983 100644 --- a/src/libcmd/legacy.hh +++ b/src/libcmd/legacy.hh @@ -9,12 +9,12 @@ namespace nix { typedef std::function MainFunction; -struct RegisterLegacyCommand +struct LegacyCommands { typedef std::map Commands; static Commands * commands; - RegisterLegacyCommand(const std::string & name, MainFunction fun) + static void add(const std::string & name, MainFunction fun) { if (!commands) commands = new Commands; (*commands)[name] = fun; diff --git a/src/meson.build b/src/meson.build index 66fbb13ba..8b63ef995 100644 --- a/src/meson.build +++ b/src/meson.build @@ -26,54 +26,9 @@ libasanoptions = declare_dependency( link_whole: asanoptions ) -build_remote_sources = files( - 'build-remote/build-remote.cc', -) -nix_build_sources = files( - 'nix-build/nix-build.cc', -) -nix_channel_sources = files( - 'nix-channel/nix-channel.cc', -) -unpack_channel_gen = gen_header.process('nix-channel/unpack-channel.nix') -nix_collect_garbage_sources = files( - 'nix-collect-garbage/nix-collect-garbage.cc', -) -nix_copy_closure_sources = files( - 'nix-copy-closure/nix-copy-closure.cc', -) -nix_env_buildenv_gen = gen_header.process('nix-env/buildenv.nix') -nix_env_sources = files( - 'nix-env/nix-env.cc', - 'nix-env/user-env.cc', -) -nix_instantiate_sources = files( - 'nix-instantiate/nix-instantiate.cc', -) -nix_store_sources = files( - 'nix-store/dotgraph.cc', - 'nix-store/graphml.cc', - 'nix-store/nix-store.cc', -) - -# Hurray for Meson list flattening! -nix2_commands_sources = [ - build_remote_sources, - nix_build_sources, - nix_channel_sources, - unpack_channel_gen, - nix_collect_garbage_sources, - nix_copy_closure_sources, - nix_env_buildenv_gen, - nix_env_sources, - nix_instantiate_sources, - nix_store_sources, -] +# Legacy commands. +subdir('legacy') # Finally, the nix command itself, which all of the other commands are implmented in terms of # as a multicall binary. subdir('nix') - -# Just copies nix-channel/unpack-channel.nix to the build directory. -# Done as a subdir to get Meson to respect the path hierarchy. -subdir('nix-channel') diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc deleted file mode 100644 index 4b8d7a2fa..000000000 --- a/src/nix-build/nix-build.cc +++ /dev/null @@ -1,617 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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" - -using namespace nix; -using namespace std::string_literals; - -extern char * * environ __attribute__((weak)); - -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 savedArgs; - - AutoDelete tmpDir(createTempDir("", myName)); - - std::string outLink = "./result"; - - // List of environment variables kept for --pure - std::set 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(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(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 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> 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 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 & 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), 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 pathsToBuild; - RealisedPath::Set pathsToCopy; - - /* Figure out what bash shell to use. If $NIX_BUILD_SHELL - is not set, then build bashInteractive from - . */ - auto shell = getEnv("NIX_BUILD_SHELL"); - std::optional shellDrv; - - if (!shell) { - - try { - auto & expr = state->parseExprFromString( - "(import {}).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 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, const DerivedPathMap::ChildNode &)> accumDerivedPath; - - accumDerivedPath = [&](ref inputDrv, const DerivedPathMap::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::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(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::ChildNode &)> accumInputClosure; - - accumInputClosure = [&](const StorePath & inputDrv, const DerivedPathMap::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 pathsToBuild; - std::vector> pathsToBuildOrdered; - RealisedPath::Set drvsToCopy; - - std::map> 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 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()) { - 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'; - } -} - -static RegisterLegacyCommand r_nix_build("nix-build", main_nix_build); -static RegisterLegacyCommand r_nix_shell("nix-shell", main_nix_build); diff --git a/src/nix-channel/meson.build b/src/nix-channel/meson.build deleted file mode 100644 index 97b92d789..000000000 --- a/src/nix-channel/meson.build +++ /dev/null @@ -1 +0,0 @@ -fs.copyfile('unpack-channel.nix') diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc deleted file mode 100644 index 971337b63..000000000 --- a/src/nix-channel/nix-channel.cc +++ /dev/null @@ -1,267 +0,0 @@ -#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 -#include -#include - -using namespace nix; - -typedef std::map 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>(channelsFile, "\n")) { - chomp(line); - if (std::regex_search(line, std::regex("^\\s*\\#"))) - continue; - auto split = tokenizeString>(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 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; - } -} - -static RegisterLegacyCommand r_nix_channel("nix-channel", main_nix_channel); diff --git a/src/nix-channel/unpack-channel.nix b/src/nix-channel/unpack-channel.nix deleted file mode 100644 index 84e324a4d..000000000 --- a/src/nix-channel/unpack-channel.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - 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/nix-collect-garbage/nix-collect-garbage.cc b/src/nix-collect-garbage/nix-collect-garbage.cc deleted file mode 100644 index c831f132f..000000000 --- a/src/nix-collect-garbage/nix-collect-garbage.cc +++ /dev/null @@ -1,113 +0,0 @@ -#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 -#include - -using 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(*arg, arg, end, true), (int64_t) 0); - else - return false; - return true; - }); - - if (removeOld) { - std::set 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(*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; - } -} - -static RegisterLegacyCommand r_nix_collect_garbage("nix-collect-garbage", main_nix_collect_garbage); diff --git a/src/nix-copy-closure/nix-copy-closure.cc b/src/nix-copy-closure/nix-copy-closure.cc deleted file mode 100644 index 7f2bb93b6..000000000 --- a/src/nix-copy-closure/nix-copy-closure.cc +++ /dev/null @@ -1,63 +0,0 @@ -#include "shared.hh" -#include "store-api.hh" -#include "legacy.hh" - -using 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; - } -} - -static RegisterLegacyCommand r_nix_copy_closure("nix-copy-closure", main_nix_copy_closure); diff --git a/src/nix-env/buildenv.nix b/src/nix-env/buildenv.nix deleted file mode 100644 index c8955a94e..000000000 --- a/src/nix-env/buildenv.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ 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/nix-env/nix-env.cc b/src/nix-env/nix-env.cc deleted file mode 100644 index 13fadb1d8..000000000 --- a/src/nix-env/nix-env.cc +++ /dev/null @@ -1,1547 +0,0 @@ -#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 -#include -#include -#include - -#include -#include -#include -#include - -using namespace nix; -using std::cout; - - -typedef enum { - srcNixExprDrvs, - srcNixExprs, - srcStorePaths, - srcProfile, - srcAttrPath, - srcUnknown -} InstallSourceType; - - -struct InstallSourceInfo -{ - InstallSourceType type; - std::shared_ptr nixExprPath; /* for srcNixExprDrvs, srcNixExprs */ - Path profile; /* for srcProfile */ - std::string systemFilter; /* for srcNixExprDrvs */ - Bindings * autoArgs; -}; - - -struct Globals -{ - InstallSourceInfo instSource; - Path profile; - std::shared_ptr 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 searchByPrefix(DrvInfos & allElems, std::string_view prefix) { - constexpr std::size_t maxResults = 3; - std::set 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 pickNewestOnly(EvalState & state, std::vector matches) { - /* Map from package names to derivations. */ - std::map 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 done; - - for (auto & selector : selectors) { - std::vector 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 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(); - 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 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 Table; - - -void printTable(Table & table) -{ - auto nrColumns = table.size() > 0 ? table.front().size() : 0; - - std::vector 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 & 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 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(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(str_max); - if (!max) - throw Error("invalid number of generations to keep '%1%'", opArgs.front()); - deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun); - } else { - std::set gens; - for (auto & i : opArgs) { - if (auto n = string2Int(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(new EvalState(myArgs.searchPath, store)); - globals.state->repair = myArgs.repair; - - globals.instSource.nixExprPath = std::make_shared( - 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; - } -} - -static RegisterLegacyCommand r_nix_env("nix-env", main_nix_env); diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc deleted file mode 100644 index f5dbd06ca..000000000 --- a/src/nix-env/user-env.cc +++ /dev/null @@ -1,155 +0,0 @@ -#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 -#include - -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 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 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::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 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(); - - 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/nix-env/user-env.hh b/src/nix-env/user-env.hh deleted file mode 100644 index af45d2d85..000000000 --- a/src/nix-env/user-env.hh +++ /dev/null @@ -1,14 +0,0 @@ -#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); - -} diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc deleted file mode 100644 index 7487af1c1..000000000 --- a/src/nix-instantiate/nix-instantiate.cc +++ /dev/null @@ -1,198 +0,0 @@ -#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 -#include - - -using 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 seen; - printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits::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(); - 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(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; - } -} - -static RegisterLegacyCommand r_nix_instantiate("nix-instantiate", main_nix_instantiate); diff --git a/src/nix-store/dotgraph.cc b/src/nix-store/dotgraph.cc deleted file mode 100644 index 2c530999b..000000000 --- a/src/nix-store/dotgraph.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include "dotgraph.hh" -#include "store-api.hh" - -#include - - -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 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, 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/nix-store/dotgraph.hh b/src/nix-store/dotgraph.hh deleted file mode 100644 index 4fd944080..000000000 --- a/src/nix-store/dotgraph.hh +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -///@file - -#include "store-api.hh" - -namespace nix { - -void printDotGraph(ref store, StorePathSet && roots); - -} diff --git a/src/nix-store/graphml.cc b/src/nix-store/graphml.cc deleted file mode 100644 index 3e789a2d8..000000000 --- a/src/nix-store/graphml.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "graphml.hh" -#include "store-api.hh" -#include "derivations.hh" - -#include - - -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(" \n", - xmlQuote(src), xmlQuote(dst)); -} - - -static std::string makeNode(const ValidPathInfo & info) -{ - return fmt( - " \n" - " %2%\n" - " %3%\n" - " %4%\n" - " \n", - info.path.to_string(), - info.narSize, - symbolicName(std::string(info.path.name())), - (info.path.isDerivation() ? "derivation" : "output-path")); -} - - -void printGraphML(ref store, StorePathSet && roots) -{ - StorePathSet workList(std::move(roots)); - StorePathSet doneSet; - std::pair ret; - - cout << "\n" - << "\n" - << "" - << "" - << "" - << "\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 << "\n"; - cout << "\n"; -} - - -} diff --git a/src/nix-store/graphml.hh b/src/nix-store/graphml.hh deleted file mode 100644 index bd3a4a37c..000000000 --- a/src/nix-store/graphml.hh +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -///@file - -#include "store-api.hh" - -namespace nix { - -void printGraphML(ref store, StorePathSet && roots); - -} diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc deleted file mode 100644 index bc43aa7b1..000000000 --- a/src/nix-store/nix-store.cc +++ /dev/null @@ -1,1181 +0,0 @@ -#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 -#include - -#include -#include -#include - - -namespace nix_store { - - -using 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; - - -ref ensureLocalStore() -{ - auto store2 = std::dynamic_pointer_cast(store); - if (!store2) throw Error("you don't have sufficient rights to use this command"); - return ref(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(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 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 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), 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 query; - bool useOutput = false; - bool includeOutputs = false; - bool forceRealise = false; - std::string bindingName; - - for (auto & i : opFlags) { - std::optional 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), 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), 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(*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(*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 { {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(*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(*store); - - if (printRoots) { - Roots roots = gcStore.findRoots(false); - std::set> 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(*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(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::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::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(*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::read(*store, rconn), out); - break; - } - - case ServeProto::Command::BuildPaths: { - - if (!writeAllowed) throw Error("building paths is not allowed"); - - std::vector paths; - for (auto & s : readStrings(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::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::read(*store, rconn); - in >> info.registrationTime >> info.narSize >> info.ultimate; - info.sigs = readStrings(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; - } -} - -static RegisterLegacyCommand r_nix_store("nix-store", main_nix_store); - -} diff --git a/src/nix/daemon-command.hh b/src/nix/daemon-command.hh new file mode 100644 index 000000000..454af88e2 --- /dev/null +++ b/src/nix/daemon-command.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixDaemon(); + +} diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc index ca65c38e6..e1d183d7b 100644 --- a/src/nix/daemon.cc +++ b/src/nix/daemon.cc @@ -14,6 +14,7 @@ #include "signals.hh" #include "daemon.hh" #include "unix-domain-socket.hh" +#include "daemon-command.hh" #include #include @@ -36,7 +37,8 @@ #include #endif -using namespace nix; +namespace nix { + using namespace nix::daemon; /** @@ -496,7 +498,9 @@ static int main_nix_daemon(int argc, char * * argv) } } -static RegisterLegacyCommand r_nix_daemon("nix-daemon", main_nix_daemon); +void registerNixDaemon() { + LegacyCommands::add("nix-daemon", main_nix_daemon); +} struct CmdDaemon : StoreCommand { @@ -560,3 +564,5 @@ struct CmdDaemon : StoreCommand }; static auto rCmdDaemon = registerCommand2({"daemon"}); + +} diff --git a/src/nix/hash-command.hh b/src/nix/hash-command.hh new file mode 100644 index 000000000..5383171a5 --- /dev/null +++ b/src/nix/hash-command.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixHash(); + +} diff --git a/src/nix/hash.cc b/src/nix/hash.cc index f6add527a..40b00c978 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -5,8 +5,9 @@ #include "shared.hh" #include "references.hh" #include "archive.hh" +#include "hash-command.hh" -using namespace nix; +namespace nix { struct CmdHashBase : Command { @@ -221,4 +222,8 @@ static int compatNixHash(int argc, char * * argv) return 0; } -static RegisterLegacyCommand r_nix_hash("nix-hash", compatNixHash); +void registerNixHash() { + LegacyCommands::add("nix-hash", compatNixHash); +} + +} diff --git a/src/nix/main.cc b/src/nix/main.cc index 4a3a7b4e7..fdd3ac2ae 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -15,6 +15,17 @@ #include "markdown.hh" #include "experimental-features-json.hh" #include "deprecated-features-json.hh" +#include "build-remote.hh" +#include "daemon-command.hh" +#include "hash-command.hh" +#include "nix-build.hh" +#include "nix-channel.hh" +#include "nix-collect-garbage.hh" +#include "nix-copy-closure.hh" +#include "nix-env.hh" +#include "nix-instantiate.hh" +#include "nix-store.hh" +#include "prefetch-command.hh" #include #include @@ -30,6 +41,21 @@ void chrootHelper(int argc, char * * argv); namespace nix { +void registerLegacyCommands() +{ + registerNixEnv(); + registerNixBuildAndNixShell(); + registerNixInstantiate(); + registerNixCopyClosure(); + registerNixCollectGarbage(); + registerNixChannel(); + registerNixStore(); + registerBuildRemote(); + registerNixDaemon(); + registerNixPrefetchUrl(); + registerNixHash(); +} + static bool haveProxyEnvironmentVariables() { static const std::vector proxyVariables = { @@ -356,8 +382,10 @@ void mainWrapped(int argc, char * * argv) // Clean up the progress bar if shown using --log-format in a legacy command too. // Otherwise, this is a harmless no-op. Finally f([] { logger->pause(); }); + { - auto legacy = (*RegisterLegacyCommand::commands)[programName]; + registerLegacyCommands(); + auto legacy = (*LegacyCommands::commands)[programName]; if (legacy) return legacy(argc, argv); } diff --git a/src/nix/meson.build b/src/nix/meson.build index 80223a390..cabdf0d2c 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -71,11 +71,21 @@ nix_sources = files( 'why-depends.cc', ) +nix_headers = files( + 'daemon-command.hh', + 'hash-command.hh', + 'prefetch-command.hh', +) + nix = executable( 'nix', nix_sources, + legacy_sources, nix_generated_headers, - nix2_commands_sources, + nix_headers, + legacy_headers, + legacy_generated_headers, + include_directories : legacy_include_directories, dependencies : [ libasanoptions, liblixcmd, diff --git a/src/nix/prefetch-command.hh b/src/nix/prefetch-command.hh new file mode 100644 index 000000000..078e83485 --- /dev/null +++ b/src/nix/prefetch-command.hh @@ -0,0 +1,8 @@ +#pragma once +/// @file + +namespace nix { + +void registerNixPrefetchUrl(); + +} diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index b99cd5dd0..0183b0008 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -9,10 +9,11 @@ #include "eval-inline.hh" // IWYU pragma: keep #include "legacy.hh" #include "terminal.hh" +#include "prefetch-command.hh" #include -using namespace nix; +namespace nix { /* If ‘url’ starts with ‘mirror://’, then resolve it using the list of mirrors defined in Nixpkgs. */ @@ -248,7 +249,9 @@ static int main_nix_prefetch_url(int argc, char * * argv) } } -static RegisterLegacyCommand r_nix_prefetch_url("nix-prefetch-url", main_nix_prefetch_url); +void registerNixPrefetchUrl() { + LegacyCommands::add("nix-prefetch-url", main_nix_prefetch_url); +} struct CmdStorePrefetchFile : StoreCommand, MixJSON { @@ -328,3 +331,5 @@ struct CmdStorePrefetchFile : StoreCommand, MixJSON }; static auto rCmdStorePrefetchFile = registerCommand2({"store", "prefetch-file"}); + +} diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 401d5bd77..6739cb5c6 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -8,7 +8,7 @@ #include "archive.hh" #include "builtins/buildenv.hh" #include "flake/flakeref.hh" -#include "../nix-env/user-env.hh" +#include "user-env.hh" #include "profiles.hh" #include "names.hh" diff --git a/tests/functional/restricted.sh b/tests/functional/restricted.sh index 450674bd6..dd6386278 100644 --- a/tests/functional/restricted.sh +++ b/tests/functional/restricted.sh @@ -11,8 +11,8 @@ nix-instantiate --restrict-eval ./simple.nix -I src1=simple.nix -I src2=config.n (! nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix') nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix' -I src=../.. -(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/nix-channel') -nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/nix-channel' -I src=../../src +(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/legacy') +nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/legacy' -I src=../../src (! nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in ') nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in ' -I src=. diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 8b0c66dd8..c831ba379 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -255,7 +255,7 @@ test( env : default_test_env + { # No special meaning here, it's just a file laying around that is unlikely to go anywhere # any time soon. - '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/nix-env/buildenv.nix', + '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/legacy/buildenv.nix', # Use a temporary home directory for the unit tests. # Otherwise, /homeless-shelter is created in the single-user sandbox, and functional tests will fail. # TODO(alois31): handle TMPDIR properly (meson can't, and setting HOME in the test is too late)… -- cgit v1.2.3