mirror of
https://github.com/NixOS/nix.git
synced 2025-11-10 04:26:01 +01:00
* It is tough to contribute to a project that doesn't use a formatter, * It is extra hard to contribute to a project which has configured the formatter, but ignores it for some files * Code formatting makes it harder to hide obscure / weird bugs by accident or on purpose, Let's rip the bandaid off? Note that PRs currently in flight should be able to be merged relatively easily by applying `clang-format` to their tip prior to merge.
405 lines
12 KiB
C++
405 lines
12 KiB
C++
#include <algorithm>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include "nix/cmd/command.hh"
|
|
#include "nix/cmd/markdown.hh"
|
|
#include "nix/store/store-open.hh"
|
|
#include "nix/store/local-fs-store.hh"
|
|
#include "nix/store/derivations.hh"
|
|
#include "nix/expr/nixexpr.hh"
|
|
#include "nix/store/profiles.hh"
|
|
#include "nix/cmd/repl.hh"
|
|
#include "nix/util/strings.hh"
|
|
#include "nix/util/environment-variables.hh"
|
|
|
|
namespace nix {
|
|
|
|
nix::Commands RegisterCommand::getCommandsFor(const std::vector<std::string> & 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) {
|
|
StringSet 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<Store> StoreCommand::getStore()
|
|
{
|
|
if (!_store)
|
|
_store = createStore();
|
|
return ref<Store>(_store);
|
|
}
|
|
|
|
ref<Store> 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<Store> CopyCommand::createStore()
|
|
{
|
|
return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri);
|
|
}
|
|
|
|
ref<Store> 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<Store> EvalCommand::getEvalStore()
|
|
{
|
|
if (!evalStore)
|
|
evalStore = evalStoreUrl ? openStore(*evalStoreUrl) : getStore();
|
|
return ref<Store>(evalStore);
|
|
}
|
|
|
|
ref<EvalState> EvalCommand::getEvalState()
|
|
{
|
|
if (!evalState) {
|
|
evalState = std::allocate_shared<EvalState>(
|
|
traceable_allocator<EvalState>(), lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore());
|
|
|
|
evalState->repair = repair;
|
|
|
|
if (startReplOnEvalErrors) {
|
|
evalState->debugRepl = &AbstractNixRepl::runSimple;
|
|
};
|
|
}
|
|
return ref<EvalState>(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> 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> 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> 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<LocalFSStore>();
|
|
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());
|
|
}
|
|
}
|
|
|
|
void MixOutLinkBase::createOutLinksMaybe(const std::vector<BuiltPathWithResult> & buildables, ref<Store> & store)
|
|
{
|
|
if (outLink != "")
|
|
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
|
|
createOutLinks(outLink, toBuiltPaths(buildables), *store2);
|
|
}
|
|
|
|
} // namespace nix
|