aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libstore/build.cc2
-rw-r--r--src/libstore/store-api.cc7
-rw-r--r--src/libutil/util.cc33
-rw-r--r--src/libutil/util.hh34
-rw-r--r--src/nix/command.hh5
-rw-r--r--src/nix/installables.cc42
-rw-r--r--src/nix/shell.cc281
7 files changed, 358 insertions, 46 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 53a0c743b..9730c75e2 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -1805,7 +1805,7 @@ void DerivationGoal::startBuilder()
concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()),
drvPath,
settings.thisSystem,
- concatStringsSep(", ", settings.systemFeatures));
+ concatStringsSep<StringSet>(", ", settings.systemFeatures));
if (drv->isBuiltin())
preloadNSS();
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index c13ff1156..8fabeeea4 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -726,12 +726,7 @@ ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven)
string showPaths(const PathSet & paths)
{
- string s;
- for (auto & i : paths) {
- if (s.size() != 0) s += ", ";
- s += "'" + i + "'";
- }
- return s;
+ return concatStringsSep(", ", quoteStrings(paths));
}
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index f4f86c5c8..75b73fcfa 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -461,6 +461,17 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
}
+std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
+{
+ Path tmpl(getEnv("TMPDIR", "/tmp") + "/" + prefix + ".XXXXXX");
+ // Strictly speaking, this is UB, but who cares...
+ AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
+ if (!fd)
+ throw SysError("creating temporary file '%s'", tmpl);
+ return {std::move(fd), tmpl};
+}
+
+
static Lazy<Path> getHome2([]() {
Path homeDir = getEnv("HOME");
if (homeDir.empty()) {
@@ -1167,28 +1178,6 @@ template StringSet tokenizeString(const string & s, const string & separators);
template vector<string> tokenizeString(const string & s, const string & separators);
-string concatStringsSep(const string & sep, const Strings & ss)
-{
- string s;
- for (auto & i : ss) {
- if (s.size() != 0) s += sep;
- s += i;
- }
- return s;
-}
-
-
-string concatStringsSep(const string & sep, const StringSet & ss)
-{
- string s;
- for (auto & i : ss) {
- if (s.size() != 0) s += sep;
- s += i;
- }
- return s;
-}
-
-
string chomp(const string & s)
{
size_t i = s.find_last_not_of(" \n\r\t");
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 9f239bff3..6c9d7c2eb 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -118,10 +118,6 @@ void deletePath(const Path & path);
void deletePath(const Path & path, unsigned long long & bytesFreed);
-/* Create a temporary directory. */
-Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
- bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
-
/* Return $HOME or the user's home directory from /etc/passwd. */
Path getHome();
@@ -199,6 +195,14 @@ public:
};
+/* Create a temporary directory. */
+Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
+ bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
+
+/* Create a temporary file, returning a file handle and its path. */
+std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
+
+
class Pipe
{
public:
@@ -334,8 +338,26 @@ template<class C> C tokenizeString(const string & s, const string & separators =
/* Concatenate the given strings with a separator between the
elements. */
-string concatStringsSep(const string & sep, const Strings & ss);
-string concatStringsSep(const string & sep, const StringSet & ss);
+template<class C>
+string concatStringsSep(const string & sep, const C & ss)
+{
+ string s;
+ for (auto & i : ss) {
+ if (s.size() != 0) s += sep;
+ s += i;
+ }
+ return s;
+}
+
+
+/* Add quotes around a collection of strings. */
+template<class C> Strings quoteStrings(const C & c)
+{
+ Strings res;
+ for (auto & s : c)
+ res.push_back("'" + s + "'");
+ return res;
+}
/* Remove trailing whitespace from a string. */
diff --git a/src/nix/command.hh b/src/nix/command.hh
index 6d43261ac..640c6cd16 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -88,6 +88,11 @@ struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
std::shared_ptr<Installable> parseInstallable(
ref<Store> store, const std::string & installable);
+ virtual Strings getDefaultFlakeAttrPaths()
+ {
+ return {"defaultPackage"};
+ }
+
private:
std::shared_ptr<EvalState> evalState;
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index c3ca87aa7..db67952e1 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -142,13 +142,18 @@ struct InstallableAttrPath : InstallableValue
struct InstallableFlake : InstallableValue
{
FlakeRef flakeRef;
- std::string attrPath;
+ Strings attrPaths;
+ bool searchPackages = false;
+
+ InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef, Strings attrPaths)
+ : InstallableValue(cmd), flakeRef(flakeRef), attrPaths(std::move(attrPaths))
+ { }
- InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef, const std::string & attrPath)
- : InstallableValue(cmd), flakeRef(flakeRef), attrPath(attrPath)
+ InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef, std::string attrPath)
+ : InstallableValue(cmd), flakeRef(flakeRef), attrPaths{attrPath}, searchPackages(true)
{ }
- std::string what() override { return flakeRef.to_string() + ":" + attrPath; }
+ std::string what() override { return flakeRef.to_string() + ":" + *attrPaths.begin(); }
Value * toValue(EvalState & state) override
{
@@ -166,18 +171,31 @@ struct InstallableFlake : InstallableValue
auto emptyArgs = state.allocBindings(0);
- if (auto aPackages = *vProvides->attrs->get(state.symbols.create("packages"))) {
+ // As a convenience, look for the attribute in
+ // 'provides.packages'.
+ if (searchPackages) {
+ if (auto aPackages = *vProvides->attrs->get(state.symbols.create("packages"))) {
+ try {
+ auto * v = findAlongAttrPath(state, *attrPaths.begin(), *emptyArgs, *aPackages->value);
+ state.forceValue(*v);
+ return v;
+ } catch (AttrPathNotFound & e) {
+ }
+ }
+ }
+
+ // Otherwise, look for it in 'provides'.
+ for (auto & attrPath : attrPaths) {
try {
- auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *aPackages->value);
+ auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *vProvides);
state.forceValue(*v);
return v;
} catch (AttrPathNotFound & e) {
}
}
- auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *vProvides);
- state.forceValue(*v);
- return v;
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
}
};
@@ -216,7 +234,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
else if (hasPrefix(s, "nixpkgs.")) {
bool static warned;
warnOnce(warned, "the syntax 'nixpkgs.<attr>' is deprecated; use 'nixpkgs:<attr>' instead");
- result.push_back(std::make_shared<InstallableFlake>(*this, FlakeRef("nixpkgs"), std::string(s, 8)));
+ result.push_back(std::make_shared<InstallableFlake>(*this, FlakeRef("nixpkgs"),
+ Strings{"packages." + std::string(s, 8)}));
}
else if ((colon = s.rfind(':')) != std::string::npos) {
@@ -233,7 +252,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
if (storePath != "")
result.push_back(std::make_shared<InstallableStorePath>(storePath));
else
- result.push_back(std::make_shared<InstallableFlake>(*this, FlakeRef(s, true), "defaultPackage"));
+ result.push_back(std::make_shared<InstallableFlake>(*this, FlakeRef(s, true),
+ getDefaultFlakeAttrPaths()));
}
else
diff --git a/src/nix/shell.cc b/src/nix/shell.cc
new file mode 100644
index 000000000..0813d122c
--- /dev/null
+++ b/src/nix/shell.cc
@@ -0,0 +1,281 @@
+#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"
+
+using namespace nix;
+
+struct BuildEnvironment
+{
+ // FIXME: figure out which vars should be exported.
+ std::map<std::string, std::string> env;
+ std::map<std::string, std::string> functions;
+};
+
+BuildEnvironment readEnvironment(const Path & path)
+{
+ BuildEnvironment res;
+
+ auto lines = tokenizeString<Strings>(readFile(path), "\n");
+
+ auto getLine =
+ [&]() {
+ if (lines.empty())
+ throw Error("shell environment '%s' ends unexpectedly", path);
+ auto line = lines.front();
+ lines.pop_front();
+ return line;
+ };
+
+ while (!lines.empty()) {
+ auto line = getLine();
+
+ auto eq = line.find('=');
+ if (eq != std::string::npos) {
+ std::string name(line, 0, eq);
+ std::string value(line, eq + 1);
+ // FIXME: parse arrays
+ res.env.insert({name, value});
+ }
+
+ else if (hasSuffix(line, " () ")) {
+ std::string name(line, 0, line.size() - 4);
+ // FIXME: validate name
+ auto l = getLine();
+ if (l != "{ ") throw Error("shell environment '%s' has unexpected line '%s'", path, l);
+ std::string body;
+ while ((l = getLine()) != "}") {
+ body += l;
+ body += '\n';
+ }
+ res.functions.insert({name, body});
+ }
+
+ else throw Error("shell environment '%s' has unexpected line '%s'", path, line);
+ }
+
+ 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. */
+BuildEnvironment getDerivationEnvironment(ref<Store> store, Derivation drv)
+{
+ auto builder = baseNameOf(drv.builder);
+ if (builder != "bash")
+ throw Error("'nix shell' only works on derivations that use 'bash' as their builder");
+
+ drv.args = {"-c", "set -e; if [[ -n $stdenv ]]; then source $stdenv/setup; fi; set > $out"};
+
+ /* 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";
+ drv.outputs["out"] = DerivationOutput("", "", "");
+ Hash h = hashDerivationModulo(*store, drv);
+ Path shellOutPath = store->makeOutputPath("out", h, drvName);
+ drv.outputs["out"].path = shellOutPath;
+ drv.env["out"] = shellOutPath;
+ Path shellDrvPath2 = writeDerivation(store, drv, drvName);
+
+ /* Build the derivation. */
+ store->buildPaths({shellDrvPath2});
+
+ assert(store->isValidPath(shellOutPath));
+
+ return readEnvironment(shellOutPath);
+}
+
+struct Common : InstallableCommand
+{
+ /*
+ std::set<string> keepVars{
+ "DISPLAY",
+ "HOME",
+ "IN_NIX_SHELL",
+ "LOGNAME",
+ "NIX_BUILD_SHELL",
+ "PAGER",
+ "PATH",
+ "TERM",
+ "TZ",
+ "USER",
+ };
+ */
+
+ std::set<string> ignoreVars{
+ "BASHOPTS",
+ "EUID",
+ "HOME", // FIXME: don't ignore in pure mode?
+ "NIX_BUILD_TOP",
+ "NIX_ENFORCE_PURITY",
+ "PPID",
+ "PWD",
+ "SHELLOPTS",
+ "SHLVL",
+ "TEMP",
+ "TEMPDIR",
+ "TMP",
+ "TMPDIR",
+ "TZ",
+ "UID",
+ };
+
+ void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out)
+ {
+ out << "export IN_NIX_SHELL=1\n";
+ out << "nix_saved_PATH=\"$PATH\"\n";
+
+ for (auto & i : buildEnvironment.env) {
+ // FIXME: shellEscape
+ // FIXME: figure out what to export
+ // FIXME: handle arrays
+ if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_"))
+ out << fmt("export %s=%s\n", i.first, i.second);
+ }
+
+ out << "PATH=\"$PATH:$nix_saved_PATH\"\n";
+
+ for (auto & i : buildEnvironment.functions) {
+ out << fmt("%s () {\n%s\n}\n", i.first, i.second);
+ }
+
+ // 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);
+
+ out << "eval \"$shellHook\"\n";
+ }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {"devShell", "defaultPackage"};
+ }
+};
+
+struct CmdDevShell : Common
+{
+
+ std::string name() override
+ {
+ return "dev-shell";
+ }
+
+ 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"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ 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());
+
+ auto & drvPath = *drvs.begin();
+
+ auto buildEnvironment = getDerivationEnvironment(store, store->derivationFromPath(drvPath));
+
+ auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");
+
+ std::ostringstream ss;
+ makeRcScript(buildEnvironment, ss);
+
+ ss << fmt("rm -f '%s'\n", rcFilePath);
+
+ writeFull(rcFileFd.get(), ss.str());
+
+ stopProgressBar();
+
+ auto shell = getEnv("SHELL", "bash");
+
+ auto args = Strings{baseNameOf(shell), "--rcfile", rcFilePath};
+
+ restoreAffinity();
+ restoreSignals();
+
+ execvp(shell.c_str(), stringsToCharPtrs(args).data());
+
+ throw SysError("executing shell '%s'", shell);
+ }
+};
+
+struct CmdPrintDevEnv : Common
+{
+
+ std::string name() override
+ {
+ return "print-dev-env";
+ }
+
+ 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
+ {
+ 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());
+
+ auto & drvPath = *drvs.begin();
+
+ auto buildEnvironment = getDerivationEnvironment(store, store->derivationFromPath(drvPath));
+
+ stopProgressBar();
+
+ makeRcScript(buildEnvironment, std::cout);
+ }
+};
+
+static RegisterCommand r1(make_ref<CmdPrintDevEnv>());
+static RegisterCommand r2(make_ref<CmdDevShell>());