vuls: init at 0.27.0 (#348530)
[NixPkgs.git] / pkgs / build-support / buildenv / builder.pl
blob12d922770a8f0834efd81cd939f3fc102d73a028
1 #! @perl@ -w
3 use strict;
4 use Cwd 'abs_path';
5 use IO::Handle;
6 use File::Path;
7 use File::Basename;
8 use File::Compare;
9 use JSON::PP;
11 STDOUT->autoflush(1);
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"};
21 sub isInPathsToLink {
22 my $path = shift;
23 $path = "/" if $path eq "";
24 foreach my $elem (@pathsToLink) {
25 return 1 if
26 $elem eq "/" ||
27 (substr($path, 0, length($elem)) eq $elem
28 && (($path eq $elem) || (substr($path, length($elem), 1) eq "/")));
30 return 0;
33 # Returns whether a path in one of the linked packages may contain
34 # files in one of the elements of pathsToLink.
35 sub hasPathsToLink {
36 my $path = shift;
37 foreach my $elem (@pathsToLink) {
38 return 1 if
39 $path eq "" ||
40 (substr($elem, 0, length($path)) eq $path
41 && (($path eq $elem) || (substr($elem, length($path), 1) eq "/")));
43 return 0;
46 # Similar to `lib.isStorePath`
47 sub isStorePath {
48 my $path = shift;
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.
56 my %symlinks;
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;
69 my $cur = "";
70 for my $x (@parts) {
71 $cur = $cur . "/$x";
72 $cur = "" if $cur eq "/";
73 $symlinks{$cur} = ["", 0];
77 sub findFiles;
79 sub findFilesInDir {
80 my ($relName, $target, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
82 opendir DIR, "$target" or die "cannot open `$target': $!";
83 my @names = readdir DIR or die;
84 closedir DIR;
86 foreach my $name (@names) {
87 next if $name eq "." || $name eq "..";
88 findFiles("$relName/$name", "$target/$name", $name, $ignoreCollisions, $checkCollisionContents, $priority);
92 sub checkCollision {
93 my ($path1, $path2) = @_;
95 if (! -e $path1 || ! -e $path2) {
96 return 0;
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);
106 return 0;
109 return compare($path1, $path2) == 0;
112 sub prependDangling {
113 my $path = shift;
114 return (-l $path && ! -e $path ? "dangling symlink " : "") . "`$path'";
117 sub findFiles {
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!";
125 # Urgh, hacky...
126 return if
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,
139 # overwrite it.
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];
147 return;
150 # If target already exists and both targets resolves to the same path, skip
151 if (
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];
160 return;
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) {
166 return;
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;
183 return;
184 } elsif ($checkCollisionContents && checkCollision($oldTarget, $target)) {
185 return;
186 } else {
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
198 my %done;
199 my %postponed;
201 sub addPkg {
202 my ($pkgDir, $ignoreCollisions, $checkCollisionContents, $priority) = @_;
204 return if (defined $done{$pkgDir});
205 $done{$pkgDir} = 1;
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>;
213 close PROP;
214 my @propagated = split ' ', $propagated;
215 foreach my $p (@propagated) {
216 $postponed{$p} = 1 unless defined $done{$p};
221 # Read packages list.
222 my $pkgs;
224 if (exists $ENV{"pkgsPath"}) {
225 open FILE, $ENV{"pkgsPath"};
226 $pkgs = <FILE>;
227 close FILE;
228 } else {
229 $pkgs = $ENV{"pkgs"}
232 # Symlink to the packages that have been installed explicitly by the
233 # user.
234 for my $pkg (@{decode_json $pkgs}) {
235 for my $path (@{$pkg->{paths}}) {
236 addPkg($path,
237 $ENV{"ignoreCollisions"} eq "1",
238 $ENV{"checkCollisionContents"} eq "1",
239 $pkg->{priority})
240 if -e $path;
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;
252 %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>) {
263 chomp $line;
264 addPkg($line,
265 $ENV{"ignoreCollisions"} eq "1",
266 $ENV{"checkCollisionContents"} eq "1",
267 1000)
268 if -d $line;
271 close FILE;
274 # Create the symlinks.
275 my $nrLinks = 0;
276 foreach my $relName (sort keys %symlinks) {
277 my ($target, $priority) = @{$symlinks{$relName}};
278 my $abs = "$out" . "$extraPrefix" . "/$relName";
279 next unless isInPathsToLink $relName;
280 if ($target eq "") {
281 #print "creating directory $relName\n";
282 mkpath $abs or die "cannot create directory `$abs': $!";
283 } else {
284 #print "creating symlink $relName to $target\n";
285 symlink $target, $abs ||
286 die "error creating link `$abs': $!";
287 $nrLinks++;
292 print STDERR "created $nrLinks symlinks in user environment\n";
295 my $manifest = $ENV{"manifest"};
296 if ($manifest) {
297 symlink($manifest, "$out/manifest") or die "cannot create manifest";