lix/src/nix-build/nix-build.cc
Eelco Dolstra 5039d3b9de Write "T x" instead of "auto x = T"
That's just silly. Hopefully this also fixes the Debian build failure:

http://hydra.nixos.org/build/40371644
2016-09-12 12:06:13 +02:00

480 lines
18 KiB
C++
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <cstring>
#include <regex>
#include "util.hh"
#include <unistd.h>
#include "shared.hh"
#include <sstream>
#include <vector>
#include <iostream>
#include <fstream>
#include "store-api.hh"
#include "globals.hh"
#include "derivations.hh"
using namespace nix;
using std::stringstream;
extern char ** environ;
/* Recreate the effect of the perl shellwords function, breaking up a
* string into arguments like a shell word, including escapes
*/
std::vector<string> shellwords(const string & s)
{
std::regex whitespace("^(\\s+).*");
auto begin = s.cbegin();
std::vector<string> res;
std::stringstream cur;
enum state {
sBegin,
sQuote
};
state st = sBegin;
auto it = begin;
for (; it != s.cend(); ++it) {
if (st == sBegin) {
std::smatch match;
if (regex_search(it, s.cend(), match, whitespace)) {
cur << string(begin, it);
res.push_back(cur.str());
cur = stringstream{};
it = match[1].second;
begin = it;
}
}
switch (*it) {
case '"':
cur << string(begin, it);
begin = it + 1;
st = st == sBegin ? sQuote : sBegin;
break;
case '\\':
/* perl shellwords mostly just treats the next char as part of the string with no special processing */
cur << string(begin, it);
begin = ++it;
break;
}
}
cur << string(begin, it);
auto last = cur.str();
if (!last.empty()) {
res.push_back(std::move(last));
}
return res;
}
int main(int argc, char ** argv)
{
return handleExceptions(argv[0], [&]() {
initNix();
auto store = openStore();
auto dryRun = false;
auto verbose = false;
auto runEnv = std::regex_search(argv[0], std::regex("nix-shell$"));
auto pure = false;
auto fromArgs = false;
auto packages = false;
auto interactive = true;
Strings instArgs;
Strings buildArgs;
Strings exprs;
auto shell = getEnv("SHELL", "/bin/sh");
std::string envCommand; // interactive shell
Strings envExclude;
auto myName = runEnv ? "nix-shell" : "nix-build";
auto inShebang = false;
std::string script;
std::vector<string> savedArgs;
AutoDelete tmpDir(createTempDir("", myName));
std::string outLink = "./result";
auto drvLink = (Path) tmpDir + "/derivation";
std::vector<string> args;
for (int i = 1; i < argc; ++i)
args.push_back(argv[i]);
// Heuristic to see if we're invoked as a shebang script, namely, if we
// have a single argument, it's the name of an executable file, and it
// starts with "#!".
if (runEnv && argc > 1 && !std::regex_search(argv[1], std::regex("nix-shell"))) {
script = argv[1];
if (access(script.c_str(), F_OK) == 0 && access(script.c_str(), X_OK) == 0) {
auto lines = tokenizeString<Strings>(readFile(script), "\n");
if (std::regex_search(lines.front(), std::regex("^#!"))) {
lines.pop_front();
inShebang = true;
for (int i = 2; i < argc - 1; ++i)
savedArgs.push_back(argv[i]);
std::vector<string> args;
for (auto line : lines) {
line = chomp(line);
std::smatch match;
if (std::regex_match(line, match, std::regex("^#!\\s*nix-shell (.*)$")))
for (const auto & word : shellwords(match[1].str()))
args.push_back(word);
}
}
}
}
for (size_t n = 0; n < args.size(); ++n) {
auto arg = args[n];
if (arg == "--help") {
deletePath(tmpDir);
tmpDir.cancel();
execlp("man", "man", myName, NULL);
throw SysError("executing man");
}
else if (arg == "--version") {
std::cout << myName << " (Nix) " << nixVersion << '\n';
return;
}
else if (arg == "--add-drv-link") {
drvLink = "./derivation";
}
else if (arg == "--no-out-link" || arg == "--no-link") {
outLink = (Path) tmpDir + "/result";
}
else if (arg == "--drv-link") {
n++;
if (n >= args.size()) {
throw UsageError("--drv-link requires an argument");
}
drvLink = args[n];
}
else if (arg == "--out-link" || arg == "-o") {
n++;
if (n >= args.size()) {
throw UsageError(format("%1% requires an argument") % arg);
}
outLink = args[n];
}
else if (arg == "--attr" || arg == "-A" || arg == "-I") {
n++;
if (n >= args.size()) {
throw UsageError(format("%1% requires an argument") % arg);
}
instArgs.push_back(arg);
instArgs.push_back(args[n]);
}
else if (arg == "--arg" || arg == "--argstr") {
if (n + 2 >= args.size()) {
throw UsageError(format("%1% requires two arguments") % arg);
}
instArgs.push_back(arg);
instArgs.push_back(args[n + 1]);
instArgs.push_back(args[n + 2]);
n += 2;
}
else if (arg == "--option") {
if (n + 2 >= args.size()) {
throw UsageError(format("%1% requires two arguments") % arg);
}
instArgs.push_back(arg);
instArgs.push_back(args[n + 1]);
instArgs.push_back(args[n + 2]);
buildArgs.push_back(arg);
buildArgs.push_back(args[n + 1]);
buildArgs.push_back(args[n + 2]);
n += 2;
}
else if (arg == "--max-jobs" || arg == "-j" || arg == "--max-silent-time" || arg == "--cores" || arg == "--timeout" || arg == "--add-root") {
n++;
if (n >= args.size()) {
throw UsageError(format("%1% requires an argument") % arg);
}
buildArgs.push_back(arg);
buildArgs.push_back(args[n]);
}
else if (arg == "--dry-run") {
buildArgs.push_back("--dry-run");
dryRun = true;
}
else if (arg == "--show-trace") {
instArgs.push_back(arg);
}
else if (arg == "-") {
exprs = Strings{"-"};
}
else if (arg == "--verbose" || (arg.size() >= 2 && arg.substr(0, 2) == "-v")) {
buildArgs.push_back(arg);
instArgs.push_back(arg);
verbose = true;
}
else if (arg == "--quiet" || arg == "--repair") {
buildArgs.push_back(arg);
instArgs.push_back(arg);
}
else if (arg == "--check") {
buildArgs.push_back(arg);
}
else if (arg == "--run-env") { // obsolete
runEnv = true;
}
else if (arg == "--command" || arg == "--run") {
n++;
if (n >= args.size()) {
throw UsageError(format("%1% requires an argument") % arg);
}
envCommand = args[n] + "\nexit";
if (arg == "--run")
interactive = false;
}
else if (arg == "--exclude") {
n++;
if (n >= args.size()) {
throw UsageError(format("%1% requires an argument") % arg);
}
envExclude.push_back(args[n]);
}
else if (arg == "--pure") { pure = true; }
else if (arg == "--impure") { pure = false; }
else if (arg == "--expr" || arg == "-E") {
fromArgs = true;
instArgs.push_back("--expr");
}
else if (arg == "--packages" || arg == "-p") {
packages = true;
}
else if (inShebang && arg == "-i") {
n++;
if (n >= args.size()) {
throw UsageError(format("%1% requires an argument") % arg);
}
auto interpreter = args[n];
auto execArgs = "";
auto shellEscape = [](const string & s) {
return "'" + std::regex_replace(s, std::regex("'"), "'\\''") + "'";
};
// Überhack to support Perl. Perl examines the shebang and
// executes it unless it contains the string "perl" or "indir",
// or (undocumented) argv[0] does not contain "perl". Exploit
// the latter by doing "exec -a".
if (std::regex_search(interpreter, std::regex("perl"))) {
execArgs = "-a PERL";
}
std::ostringstream joined;
for (const auto & i : savedArgs)
joined << shellEscape(i) << ' ';
if (std::regex_search(interpreter, std::regex("ruby"))) {
// Hack for Ruby. Ruby also examines the shebang. It tries to
// read the shebang to understand which packages to read from. Since
// this is handled via nix-shell -p, we wrap our ruby script execution
// in ruby -e 'load' which ignores the shebangs.
envCommand = (format("exec %1% %2% -e 'load(\"%3%\") -- %4%") % execArgs % interpreter % script % joined.str()).str();
} else {
envCommand = (format("exec %1% %2% %3% %4%") % execArgs % interpreter % script % joined.str()).str();
}
}
else if (!arg.empty() && arg[0] == '-') {
buildArgs.push_back(arg);
}
else if (arg == "-Q" || arg == "--no-build-output") {
buildArgs.push_back(arg);
instArgs.push_back(arg);
}
else {
exprs.push_back(arg);
}
}
if (packages && fromArgs) {
throw UsageError("-p and -E are mutually exclusive");
}
if (packages) {
instArgs.push_back("--expr");
std::ostringstream joined;
joined << "with import <nixpkgs> { }; runCommand \"shell\" { buildInputs = [ ";
for (const auto & i : exprs)
joined << '(' << i << ") ";
joined << "]; } \"\"";
exprs = Strings{joined.str()};
} else if (!fromArgs) {
if (exprs.empty() && runEnv && access("shell.nix", F_OK) == 0)
exprs.push_back("shell.nix");
if (exprs.empty())
exprs.push_back("default.nix");
}
if (runEnv)
setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1);
for (auto & expr : exprs) {
// Instantiate.
std::vector<string> drvPaths;
if (!std::regex_match(expr, std::regex("^/.*\\.drv$"))) {
// If we're in a #! script, interpret filenames relative to the
// script.
if (inShebang && !packages)
expr = absPath(expr, dirOf(script));
Strings instantiateArgs{"--add-root", drvLink, "--indirect"};
for (const auto & arg : instArgs)
instantiateArgs.push_back(arg);
instantiateArgs.push_back(expr);
auto instOutput = runProgram(settings.nixBinDir + "/nix-instantiate", false, instantiateArgs);
drvPaths = tokenizeString<std::vector<string>>(instOutput);
} else {
drvPaths.push_back(expr);
}
if (runEnv) {
if (drvPaths.size() != 1)
throw UsageError("a single derivation is required");
auto drvPath = drvPaths[0];
drvPath = drvPath.substr(0, drvPath.find_first_of('!'));
if (isLink(drvPath))
drvPath = readLink(drvPath);
auto drv = store->derivationFromPath(drvPath);
// Build or fetch all dependencies of the derivation.
Strings nixStoreArgs{"-r", "--no-output", "--no-gc-warning"};
for (const auto & arg : buildArgs)
nixStoreArgs.push_back(arg);
for (const auto & input : drv.inputDrvs)
if (std::all_of(envExclude.cbegin(), envExclude.cend(), [&](const string & exclude) { return !std::regex_search(input.first, std::regex(exclude)); }))
nixStoreArgs.push_back(input.first);
for (const auto & src : drv.inputSrcs)
nixStoreArgs.push_back(src);
runProgram(settings.nixBinDir + "/nix-store", false, nixStoreArgs);
// Set the environment.
auto tmp = getEnv("TMPDIR", getEnv("XDG_RUNTIME_DIR", "/tmp"));
if (pure) {
std::vector<string> skippedEnv{"HOME", "USER", "LOGNAME", "DISPLAY", "PATH", "TERM", "IN_NIX_SHELL", "TZ", "PAGER", "NIX_BUILD_SHELL"};
std::vector<string> removed;
for (auto i = size_t{0}; environ[i]; ++i) {
auto eq = strchr(environ[i], '=');
if (!eq)
// invalid env, just keep going
continue;
std::string name(environ[i], eq);
if (find(skippedEnv.begin(), skippedEnv.end(), name) == skippedEnv.end())
removed.emplace_back(std::move(name));
}
for (const auto & name : removed)
unsetenv(name.c_str());
// NixOS hack: prevent /etc/bashrc from sourcing /etc/profile.
setenv("__ETC_PROFILE_SOURCED", "1", 1);
}
setenv("NIX_BUILD_TOP", tmp.c_str(), 1);
setenv("TMPDIR", tmp.c_str(), 1);
setenv("TEMPDIR", tmp.c_str(), 1);
setenv("TMP", tmp.c_str(), 1);
setenv("TEMP", tmp.c_str(), 1);
setenv("NIX_STORE", store->storeDir.c_str(), 1);
for (const auto & env : drv.env)
setenv(env.first.c_str(), env.second.c_str(), 1);
// Run a shell using the derivation's environment. For
// convenience, source $stdenv/setup to setup additional
// environment variables and shell functions. Also don't lose
// the current $PATH directories.
auto rcfile = (Path) tmpDir + "/rc";
writeFile(rcfile, (format(
"rm -rf '%1%'; "
"[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc; "
"%2%"
"dontAddDisableDepTrack=1; "
"[ -e $stdenv/setup ] && source $stdenv/setup; "
"%3%"
"set +e; "
"[ -n \"$PS1\" ] && PS1=\"\\n\\[\\033[1;32m\\][nix-shell:\\w]$\\[\\033[0m\\] \"; "
"if [ \"$(type -t runHook)\" = function ]; then runHook shellHook; fi; "
"unset NIX_ENFORCE_PURITY; "
"unset NIX_INDENT_MAKE; "
"shopt -u nullglob; "
"unset TZ; %4%"
"%5%"
) % (Path) tmpDir % (pure ? "" : "p=$PATH") % (pure ? "" : "PATH=$PATH:$p; unset p; ") % (getenv("TZ") ? (string("export TZ='") + getenv("TZ") + "'; ") : "") % envCommand).str());
if (interactive)
execlp(getEnv("NIX_BUILD_SHELL", "bash").c_str(), "bash", "--rcfile", rcfile.c_str(), NULL);
else
execlp(getEnv("NIX_BUILD_SHELL", "bash").c_str(), "bash", rcfile.c_str(), NULL);
throw SysError("executing shell");
}
// Ugly hackery to make "nix-build -A foo.all" produce symlinks
// ./result, ./result-dev, and so on, rather than ./result,
// ./result-2-dev, and so on. This combines multiple derivation
// paths into one "/nix/store/drv-path!out1,out2,..." argument.
std::string prevDrvPath;
Strings drvPaths2;
for (const auto & drvPath : drvPaths) {
auto p = drvPath;
std::string output = "out";
std::smatch match;
if (std::regex_match(drvPath, match, std::regex("(.*)!(.*)"))) {
p = match[1].str();
output = match[2].str();
}
auto target = readLink(p);
if (verbose)
std::cerr << "derivation is " << target << '\n';
if (target == prevDrvPath) {
auto last = drvPaths2.back();
drvPaths2.pop_back();
drvPaths2.push_back(last + "," + output);
} else {
drvPaths2.push_back(target + "!" + output);
prevDrvPath = target;
}
}
// Build.
Strings outPaths;
Strings nixStoreArgs{"--add-root", outLink, "--indirect", "-r"};
for (const auto & arg : buildArgs)
nixStoreArgs.push_back(arg);
for (const auto & path : drvPaths2)
nixStoreArgs.push_back(path);
auto nixStoreRes = runProgram(settings.nixBinDir + "/nix-store", false, nixStoreArgs);
for (const auto & outpath : tokenizeString<std::vector<string>>(nixStoreRes))
outPaths.push_back(chomp(outpath));
if (dryRun)
continue;
for (const auto & outPath : outPaths)
std::cout << readLink(outPath) << '\n';
}
});
}