2019-05-02 18:22:14 +00:00
|
|
|
#include "eval.hh"
|
|
|
|
#include "command.hh"
|
|
|
|
#include "common-args.hh"
|
|
|
|
#include "shared.hh"
|
|
|
|
#include "store-api.hh"
|
|
|
|
#include "derivations.hh"
|
|
|
|
#include "affinity.hh"
|
|
|
|
#include "progress-bar.hh"
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
#include <regex>
|
|
|
|
|
2019-05-02 18:22:14 +00:00
|
|
|
using namespace nix;
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
struct Var
|
|
|
|
{
|
|
|
|
bool exported;
|
|
|
|
std::string value; // quoted string or array
|
|
|
|
};
|
|
|
|
|
2019-05-02 18:22:14 +00:00
|
|
|
struct BuildEnvironment
|
|
|
|
{
|
2019-09-27 15:01:25 +00:00
|
|
|
std::map<std::string, Var> env;
|
|
|
|
std::string bashFunctions;
|
2019-05-02 18:22:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
BuildEnvironment readEnvironment(const Path & path)
|
|
|
|
{
|
|
|
|
BuildEnvironment res;
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
std::set<std::string> exported;
|
|
|
|
|
2019-11-12 11:45:48 +00:00
|
|
|
debug("reading environment file '%s'", path);
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
auto file = readFile(path);
|
|
|
|
|
|
|
|
auto pos = file.cbegin();
|
|
|
|
|
|
|
|
static std::string varNameRegex =
|
|
|
|
R"re((?:[a-zA-Z_][a-zA-Z0-9_]*))re";
|
|
|
|
|
|
|
|
static std::regex declareRegex(
|
|
|
|
"^declare -x (" + varNameRegex + ")" +
|
|
|
|
R"re((?:="((?:[^"\\]|\\.)*)")?\n)re");
|
|
|
|
|
|
|
|
static std::string simpleStringRegex =
|
2019-11-12 11:45:48 +00:00
|
|
|
R"re((?:[a-zA-Z0-9_/:\.\-\+=]*))re";
|
2019-09-27 15:01:25 +00:00
|
|
|
|
|
|
|
static std::string quotedStringRegex =
|
2019-11-12 11:45:48 +00:00
|
|
|
R"re((?:\$?'(?:[^'\\]|\\[abeEfnrtv\\'"?])*'))re";
|
2019-09-27 15:01:25 +00:00
|
|
|
|
|
|
|
static std::string arrayRegex =
|
|
|
|
R"re((?:\(( *\[[^\]]+\]="(?:[^"\\]|\\.)*")*\)))re";
|
|
|
|
|
|
|
|
static std::regex varRegex(
|
|
|
|
"^(" + varNameRegex + ")=(" + simpleStringRegex + "|" + quotedStringRegex + "|" + arrayRegex + ")\n");
|
|
|
|
|
|
|
|
static std::regex functionRegex(
|
|
|
|
"^" + varNameRegex + " \\(\\) *\n");
|
|
|
|
|
|
|
|
while (pos != file.end()) {
|
|
|
|
|
|
|
|
std::smatch match;
|
|
|
|
|
|
|
|
if (std::regex_search(pos, file.cend(), match, declareRegex)) {
|
|
|
|
pos = match[0].second;
|
|
|
|
exported.insert(match[1]);
|
2019-05-02 18:22:14 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
else if (std::regex_search(pos, file.cend(), match, varRegex)) {
|
|
|
|
pos = match[0].second;
|
|
|
|
res.env.insert({match[1], Var { (bool) exported.count(match[1]), match[2] }});
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (std::regex_search(pos, file.cend(), match, functionRegex)) {
|
|
|
|
res.bashFunctions = std::string(pos, file.cend());
|
|
|
|
break;
|
2019-05-02 18:22:14 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
else throw Error("shell environment '%s' has unexpected line '%s'",
|
|
|
|
path, file.substr(pos - file.cbegin(), 60));
|
2019-05-02 18:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Given an existing derivation, return the shell environment as
|
|
|
|
initialised by stdenv's setup script. We do this by building a
|
|
|
|
modified derivation with the same dependencies and nearly the same
|
|
|
|
initial environment variables, that just writes the resulting
|
|
|
|
environment to a file and exits. */
|
2019-12-11 13:53:30 +00:00
|
|
|
StorePath getDerivationEnvironment(ref<Store> store, Derivation drv)
|
2019-05-02 18:22:14 +00:00
|
|
|
{
|
|
|
|
auto builder = baseNameOf(drv.builder);
|
|
|
|
if (builder != "bash")
|
|
|
|
throw Error("'nix shell' only works on derivations that use 'bash' as their builder");
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
drv.args = {
|
|
|
|
"-c",
|
|
|
|
"set -e; "
|
|
|
|
"export IN_NIX_SHELL=impure; "
|
|
|
|
"export dontAddDisableDepTrack=1; "
|
|
|
|
"if [[ -n $stdenv ]]; then "
|
|
|
|
" source $stdenv/setup; "
|
|
|
|
"fi; "
|
|
|
|
"export > $out; "
|
|
|
|
"set >> $out "};
|
2019-05-02 18:22:14 +00:00
|
|
|
|
|
|
|
/* Remove derivation checks. */
|
|
|
|
drv.env.erase("allowedReferences");
|
|
|
|
drv.env.erase("allowedRequisites");
|
|
|
|
drv.env.erase("disallowedReferences");
|
|
|
|
drv.env.erase("disallowedRequisites");
|
|
|
|
|
|
|
|
// FIXME: handle structured attrs
|
|
|
|
|
|
|
|
/* Rehash and write the derivation. FIXME: would be nice to use
|
|
|
|
'buildDerivation', but that's privileged. */
|
|
|
|
auto drvName = drv.env["name"] + "-env";
|
|
|
|
for (auto & output : drv.outputs)
|
|
|
|
drv.env.erase(output.first);
|
|
|
|
drv.env["out"] = "";
|
|
|
|
drv.env["outputs"] = "out";
|
2019-12-11 13:53:30 +00:00
|
|
|
Hash h = hashDerivationModulo(*store, drv, true);
|
|
|
|
auto shellOutPath = store->makeOutputPath("out", h, drvName);
|
|
|
|
drv.outputs.insert_or_assign("out", DerivationOutput(shellOutPath.clone(), "", ""));
|
|
|
|
drv.env["out"] = store->printStorePath(shellOutPath);
|
|
|
|
auto shellDrvPath2 = writeDerivation(store, drv, drvName);
|
2019-05-02 18:22:14 +00:00
|
|
|
|
|
|
|
/* Build the derivation. */
|
|
|
|
store->buildPaths({shellDrvPath2});
|
|
|
|
|
|
|
|
assert(store->isValidPath(shellOutPath));
|
|
|
|
|
2019-07-12 14:10:58 +00:00
|
|
|
return shellOutPath;
|
2019-05-02 18:22:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-12 14:16:27 +00:00
|
|
|
struct Common : InstallableCommand, MixProfile
|
2019-05-02 18:22:14 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
std::set<string> keepVars{
|
|
|
|
"DISPLAY",
|
|
|
|
"HOME",
|
|
|
|
"IN_NIX_SHELL",
|
|
|
|
"LOGNAME",
|
|
|
|
"NIX_BUILD_SHELL",
|
|
|
|
"PAGER",
|
|
|
|
"PATH",
|
|
|
|
"TERM",
|
|
|
|
"TZ",
|
|
|
|
"USER",
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
|
|
|
std::set<string> ignoreVars{
|
|
|
|
"BASHOPTS",
|
|
|
|
"EUID",
|
2019-05-02 19:28:52 +00:00
|
|
|
"HOME", // FIXME: don't ignore in pure mode?
|
2019-05-02 18:22:14 +00:00
|
|
|
"NIX_BUILD_TOP",
|
2019-05-02 19:10:13 +00:00
|
|
|
"NIX_ENFORCE_PURITY",
|
2019-10-02 08:52:56 +00:00
|
|
|
"NIX_LOG_FD",
|
2019-05-02 18:22:14 +00:00
|
|
|
"PPID",
|
|
|
|
"PWD",
|
|
|
|
"SHELLOPTS",
|
|
|
|
"SHLVL",
|
2019-05-10 23:50:28 +00:00
|
|
|
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
|
2019-05-02 18:22:14 +00:00
|
|
|
"TEMP",
|
|
|
|
"TEMPDIR",
|
2019-05-08 15:07:35 +00:00
|
|
|
"TERM",
|
2019-05-02 18:22:14 +00:00
|
|
|
"TMP",
|
|
|
|
"TMPDIR",
|
2019-05-02 19:28:52 +00:00
|
|
|
"TZ",
|
2019-05-02 18:22:14 +00:00
|
|
|
"UID",
|
|
|
|
};
|
|
|
|
|
|
|
|
void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out)
|
|
|
|
{
|
2019-05-02 19:28:52 +00:00
|
|
|
out << "nix_saved_PATH=\"$PATH\"\n";
|
|
|
|
|
2019-05-02 18:22:14 +00:00
|
|
|
for (auto & i : buildEnvironment.env) {
|
2019-09-27 15:01:25 +00:00
|
|
|
if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_")) {
|
|
|
|
out << fmt("%s=%s\n", i.first, i.second.value);
|
|
|
|
if (i.second.exported)
|
|
|
|
out << fmt("export %s\n", i.first);
|
|
|
|
}
|
2019-05-02 18:22:14 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 19:28:52 +00:00
|
|
|
out << "PATH=\"$PATH:$nix_saved_PATH\"\n";
|
|
|
|
|
2019-09-27 15:01:25 +00:00
|
|
|
out << buildEnvironment.bashFunctions << "\n";
|
2019-05-02 18:22:14 +00:00
|
|
|
|
|
|
|
// FIXME: set outputs
|
|
|
|
|
|
|
|
out << "export NIX_BUILD_TOP=\"$(mktemp -d --tmpdir nix-shell.XXXXXX)\"\n";
|
|
|
|
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
|
|
|
|
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
|
2019-05-02 19:13:19 +00:00
|
|
|
|
|
|
|
out << "eval \"$shellHook\"\n";
|
2019-05-02 18:22:14 +00:00
|
|
|
}
|
2019-05-02 19:10:13 +00:00
|
|
|
|
|
|
|
Strings getDefaultFlakeAttrPaths() override
|
|
|
|
{
|
Support non-x86_64-linux system types in flakes
A command like
$ nix run nixpkgs#hello
will now build the attribute 'packages.${system}.hello' rather than
'packages.hello'. Note that this does mean that the flake needs to
export an attribute for every system type it supports, and you can't
build on unsupported systems. So 'packages' typically looks like this:
packages = nixpkgs.lib.genAttrs ["x86_64-linux" "i686-linux"] (system: {
hello = ...;
});
The 'checks', 'defaultPackage', 'devShell', 'apps' and 'defaultApp'
outputs similarly are now attrsets that map system types to
derivations/apps. 'nix flake check' checks that the derivations for
all platforms evaluate correctly, but only builds the derivations in
'checks.${system}'.
Fixes #2861. (That issue also talks about access to ~/.config/nixpkgs
and --arg, but I think it's reasonable to say that flakes shouldn't
support those.)
The alternative to attribute selection is to pass the system type as
an argument to the flake's 'outputs' function, e.g. 'outputs = { self,
nixpkgs, system }: ...'. However, that approach would be at odds with
hermetic evaluation and make it impossible to enumerate the packages
provided by a flake.
2019-10-15 15:52:10 +00:00
|
|
|
return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()};
|
2019-05-02 19:10:13 +00:00
|
|
|
}
|
2019-07-12 14:16:27 +00:00
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
StorePath getShellOutPath(ref<Store> store)
|
2019-07-12 14:16:27 +00:00
|
|
|
{
|
2019-07-12 14:28:39 +00:00
|
|
|
auto path = installable->getStorePath();
|
2019-12-11 13:53:30 +00:00
|
|
|
if (path && hasSuffix(path->to_string(), "-env"))
|
|
|
|
return path->clone();
|
2019-07-12 14:28:39 +00:00
|
|
|
else {
|
|
|
|
auto drvs = toDerivations(store, {installable});
|
|
|
|
|
|
|
|
if (drvs.size() != 1)
|
|
|
|
throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
|
|
|
|
installable->what(), drvs.size());
|
2019-07-12 14:16:27 +00:00
|
|
|
|
2019-07-12 14:28:39 +00:00
|
|
|
auto & drvPath = *drvs.begin();
|
2019-07-12 14:16:27 +00:00
|
|
|
|
2019-07-12 14:28:39 +00:00
|
|
|
return getDerivationEnvironment(store, store->derivationFromPath(drvPath));
|
|
|
|
}
|
|
|
|
}
|
2019-07-12 14:16:27 +00:00
|
|
|
|
2019-07-12 14:28:39 +00:00
|
|
|
BuildEnvironment getBuildEnvironment(ref<Store> store)
|
|
|
|
{
|
|
|
|
auto shellOutPath = getShellOutPath(store);
|
2019-07-12 14:16:27 +00:00
|
|
|
|
|
|
|
updateProfile(shellOutPath);
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
return readEnvironment(store->printStorePath(shellOutPath));
|
2019-07-12 14:16:27 +00:00
|
|
|
}
|
2019-05-02 18:22:14 +00:00
|
|
|
};
|
|
|
|
|
2019-11-07 23:18:31 +00:00
|
|
|
struct CmdDevShell : Common, MixEnvironment
|
2019-05-02 18:22:14 +00:00
|
|
|
{
|
2020-02-27 14:17:37 +00:00
|
|
|
std::vector<std::string> command;
|
|
|
|
|
|
|
|
CmdDevShell()
|
|
|
|
{
|
|
|
|
mkFlag()
|
|
|
|
.longName("command")
|
|
|
|
.shortName('c')
|
|
|
|
.description("command and arguments to be executed insted of an interactive shell")
|
|
|
|
.labels({"command", "args"})
|
|
|
|
.arity(ArityAny)
|
|
|
|
.handler([&](std::vector<std::string> ss) {
|
|
|
|
if (ss.empty()) throw UsageError("--command requires at least one argument");
|
|
|
|
command = ss;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-05-02 18:22:14 +00:00
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "run a bash shell that provides the build environment of a derivation";
|
|
|
|
}
|
|
|
|
|
|
|
|
Examples examples() override
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
Example{
|
|
|
|
"To get the build environment of GNU hello:",
|
|
|
|
"nix dev-shell nixpkgs:hello"
|
|
|
|
},
|
|
|
|
Example{
|
|
|
|
"To get the build environment of the default package of flake in the current directory:",
|
|
|
|
"nix dev-shell"
|
|
|
|
},
|
2019-07-12 14:10:58 +00:00
|
|
|
Example{
|
|
|
|
"To store the build environment in a profile:",
|
2019-07-12 14:28:39 +00:00
|
|
|
"nix dev-shell --profile /tmp/my-shell nixpkgs:hello"
|
|
|
|
},
|
|
|
|
Example{
|
|
|
|
"To use a build environment previously recorded in a profile:",
|
|
|
|
"nix dev-shell /tmp/my-shell"
|
2019-07-12 14:10:58 +00:00
|
|
|
},
|
2019-05-02 18:22:14 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-07-12 14:16:27 +00:00
|
|
|
auto buildEnvironment = getBuildEnvironment(store);
|
2019-05-02 18:22:14 +00:00
|
|
|
|
|
|
|
auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");
|
|
|
|
|
|
|
|
std::ostringstream ss;
|
|
|
|
makeRcScript(buildEnvironment, ss);
|
|
|
|
|
|
|
|
ss << fmt("rm -f '%s'\n", rcFilePath);
|
|
|
|
|
2020-02-27 14:17:37 +00:00
|
|
|
if (!command.empty()) {
|
|
|
|
std::vector<std::string> args;
|
|
|
|
for (auto s : command)
|
|
|
|
args.push_back(shellEscape(s));
|
|
|
|
ss << fmt("exec %s\n", concatStringsSep(" ", args));
|
|
|
|
}
|
|
|
|
|
2019-05-02 18:22:14 +00:00
|
|
|
writeFull(rcFileFd.get(), ss.str());
|
|
|
|
|
|
|
|
stopProgressBar();
|
|
|
|
|
2019-12-03 23:31:09 +00:00
|
|
|
auto shell = getEnv("SHELL").value_or("bash");
|
2019-05-02 18:22:14 +00:00
|
|
|
|
2019-11-07 23:18:31 +00:00
|
|
|
setEnviron();
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
auto args = Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath};
|
2019-05-02 18:22:14 +00:00
|
|
|
|
|
|
|
restoreAffinity();
|
|
|
|
restoreSignals();
|
|
|
|
|
|
|
|
execvp(shell.c_str(), stringsToCharPtrs(args).data());
|
|
|
|
|
|
|
|
throw SysError("executing shell '%s'", shell);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CmdPrintDevEnv : Common
|
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "print shell code that can be sourced by bash to reproduce the build environment of a derivation";
|
|
|
|
}
|
|
|
|
|
|
|
|
Examples examples() override
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
Example{
|
|
|
|
"To apply the build environment of GNU hello to the current shell:",
|
|
|
|
". <(nix print-dev-env nixpkgs:hello)"
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-07-12 14:16:27 +00:00
|
|
|
auto buildEnvironment = getBuildEnvironment(store);
|
2019-05-02 18:22:14 +00:00
|
|
|
|
|
|
|
stopProgressBar();
|
|
|
|
|
|
|
|
makeRcScript(buildEnvironment, std::cout);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-06-18 14:01:35 +00:00
|
|
|
static auto r1 = registerCommand<CmdPrintDevEnv>("print-dev-env");
|
|
|
|
static auto r2 = registerCommand<CmdDevShell>("dev-shell");
|