13 $SIG{__WARN__
} = sub { warn "warning: ", @_ };
14 $SIG{__DIE__
} = sub { die "error: ", @_ };
16 my $out = $ENV{"out"};
17 my $extraPrefix = $ENV{"extraPrefix"};
19 my @pathsToLink = split ' ', $ENV{"pathsToLink"};
23 $path = "/" if $path eq "";
24 foreach my $elem (@pathsToLink) {
27 (substr($path, 0, length($elem)) eq $elem
28 && (($path eq $elem) || (substr($path, length($elem), 1) eq "/")));
33 # Returns whether a path in one of the linked packages may contain
34 # files in one of the elements of pathsToLink.
37 foreach my $elem (@pathsToLink) {
40 (substr($elem, 0, length($path)) eq $path
41 && (($path eq $elem) || (substr($elem, length($path), 1) eq "/")));
46 # Similar to `lib.isStorePath`
49 my $storePath = "@storeDir@";
51 return substr($path, 0, 1) eq "/" && dirname
($path) eq $storePath;
54 # For each activated package, determine what symlinks to create.
58 # Add all pathsToLink and all parent directories.
60 # For "/a/b/c" that will include
61 # [ "", "/a", "/a/b", "/a/b/c" ]
63 # That ensures the whole directory tree needed by pathsToLink is
64 # created as directories and not symlinks.
65 $symlinks{""} = ["", 0];
66 for my $p (@pathsToLink) {
67 my @parts = split '/', $p;
72 $cur = "" if $cur eq "/";
73 $symlinks{$cur} = ["", 0];
80 my ($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
82 opendir DIR
, "$target" or die "cannot open `$target': $!";
83 my @names = readdir DIR
or die;
86 foreach my $name (@names) {
87 next if $name eq "." || $name eq "..";
88 findFiles
("$relName/$name", "$target/$name", $name, $ignoreCollisions, $checkCollisionContents, $priority);
93 my ($path1, $path2) = @_;
95 if (! -e
$path1 || ! -e
$path2) {
99 my $stat1 = (stat($path1))[2];
100 my $stat2 = (stat($path2))[2];
102 if ($stat1 != $stat2) {
103 warn "different permissions in `$path1' and `$path2': "
104 . sprintf("%04o", $stat1 & 07777) . " <-> "
105 . sprintf("%04o", $stat2 & 07777);
109 return compare
($path1, $path2) == 0;
112 sub prependDangling
{
114 return (-l
$path && ! -e
$path ?
"dangling symlink " : "") . "`$path'";
118 my ($relName, $target, $baseName, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
120 # The store path must not be a file
121 if (-f
$target && isStorePath
$target) {
122 die "The store path $target is a file and can't be merged into an environment using pkgs.buildEnv!";
127 $relName eq "/propagated-build-inputs" ||
128 $relName eq "/nix-support" ||
129 $relName =~ /info\/dir
$/ ||
130 ( $relName =~ /^\/share\
/mime\// && !( $relName =~ /^\
/share\/mime\
/packages/ ) ) ||
131 $baseName eq "perllocal.pod" ||
132 $baseName eq "log" ||
133 ! (hasPathsToLink
($relName) || isInPathsToLink
($relName));
135 my ($oldTarget, $oldPriority) = @
{$symlinks{$relName} // [undef, undef]};
137 # If target doesn't exist, create it. If it already exists as a
138 # symlink to a file (not a directory) in a lower-priority package,
140 if (!defined $oldTarget || ($priority < $oldPriority && ($oldTarget ne "" && ! -d
$oldTarget))) {
141 # If target is a dangling symlink, emit a warning.
142 if (-l
$target && ! -e
$target) {
143 my $link = readlink $target;
144 warn "creating dangling symlink `$out$extraPrefix/$relName' -> `$target' -> `$link'\n";
146 $symlinks{$relName} = [$target, $priority];
150 # If target already exists and both targets resolves to the same path, skip
152 defined $oldTarget && $oldTarget ne "" &&
153 defined abs_path
($target) && defined abs_path
($oldTarget) &&
154 abs_path
($target) eq abs_path
($oldTarget)
156 # Prefer the target that is not a symlink, if any
157 if (-l
$oldTarget && ! -l
$target) {
158 $symlinks{$relName} = [$target, $priority];
163 # If target already exists as a symlink to a file (not a
164 # directory) in a higher-priority package, skip.
165 if (defined $oldTarget && $priority > $oldPriority && $oldTarget ne "" && ! -d
$oldTarget) {
169 # If target is supposed to be a directory but it isn't, die with an error message
170 # instead of attempting to recurse into it, only to fail then.
171 # This happens e.g. when pathsToLink contains a non-directory path.
172 if ($oldTarget eq "" && ! -d
$target) {
173 die "not a directory: `$target'\n";
176 unless (-d
$target && ($oldTarget eq "" || -d
$oldTarget)) {
177 # Prepend "dangling symlink" to paths if applicable.
178 my $targetRef = prependDangling
($target);
179 my $oldTargetRef = prependDangling
($oldTarget);
181 if ($ignoreCollisions) {
182 warn "collision between $targetRef and $oldTargetRef\n" if $ignoreCollisions == 1;
184 } elsif ($checkCollisionContents && checkCollision
($oldTarget, $target)) {
187 die "collision between $targetRef and $oldTargetRef\n";
191 findFilesInDir
($relName, $oldTarget, $ignoreCollisions, $checkCollisionContents, $oldPriority) unless $oldTarget eq "";
192 findFilesInDir
($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority);
194 $symlinks{$relName} = ["", $priority]; # denotes directory
202 my ($pkgDir, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
204 return if (defined $done{$pkgDir});
207 findFiles
("", $pkgDir, "", $ignoreCollisions, $checkCollisionContents, $priority);
209 my $propagatedFN = "$pkgDir/nix-support/propagated-user-env-packages";
210 if (-e
$propagatedFN) {
211 open PROP
, "<$propagatedFN" or die;
212 my $propagated = <PROP
>;
214 my @propagated = split ' ', $propagated;
215 foreach my $p (@propagated) {
216 $postponed{$p} = 1 unless defined $done{$p};
221 # Read packages list.
224 if (exists $ENV{"pkgsPath"}) {
225 open FILE
, $ENV{"pkgsPath"};
232 # Symlink to the packages that have been installed explicitly by the
234 for my $pkg (@
{decode_json
$pkgs}) {
235 for my $path (@
{$pkg->{paths
}}) {
237 $ENV{"ignoreCollisions"} eq "1",
238 $ENV{"checkCollisionContents"} eq "1",
245 # Symlink to the packages that have been "propagated" by packages
246 # installed by the user (i.e., package X declares that it wants Y
247 # installed as well). We do these later because they have a lower
248 # priority in case of collisions.
249 my $priorityCounter = 1000; # don't care about collisions
250 while (scalar(keys %postponed) > 0) {
251 my @pkgDirs = keys %postponed;
253 foreach my $pkgDir (sort @pkgDirs) {
254 addPkg
($pkgDir, 2, $ENV{"checkCollisionContents"} eq "1", $priorityCounter++);
258 my $extraPathsFilePath = $ENV{"extraPathsFrom"};
259 if ($extraPathsFilePath) {
260 open FILE
, $extraPathsFilePath or die "cannot open extra paths file $extraPathsFilePath: $!";
262 while(my $line = <FILE
>) {
265 $ENV{"ignoreCollisions"} eq "1",
266 $ENV{"checkCollisionContents"} eq "1",
274 # Create the symlinks.
276 foreach my $relName (sort keys %symlinks) {
277 my ($target, $priority) = @
{$symlinks{$relName}};
278 my $abs = "$out" . "$extraPrefix" . "/$relName";
279 next unless isInPathsToLink
$relName;
281 #print "creating directory $relName\n";
282 mkpath
$abs or die "cannot create directory `$abs': $!";
284 #print "creating symlink $relName to $target\n";
285 symlink $target, $abs ||
286 die "error creating link `$abs': $!";
292 print STDERR
"created $nrLinks symlinks in user environment\n";
295 my $manifest = $ENV{"manifest"};
297 symlink($manifest, "$out/manifest") or die "cannot create manifest";