Merge pull request #10807 from hercules-ci/issue-10504-nix-env-shell

Add `nix env shell`
This commit is contained in:
Robert Hensing 2024-05-31 12:34:03 +02:00 committed by GitHub
commit 138aa2b0a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 164 additions and 103 deletions

View file

@ -0,0 +1,12 @@
---
synopsis: "`nix env shell` is the new `nix shell`, and `nix shell` remains an accepted alias"
issues: 10504
prs: 10807
---
This is part of an effort to bring more structure to the CLI subcommands.
`nix env` will be about the process environment.
Future commands may include `nix env run` and `nix env print-env`.
It is also somewhat analogous to the [planned](https://github.com/NixOS/nix/issues/10504) `nix dev shell` (currently `nix develop`), which is less about environment variables, and more about running a development shell, which is a more powerful command, but also requires more setup.

106
src/nix/env.cc Normal file
View file

@ -0,0 +1,106 @@
#include "command.hh"
#include "run.hh"
#include <queue>
using namespace nix;
struct CmdEnv : NixMultiCommand
{
CmdEnv()
: NixMultiCommand("env", RegisterCommand::getCommandsFor({"env"}))
{
}
std::string description() override
{
return "manipulate the process environment";
}
Category category() override
{
return catUtility;
}
};
static auto rCmdEnv = registerCommand<CmdEnv>("env");
struct CmdShell : InstallablesCommand, MixEnvironment
{
using InstallablesCommand::run;
std::vector<std::string> command = {getEnv("SHELL").value_or("bash")};
CmdShell()
{
addFlag(
{.longName = "command",
.shortName = 'c',
.description = "Command and arguments to be executed, defaulting to `$SHELL`",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty())
throw UsageError("--command requires at least one argument");
command = ss;
}}});
}
std::string description() override
{
return "run a shell in which the specified packages are available";
}
std::string doc() override
{
return
#include "shell.md"
;
}
void run(ref<Store> store, Installables && installables) override
{
auto outPaths =
Installable::toStorePaths(getEvalStore(), store, Realise::Outputs, OperateOn::Output, installables);
auto accessor = store->getFSAccessor();
std::unordered_set<StorePath> done;
std::queue<StorePath> todo;
for (auto & path : outPaths)
todo.push(path);
setEnviron();
std::vector<std::string> pathAdditions;
while (!todo.empty()) {
auto path = todo.front();
todo.pop();
if (!done.insert(path).second)
continue;
if (true)
pathAdditions.push_back(store->printStorePath(path) + "/bin");
auto propPath = accessor->resolveSymlinks(
CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages");
if (auto st = accessor->maybeLstat(propPath); st && st->type == SourceAccessor::tRegular) {
for (auto & p : tokenizeString<Paths>(accessor->readFile(propPath)))
todo.push(store->parseStorePath(p));
}
}
auto unixPath = tokenizeString<Strings>(getEnv("PATH").value_or(""), ":");
unixPath.insert(unixPath.begin(), pathAdditions.begin(), pathAdditions.end());
auto unixPathString = concatStringsSep(":", unixPath);
setEnv("PATH", unixPathString.c_str());
Strings args;
for (auto & arg : command)
args.push_back(arg);
runProgramInStore(store, UseLookupPath::Use, *command.begin(), args);
}
};
static auto rCmdShell = registerCommand2<CmdShell>({"env", "shell"});

View file

