Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at python-updates 292 lines 9.6 kB view raw
1use strict; 2use feature 'signatures'; 3use Cwd 'abs_path'; 4use IO::Handle; 5use File::Path; 6use File::Basename; 7use File::Compare; 8use JSON::PP; 9 10STDOUT->autoflush(1); 11 12$SIG{__WARN__} = sub { warn "pkgs.buildEnv warning: ", @_ }; 13$SIG{__DIE__} = sub { die "pkgs.buildEnv error: ", @_ }; 14 15my $out = $ENV{"out"}; 16my $extraPrefix = $ENV{"extraPrefix"}; 17 18my @pathsToLink = split ' ', $ENV{"pathsToLink"}; 19 20sub isInPathsToLink($path) { 21 $path = "/" if $path eq ""; 22 foreach my $elem (@pathsToLink) { 23 return 1 if 24 $elem eq "/" || 25 (substr($path, 0, length($elem)) eq $elem 26 && (($path eq $elem) || (substr($path, length($elem), 1) eq "/"))); 27 } 28 return 0; 29} 30 31# Returns whether a path in one of the linked packages may contain 32# files in one of the elements of pathsToLink. 33sub hasPathsToLink($path) { 34 foreach my $elem (@pathsToLink) { 35 return 1 if 36 $path eq "" || 37 (substr($elem, 0, length($path)) eq $path 38 && (($path eq $elem) || (substr($elem, length($path), 1) eq "/"))); 39 } 40 return 0; 41} 42 43# Similar to `lib.isStorePath` 44sub isStorePath($path) { 45 my $storePath = "@storeDir@"; 46 47 return substr($path, 0, 1) eq "/" && dirname($path) eq $storePath; 48} 49 50# For each activated package, determine what symlinks to create. 51 52my %symlinks; 53 54# Add all pathsToLink and all parent directories. 55# 56# For "/a/b/c" that will include 57# [ "", "/a", "/a/b", "/a/b/c" ] 58# 59# That ensures the whole directory tree needed by pathsToLink is 60# created as directories and not symlinks. 61$symlinks{""} = ["", 0]; 62for my $p (@pathsToLink) { 63 my @parts = split '/', $p; 64 65 my $cur = ""; 66 for my $x (@parts) { 67 $cur = $cur . "/$x"; 68 $cur = "" if $cur eq "/"; 69 $symlinks{$cur} = ["", 0]; 70 } 71} 72 73sub findFiles; 74 75sub findFilesInDir($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority, $ignoreSingleFileOutputs) { 76 opendir DIR, "$target" or die "cannot open `$target': $!"; 77 my @names = readdir DIR or die; 78 closedir DIR; 79 80 foreach my $name (@names) { 81 next if $name eq "." || $name eq ".."; 82 findFiles("$relName/$name", "$target/$name", $name, $ignoreCollisions, $checkCollisionContents, $priority, $ignoreSingleFileOutputs); 83 } 84} 85 86sub checkCollision($path1, $path2) { 87 if (! -e $path1 || ! -e $path2) { 88 return 0; 89 } 90 91 my $stat1 = (stat($path1))[2]; 92 my $stat2 = (stat($path2))[2]; 93 94 if ($stat1 != $stat2) { 95 warn "different permissions in `$path1' and `$path2': " 96 . sprintf("%04o", $stat1 & 07777) . " <-> " 97 . sprintf("%04o", $stat2 & 07777); 98 return 0; 99 } 100 101 return compare($path1, $path2) == 0; 102} 103 104sub prependDangling($path) { 105 return (-l $path && ! -e $path ? "dangling symlink " : "") . "`$path'"; 106} 107 108sub findFiles($relName, $target, $baseName, $ignoreCollisions, $checkCollisionContents, $priority, $ignoreSingleFileOutputs) { 109 # The store path must not be a file when not ignoreSingleFileOutputs 110 if (-f $target && isStorePath $target) { 111 if ($ignoreSingleFileOutputs) { 112 warn "The store path $target is a file and can't be merged into an environment using pkgs.buildEnv, ignoring it"; 113 return; 114 } else { 115 die "The store path $target is a file and can't be merged into an environment using pkgs.buildEnv!"; 116 } 117 } 118 119 # Urgh, hacky... 120 return if 121 $relName eq "/propagated-build-inputs" || 122 $relName eq "/nix-support" || 123 $relName =~ /info\/dir$/ || 124 ( $relName =~ /^\/share\/mime\// && !( $relName =~ /^\/share\/mime\/packages/ ) ) || 125 $baseName eq "perllocal.pod" || 126 $baseName eq "log" || 127 ! (hasPathsToLink($relName) || isInPathsToLink($relName)); 128 129 my ($oldTarget, $oldPriority) = @{$symlinks{$relName} // [undef, undef]}; 130 131 # If target doesn't exist, create it. If it already exists as a 132 # symlink to a file (not a directory) in a lower-priority package, 133 # overwrite it. 134 if (!defined $oldTarget || ($priority < $oldPriority && ($oldTarget ne "" && ! -d $oldTarget))) { 135 # If target is a dangling symlink, emit a warning. 136 if (-l $target && ! -e $target) { 137 my $link = readlink $target; 138 warn "creating dangling symlink `$out$extraPrefix/$relName' -> `$target' -> `$link'\n"; 139 } 140 $symlinks{$relName} = [$target, $priority]; 141 return; 142 } 143 144 # If target already exists and both targets resolves to the same path, skip 145 if ( 146 defined $oldTarget && $oldTarget ne "" && 147 defined abs_path($target) && defined abs_path($oldTarget) && 148 abs_path($target) eq abs_path($oldTarget) 149 ) { 150 # Prefer the target that is not a symlink, if any 151 if (-l $oldTarget && ! -l $target) { 152 $symlinks{$relName} = [$target, $priority]; 153 } 154 return; 155 } 156 157 # If target already exists as a symlink to a file (not a 158 # directory) in a higher-priority package, skip. 159 if (defined $oldTarget && $priority > $oldPriority && $oldTarget ne "" && ! -d $oldTarget) { 160 return; 161 } 162 163 # If target is supposed to be a directory but it isn't, die with an error message 164 # instead of attempting to recurse into it, only to fail then. 165 # This happens e.g. when pathsToLink contains a non-directory path. 166 if ($oldTarget eq "" && ! -d $target) { 167 die "not a directory: `$target'\n"; 168 } 169 170 unless (-d $target && ($oldTarget eq "" || -d $oldTarget)) { 171 # Prepend "dangling symlink" to paths if applicable. 172 my $targetRef = prependDangling($target); 173 my $oldTargetRef = prependDangling($oldTarget); 174 175 if ($ignoreCollisions) { 176 warn "colliding subpath (ignored): $targetRef and $oldTargetRef\n" if $ignoreCollisions == 1; 177 return; 178 } elsif ($checkCollisionContents && checkCollision($oldTarget, $target)) { 179 return; 180 } else { 181 die "two given paths contain a conflicting subpath:\n $targetRef and\n $oldTargetRef\nhint: this may be caused by two different versions of the same package in buildEnv's `paths` parameter\nhint: `pkgs.nix-diff` can be used to compare derivations\n"; 182 } 183 } 184 185 findFilesInDir($relName, $oldTarget, $ignoreCollisions, $checkCollisionContents, $oldPriority, $ignoreSingleFileOutputs) unless $oldTarget eq ""; 186 findFilesInDir($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority, $ignoreSingleFileOutputs); 187 188 $symlinks{$relName} = ["", $priority]; # denotes directory 189} 190 191 192my %done; 193my %postponed; 194 195sub addPkg($pkgDir, $ignoreCollisions, $checkCollisionContents, $priority, $ignoreSingleFileOutputs) { 196 return if (defined $done{$pkgDir}); 197 $done{$pkgDir} = 1; 198 199 findFiles("", $pkgDir, "", $ignoreCollisions, $checkCollisionContents, $priority, $ignoreSingleFileOutputs); 200 201 my $propagatedFN = "$pkgDir/nix-support/propagated-user-env-packages"; 202 if (-e $propagatedFN) { 203 open PROP, "<$propagatedFN" or die; 204 my $propagated = <PROP>; 205 close PROP; 206 my @propagated = split ' ', $propagated; 207 foreach my $p (@propagated) { 208 $postponed{$p} = 1 unless defined $done{$p}; 209 } 210 } 211} 212 213# Read packages list. 214my $pkgs; 215 216if (exists $ENV{"pkgsPath"}) { 217 open FILE, $ENV{"pkgsPath"}; 218 $pkgs = <FILE>; 219 close FILE; 220} else { 221 $pkgs = $ENV{"pkgs"} 222} 223 224# Symlink to the packages that have been installed explicitly by the 225# user. 226for my $pkg (@{decode_json $pkgs}) { 227 for my $path (@{$pkg->{paths}}) { 228 addPkg($path, 229 $ENV{"ignoreCollisions"} eq "1", 230 $ENV{"checkCollisionContents"} eq "1", 231 $pkg->{priority}, 232 $ENV{"ignoreSingleFileOutputs"} eq "1") 233 if -e $path; 234 } 235} 236 237 238# Symlink to the packages that have been "propagated" by packages 239# installed by the user (i.e., package X declares that it wants Y 240# installed as well). We do these later because they have a lower 241# priority in case of collisions. 242my $priorityCounter = 1000; # don't care about collisions 243while (scalar(keys %postponed) > 0) { 244 my @pkgDirs = keys %postponed; 245 %postponed = (); 246 foreach my $pkgDir (sort @pkgDirs) { 247 addPkg($pkgDir, 2, $ENV{"checkCollisionContents"} eq "1", $priorityCounter++, $ENV{"ignoreSingleFileOutputs"} eq "1"); 248 } 249} 250 251my $extraPathsFilePath = $ENV{"extraPathsFrom"}; 252if ($extraPathsFilePath) { 253 open FILE, $extraPathsFilePath or die "cannot open extra paths file $extraPathsFilePath: $!"; 254 255 while(my $line = <FILE>) { 256 chomp $line; 257 addPkg($line, 258 $ENV{"ignoreCollisions"} eq "1", 259 $ENV{"checkCollisionContents"} eq "1", 260 1000, 261 $ENV{"ignoreSingleFileOutputs"} eq "1") 262 if -d $line; 263 } 264 265 close FILE; 266} 267 268# Create the symlinks. 269my $nrLinks = 0; 270foreach my $relName (sort keys %symlinks) { 271 my ($target, $priority) = @{$symlinks{$relName}}; 272 my $abs = "$out" . "$extraPrefix" . "/$relName"; 273 next unless isInPathsToLink $relName; 274 if ($target eq "") { 275 #print "creating directory $relName\n"; 276 mkpath $abs or die "cannot create directory `$abs': $!"; 277 } else { 278 #print "creating symlink $relName to $target\n"; 279 symlink $target, $abs || 280 die "error creating link `$abs': $!"; 281 $nrLinks++; 282 } 283} 284 285 286print STDERR "created $nrLinks symlinks in user environment\n"; 287 288 289my $manifest = $ENV{"manifest"}; 290if ($manifest) { 291 symlink($manifest, "$out/manifest") or die "cannot create manifest"; 292}