#include "command.hh" #include "store-api.hh" #include "local-fs-store.hh" #include "derivations.hh" #include "nixexpr.hh" #include "profiles.hh" #include extern char * * environ __attribute__((weak)); 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(); } StoreCommand::StoreCommand() { } ref StoreCommand::getStore() { if (!_store) _store = createStore(); return ref(_store); } ref StoreCommand::createStore() { return openStore(); } void StoreCommand::run() { run(getStore()); } EvalCommand::EvalCommand() { addFlag({ .longName = "debugger", .description = "start an interactive environment if evaluation fails", .handler = {&startReplOnEvalErrors, true}, }); } extern std::function debuggerHook; ref EvalCommand::getEvalState() { if (!evalState) { evalState = #if HAVE_BOEHMGC std::allocate_shared(traceable_allocator(), #else std::make_shared( #endif searchPath, getEvalStore(), getStore()); if (startReplOnEvalErrors) debuggerHook = [evalState{ref(evalState)}](const Error * error, const Env & env, const Expr & expr) { if (error) printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error->what()); if (expr.staticenv) { auto vm = mapStaticEnvBindings(*expr.staticenv.get(), env); runRepl(evalState, error, expr, *vm); } }; } return ref(evalState); } EvalCommand::~EvalCommand() { if (evalState) evalState->printStats(); } ref EvalCommand::getEvalStore() { if (!evalStore) evalStore = evalStoreUrl ? openStore(*evalStoreUrl) : getStore(); return ref(evalStore); } 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) { BuiltPaths paths; 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()) paths.push_back(BuiltPath::Opaque{p}); } else { paths = toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables); if (recursive) { // XXX: This only computes the store path closure, ignoring // intermediate realisations StorePathSet pathsRoots, pathsClosure; for (auto & root : paths) { auto rootFromThis = root.outPaths(); pathsRoots.insert(rootFromThis.begin(), rootFromThis.end()); } store->computeFSClosure(pathsRoots, pathsClosure); for (auto & path : pathsClosure) paths.push_back(BuiltPath::Opaque{path}); } } run(store, std::move(paths)); } StorePathsCommand::StorePathsCommand(bool recursive) : BuiltPathsCommand(recursive) { } void StorePathsCommand::run(ref store, BuiltPaths && paths) { StorePathSet storePaths; for (auto & builtPath : paths) 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, std::vector && storePaths) { if (storePaths.size() != 1) throw UsageError("this command requires exactly one store path"); run(store, *storePaths.begin()); } Strings editorFor(const Pos & pos) { auto editor = getEnv("EDITOR").value_or("cat"); auto args = tokenizeString(editor); if (pos.line > 0 && ( editor.find("emacs") != std::string::npos || editor.find("nano") != std::string::npos || editor.find("vim") != std::string::npos)) args.push_back(fmt("+%d", pos.line)); args.push_back(pos.file); return args; } MixProfile::MixProfile() { addFlag({ .longName = "profile", .description = "The profile to update.", .labels = {"path"}, .handler = {&profile}, .completer = completePath }); } void MixProfile::updateProfile(const StorePath & storePath) { if (!profile) return; auto store = getStore().dynamic_pointer_cast(); if (!store) throw Error("'--profile' is not supported for this Nix store"); auto profile2 = absPath(*profile); switchLink(profile2, createGeneration( ref(store), profile2, storePath)); } void MixProfile::updateProfile(const BuiltPaths & buildables) { if (!profile) return; std::vector 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-environment", .shortName = 'i', .description = "Clear the entire environment (except those specified with `--keep`).", .handler = {&ignoreEnvironment, true}, }); addFlag({ .longName = "keep", .shortName = 'k', .description = "Keep the environment variable *name*.", .labels = {"name"}, .handler = {[&](std::string s) { keep.insert(s); }}, }); addFlag({ .longName = "unset", .shortName = 'u', .description = "Unset the environment variable *name*.", .labels = {"name"}, .handler = {[&](std::string s) { unset.insert(s); }}, }); } void MixEnvironment::setEnviron() { if (ignoreEnvironment) { if (!unset.empty()) throw UsageError("--unset does not make sense with --ignore-environment"); for (const auto & var : keep) { auto val = getenv(var.c_str()); if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val)); } vectorEnv = stringsToCharPtrs(stringsEnv); environ = vectorEnv.data(); } else { if (!keep.empty()) throw UsageError("--keep does not make sense without --ignore-environment"); for (const auto & var : unset) unsetenv(var.c_str()); } } }