diff options
Diffstat (limited to 'src/nix/develop.cc')
-rw-r--r-- | src/nix/develop.cc | 191 |
1 files changed, 147 insertions, 44 deletions
diff --git a/src/nix/develop.cc b/src/nix/develop.cc index eb93f56fc..a46ea39b6 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -15,7 +15,7 @@ struct Var { bool exported = true; bool associative = false; - std::string value; // quoted string or array + std::string quoted; // quoted string or array }; struct BuildEnvironment @@ -68,22 +68,22 @@ BuildEnvironment readEnvironment(const Path & path) std::smatch match; - if (std::regex_search(pos, file.cend(), match, declareRegex)) { + if (std::regex_search(pos, file.cend(), match, declareRegex, std::regex_constants::match_continuous)) { pos = match[0].second; exported.insert(match[1]); } - else if (std::regex_search(pos, file.cend(), match, varRegex)) { + else if (std::regex_search(pos, file.cend(), match, varRegex, std::regex_constants::match_continuous)) { pos = match[0].second; - res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .value = match[2] }}); + res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .quoted = match[2] }}); } - else if (std::regex_search(pos, file.cend(), match, assocArrayRegex)) { + else if (std::regex_search(pos, file.cend(), match, assocArrayRegex, std::regex_constants::match_continuous)) { pos = match[0].second; - res.env.insert({match[1], Var { .associative = true, .value = match[2] }}); + res.env.insert({match[1], Var { .associative = true, .quoted = match[2] }}); } - else if (std::regex_search(pos, file.cend(), match, functionRegex)) { + else if (std::regex_search(pos, file.cend(), match, functionRegex, std::regex_constants::match_continuous)) { res.bashFunctions = std::string(pos, file.cend()); break; } @@ -92,6 +92,8 @@ BuildEnvironment readEnvironment(const Path & path) path, file.substr(pos - file.cbegin(), 60)); } + res.env.erase("__output"); + return res; } @@ -124,27 +126,36 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath) /* Rehash and write the derivation. FIXME: would be nice to use 'buildDerivation', but that's privileged. */ - auto drvName = std::string(drvPath.name()); - assert(hasSuffix(drvName, ".drv")); - drvName.resize(drvName.size() - 4); - drvName += "-env"; - for (auto & output : drv.outputs) - drv.env.erase(output.first); - drv.env["out"] = ""; - drv.env["outputs"] = "out"; + drv.name += "-env"; + for (auto & output : drv.outputs) { + output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } }; + drv.env[output.first] = ""; + } drv.inputSrcs.insert(std::move(getEnvShPath)); - Hash h = hashDerivationModulo(*store, drv, true); - auto shellOutPath = store->makeOutputPath("out", h, drvName); - drv.outputs.insert_or_assign("out", DerivationOutput { .path = shellOutPath }); - drv.env["out"] = store->printStorePath(shellOutPath); - auto shellDrvPath2 = writeDerivation(store, drv, drvName); + Hash h = std::get<0>(hashDerivationModulo(*store, drv, true)); - /* Build the derivation. */ - store->buildPaths({{shellDrvPath2}}); + for (auto & output : drv.outputs) { + auto outPath = store->makeOutputPath(output.first, h, drv.name); + output.second = { .output = DerivationOutputInputAddressed { .path = outPath } }; + drv.env[output.first] = store->printStorePath(outPath); + } - assert(store->isValidPath(shellOutPath)); + auto shellDrvPath = writeDerivation(*store, drv); - return shellOutPath; + /* Build the derivation. */ + store->buildPaths({{shellDrvPath}}); + + for (auto & [_0, outputAndOptPath] : drv.outputsAndOptPaths(*store)) { + auto & [_1, optPath] = outputAndOptPath; + assert(optPath); + auto & outPath = *optPath; + assert(store->isValidPath(outPath)); + auto outPathS = store->toRealPath(outPath); + if (lstat(outPathS).st_size) + return outPath; + } + + throw Error("get-env.sh failed to produce an environment"); } struct Common : InstallableCommand, MixProfile @@ -170,8 +181,12 @@ struct Common : InstallableCommand, MixProfile "UID", }; - void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out) + std::string makeRcScript( + const BuildEnvironment & buildEnvironment, + const Path & outputsDir = absPath(".") + "/outputs") { + std::ostringstream out; + out << "unset shellHook\n"; out << "nix_saved_PATH=\"$PATH\"\n"; @@ -179,9 +194,9 @@ struct Common : InstallableCommand, MixProfile for (auto & i : buildEnvironment.env) { if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_")) { if (i.second.associative) - out << fmt("declare -A %s=(%s)\n", i.first, i.second.value); + out << fmt("declare -A %s=(%s)\n", i.first, i.second.quoted); else { - out << fmt("%s=%s\n", i.first, i.second.value); + out << fmt("%s=%s\n", i.first, i.second.quoted); if (i.second.exported) out << fmt("export %s\n", i.first); } @@ -192,13 +207,31 @@ struct Common : InstallableCommand, MixProfile out << buildEnvironment.bashFunctions << "\n"; - // 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"; + + /* Substitute occurrences of output paths. */ + auto outputs = buildEnvironment.env.find("outputs"); + assert(outputs != buildEnvironment.env.end()); + + // FIXME: properly unquote 'outputs'. + StringMap rewrites; + for (auto & outputName : tokenizeString<std::vector<std::string>>(replaceStrings(outputs->second.quoted, "'", ""))) { + auto from = buildEnvironment.env.find(outputName); + assert(from != buildEnvironment.env.end()); + // FIXME: unquote + rewrites.insert({from->second.quoted, outputsDir + "/" + outputName}); + } + + return rewriteStrings(out.str(), rewrites); + } + + Strings getDefaultFlakeAttrPaths() override + { + return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()}; } StorePath getShellOutPath(ref<Store> store) @@ -234,19 +267,57 @@ struct Common : InstallableCommand, MixProfile struct CmdDevelop : Common, MixEnvironment { std::vector<std::string> command; + std::optional<std::string> phase; CmdDevelop() { addFlag({ .longName = "command", .shortName = 'c', - .description = "command and arguments to be executed insted of an interactive shell", + .description = "command and arguments to be executed instead of an interactive shell", .labels = {"command", "args"}, .handler = {[&](std::vector<std::string> ss) { if (ss.empty()) throw UsageError("--command requires at least one argument"); command = ss; }} }); + + addFlag({ + .longName = "phase", + .description = "phase to run (e.g. `build` or `configure`)", + .labels = {"phase-name"}, + .handler = {&phase}, + }); + + addFlag({ + .longName = "configure", + .description = "run the configure phase", + .handler = {&phase, {"configure"}}, + }); + + addFlag({ + .longName = "build", + .description = "run the build phase", + .handler = {&phase, {"build"}}, + }); + + addFlag({ + .longName = "check", + .description = "run the check phase", + .handler = {&phase, {"check"}}, + }); + + addFlag({ + .longName = "install", + .description = "run the install phase", + .handler = {&phase, {"install"}}, + }); + + addFlag({ + .longName = "installcheck", + .description = "run the installcheck phase", + .handler = {&phase, {"installCheck"}}, + }); } std::string description() override @@ -259,11 +330,15 @@ struct CmdDevelop : Common, MixEnvironment return { Example{ "To get the build environment of GNU hello:", - "nix develop nixpkgs.hello" + "nix develop nixpkgs#hello" + }, + Example{ + "To get the build environment of the default package of flake in the current directory:", + "nix develop" }, Example{ "To store the build environment in a profile:", - "nix develop --profile /tmp/my-shell nixpkgs.hello" + "nix develop --profile /tmp/my-shell nixpkgs#hello" }, Example{ "To use a build environment previously recorded in a profile:", @@ -278,28 +353,56 @@ struct CmdDevelop : Common, MixEnvironment auto [rcFileFd, rcFilePath] = createTempFile("nix-shell"); - std::ostringstream ss; - makeRcScript(buildEnvironment, ss); + auto script = makeRcScript(buildEnvironment); + + if (verbosity >= lvlDebug) + script += "set -x\n"; - ss << fmt("rm -f '%s'\n", rcFilePath); + script += fmt("rm -f '%s'\n", rcFilePath); - if (!command.empty()) { + if (phase) { + if (!command.empty()) + throw UsageError("you cannot use both '--command' and '--phase'"); + // FIXME: foundMakefile is set by buildPhase, need to get + // rid of that. + script += fmt("foundMakefile=1\n"); + script += fmt("runHook %1%Phase\n", *phase); + script += fmt("exit 0\n", *phase); + } + + else if (!command.empty()) { std::vector<std::string> args; for (auto s : command) args.push_back(shellEscape(s)); - ss << fmt("exec %s\n", concatStringsSep(" ", args)); + script += fmt("exec %s\n", concatStringsSep(" ", args)); } - writeFull(rcFileFd.get(), ss.str()); + writeFull(rcFileFd.get(), script); stopProgressBar(); - auto shell = getEnv("SHELL").value_or("bash"); - setEnviron(); // prevent garbage collection until shell exits setenv("NIX_GCROOT", gcroot.data(), 1); + Path shell = "bash"; + + try { + auto state = getEvalState(); + + auto bashInstallable = std::make_shared<InstallableFlake>( + state, + installable->nixpkgsFlakeRef(), + Strings{"bashInteractive"}, + Strings{"legacyPackages." + settings.thisSystem.get() + "."}, + lockFlags); + + shell = state->store->printStorePath( + toStorePath(state->store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash"; + } catch (Error &) { + ignoreException(); + } + auto args = Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath}; restoreAffinity(); @@ -323,7 +426,7 @@ struct CmdPrintDevEnv : Common return { Example{ "To apply the build environment of GNU hello to the current shell:", - ". <(nix print-dev-env nixpkgs.hello)" + ". <(nix print-dev-env nixpkgs#hello)" }, }; } @@ -336,9 +439,9 @@ struct CmdPrintDevEnv : Common stopProgressBar(); - makeRcScript(buildEnvironment, std::cout); + std::cout << makeRcScript(buildEnvironment); } }; -static auto r1 = registerCommand<CmdPrintDevEnv>("print-dev-env"); -static auto r2 = registerCommand<CmdDevelop>("develop"); +static auto rCmdPrintDevEnv = registerCommand<CmdPrintDevEnv>("print-dev-env"); +static auto rCmdDevelop = registerCommand<CmdDevelop>("develop"); |