feat: add flag set-env-var to MixEnvironment

This commit is contained in:
Bryan Honof 2024-09-12 03:04:52 +02:00
parent 655bfa6b59
commit 0b790b4849
No known key found for this signature in database
4 changed files with 76 additions and 33 deletions

View file

@ -1,3 +1,4 @@
#include <algorithm>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "command.hh" #include "command.hh"
@ -9,8 +10,7 @@
#include "profiles.hh" #include "profiles.hh"
#include "repl.hh" #include "repl.hh"
#include "strings.hh" #include "strings.hh"
#include "environment-variables.hh"
extern char * * environ __attribute__((weak));
namespace nix { namespace nix {
@ -285,48 +285,88 @@ MixDefaultProfile::MixDefaultProfile()
MixEnvironment::MixEnvironment() : ignoreEnvironment(false) MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
{ {
addFlag({ addFlag({
.longName = "ignore-environment", .longName = "ignore-env",
.aliases = {"ignore-environment"},
.shortName = 'i', .shortName = 'i',
.description = "Clear the entire environment (except those specified with `--keep`).", .description = "Clear the entire environment, except for those specified with `--keep-env-var`.",
.category = environmentVariablesCategory,
.handler = {&ignoreEnvironment, true}, .handler = {&ignoreEnvironment, true},
}); });
addFlag({ addFlag({
.longName = "keep", .longName = "keep-env-var",
.aliases = {"keep"},
.shortName = 'k', .shortName = 'k',
.description = "Keep the environment variable *name*.", .description = "Keep the environment variable *name*, when using `--ignore-env`.",
.category = environmentVariablesCategory,
.labels = {"name"}, .labels = {"name"},
.handler = {[&](std::string s) { keep.insert(s); }}, .handler = {[&](std::string s) { keepVars.insert(s); }},
}); });
addFlag({ addFlag({
.longName = "unset", .longName = "unset-env-var",
.aliases = {"unset"},
.shortName = 'u', .shortName = 'u',
.description = "Unset the environment variable *name*.", .description = "Unset the environment variable *name*.",
.category = environmentVariablesCategory,
.labels = {"name"}, .labels = {"name"},
.handler = {[&](std::string s) { unset.insert(s); }}, .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 = "Add/override an environment variable *name* with *value*.\n\n"
"> **Notes**\n"
">\n"
"> Duplicate definitions will be overwritten, last one wins.\n\n"
"> Cancles out with `--unset-env-var`.\n\n",
.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() { void MixEnvironment::setEnviron() {
if (ignoreEnvironment) { if (ignoreEnvironment && !unsetVars.empty())
if (!unset.empty()) throw UsageError("--unset-env-var does not make sense with --ignore-env");
throw UsageError("--unset does not make sense with --ignore-environment");
for (const auto & var : keep) { if (!ignoreEnvironment && !keepVars.empty())
auto val = getenv(var.c_str()); throw UsageError("--keep-env-var does not make sense without --ignore-env");
if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
}
vectorEnv = stringsToCharPtrs(stringsEnv); auto env = getEnv();
environ = vectorEnv.data();
} else {
if (!keep.empty())
throw UsageError("--keep does not make sense without --ignore-environment");
for (const auto & var : unset) if (ignoreEnvironment)
unsetenv(var.c_str()); 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;
} }
} }

View file

@ -315,17 +315,18 @@ struct MixDefaultProfile : MixProfile
struct MixEnvironment : virtual Args { struct MixEnvironment : virtual Args {
StringSet keep, unset; StringSet keepVars;
Strings stringsEnv; StringSet unsetVars;
std::vector<char*> vectorEnv; std::map<std::string, std::string> setVars;
bool ignoreEnvironment; bool ignoreEnvironment;
MixEnvironment(); MixEnvironment();
/*** /***
* Modify global environ based on `ignoreEnvironment`, `keep`, and * Modify global environ based on `ignoreEnvironment`, `keep`,
* `unset`. It's expected that exec will be called before this class * `unset`, and `added`. It's expected that exec will be called
* goes out of scope, otherwise `environ` will become invalid. * before this class goes out of scope, otherwise `environ` will
* become invalid.
*/ */
void setEnviron(); void setEnviron();
}; };

View file

@ -13,6 +13,8 @@
namespace nix { namespace nix {
static constexpr auto environmentVariablesCategory = "Options that change environment variables";
/** /**
* @return an environment variable. * @return an environment variable.
*/ */

View file

@ -47,9 +47,9 @@ echo "\$ENVVAR"
EOF EOF
)" = "a" ]] )" = "a" ]]
# Test whether `nix develop --ignore-environment` does _not_ pass through environment variables. # Test whether `nix develop --ignore-env` does _not_ pass through environment variables.
[[ -z "$( [[ -z "$(
ENVVAR=a nix develop --ignore-environment --no-write-lock-file .#hello <<EOF ENVVAR=a nix develop --ignore-env --no-write-lock-file .#hello <<EOF
echo "\$ENVVAR" echo "\$ENVVAR"
EOF EOF
)" ]] )" ]]
@ -67,7 +67,7 @@ EOF
# Test whether `nix develop` with ignore environment sets `SHELL` to nixpkgs#bashInteractive shell. # Test whether `nix develop` with ignore environment sets `SHELL` to nixpkgs#bashInteractive shell.
[[ "$( [[ "$(
SHELL=custom nix develop --ignore-environment --no-write-lock-file .#hello <<EOF SHELL=custom nix develop --ignore-env --no-write-lock-file .#hello <<EOF
echo "\$SHELL" echo "\$SHELL"
EOF EOF
)" -ef "$BASH_INTERACTIVE_EXECUTABLE" ]] )" -ef "$BASH_INTERACTIVE_EXECUTABLE" ]]