about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/buildenv
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/build-support/buildenv')
-rwxr-xr-xnixpkgs/pkgs/build-support/buildenv/builder.pl283
-rw-r--r--nixpkgs/pkgs/build-support/buildenv/default.nix81
2 files changed, 364 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/buildenv/builder.pl b/nixpkgs/pkgs/build-support/buildenv/builder.pl
new file mode 100755
index 000000000000..975e76df05c0
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/buildenv/builder.pl
@@ -0,0 +1,283 @@
+#! @perl@ -w
+
+use strict;
+use Cwd 'abs_path';
+use IO::Handle;
+use File::Path;
+use File::Basename;
+use File::Compare;
+use JSON::PP;
+
+STDOUT->autoflush(1);
+
+$SIG{__WARN__} = sub { warn "warning: ", @_ };
+$SIG{__DIE__}  = sub { die "error: ", @_ };
+
+my $out = $ENV{"out"};
+my $extraPrefix = $ENV{"extraPrefix"};
+
+my @pathsToLink = split ' ', $ENV{"pathsToLink"};
+
+sub isInPathsToLink {
+    my $path = shift;
+    $path = "/" if $path eq "";
+    foreach my $elem (@pathsToLink) {
+        return 1 if
+            $elem eq "/" ||
+            (substr($path, 0, length($elem)) eq $elem
+             && (($path eq $elem) || (substr($path, length($elem), 1) eq "/")));
+    }
+    return 0;
+}
+
+# Returns whether a path in one of the linked packages may contain
+# files in one of the elements of pathsToLink.
+sub hasPathsToLink {
+    my $path = shift;
+    foreach my $elem (@pathsToLink) {
+        return 1 if
+            $path eq "" ||
+            (substr($elem, 0, length($path)) eq $path
+             && (($path eq $elem) || (substr($elem, length($path), 1) eq "/")));
+    }
+    return 0;
+}
+
+# Similar to `lib.isStorePath`
+sub isStorePath {
+    my $path = shift;
+    my $storePath = "@storeDir@";
+
+    return substr($path, 0, 1) eq "/" && dirname($path) eq $storePath;
+}
+
+# For each activated package, determine what symlinks to create.
+
+my %symlinks;
+
+# Add all pathsToLink and all parent directories.
+#
+# For "/a/b/c" that will include
+# [ "", "/a", "/a/b", "/a/b/c" ]
+#
+# That ensures the whole directory tree needed by pathsToLink is
+# created as directories and not symlinks.
+$symlinks{""} = ["", 0];
+for my $p (@pathsToLink) {
+    my @parts = split '/', $p;
+
+    my $cur = "";
+    for my $x (@parts) {
+        $cur = $cur . "/$x";
+        $cur = "" if $cur eq "/";
+        $symlinks{$cur} = ["", 0];
+    }
+}
+
+sub findFiles;
+
+sub findFilesInDir {
+    my ($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
+
+    opendir DIR, "$target" or die "cannot open `$target': $!";
+    my @names = readdir DIR or die;
+    closedir DIR;
+
+    foreach my $name (@names) {
+        next if $name eq "." || $name eq "..";
+        findFiles("$relName/$name", "$target/$name", $name, $ignoreCollisions, $checkCollisionContents, $priority);
+    }
+}
+
+sub checkCollision {
+    my ($path1, $path2) = @_;
+
+    if (! -e $path1 || ! -e $path2) {
+        return 0;
+    }
+
+    my $stat1 = (stat($path1))[2];
+    my $stat2 = (stat($path2))[2];
+
+    if ($stat1 != $stat2) {
+        warn "different permissions in `$path1' and `$path2': "
+           . sprintf("%04o", $stat1 & 07777) . " <-> "
+           . sprintf("%04o", $stat2 & 07777);
+        return 0;
+    }
+
+    return compare($path1, $path2) == 0;
+}
+
+sub prependDangling {
+    my $path = shift;
+    return (-l $path && ! -e $path ? "dangling symlink " : "") . "`$path'";
+}
+
+sub findFiles {
+    my ($relName, $target, $baseName, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
+
+    # The store path must not be a file
+    if (-f $target && isStorePath $target) {
+        die "The store path $target is a file and can't be merged into an environment using pkgs.buildEnv!";
+    }
+
+    # Urgh, hacky...
+    return if
+        $relName eq "/propagated-build-inputs" ||
+        $relName eq "/nix-support" ||
+        $relName =~ /info\/dir$/ ||
+        ( $relName =~ /^\/share\/mime\// && !( $relName =~ /^\/share\/mime\/packages/ ) ) ||
+        $baseName eq "perllocal.pod" ||
+        $baseName eq "log" ||
+        ! (hasPathsToLink($relName) || isInPathsToLink($relName));
+
+    my ($oldTarget, $oldPriority) = @{$symlinks{$relName} // [undef, undef]};
+
+    # If target doesn't exist, create it. If it already exists as a
+    # symlink to a file (not a directory) in a lower-priority package,
+    # overwrite it.
+    if (!defined $oldTarget || ($priority < $oldPriority && ($oldTarget ne "" && ! -d $oldTarget))) {
+        # If target is a dangling symlink, emit a warning.
+        if (-l $target && ! -e $target) {
+            my $link = readlink $target;
+            warn "creating dangling symlink `$out$extraPrefix/$relName' -> `$target' -> `$link'\n";
+        }
+        $symlinks{$relName} = [$target, $priority];
+        return;
+    }
+
+    # If target already exists and both targets resolves to the same path, skip
+    if (
+        defined $oldTarget && $oldTarget ne "" &&
+        defined abs_path($target) && defined abs_path($oldTarget) &&
+        abs_path($target) eq abs_path($oldTarget)
+    ) {
+        # Prefer the target that is not a symlink, if any
+        if (-l $oldTarget && ! -l $target) {
+            $symlinks{$relName} = [$target, $priority];
+        }
+        return;
+    }
+
+    # If target already exists as a symlink to a file (not a
+    # directory) in a higher-priority package, skip.
+    if (defined $oldTarget && $priority > $oldPriority && $oldTarget ne "" && ! -d $oldTarget) {
+        return;
+    }
+
+    # If target is supposed to be a directory but it isn't, die with an error message
+    # instead of attempting to recurse into it, only to fail then.
+    # This happens e.g. when pathsToLink contains a non-directory path.
+    if ($oldTarget eq "" && ! -d $target) {
+        die "not a directory: `$target'\n";
+    }
+
+    unless (-d $target && ($oldTarget eq "" || -d $oldTarget)) {
+        # Prepend "dangling symlink" to paths if applicable.
+        my $targetRef = prependDangling($target);
+        my $oldTargetRef = prependDangling($oldTarget);
+
+        if ($ignoreCollisions) {
+            warn "collision between $targetRef and $oldTargetRef\n" if $ignoreCollisions == 1;
+            return;
+        } elsif ($checkCollisionContents && checkCollision($oldTarget, $target)) {
+            return;
+        } else {
+            die "collision between $targetRef and $oldTargetRef\n";
+        }
+    }
+
+    findFilesInDir($relName, $oldTarget, $ignoreCollisions, $checkCollisionContents, $oldPriority) unless $oldTarget eq "";
+    findFilesInDir($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority);
+
+    $symlinks{$relName} = ["", $priority]; # denotes directory
+}
+
+
+my %done;
+my %postponed;
+
+sub addPkg {
+    my ($pkgDir, $ignoreCollisions, $checkCollisionContents, $priority)  = @_;
+
+    return if (defined $done{$pkgDir});
+    $done{$pkgDir} = 1;
+
+    findFiles("", $pkgDir, "", $ignoreCollisions, $checkCollisionContents, $priority);
+
+    my $propagatedFN = "$pkgDir/nix-support/propagated-user-env-packages";
+    if (-e $propagatedFN) {
+        open PROP, "<$propagatedFN" or die;
+        my $propagated = <PROP>;
+        close PROP;
+        my @propagated = split ' ', $propagated;
+        foreach my $p (@propagated) {
+            $postponed{$p} = 1 unless defined $done{$p};
+        }
+    }
+}
+
+# Read packages list.
+my $pkgs;
+
+if (exists $ENV{"pkgsPath"}) {
+    open FILE, $ENV{"pkgsPath"};
+    $pkgs = <FILE>;
+    close FILE;
+} else {
+    $pkgs = $ENV{"pkgs"}
+}
+
+# Symlink to the packages that have been installed explicitly by the
+# user.
+for my $pkg (@{decode_json $pkgs}) {
+    for my $path (@{$pkg->{paths}}) {
+        addPkg($path,
+               $ENV{"ignoreCollisions"} eq "1",
+               $ENV{"checkCollisionContents"} eq "1",
+               $pkg->{priority})
+           if -e $path;
+    }
+}
+
+
+# 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, 2, $ENV{"checkCollisionContents"} eq "1", $priorityCounter++);
+    }
+}
+
+
+# Create the symlinks.
+my $nrLinks = 0;
+foreach my $relName (sort keys %symlinks) {
+    my ($target, $priority) = @{$symlinks{$relName}};
+    my $abs = "$out" . "$extraPrefix" . "/$relName";
+    next unless isInPathsToLink $relName;
+    if ($target eq "") {
+        #print "creating directory $relName\n";
+        mkpath $abs or die "cannot create directory `$abs': $!";
+    } else {
+        #print "creating symlink $relName to $target\n";
+        symlink $target, $abs ||
+            die "error creating link `$abs': $!";
+        $nrLinks++;
+    }
+}
+
+
+print STDERR "created $nrLinks symlinks in user environment\n";
+
+
+my $manifest = $ENV{"manifest"};
+if ($manifest) {
+    symlink($manifest, "$out/manifest") or die "cannot create manifest";
+}
diff --git a/nixpkgs/pkgs/build-support/buildenv/default.nix b/nixpkgs/pkgs/build-support/buildenv/default.nix
new file mode 100644
index 000000000000..560f59bcce7d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/buildenv/default.nix
@@ -0,0 +1,81 @@
+# buildEnv creates a tree of symlinks to the specified paths.  This is
+# a fork of the hardcoded buildEnv in the Nix distribution.
+
+{ buildPackages, runCommand, lib, substituteAll }:
+
+let
+  builder = substituteAll {
+    src = ./builder.pl;
+    inherit (builtins) storeDir;
+  };
+in
+
+lib.makeOverridable
+({ name
+
+, # The manifest file (if any).  A symlink $out/manifest will be
+  # created to it.
+  manifest ? ""
+
+, # The paths to symlink.
+  paths
+
+, # Whether to ignore collisions or abort.
+  ignoreCollisions ? false
+
+, # If there is a collision, check whether the contents and permissions match
+  # and only if not, throw a collision error.
+  checkCollisionContents ? true
+
+, # The paths (relative to each element of `paths') that we want to
+  # symlink (e.g., ["/bin"]).  Any file not inside any of the
+  # directories in the list is not symlinked.
+  pathsToLink ? ["/"]
+
+, # The package outputs to include. By default, only the default
+  # output is included.
+  extraOutputsToInstall ? []
+
+, # Root the result in directory "$out${extraPrefix}", e.g. "/share".
+  extraPrefix ? ""
+
+, # Shell commands to run after building the symlink tree.
+  postBuild ? ""
+
+# Additional inputs
+, nativeBuildInputs ? [] # Handy e.g. if using makeWrapper in `postBuild`.
+, buildInputs ? []
+
+, passthru ? {}
+, meta ? {}
+}:
+
+runCommand name
+  rec {
+    inherit manifest ignoreCollisions checkCollisionContents passthru
+            meta pathsToLink extraPrefix postBuild
+            nativeBuildInputs buildInputs;
+    pkgs = builtins.toJSON (map (drv: {
+      paths =
+        # First add the usual output(s): respect if user has chosen explicitly,
+        # and otherwise use `meta.outputsToInstall`. The attribute is guaranteed
+        # to exist in mkDerivation-created cases. The other cases (e.g. runCommand)
+        # aren't expected to have multiple outputs.
+        (if (! drv ? outputSpecified || ! drv.outputSpecified)
+            && drv.meta.outputsToInstall or null != null
+          then map (outName: drv.${outName}) drv.meta.outputsToInstall
+          else [ drv ])
+        # Add any extra outputs specified by the caller of `buildEnv`.
+        ++ lib.filter (p: p!=null)
+          (builtins.map (outName: drv.${outName} or null) extraOutputsToInstall);
+      priority = drv.meta.priority or 5;
+    }) paths);
+    preferLocalBuild = true;
+    allowSubstitutes = false;
+    # XXX: The size is somewhat arbitrary
+    passAsFile = if builtins.stringLength pkgs >= 128*1024 then [ "pkgs" ] else [ ];
+  }
+  ''
+    ${buildPackages.perl}/bin/perl -w ${builder}
+    eval "$postBuild"
+  '')