* Add build-remote.pl to the Nix distribution.
This commit is contained in:
		
							parent
							
								
									6fc60e2060
								
							
						
					
					
						commit
						bfec245efa
					
				
					 2 changed files with 211 additions and 1 deletions
				
			
		| 
						 | 
					@ -4,7 +4,8 @@ bin_SCRIPTS = nix-collect-garbage \
 | 
				
			||||||
  nix-pack-closure nix-unpack-closure \
 | 
					  nix-pack-closure nix-unpack-closure \
 | 
				
			||||||
  nix-copy-closure
 | 
					  nix-copy-closure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
noinst_SCRIPTS = nix-profile.sh generate-patches.pl find-runtime-roots.pl
 | 
					noinst_SCRIPTS = nix-profile.sh generate-patches.pl \
 | 
				
			||||||
 | 
					  find-runtime-roots.pl build-remote.pl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nix-pull nix-push: readmanifest.pm readconfig.pm download-using-manifests.pl
 | 
					nix-pull nix-push: readmanifest.pm readconfig.pm download-using-manifests.pl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +18,7 @@ install-exec-local: readmanifest.pm download-using-manifests.pl find-runtime-roo
 | 
				
			||||||
	$(INSTALL_PROGRAM) download-using-manifests.pl $(DESTDIR)$(libexecdir)/nix 
 | 
						$(INSTALL_PROGRAM) download-using-manifests.pl $(DESTDIR)$(libexecdir)/nix 
 | 
				
			||||||
	$(INSTALL_PROGRAM) find-runtime-roots.pl $(DESTDIR)$(libexecdir)/nix 
 | 
						$(INSTALL_PROGRAM) find-runtime-roots.pl $(DESTDIR)$(libexecdir)/nix 
 | 
				
			||||||
	$(INSTALL_PROGRAM) generate-patches.pl $(DESTDIR)$(libexecdir)/nix 
 | 
						$(INSTALL_PROGRAM) generate-patches.pl $(DESTDIR)$(libexecdir)/nix 
 | 
				
			||||||
 | 
						$(INSTALL_PROGRAM) build-remote.pl $(DESTDIR)$(libexecdir)/nix 
 | 
				
			||||||
	$(INSTALL) -d $(DESTDIR)$(sysconfdir)/nix
 | 
						$(INSTALL) -d $(DESTDIR)$(sysconfdir)/nix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include ../substitute.mk
 | 
					include ../substitute.mk
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										208
									
								
								scripts/build-remote.pl.in
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										208
									
								
								scripts/build-remote.pl.in
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
					@ -0,0 +1,208 @@
 | 
				
			||||||
 | 
					#! @perl@ -w
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use strict;
 | 
				
			||||||
 | 
					use Fcntl ':flock';
 | 
				
			||||||
 | 
					use English '-no_match_vars';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# General operation:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Try to find a free machine of type $neededSystem.  We do this as
 | 
				
			||||||
 | 
					# follows:
 | 
				
			||||||
 | 
					# - We acquire an exclusive lock on $currentLoad/main-lock.
 | 
				
			||||||
 | 
					# - For each machine $machine of type $neededSystem and for each $slot
 | 
				
			||||||
 | 
					#   less than the maximum load for that machine, we try to get an
 | 
				
			||||||
 | 
					#   exclusive lock on $currentLoad/$machine-$slot (without blocking).
 | 
				
			||||||
 | 
					#   If we get such a lock, we send "accept" to the caller.  Otherwise,
 | 
				
			||||||
 | 
					#   we send "postpone" and exit. 
 | 
				
			||||||
 | 
					# - We release the exclusive lock on $currentLoad/main-lock.
 | 
				
			||||||
 | 
					# - We perform the build on $neededSystem.
 | 
				
			||||||
 | 
					# - We release the exclusive lock on $currentLoad/$machine-$slot.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The nice thing about this scheme is that if we die prematurely, the
 | 
				
			||||||
 | 
					# locks are released automatically.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $loadIncreased = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $amWilling = shift @ARGV;
 | 
				
			||||||
 | 
					my $localSystem = shift @ARGV;
 | 
				
			||||||
 | 
					my $neededSystem = shift @ARGV;
 | 
				
			||||||
 | 
					my $drvPath = shift @ARGV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sub sendReply {
 | 
				
			||||||
 | 
					    my $reply = shift;
 | 
				
			||||||
 | 
					    open OUT, ">&3" or die;
 | 
				
			||||||
 | 
					    print OUT "$reply\n";
 | 
				
			||||||
 | 
					    close OUT;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sub decline {
 | 
				
			||||||
 | 
					    sendReply "decline";
 | 
				
			||||||
 | 
					    exit 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $currentLoad = $ENV{"NIX_CURRENT_LOAD"};
 | 
				
			||||||
 | 
					decline unless defined $currentLoad;
 | 
				
			||||||
 | 
					mkdir $currentLoad, 0777 or die unless -d $currentLoad;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $conf = $ENV{"NIX_REMOTE_SYSTEMS"};
 | 
				
			||||||
 | 
					decline if !defined $conf || ! -e $conf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Decline if the local system can do the build.
 | 
				
			||||||
 | 
					decline if $amWilling && ($localSystem eq $neededSystem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Otherwise find a willing remote machine.
 | 
				
			||||||
 | 
					my %machines;
 | 
				
			||||||
 | 
					my %systemTypes;
 | 
				
			||||||
 | 
					my %sshKeys;
 | 
				
			||||||
 | 
					my %maxJobs;
 | 
				
			||||||
 | 
					my %curJobs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Read the list of machines.
 | 
				
			||||||
 | 
					open CONF, "< $conf" or die;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					while (<CONF>) {
 | 
				
			||||||
 | 
					    chomp;
 | 
				
			||||||
 | 
					    s/\#.*$//g;
 | 
				
			||||||
 | 
					    next if /^\s*$/;
 | 
				
			||||||
 | 
					    /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s*$/ or die;
 | 
				
			||||||
 | 
					    $machines{$1} = "";
 | 
				
			||||||
 | 
					    $systemTypes{$1} = $2;
 | 
				
			||||||
 | 
					    $sshKeys{$1} = $3;
 | 
				
			||||||
 | 
					    $maxJobs{$1} = $4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					close CONF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Acquire the exclusive lock on $currentLoad/main-lock.
 | 
				
			||||||
 | 
					my $mainLock = "$currentLoad/main-lock";
 | 
				
			||||||
 | 
					open MAINLOCK, ">>$mainLock" or die;
 | 
				
			||||||
 | 
					flock(MAINLOCK, LOCK_EX) or die;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Find a suitable system.
 | 
				
			||||||
 | 
					my $rightType = 0;
 | 
				
			||||||
 | 
					my $machine;
 | 
				
			||||||
 | 
					LOOP: foreach my $cur (keys %machines) {
 | 
				
			||||||
 | 
					    if ($neededSystem eq $systemTypes{$cur}) {
 | 
				
			||||||
 | 
					        $rightType = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # We have a machine of the right type.  Try to get a lock on
 | 
				
			||||||
 | 
					        # one of the machine's lock files.
 | 
				
			||||||
 | 
					        my $slot = 0;
 | 
				
			||||||
 | 
					        while ($slot < $maxJobs{$cur}) {
 | 
				
			||||||
 | 
					            my $slotLock = "$currentLoad/$cur-$slot";
 | 
				
			||||||
 | 
					            open SLOTLOCK, ">>$slotLock" or die;
 | 
				
			||||||
 | 
					            if (flock(SLOTLOCK, LOCK_EX | LOCK_NB)) {
 | 
				
			||||||
 | 
					                $machine = $cur;
 | 
				
			||||||
 | 
					                last LOOP;
 | 
				
			||||||
 | 
					            }                
 | 
				
			||||||
 | 
					            close SLOTLOCK;
 | 
				
			||||||
 | 
					            $slot++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					close MAINLOCK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Didn't find one?
 | 
				
			||||||
 | 
					if (!defined $machine) {
 | 
				
			||||||
 | 
					    if ($rightType) {
 | 
				
			||||||
 | 
					        sendReply "postpone";
 | 
				
			||||||
 | 
					        exit 0;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        decline;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Yes we did, accept.
 | 
				
			||||||
 | 
					sendReply "accept";
 | 
				
			||||||
 | 
					open IN, "<&4" or die;
 | 
				
			||||||
 | 
					my $x = <IN>;
 | 
				
			||||||
 | 
					chomp $x;
 | 
				
			||||||
 | 
					#print "got $x\n";  
 | 
				
			||||||
 | 
					close IN;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($x ne "okay") {
 | 
				
			||||||
 | 
					    exit 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Do the actual job.
 | 
				
			||||||
 | 
					print "BUILDING REMOTE: $drvPath on $machine\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Make sure that we don't get any SSH passphrase or host key popups -
 | 
				
			||||||
 | 
					# if there is any problem it should fail, not do something
 | 
				
			||||||
 | 
					# interactive.
 | 
				
			||||||
 | 
					$ENV{"DISPLAY"} = "";
 | 
				
			||||||
 | 
					$ENV{"SSH_PASSWORD_FILE="} = "";
 | 
				
			||||||
 | 
					$ENV{"SSH_ASKPASS="} = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $sshOpts = "-i $sshKeys{$machine} -x";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Hack to support Cygwin: if we login without a password, we don't
 | 
				
			||||||
 | 
					# have exactly the same right as when we do.  This causes the
 | 
				
			||||||
 | 
					# Microsoft C compiler to fail with certain flags:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#   http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=99676
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# So as a workaround, we pass a verbatim password.  ssh tries to makes
 | 
				
			||||||
 | 
					# this very hard; the trick is to make it call SSH_ASKPASS to get the
 | 
				
			||||||
 | 
					# password.  (It only calls this command when there is no controlling
 | 
				
			||||||
 | 
					# terminal, but Nix ensures that is is the case.  When doing this
 | 
				
			||||||
 | 
					# manually, use setsid(1).)
 | 
				
			||||||
 | 
					if ($sshKeys{$machine} =~ /^password:/) {
 | 
				
			||||||
 | 
					    my $passwordFile = $sshKeys{$machine};
 | 
				
			||||||
 | 
					    $passwordFile =~ s/^password://;
 | 
				
			||||||
 | 
					    $sshOpts = "ssh -x";
 | 
				
			||||||
 | 
					    $ENV{"SSH_PASSWORD_FILE"} = $passwordFile;
 | 
				
			||||||
 | 
					    $ENV{"SSH_ASKPASS"} = "/tmp/writepass";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    open WRITEPASS, ">/tmp/writepass" or die;
 | 
				
			||||||
 | 
					    print WRITEPASS "#! /bin/sh\ncat \"\$SSH_PASSWORD_FILE\"";
 | 
				
			||||||
 | 
					    close WRITEPASS;
 | 
				
			||||||
 | 
					    chmod 0755, "/tmp/writepass" or die;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $inputs = `cat inputs`; die if ($? != 0);
 | 
				
			||||||
 | 
					$inputs =~ s/\n/ /g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $outputs = `cat outputs`; die if ($? != 0);
 | 
				
			||||||
 | 
					$outputs =~ s/\n/ /g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print "COPYING INPUTS...\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					my $maybeSign = "";
 | 
				
			||||||
 | 
					$maybeSign = "--sign" if -e "/nix/etc/nix/signing-key.sec";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					system("NIX_SSHOPTS=\"$sshOpts\" nix-copy-closure $machine $maybeSign $drvPath $inputs") == 0
 | 
				
			||||||
 | 
					    or die "cannot copy inputs to $machine: $?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print "BUILDING...\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					system("ssh $sshOpts $machine 'nix-store -rvvK $drvPath'") == 0
 | 
				
			||||||
 | 
					    or die "remote build on $machine failed: $?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print "REMOTE BUILD DONE: $drvPath on $machine\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foreach my $output (split '\n', $outputs) {
 | 
				
			||||||
 | 
					    my $maybeSignRemote = "";
 | 
				
			||||||
 | 
					    $maybeSignRemote = "--sign" if $UID != 0;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    system("ssh $sshOpts $machine 'nix-store --export $maybeSignRemote $output' > dump") == 0
 | 
				
			||||||
 | 
						or die "cannot copy $output from $machine: $?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # This doesn't work yet, since the caller has a lock on the output
 | 
				
			||||||
 | 
					    # path.  We should move towards lock-free invocation of build
 | 
				
			||||||
 | 
					    # hooks and substitutes.
 | 
				
			||||||
 | 
					    #system("nix-store --import < dump") == 0
 | 
				
			||||||
 | 
					    #    or die "cannot import $output: $?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Hack: skip the first 8 bytes (the nix-store --export next
 | 
				
			||||||
 | 
					    # archive marker).  The archive follows.
 | 
				
			||||||
 | 
					    system("(dd bs=1 count=8 of=/dev/null && cat) < dump | nix-store --restore $output") == 0
 | 
				
			||||||
 | 
						or die "cannot restore $output: $?";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue