diff --git a/.gitignore b/.gitignore index a524e9b6..2b90b549 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ Makefile.config # /src/download-via-ssh/ /src/download-via-ssh/download-via-ssh +# /src/buildenv/ +/src/buildenv/buildenv + # /tests/ /tests/test-tmp /tests/common.sh diff --git a/Makefile b/Makefile index 90dca473..11b3309f 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ makefiles = \ src/nix-daemon/local.mk \ src/nix-collect-garbage/local.mk \ src/nix-prefetch-url/local.mk \ + src/buildenv/local.mk \ perl/local.mk \ scripts/local.mk \ corepkgs/local.mk \ diff --git a/corepkgs/buildenv.nix b/corepkgs/buildenv.nix index 70981a75..5e7b40ea 100644 --- a/corepkgs/buildenv.nix +++ b/corepkgs/buildenv.nix @@ -5,8 +5,7 @@ with import ; derivation { name = "user-environment"; system = builtins.currentSystem; - builder = perl; - args = [ "-w" ./buildenv.pl ]; + builder = nixLibexecDir + "/nix/buildenv"; inherit manifest; diff --git a/corepkgs/buildenv.pl b/corepkgs/buildenv.pl deleted file mode 100644 index dacc5370..00000000 --- a/corepkgs/buildenv.pl +++ /dev/null @@ -1,168 +0,0 @@ -use strict; -use Cwd; -use IO::Handle; -use utf8; - -STDOUT->autoflush(1); - -my $out = $ENV{"out"}; -mkdir "$out", 0755 || die "error creating $out"; - - -my $symlinks = 0; - -my %priorities; - - -# For each activated package, create symlinks. - -sub createLinks { - my $srcDir = shift; - my $dstDir = shift; - my $priority = shift; - - my @srcFiles = glob("$srcDir/*"); - - foreach my $srcFile (@srcFiles) { - my $baseName = $srcFile; - $baseName =~ s/^.*\///g; # strip directory - my $dstFile = "$dstDir/$baseName"; - - # The files below are special-cased so that they don't show up - # in user profiles, either because they are useless, or - # because they would cause pointless collisions (e.g., each - # Python package brings its own - # `$out/lib/pythonX.Y/site-packages/easy-install.pth'.) - # Urgh, hacky... - if ($srcFile =~ /\/propagated-build-inputs$/ || - $srcFile =~ /\/nix-support$/ || - $srcFile =~ /\/perllocal.pod$/ || - $srcFile =~ /\/info\/dir$/ || - $srcFile =~ /\/log$/) - { - # Do nothing. - } - - elsif (-d $srcFile) { - - lstat $dstFile; - - if (-d _) { - createLinks($srcFile, $dstFile, $priority); - } - - elsif (-l _) { - my $target = readlink $dstFile or die; - if (!-d $target) { - die "collision between directory ‘$srcFile’ and non-directory ‘$target’"; - } - unlink $dstFile or die "error unlinking ‘$dstFile’: $!"; - mkdir $dstFile, 0755 || - die "error creating directory ‘$dstFile’: $!"; - createLinks($target, $dstFile, $priorities{$dstFile}); - createLinks($srcFile, $dstFile, $priority); - } - - else { - symlink($srcFile, $dstFile) || - die "error creating link ‘$dstFile’: $!"; - $priorities{$dstFile} = $priority; - $symlinks++; - } - } - - else { - - if (-l $dstFile) { - my $target = readlink $dstFile; - my $prevPriority = $priorities{$dstFile}; - die("collision between ‘$srcFile’ and ‘$target’; " . - "use ‘nix-env --set-flag priority NUMBER PKGNAME’ " . - "to change the priority of one of the conflicting packages\n") - if $prevPriority == $priority; - next if $prevPriority < $priority; - unlink $dstFile or die; - } - - symlink($srcFile, $dstFile) || - die "error creating link ‘$dstFile’: $!"; - $priorities{$dstFile} = $priority; - $symlinks++; - } - } -} - - -my %done; -my %postponed; - -sub addPkg; -sub addPkg { - my $pkgDir = shift; - my $priority = shift; - - return if (defined $done{$pkgDir}); - $done{$pkgDir} = 1; - -# print "symlinking $pkgDir\n"; - createLinks("$pkgDir", "$out", $priority); - - my $propagatedFN = "$pkgDir/nix-support/propagated-user-env-packages"; - if (-e $propagatedFN) { - open PROP, "<$propagatedFN" or die; - my $propagated = ; - close PROP; - my @propagated = split ' ', $propagated; - foreach my $p (@propagated) { - $postponed{$p} = 1 unless defined $done{$p}; - } - } -} - - -# Convert the stuff we get from the environment back into a coherent -# data type. -my @pkgs; -my @derivations = split ' ', $ENV{"derivations"}; -while (scalar @derivations) { - my $active = shift @derivations; - my $priority = shift @derivations; - my $outputs = shift @derivations; - for (my $n = 0; $n < $outputs; $n++) { - my $path = shift @derivations; - push @pkgs, - { path => $path - , active => $active ne "false" - , priority => int($priority) }; - } -} - - -# Symlink to the packages that have been installed explicitly by the -# user. Process in priority order to reduce unnecessary -# symlink/unlink steps. -@pkgs = sort { $a->{priority} <=> $b->{priority} || $a->{path} cmp $b->{path} } @pkgs; -foreach my $pkg (@pkgs) { - #print $pkg, " ", $pkgs{$pkg}->{priority}, "\n"; - addPkg($pkg->{path}, $pkg->{priority}) if $pkg->{active}; -} - - -# Symlink to the packages that have been "propagated" by packages -# installed by the user (i.e., package X declares that it wants Y -# installed as well). We do these later because they have a lower -# priority in case of collisions. -my $priorityCounter = 1000; # don't care about collisions -while (scalar(keys %postponed) > 0) { - my @pkgDirs = keys %postponed; - %postponed = (); - foreach my $pkgDir (sort @pkgDirs) { - addPkg($pkgDir, $priorityCounter++); - } -} - - -print STDERR "created $symlinks symlinks in user environment\n"; - - -symlink($ENV{"manifest"}, "$out/manifest.nix") or die "cannot create manifest"; diff --git a/corepkgs/config.nix.in b/corepkgs/config.nix.in index 90e8edbe..f0f4890a 100644 --- a/corepkgs/config.nix.in +++ b/corepkgs/config.nix.in @@ -3,7 +3,6 @@ let let val = builtins.getEnv var; in if val != "" then val else def; in rec { - perl = "@perl@"; shell = "@bash@"; coreutils = "@coreutils@"; bzip2 = "@bzip2@"; @@ -14,6 +13,7 @@ in rec { tr = "@tr@"; nixBinDir = fromEnv "NIX_BIN_DIR" "@bindir@"; nixPrefix = "@prefix@"; + nixLibexecDir = fromEnv "NIX_LIBEXEC_DIR" "@libexecdir@"; # If Nix is installed in the Nix store, then automatically add it as # a dependency to the core packages. This ensures that they work diff --git a/corepkgs/local.mk b/corepkgs/local.mk index 19c1d069..8ea66a7c 100644 --- a/corepkgs/local.mk +++ b/corepkgs/local.mk @@ -1,4 +1,4 @@ -corepkgs_FILES = nar.nix buildenv.nix buildenv.pl unpack-channel.nix derivation.nix fetchurl.nix imported-drv-to-derivation.nix +corepkgs_FILES = nar.nix buildenv.nix unpack-channel.nix derivation.nix fetchurl.nix imported-drv-to-derivation.nix $(foreach file,config.nix $(corepkgs_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/corepkgs))) diff --git a/src/buildenv/buildenv.cc b/src/buildenv/buildenv.cc new file mode 100644 index 00000000..f997096e --- /dev/null +++ b/src/buildenv/buildenv.cc @@ -0,0 +1,186 @@ +#include "shared.hh" +#include +#include +#include +#include + +using namespace nix; + +typedef std::map Priorities; + +static bool isDirectory (const Path & path) +{ + struct stat st; + if (stat(path.c_str(), &st) == -1) + throw SysError(format("getting status of ‘%1%’") % path); + return S_ISDIR(st.st_mode); +} + +static auto priorities = Priorities{}; + +static auto symlinks = 0; + +/* For each activated package, create symlinks */ +static void createLinks(const Path & srcDir, const Path & dstDir, int priority) +{ + auto srcFiles = readDirectory(srcDir); + for (const auto & ent : srcFiles) { + if (ent.name[0] == '.') + /* not matched by glob */ + continue; + const auto & srcFile = srcDir + "/" + ent.name; + auto dstFile = dstDir + "/" + ent.name; + + /* The files below are special-cased to that they don't show up + * in user profiles, either because they are useless, or + * because they would cauase pointless collisions (e.g., each + * Python package brings its own + * `$out/lib/pythonX.Y/site-packages/easy-install.pth'.) + */ + if (hasSuffix(srcFile, "/propagated-build-inputs") || + hasSuffix(srcFile, "/nix-support") || + hasSuffix(srcFile, "/perllocal.pod") || + hasSuffix(srcFile, "/info/dir") || + hasSuffix(srcFile, "/log")) { + continue; + } else if (isDirectory(srcFile)) { + struct stat dstSt; + auto res = lstat(dstFile.c_str(), &dstSt); + if (res == 0) { + if (S_ISDIR(dstSt.st_mode)) { + createLinks(srcFile, dstFile, priority); + continue; + } else if (S_ISLNK(dstSt.st_mode)) { + auto target = readLink(dstFile); + if (!isDirectory(target)) + throw Error(format("collision between ‘%1%’ and non-directory ‘%2%’") + % srcFile % target); + if (unlink(dstFile.c_str()) == -1) + throw SysError(format("unlinking ‘%1%’") % dstFile); + if (mkdir(dstFile.c_str(), 0755) == -1) + throw SysError(format("creating directory ‘%1%’")); + createLinks(target, dstFile, priorities[dstFile]); + createLinks(srcFile, dstFile, priority); + continue; + } + } else if (errno != ENOENT) + throw SysError(format("getting status of ‘%1%’") % dstFile); + } else { + struct stat dstSt; + auto res = lstat(dstFile.c_str(), &dstSt); + if (res == 0) { + if (S_ISLNK(dstSt.st_mode)) { + auto target = readLink(dstFile); + auto prevPriority = priorities[dstFile]; + if (prevPriority == priority) + throw Error(format( + "collision between ‘%1%’ and ‘%2%’; " + "use ‘nix-env --set-flag priority NUMBER PKGNAME’ " + "to change the priority of one of the conflicting packages" + ) % srcFile % target); + if (prevPriority < priority) + continue; + if (unlink(dstFile.c_str()) == -1) + throw SysError(format("unlinking ‘%1%’") % dstFile); + } + } else if (errno != ENOENT) + throw SysError(format("getting status of ‘%1%’") % dstFile); + } + createSymlink(srcFile, dstFile); + priorities[dstFile] = priority; + symlinks++; + } +} + +typedef std::set FileProp; + +static auto done = FileProp{}; +static auto postponed = FileProp{}; + +static auto out = string{}; + +static void addPkg(const Path & pkgDir, int priority) +{ + if (done.find(pkgDir) != done.end()) + return; + done.insert(pkgDir); + createLinks(pkgDir, out, priority); + auto propagatedFN = pkgDir + "/nix-support/propagated-user-env-packages"; + auto propagated = string{}; + { + AutoCloseFD fd = open(propagatedFN.c_str(), O_RDONLY | O_CLOEXEC); + if (!fd) { + if (errno == ENOENT) + return; + throw SysError(format("opening ‘%1%’") % propagatedFN); + } + propagated = readLine(fd.get()); + } + for (const auto & p : tokenizeString>(propagated, " ")) + if (done.find(p) == done.end()) + postponed.insert(p); +} + +struct Package { + Path path; + bool active; + int priority; + Package(Path path, bool active, int priority) : path{std::move(path)}, active{active}, priority{priority} {} +}; + +typedef std::vector Packages; + +int main(int argc, char ** argv) +{ + return handleExceptions(argv[0], [&]() { + initNix(); + out = getEnv("out"); + if (mkdir(out.c_str(), 0755) == -1) + throw SysError(format("creating %1%") % out); + + /* Convert the stuff we get from the environment back into a coherent + * data type. + */ + auto pkgs = Packages{}; + auto derivations = tokenizeString(getEnv("derivations")); + while (!derivations.empty()) { + /* !!! We're trusting the caller to structure derivations env var correctly */ + auto active = derivations.front(); derivations.pop_front(); + auto priority = stoi(derivations.front()); derivations.pop_front(); + auto outputs = stoi(derivations.front()); derivations.pop_front(); + for (auto n = 0; n < outputs; n++) { + auto path = derivations.front(); derivations.pop_front(); + pkgs.emplace_back(path, active != "false", priority); + } + } + + /* Symlink to the packages that have been installed explicitly by the + * user. Process in priority order to reduce unnecessary + * symlink/unlink steps. + */ + std::sort(pkgs.begin(), pkgs.end(), [](const Package & a, const Package & b) { + return a.priority < b.priority || (a.priority == b.priority && a.path < b.path); + }); + for (const auto & pkg : pkgs) + if (pkg.active) + addPkg(pkg.path, pkg.priority); + + /* Symlink to the packages that have been "propagated" by packages + * installed by the user (i.e., package X declares that it wants Y + * installed as well). We do these later because they have a lower + * priority in case of collisions. + */ + auto priorityCounter = 1000; + while (!postponed.empty()) { + auto pkgDirs = postponed; + postponed = FileProp{}; + for (const auto & pkgDir : pkgDirs) + addPkg(pkgDir, priorityCounter++); + } + + std::cerr << "created " << symlinks << " symlinks in user environment\n"; + + createSymlink(getEnv("manifest"), out + "/manifest.nix"); + }); +} + diff --git a/src/buildenv/local.mk b/src/buildenv/local.mk new file mode 100644 index 00000000..493acfad --- /dev/null +++ b/src/buildenv/local.mk @@ -0,0 +1,9 @@ +programs += buildenv + +buildenv_DIR := $(d) + +buildenv_INSTALL_DIR := $(libexecdir)/nix + +buildenv_LIBS = libmain libutil libformat + +buildenv_SOURCES := $(d)/buildenv.cc