#include #include #include "nix/command.hh" #include "nix/markdown.hh" #include "nix/store-api.hh" #include "nix/local-fs-store.hh" #include "nix/derivations.hh" #include "nix/nixexpr.hh" #include "nix/profiles.hh" #include "nix/repl.hh" #include "nix/strings.hh" #include "nix/environment-variables.hh" namespace nix { RegisterCommand::Commands * RegisterCommand::commands = nullptr; nix::Commands RegisterCommand::getCommandsFor(const std::vector & prefix) { nix::Commands res; for (auto & [name, command] : *RegisterCommand::commands) if (name.size() == prefix.size() + 1) { bool equal = true; for (size_t i = 0; i < prefix.size(); ++i) if (name[i] != prefix[i]) equal = false; if (equal) res.insert_or_assign(name[prefix.size()], command); } return res; } nlohmann::json NixMultiCommand::toJSON() { // FIXME: use Command::toJSON() as well. return MultiCommand::toJSON(); } void NixMultiCommand::run() { if (!command) { std::set subCommandTextLines; for (auto & [name, _] : commands) subCommandTextLines.insert(fmt("- `%s`", name)); std::string markdownError = fmt("`nix %s` requires a sub-command. Available sub-commands:\n\n%s\n", commandName, concatStringsSep("\n", subCommandTextLines)); throw UsageError(renderMarkdownToTerminal(markdownError)); } command->second->run(); } StoreCommand::StoreCommand() {} ref StoreCommand::getStore() { if (!_store) _store = createStore(); return ref(_store); } ref StoreCommand::createStore() { return openStore(); } void StoreCommand::run() { run(getStore()); } CopyCommand::CopyCommand() { addFlag({ .longName = "from", .description = "URL of the source Nix store.", .labels = {"store-uri"}, .handler = {&srcUri}, }); addFlag({ .longName = "to", .description = "URL of the destination Nix store.", .labels = {"store-uri"}, .handler = {&dstUri}, }); } ref CopyCommand::createStore() { return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri); } ref CopyCommand::getDstStore() { if (srcUri.empty() && dstUri.empty()) throw UsageError("you must pass '--from' and/or '--to'"); return dstUri.empty() ? openStore() : openStore(dstUri); } EvalCommand::EvalCommand() { addFlag({ .longName = "debugger", .description = "Start an interactive environment if evaluation fails.", .category = MixEvalArgs::category, .handler = {&startReplOnEvalErrors, true}, }); } EvalCommand::~EvalCommand() { if (evalState) evalState->maybePrintStats(); } ref EvalCommand::getEvalStore() { if (!evalStore) evalStore = evalStoreUrl ? openStore(*evalStoreUrl) : getStore(); return ref(evalStore); } ref EvalCommand::getEvalState() { if (!evalState) { evalState = std::allocate_shared( traceable_allocator(), lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore()); evalState->repair = repair; if (startReplOnEvalErrors) { evalState->debugRepl = &AbstractNixRepl::runSimple; }; } return ref(evalState); } MixOperateOnOptions::MixOperateOnOptions() { addFlag({ .longName = "derivation", .description = "Operate on the [store derivation](@docroot@/glossary.md#gloss-store-derivation) rather than its outputs.", .category = installablesCategory, .handler = {&operateOn, OperateOn::Derivation}, }); } BuiltPathsCommand::BuiltPathsCommand(bool recursive) : recursive(recursive) { if (recursive) addFlag({ .longName = "no-recursive", .description = "Apply operation to specified paths only.", .category = installablesCategory, .handler = {&this->recursive, false}, }); else addFlag({ .longName = "recursive", .shortName = 'r', .description = "Apply operation to closure of the specified paths.", .category = installablesCategory, .handler = {&this->recursive, true}, }); addFlag({ .longName = "all", .description = "Apply the operation to every store path.", .category = installablesCategory, .handler = {&all, true}, }); } void BuiltPathsCommand::run(ref store, Installables && installables) { BuiltPaths rootPaths, allPaths; if (all) { if (installables.size()) throw UsageError("'--all' does not expect arguments"); // XXX: Only uses opaque paths, ignores all the realisations for (auto & p : store->queryAllValidPaths()) rootPaths.emplace_back(BuiltPath::Opaque{p}); allPaths = rootPaths; } else { rootPaths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables); allPaths = rootPaths; if (recursive) { // XXX: This only computes the store path closure, ignoring // intermediate realisations StorePathSet pathsRoots, pathsClosure; for (auto & root : rootPaths) { auto rootFromThis = root.outPaths(); pathsRoots.insert(rootFromThis.begin(), rootFromThis.end()); } store->computeFSClosure(pathsRoots, pathsClosure); for (auto & path : pathsClosure) allPaths.emplace_back(BuiltPath::Opaque{path}); } } run(store, std::move(allPaths), std::move(rootPaths)); } StorePathsCommand::StorePathsCommand(bool recursive) : BuiltPathsCommand(recursive) { } void StorePathsCommand::run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths) { StorePathSet storePaths; for (auto & builtPath : allPaths) for (auto & p : builtPath.outPaths()) storePaths.insert(p); auto sorted = store->topoSortPaths(storePaths); std::reverse(sorted.begin(), sorted.end()); run(store, std::move(sorted)); } void StorePathCommand::run(ref store, StorePaths && storePaths) { if (storePaths.size() != 1) throw UsageError("this command requires exactly one store path"); run(store, *storePaths.begin()); } MixProfile::MixProfile() { addFlag( {.longName = "profile", .description = "The profile to operate on.", .labels = {"path"}, .handler = {&profile}, .completer = completePath}); } void MixProfile::updateProfile(const StorePath & storePath) { if (!profile) return; auto store = getDstStore().dynamic_pointer_cast(); if (!store) throw Error("'--profile' is not supported for this Nix store"); auto profile2 = absPath(*profile); switchLink(profile2, createGeneration(*store, profile2, storePath)); } void MixProfile::updateProfile(const BuiltPaths & buildables) { if (!profile) return; StorePaths result; for (auto & buildable : buildables) { std::visit( overloaded{ [&](const BuiltPath::Opaque & bo) { result.push_back(bo.path); }, [&](const BuiltPath::Built & bfd) { for (auto & output : bfd.outputs) { result.push_back(output.second); } }, }, buildable.raw()); } if (result.size() != 1) throw UsageError( "'--profile' requires that the arguments produce a single store path, but there are %d", result.size()); updateProfile(result[0]); } MixDefaultProfile::MixDefaultProfile() { profile = getDefaultProfile(); } MixEnvironment::MixEnvironment() : ignoreEnvironment(false) { addFlag({ .longName = "ignore-env", .aliases = {"ignore-environment"}, .shortName = 'i', .description = "Clear the entire environment, except for those specified with `--keep-env-var`.", .category = environmentVariablesCategory, .handler = {&ignoreEnvironment, true}, }); addFlag({ .longName = "keep-env-var", .aliases = {"keep"}, .shortName = 'k', .description = "Keep the environment variable *name*, when using `--ignore-env`.", .category = environmentVariablesCategory, .labels = {"name"}, .handler = {[&](std::string s) { keepVars.insert(s); }}, }); addFlag({ .longName = "unset-env-var", .aliases = {"unset"}, .shortName = 'u', .description = "Unset the environment variable *name*.", .category = environmentVariablesCategory, .labels = {"name"}, .handler = {[&](std::string name) { if (setVars.contains(name)) throw UsageError("Cannot unset environment variable '%s' that is set with '%s'", name, "--set-env-var"); unsetVars.insert(name); }}, }); addFlag({ .longName = "set-env-var", .shortName = 's', .description = "Sets an environment variable *name* with *value*.", .category = environmentVariablesCategory, .labels = {"name", "value"}, .handler = {[&](std::string name, std::string value) { if (unsetVars.contains(name)) throw UsageError( "Cannot set environment variable '%s' that is unset with '%s'", name, "--unset-env-var"); if (setVars.contains(name)) throw UsageError( "Duplicate definition of environment variable '%s' with '%s' is ambiguous", name, "--set-env-var"); setVars.insert_or_assign(name, value); }}, }); } void MixEnvironment::setEnviron() { if (ignoreEnvironment && !unsetVars.empty()) throw UsageError("--unset-env-var does not make sense with --ignore-env"); if (!ignoreEnvironment && !keepVars.empty()) throw UsageError("--keep-env-var does not make sense without --ignore-env"); auto env = getEnv(); if (ignoreEnvironment) std::erase_if(env, [&](const auto & var) { return !keepVars.contains(var.first); }); for (const auto & [name, value] : setVars) env[name] = value; if (!unsetVars.empty()) std::erase_if(env, [&](const auto & var) { return unsetVars.contains(var.first); }); replaceEnv(env); return; } void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store) { for (const auto & [_i, buildable] : enumerate(buildables)) { auto i = _i; std::visit( overloaded{ [&](const BuiltPath::Opaque & bo) { auto symlink = outLink; if (i) symlink += fmt("-%d", i); store.addPermRoot(bo.path, absPath(symlink.string())); }, [&](const BuiltPath::Built & bfd) { for (auto & output : bfd.outputs) { auto symlink = outLink; if (i) symlink += fmt("-%d", i); if (output.first != "out") symlink += fmt("-%s", output.first); store.addPermRoot(output.second, absPath(symlink.string())); } }, }, buildable.raw()); } } }