@ -42,6 +42,19 @@ void chrootHelper(int argc, char * * argv);
namespace nix {
enum struct AliasStatus {
/** Aliases that don't go away */
AcceptedShorthand,
/** Aliases that will go away */
Deprecated,
};
/** An alias, except for the original syntax, which is in the map key. */
struct AliasInfo {
AliasStatus status;
std::vector<std::string> replacement;
};
/* Check if we have a non-loopback/link-local network interface. */
static bool haveInternet()
{
@ -134,29 +147,30 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
});
}
std::map<std::string, std::vector<std::string>> aliases = {
{"add-to-store", {"store", "add-path"}},
{"cat-nar", {"nar", "cat"}},
{"cat-store", {"store", "cat"}},
{"copy-sigs", {"store", "copy-sigs"}},
{"dev-shell", {"develop"}},
{"diff-closures", {"store", "diff-closures"}},
{"dump-path", {"store", "dump-path"}},
{"hash-file", {"hash", "file"}},
{"hash-path", {"hash", "path"}},
{"ls-nar", {"nar", "ls"}},
{"ls-store", {"store", "ls"}},
{"make-content-addressable", {"store", "make-content-addressed"}},
{"optimise-store", {"store", "optimise"}},
{"ping-store", {"store", "ping"}},
{"sign-paths", {"store", "sign"}},
{"show-derivation", {"derivation", "show"}},
{"show-config", {"config", "show"}},
{"to-base16", {"hash", "to-base16"}},
{"to-base32", {"hash", "to-base32"}},
{"to-base64", {"hash", "to-base64"}},
{"verify", {"store", "verify"}},
{"doctor", {"config", "check"}},
std::map<std::string, AliasInfo> aliases = {
{"add-to-store", { AliasStatus::Deprecated, {"store", "add-path"}}},
{"cat-nar", { AliasStatus::Deprecated, {"nar", "cat"}}},
{"cat-store", { AliasStatus::Deprecated, {"store", "cat"}}},
{"copy-sigs", { AliasStatus::Deprecated, {"store", "copy-sigs"}}},
{"dev-shell", { AliasStatus::Deprecated, {"develop"}}},
{"diff-closures", { AliasStatus::Deprecated, {"store", "diff-closures"}}},
{"dump-path", { AliasStatus::Deprecated, {"store", "dump-path"}}},
{"hash-file", { AliasStatus::Deprecated, {"hash", "file"}}},
{"hash-path", { AliasStatus::Deprecated, {"hash", "path"}}},
{"ls-nar", { AliasStatus::Deprecated, {"nar", "ls"}}},
{"ls-store", { AliasStatus::Deprecated, {"store", "ls"}}},
{"make-content-addressable", { AliasStatus::Deprecated, {"store", "make-content-addressed"}}},
{"optimise-store", { AliasStatus::Deprecated, {"store", "optimise"}}},
{"ping-store", { AliasStatus::Deprecated, {"store", "ping"}}},
{"sign-paths", { AliasStatus::Deprecated, {"store", "sign"}}},
{"shell", { AliasStatus::AcceptedShorthand, {"env", "shell"}}},
{"show-derivation", { AliasStatus::Deprecated, {"derivation", "show"}}},
{"show-config", { AliasStatus::Deprecated, {"config", "show"}}},
{"to-base16", { AliasStatus::Deprecated, {"hash", "to-base16"}}},
{"to-base32", { AliasStatus::Deprecated, {"hash", "to-base32"}}},
{"to-base64", { AliasStatus::Deprecated, {"hash", "to-base64"}}},
{"verify", { AliasStatus::Deprecated, {"store", "verify"}}},
{"doctor", { AliasStatus::Deprecated, {"config", "check"}}},
};
bool aliasUsed = false;
@ -167,10 +181,13 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
auto arg = *pos;
auto i = aliases.find(arg);
if (i == aliases.end()) return pos;
warn("'%s' is a deprecated alias for '%s'",
arg, concatStringsSep(" ", i->second));
auto & info = i->second;
if (info.status == AliasStatus::Deprecated) {
warn("'%s' is a deprecated alias for '%s'",
arg, concatStringsSep(" ", info.replacement));
}
pos = args.erase(pos);
for (auto j = i->second.rbegin(); j != i->second.rend(); ++j)
for (auto j = info.replacement.rbegin(); j != info.replacement.rend(); ++j)
pos = args.insert(pos, *j);
aliasUsed = true;
return pos;

View file

@ -71,83 +71,6 @@ void runProgramInStore(ref<Store> store,
}
struct CmdShell : InstallablesCommand, MixEnvironment
{
using InstallablesCommand::run;
std::vector<std::string> command = { getEnv("SHELL").value_or("bash") };
CmdShell()
{
addFlag({
.longName = "command",
.shortName = 'c',
.description = "Command and arguments to be executed, defaulting to `$SHELL`",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty()) throw UsageError("--command requires at least one argument");
command = ss;
}}
});
}
std::string description() override
{
return "run a shell in which the specified packages are available";
}
std::string doc() override
{
return
#include "shell.md"
;
}
void run(ref<Store> store, Installables && installables) override
{
auto outPaths = Installable::toStorePaths(getEvalStore(), store, Realise::Outputs, OperateOn::Output, installables);
auto accessor = store->getFSAccessor();
std::unordered_set<StorePath> done;
std::queue<StorePath> todo;
for (auto & path : outPaths) todo.push(path);
setEnviron();
std::vector<std::string> pathAdditions;
while (!todo.empty()) {
auto path = todo.front();
todo.pop();
if (!done.insert(path).second) continue;
if (true)
pathAdditions.push_back(store->printStorePath(path) + "/bin");
auto propPath = accessor->resolveSymlinks(
CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages");
if (auto st = accessor->maybeLstat(propPath); st && st->type == SourceAccessor::tRegular) {
for (auto & p : tokenizeString<Paths>(accessor->readFile(propPath)))
todo.push(store->parseStorePath(p));
}
}
auto unixPath = tokenizeString<Strings>(getEnv("PATH").value_or(""), ":");
unixPath.insert(unixPath.begin(), pathAdditions.begin(), pathAdditions.end());
auto unixPathString = concatStringsSep(":", unixPath);
setEnv("PATH", unixPathString.c_str());
Strings args;
for (auto & arg : command) args.push_back(arg);
runProgramInStore(store, UseLookupPath::Use, *command.begin(), args);
}
};
static auto rCmdShell = registerCommand<CmdShell>("shell");
struct CmdRun : InstallableValueCommand
{
using InstallableCommand::run;

View file

@ -5,6 +5,9 @@ source common.sh
clearStore
clearCache
# nix shell is an alias for nix env shell. We'll use the shorter form in the rest of the test.
nix env shell -f shell-hello.nix hello -c hello | grep 'Hello World'
nix shell -f shell-hello.nix hello -c hello | grep 'Hello World'
nix shell -f shell-hello.nix hello -c hello NixOS | grep 'Hello NixOS'