#!/usr/bin/perl -U # rcbintool # push plib onto search path BEGIN { if ($ENV{'PERL_LIB'}) { push(@INC, $ENV{'PERL_LIB'}); } else { my($plib) = `echo ~rcdev/xbs-Current/plib`; chomp($plib); push(@INC, $plib); } } # required libraries use File::Basename; use Getopt::Long; use Cwd; #require 'hostname.pl'; # flush output after every write select((select(STDOUT), $| = 1)[0]); select((select(STDERR), $| = 1)[0]); if ( $> ) { my ($args) = join(' ', @ARGV); ($progname = $0) =~ s|^.*/||; print "\n"; print "*** WARNING: buildit should be run as root. We recommend using the\n"; print "*** following command to run this buildit attempt as the root user:\n"; print "***\n"; print "*** sudo $progname $args\n"; sleep 10; #} elsif ( $ENV{'GROUP'} ne "wheel") { # my ($args) = join(' ', @ARGV); # ($progname = $0) =~ s|^.*/||; # print "\n"; # print "*** WARNING: this root account isn't properly configured. (Perhaps you\n"; # print "*** typed \"su\" instead of \"su -\" to become root?)\n"; # print "*** We recommend using the following command to run this buildit\n"; # print "*** attempt as the root user:\n"; # print "***\n"; # print "*** sudo $progname $args\n"; # sleep 15; } # set group to wheel $ENV{GROUP} = 'wheel' if $> == 0; # set environment variables $ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'; $ENV{'RC_OBJCUNIQUE_SILENT'} = 'YES'; $ENV{'INSTALLED_PRODUCT_ASIDES'} = 'YES'; $ENV{'SEPARATE_STRIP'} = 'YES'; # set umask umask(022); # define list of supported architectures @archList = ( 'hppa', 'i386', 'm68k', 'ppc', 'ppc64', 'sparc' ); # set host name chomp($hostname = `hostname`); #$hostname = &hostname(); # get file name of this program ($buildit = $0) =~ s|^.*/(.*)$|$1|; # execute and exit &main(); ################################ SUBROUTINES ################################ sub usage { print "@_\n" if @_; print < [-release ] [-arch ]* [-noclean] [-noinstall] [-noinstallhdrs] [-noinstallsrc] [-noverify] [-nosum] [-noprebind] [-noperms] [-notilde] [-merge ] [-target ] [-buildstyle ] [-project ] [-othercflags ] [-- other arguments] For more detailed information, enter \"$buildit -help\". END_OF_USAGE exit 1; } sub help { print < Build with specified architectures(s) (-arch can be multiply specified: -arch ppc -arch ppc64 -arch i386) (default is native architecture if no -arch is specified) -noclean Disables deletion of {OBJ,SYM,DST}ROOT before start of build -noinstall Disables invocation of install (build) target -noinstallhdrs Disables invocation of installhdrs target -noinstallsrc Disables invocation of installsrc target -noverify Disables verification of project DSTROOT after build -nosum Disables before-and-after checksumming of project source -noprebind Disables prebinding -noperms Disables permissions setting on DSTROOT after build -notilde Disables inclusion of '~' character in DSTROOT, etc., paths -noarchwarn Disables warning about not building architecture for local machine -merge Merges resulting DSTROOT into \"mergeDir\" -target Specifies an alternate Make or Jam build target -buildstyle Specifies a ProjectBuilder buildstyle -project Project name to be used for verification exceptions -othercflags Specified cflags are appended to RC_CFLAGS for build Other arguments: Other arguments can be passed to the build (that is, to Make or Jam) by putting them at the end of the buildit argument list following a double dash (\"--\"). Arguments that contain whitespace should be enclosed in quotes. Examples: # Build in the current directory using all defaults buildit . # Build in specified directory for ppc architecture buildit myProjectDirectory -arch ppc # Build using install_debug Make or Jam target buildit myProjectDirectory -target install_debug # Only install headers and merge result into system root buildit myProjectDirectory -noinstall -merge / # Use existing {OBJ,SYM,DST}ROOT's and pass additional compiler flags buildit myProjectDirectory -noclean -othercflags \"-DMY_SWITCH -O3 -no-nasty-warnings\" # Suppress installsrc and pass other arguments to Make or Jam buildit myProjectDirectory -noinstallsrc -- -d \"NEXT_ROOT = /Network/Servers/builder/BuildRoot\" END_OF_HELP exit 0; } sub parseArgs { # default values $release = ''; @archs = (); $noclean = ''; $noinstall = ''; $noinstallhdrs = ''; $noinstallsrc = ''; $noverify = ''; $nosum = ''; $noprebind = ''; $noperms = ''; $notilde = ''; $noarchwarn = ''; $merge = ''; $target = ''; $buildstyle = ''; $project = ''; $othercflags = ''; $help = ''; # parse arguments for options &usage() unless @ARGV and GetOptions( 'release=s' => \$release, 'arch:s@' => \@archs, 'noclean' => \$noclean, 'noinstall' => \$noinstall, 'noinstallhdrs' => \$noinstallhdrs, 'noinstallsrc' => \$noinstallsrc, 'noverify' => \$noverify, 'nosum' => \$nosum, 'noprebind' => \$noprebind, 'noperms' => \$noperms, 'notilde' => \$notilde, 'noarchwarn' => \$noarchwarn, 'merge=s' => \$merge, 'target=s' => \$target, 'buildstyle=s' => \$buildstyle, 'project=s' => \$project, 'othercflags=s' => \$othercflags, 'help' => \$help, ); # check for help option &help() if $help; # make sure that release is defined $release = &defaultRelease() unless $release; warn "**** WARNING **** Can't get the release name for the OS version on your machine\n" unless $release; # If no arch specified, use the host arch if (scalar(@archs) == 0) { chomp(my ($arch) = `arch`); push(@archs, $arch); } # check that all specified archs are supported my @bogusArchs = (); my %knownArch; grep($knownArch{$_}++, @archList); foreach my $arch ( @archs ) { push(@bogusArchs, $arch) unless $knownArch{$arch}; } die "**** ERROR **** The following specified archs are not supported by buildit: @bogusArchs\n" if @bogusArchs; # warn if none of the specified archs match this machine's arch and we're merging # note: allow "ppc64" to match local arch of "ppc" if ($merge and !$noarchwarn) { my $myArch = `arch`; chomp($myArch); my @matches = grep(/^$myArch/, @archs); unless (@matches) { print "**** WARNING **** The root you are about to produce does not contain the architecture of the machine on which you're building. "; while (1) { print "Continue? ([y]/n) "; chomp($reply = ); if ($reply eq "y" or $reply eq "Y" or $reply eq "") { last; } if ($reply eq "n" or $reply eq "N") { exit(1); } } } } # get the view path $rc = (getpwnam('rc'))[7]; $viewPath = "$rc/Software/$release" if $rc; # get project source directory (must be first remaining argument) &usage("No project directory specified") unless @ARGV; $projectSourceDirectory = shift(@ARGV); @otherArguments = @ARGV; } sub main { my ($origNextRoot) = $ENV{'NEXT_ROOT'}; &parseArgs(); # process arguments &sourceSetupForRelease($release); # load our environment # &printenv(); # figure out the LD_SEG_ADDR_TABLE shell variable. # 1) If LD_SEG_ADDR_TABLE is set, use that. # 2) If seg_addr_table exists, set LD_SEG_ADDR_TABLE # 3) Leave LD_SEG_ADDR_TABLE unset. if (!defined($ENV{'LD_SEG_ADDR_TABLE'})) { my ($seg_addr_table) = "/AppleInternal/Developer/seg_addr_table"; if ( -f $seg_addr_table) { print "setting LD_SEG_ADDR_TABLE to '$seg_addr_table'\n"; $ENV{'LD_SEG_ADDR_TABLE'} = $seg_addr_table; } } # MDT 6/29/2001 set LD_TWOLEVEL_NAMESPACE print "setting LD_TWOLEVEL_NAMESPACE\n"; $ENV{'LD_TWOLEVEL_NAMESPACE'} = 'YES'; # figure out which NEXT_ROOT to use: # 1) If NEXT_ROOT is set as a shell variable, use it. #### 2) Hack: If we're building natively on beaker, clear NEXT_ROOT. # 2) Hack: If we're build for beaker on a non-beaker system, set NEXT_ROOT # for Matt Watson. # 3) Use the NEXT_ROOT specified by the build for this system # 3a) look at the build's .crossRoot file # 3b) assume we've set it in sourceSetupForRelease if ($origNextRoot) { $NEXT_ROOT = $origNextRoot; # $ENV{'NEXT_ROOT'} = $origNextRoot; } elsif ($release eq 'Beaker' && &defaultRelease() ne 'Beaker') { $NEXT_ROOT = '/Local/Public/MacOSX'; # $ENV{'NEXT_ROOT'} = $NEXT_ROOT; } else { $setup = "$viewPath/Updates/Newest$release/.crossRoot"; if (-f $setup) { # We're on DBS. Use the value in the .crossRoot file chomp($NEXT_ROOT = `cat $setup`); } else { # We're on the old system. Trust sourceSetupForRelease $NEXT_ROOT = defined($ENV{'NEXT_ROOT'}) ? $ENV{'NEXT_ROOT'} : ''; } # $ENV{'NEXT_ROOT'} = $NEXT_ROOT; } # set environment for prebinding if ($noprebind) { delete($ENV{'LD_PREBIND'}); } else { $ENV{'LD_PREBIND'}=''; } # go to specified project source directory chdir($projectSourceDirectory) || die "Can't change directory to $projectSourceDirectory: $!\n"; $projectSourceDirectory = getcwd(); # print "projectSourceDirectory = $projectSourceDirectory\n"; # extract project and project version $projectVersion = &basename($projectSourceDirectory); ($project = $projectVersion) =~ s/^(.*)-.*$/$1/ unless $project; ($projectSourceVersion = $projectVersion) =~ s/^.*-(.*)$/$1/; $projectSourceVersion = '' unless $projectSourceVersion and $projectSourceVersion ne $projectVersion; # print "projectVersion = $projectVersion\n"; # make the roots directory &makeRootsDirectory($projectVersion); # define the various source and binary directories if ($notilde) { $srcRoot = "$rootsDirectory/$projectVersion"; $objRoot = "$rootsDirectory/$projectVersion.obj"; $symRoot = "$rootsDirectory/$projectVersion.sym"; $dstRoot = "$rootsDirectory/$projectVersion.dst"; $hdrObjRoot = "$rootsDirectory/$projectVersion.hdrObj"; $hdrSymRoot = "$rootsDirectory/$projectVersion.hdrSym"; $hdrDstRoot = "$rootsDirectory/$projectVersion.hdrDst"; $buildLog = "$rootsDirectory/$projectVersion.log"; $hdrLog = "$rootsDirectory/$projectVersion.hdrlog"; $preBom = "$rootsDirectory/$projectVersion.prebom"; $postBom = "$rootsDirectory/$projectVersion.postbom"; $subRoot = "/usr/local/objs"; } else { $srcRoot = "$rootsDirectory/$projectVersion"; $objRoot = "$rootsDirectory/$projectVersion~obj"; $symRoot = "$rootsDirectory/$projectVersion~sym"; $dstRoot = "$rootsDirectory/$projectVersion~dst"; $hdrObjRoot = "$rootsDirectory/$projectVersion~hdrObj"; $hdrSymRoot = "$rootsDirectory/$projectVersion~hdrSym"; $hdrDstRoot = "$rootsDirectory/$projectVersion~hdrDst"; $buildLog = "$rootsDirectory/$projectVersion~log"; $hdrLog = "$rootsDirectory/$projectVersion~hdrlog"; $preBom = "$rootsDirectory/$projectVersion~prebom"; $postBom = "$rootsDirectory/$projectVersion~postbom"; $subRoot = "/usr/local/objs"; } # set location for compiler options file $ENV{'CC_PRINT_OPTIONS_FILE'} = "$symRoot/.CC_PRINT_OPTIONS" if $ENV{'CC_PRINT_OPTIONS'}; # get the build tool to be used for project build &getBuildTool($projectSourceDirectory); # do the installsrc if ($noinstallsrc) { $srcRoot = $projectSourceDirectory; } else { &installsrc(); } # verify the sources &verifySrc($srcRoot) unless $noverify; # checksum the source directory &checksum($srcRoot, $preBom) unless $nosum; # empty out the binary roots &cleanRoots($objRoot, $symRoot, $dstRoot, $hdrObjRoot, $hdrSymRoot, $hdrDstRoot) unless $noclean; # set up the build flags &buildParameters(); # do the installhdrs &build('installhdrs', $hdrLog) unless $noinstallhdrs; # set permissions on installhdrs root &setPerms($hdrDstRoot) unless $noinstallhdrs or $noperms; # Merge headers &doMerge($hdrDstRoot, $hdrLog); # do the build &build('install', $buildLog) unless $noinstall; # set permissions &setPerms($dstRoot) unless $noinstall or $noperms; # verify the results &verify($dstRoot, $buildLog) unless $noverify; # Now that the root has had permissions fixed and been verified # we can merge it &doMerge($dstRoot, $buildLog); # checksum the source directory &checksum($srcRoot, $postBom) unless $nosum; # compare the prebom and postbom &compareBoms($preBom, $postBom) unless $nosum; # all done print "\n*** $buildit ***: Done\n"; exit(0); } sub makeRootsDirectory { my($projectVersion) = @_; # figure out where to put the roots directory if (defined($ENV{'BUILDIT_DIR'} && $ENV{'BUILDIT_DIR'})) { $rootsDirectory = "$ENV{'BUILDIT_DIR'}/$projectVersion.roots"; } else { $rootsDirectory = "/private/tmp/$projectVersion.roots"; } # make the roots directory &mkdirs($rootsDirectory) unless -d $rootsDirectory; } sub mkdirs { my($dir) = @_; # recursively make all directories on path if (! -d $dir) { &mkdirs(&dirname($dir)); mkdir($dir, 0755) || die "Can't mkdir $dir: $!\n"; } } sub getBuildTool { my($projectSourceDirectory) = @_; # first figure out whether the projectInfo command is installed $projectInfo = (-x '/usr/bin/projectInfo') ? '/usr/bin/projectInfo' : ''; if (!$projectInfo) { $buildTool = 'make'; $buildDirectory = ''; $buildParams{'RC_OS'} = 'nextstep'; return; } # if we have an explicit OS name, use that. if ( -f "$viewPath/Updates/Newest$release/.osName" ) { chomp($buildParams{'RC_OS'} = `cat $viewPath/Updates/Newest$release/.osName`); } elsif ( -f "$viewPath/.osName" ) { chomp($buildParams{'RC_OS'} = `cat $viewPath/.osName`); } elsif ( -f "$viewPath/.releaseOSName" ) { chomp($buildParams{'RC_OS'} = `cat $viewPath/.releaseOSName`); } else { # now get OS name from pb_makefiles if ( -d "/System/Developer/Makefiles/pb_makefiles" ) { $buildParams{'RC_OS'} = 'teflon'; } elsif ( -f "$NEXT_ROOT/NextDeveloper/Makefiles/pb_makefiles/platform.make" ) { $platformFile = "$NEXT_ROOT/NextDeveloper/Makefiles/pb_makefiles/platform.make"; open(PLATFORMFILE, "< $platformFile") || die "Can't open $platformFile: $!\n"; while () { m/^\s*(\S*)\s*=\s*(\S*)\s*$/; if ($1 eq 'PLATFORM_OS') { $buildParams{'RC_OS'} = $2; } } close(PLATFORMFILE); if (!$buildParams{'RC_OS'}) { die "Can't get PLATFORM_OS value from $platformFile\n"; } } else { # default to NEXTSTEP $buildParams{'RC_OS'} = 'nextstep'; } } # first try for OS-specific build tool property chomp($buildTool = `$projectInfo NEXTSTEP_BUILDTOOL -inDirectory "$projectSourceDirectory"`); die "$projectInfo reported errors accessing key NEXTSTEP_BUILDTOOL in $projectSourceDirectory" if $?; # if need be, try for generic property if (!$buildTool) { chomp($buildTool = `$projectInfo BUILDTOOL -inDirectory "$projectSourceDirectory"`); die "$projectInfo reported errors accessing key BUILDTOOL in $projectSourceDirectory" if $?; } # couldn't get it, use default if (!$buildTool) { $buildTool = 'make'; $buildDirectory = ''; # This looks like a bad idea to me - Mike Trent # $buildParams{'RC_OS'} = (-d "/System/Developer/Makefiles/pb_makefiles" ) ? 'teflon' : 'nextstep'; return; } # we can use the presence of the BUILDDIR property to distinguish old project makefiles $buildDirectory = `$projectInfo NEXTSTEP_BUILDDIR -inDirectory "$projectSourceDirectory"`; die "$projectInfo reported errors accessing key NEXTSTEP_BUILDDIR in $projectSourceDirectory" if $?; if (!$buildDirectory) { $buildDirectory = `$projectInfo BUILDDIR -inDirectory "$projectSourceDirectory"`; die "$projectInfo reported errors accessing key BUILDDIR in $projectSourceDirectory" if $?; } chomp($buildDirectory); # print "buildTool is $buildTool\n"; # print "buildDirectory is $buildDirectory\n"; } sub installsrc { # initialize the new source directory if (-d $srcRoot) { print "\n*** $buildit ***: Removing existing SRCROOT $srcRoot ...\n"; &removeDirectory($srcRoot); } &mkdirs($srcRoot); # do the installsrc print "\n*** $buildit ***: Invoking installsrc target ...\n"; print "$buildTool installsrc \"SRCROOT=$srcRoot\"\n"; system($buildTool, 'installsrc', "SRCROOT=$srcRoot"); if ($?) { print "Removing $srcRoot ... "; &removeDirectory($srcRoot); exit 1; } # clean the installed sources print "\n*** $buildit ***: Cleaning the installed sources ...\n"; chdir($srcRoot) || die "Can't change directory to $srcRoot: $!\n"; if (!$buildDirectory) { print "$buildTool clean \"SRCROOT=$srcRoot\"\n"; system($buildTool, 'clean', "SRCROOT=$srcRoot"); if ($?) { chdir("/") || die "Can't change directory to /: $!\n"; print "Removing $srcRoot ... "; &removeDirectory($srcRoot); exit 1; } } else { print "$buildTool clean " . "\"SRCROOT=$srcRoot\" " . "\"OBJROOT=$srcRoot\" " . "\"SYMROOT=$srcRoot\"\n"; system($buildTool, 'clean', "SRCROOT=$srcRoot", "OBJROOT=$srcRoot", "SYMROOT=$srcRoot"); if ($?) { chdir("/") || die "Can't change directory to /: $!\n"; print "Removing $srcRoot ... "; &removeDirectory($srcRoot); exit 1; } } # chmod the source to be unwritable print "\n*** $buildit ***: Chmod'ing $srcRoot ...\n"; system('chmod', '-R', 'a-w', $srcRoot); if ($?) { chdir("/") || die "Can't change directory to /: $!\n"; print "Removing $srcRoot ... "; &removeDirectory($srcRoot); exit 1; } } sub scanForDylibsWithBadInstallNames { my($dstroot) = @_; # tools and table must be available return unless -x "/usr/local/bin/check_dylib"; return unless -x "/usr/bin/redo_prebinding"; return unless -f "/Local/Developer/seg_addr_table"; # chdir down to the dstroot my($cwd) = getcwd(); chdir($dstroot) or die "**** ERROR **** Can't chdir to $dstroot\n"; # run find command on the dstroot open(FIND, "find . -type f -exec /usr/bin/redo_prebinding -i -d {} \\; -print |") or die "Can't run find command on $dstroot\n"; # process the found dylibs $dylibError = 0; while ($foundDylib = ) { # munge the dylib path chomp($foundDylib); $foundDylib =~ s|^\.||; $foundDylibPath = "$dstroot$foundDylib"; # check the dylib $cmd = "/usr/local/bin/check_dylib $foundDylibPath -install_name $foundDylib -seg_addr_table /Local/Developer/seg_addr_table"; # print "$cmd\n"; $checkStat = system($cmd); $checkStat >>= 8; if ($checkStat == 2) { $dylibError = 1; print "\n**** ERROR **** Dylib $foundDylibPath has an incorrect install_name. The incorrect install_name can be examined by running \"otool -L $foundDylibPath\". The correct install_name is $foundDylib\n"; } } close(FIND); print "\n"; if ($dylibError) { exit(1); } # chdir back to original directory chdir($cwd) or die "**** ERROR **** Can't chdir to $cwd\n"; } sub setPerms { my($dstRoot) = @_; my($rcdev) = (getpwnam('rcdev'))[7]; print "\n*** $buildit ***: Setting permissions in $dstRoot ...\n"; if ( ! -f "$rcdev/plib/Permissions.pm" ) { warn "Can't find Permissions.pm, permissions will not be set\n"; return; } # reset the directory permissions require "Permissions.pm"; &Permissions::applyPermissionsToDirectory($viewPath, $dstRoot); } sub verify { my($dstroot, $log) = @_; local($verifyStatus, $verifyLog, $plib, $fatal); local($rcdev) = (getpwnam('rcdev'))[7]; print "\n*** $buildit ***: Verifying $project $dstroot ...\n"; # specialized dylib checking &scanForDylibsWithBadInstallNames($dstroot); # check for verification scripts # if ( ! -f "$rcdev/.Verification/Verify.config" ) { # warn "Can't find ~rcdev/.Verification/Verify.config, skipping verification\n"; # return; # } elsif ( ! -f "$rcdev/plib/Verify.pm" ) { # warn "Can't find ~rcdev/plib/Verify.pm, skipping verification\n"; # return; # } if ( ! -f "$rcdev/plib/Verify.pm" ) { warn "Can't find ~rcdev/plib/Verify.pm, skipping verification\n"; return; } # include Verify.pm require "Verify.pm"; # perform verification &Verify::readVerifyConfig("Newest$release"); $verifyStatus = &Verify::verifyProject($project, $dstroot); $verifyLog = &Verify::errorLog(); # print errors & warnings. if ($verifyLog) { $fatal = ($verifyStatus) ? ", and the overall project build has failed" : ""; # print to console print "\nYour project has verification failures$fatal. The list of verification failures follows below.\n\n"; print "Consult the following web page for descriptions of these errors:\n\n"; print " http://xbs.apple.com/Docs/Verification.html\n\n"; print "The verification failures are:\n\n"; print "$verifyLog"; # print to log open(LOG, ">>$log") || die "Can't open $log: $!\n"; select(LOG); $| = 1; select(STDOUT); print LOG "\nYour project has verification failures$fatal. The list of verification failures follows below.\n\n"; print LOG "Consult the following web page for descriptions of these errors:\n\n"; print LOG " http://xbs.apple.com/Docs/Verification.html\n\n"; print LOG "The verification failures are:\n\n"; print LOG "$verifyLog"; close(LOG); die ("Verification failed with exit status $verifyStatus\n") if ($verifyStatus); } } sub verifySrc { my($srcroot) = @_; local($verifyStatus, $verifyLog, $plib, $fatal); local($rcdev) = (getpwnam('rcdev'))[7]; print "\n*** $buildit ***: Verifying $project $srcroot ...\n"; if ( ! -f "$rcdev/plib/Verify.pm" ) { warn "Can't find ~rcdev/plib/Verify.pm, skipping verification\n"; return; } # include Verify.pm require "Verify.pm"; # locate the source verification file my $verifyConfig = -e "$viewPath/.Verification/SourceVerify.config" ? "$viewPath/.Verification/SourceVerify.config" : "$rcdev/xbs-Current/config/SourceVerify.config"; $verifyConfig = "SourceVerify.config" unless -e verifyConfig; # perform verification if (&Verify::readConfigFile($verifyConfig)) { print "skipping ... \n"; return; } $verifyStatus = &Verify::verifyProject($project, $srcroot); $verifyLog = &Verify::errorLog(); # print errors & warnings. if ($verifyLog) { $fatal = ($verifyStatus) ? ", and the overall project build has failed" : ""; print "\nYour project sources have verification failures$fatal. The list of verification failures follows below.\n\n"; print "Consult the following web page for descriptions of these errors:\n\n"; print " http://xbs.apple.com/Docs/Verification.html\n\n"; print "The verification failures are:\n\n"; print "$verifyLog"; die ("Verification failed with exit status $verifyStatus\n") if ($verifyStatus); } } sub checksum { my($directory, $bomFile) = @_; # remove any existing bom file if (-f $bomFile) { unlink($bomFile) || die "Can't unlink $bomFile: $!\n"; } # checksum the source print "\n*** $buildit ***: Checksumming $srcRoot ...\n"; executeCommand('mkbom', $directory, $bomFile); } sub compareBoms { my($bom1, $bom2) = @_; my($tmp1, $tmp2) = ("$bom1.lsbom", "$bom2.lsbom"); my($diffFile) = &dirname($bom1) . "/bomDiff.$$"; # dump the boms to text files print "\n*** $buildit ***: Comparing source checksums ...\n"; die "$bom1: $?\n" if system("lsbom $bom1 | sort > $tmp1"); die "$bom2: $?\n" if system("lsbom $bom2 | sort > $tmp2"); # compare the listings system("diff $tmp1 $tmp2 > $diffFile"); # complain if diffs are found if (-s $diffFile) { print "**** WARNING **** The following project source files were modified:\n"; system("cat $diffFile"); } # clean up temp files unlink($tmp1, $tmp2, $diffFile); } sub cleanRoots { my(@roots) = @_; # loop over the roots list print "\n*** $buildit ***: Cleaning up the binary roots ...\n"; foreach $root (@roots) { # remove the root &removeDirectory($root) if -d $root; # and recreate it &mkdirs($root); } } sub defaultRelease { local($file, $result, @list); # try sw_vers first if ( -x '/usr/bin/sw_vers' ) { open(SW_VERS, "/usr/bin/sw_vers |") or die "**** ERROR **** Can't run /usr/bin/sw_vers: $!\n"; while ($line = ) { next unless $line =~ m|BuildVersion:|; chomp($line); $systemBuildVersion = (split('\s+', $line))[1]; last if $systemBuildVersion; } close(SW_VERS); if ($systemBuildVersion) { ($majorNumber = $systemBuildVersion) =~ s|^([0-9]+).*$|$1|; if ($majorNumber) { $mapFile = `echo ~rc/Data/release_to_majorNumber.map`; chomp($mapFile); if ( -f $mapFile) { open(MAPFILE, "< $mapFile") or die "**** ERROR **** Can't open $mapFile: $!\n"; while ($entry = ) { next unless $entry =~ m|^\s*[0-9]|; chomp($entry); $entry =~ s|^\s+||; ($number, $releaseName) = split('\s+', $entry); if ($number == $majorNumber) { return $releaseName; } } } } } } @list = ( '/System/Library/CoreServices/release_name', '/System/Library/CoreServices/software_version', '/NextLibrary/CoreServices/release_name', '/NextLibrary/CoreServices/software_version', '/usr/lib/NextStep/software_version', ); foreach $file (@list) { if (-f $file) { chomp ($result = `cat $file | tail -1`); $result =~ s/\d+[a-zA-Z]\d*[a-z]?$//; # chomp ($result = `cat $file`); last; } } return $result; } # MDT deprecated by sourceSetupForRelease sub cflagsForRelease { local($release) = @_; local($result,$setup, @lines, $line); $setup = "$viewPath/Updates/Newest$release/.setupBuildEnvironment"; if (-f $setup) { open (SETUP, $setup) or die ("can't open $file: $_"); chomp(@lines = ); close(SETUP); foreach $line (@lines) { if ($line =~ /^setenv MORE_RC_CFLAGS/) { ($result = $line) =~ s/^setenv MORE_RC_CFLAGS//; $result =~ s/^\s*\"(.*)\"\s*$/$1/; last; } } } return $result; } sub sourceSetupForRelease { local($release) = @_; local($key, $value ,$setup, @lines, $line); $setup = "$viewPath/Updates/Newest$release/.setupBuildEnvironment"; if (-f $setup) { open (SETUP, $setup) or die ("can't open $file: $_"); chomp($top = ); close(SETUP); if ($top =~ /perl/) { # file is a perl script eval (`cat $setup`) || die ("can't eval setup file: $file"); } else { # file is a csh script system ("/bin/echo unsetenv \\* > /tmp/setup.$$"); system ("/bin/cat $setup >> /tmp/setup.$$"); system ("/bin/echo printenv >> /tmp/setup.$$"); chomp ($line = `/bin/csh /tmp/setup.$$`); @lines = split ('\n', $line); foreach (@lines) { if (/^(\S+)=(.*)$/) { $ENV{$1}=$2; } } unlink "/tmp/setup.$$"; } } } sub buildParameters { # initialize optional parameters foreach $arch (@archList) { $buildParams{"RC_$arch"} = ''; } # loop through the archs $buildParams{'RC_ARCHS'} = join(' ', @archs); foreach $arch (@archs) { if ($buildParams{'RC_CFLAGS'}) { $buildParams{'RC_CFLAGS'} .= " -arch $arch"; } else { $buildParams{'RC_CFLAGS'} = "-arch $arch"; } $buildParams{"RC_$arch"} = 'YES'; } # append other cflags $buildParams{'RC_CFLAGS'} .= " -pipe"; $buildParams{'RC_NONARCH_CFLAGS'} = "-pipe"; $buildParams{'RC_CFLAGS'} .= " $othercflags" if $othercflags; $buildParams{'RC_NONARCH_CFLAGS'} .= " $othercflags" if $othercflags; $buildParams{'RC_CFLAGS'} .= " $ENV{'MORE_RC_CFLAGS'}" if (defined($ENV{'MORE_RC_CFLAGS'})); $buildParams{'RC_NONARCH_CFLAGS'} .= " $ENV{'MORE_RC_CFLAGS'}" if (defined($ENV{'MORE_RC_CFLAGS'})); # we would like to obsolete this # if (-f "$NEXT_ROOT/System/Library/CoreServices/release_name") { # $buildParams{'RC_RELEASE'} = `cat $NEXT_ROOT/System/Library/CoreServices/release_name`; # chomp($buildParams{'RC_RELEASE'}); # } elsif (-f "$NEXT_ROOT/usr/lib/NextStep/release_name") { # $buildParams{'RC_RELEASE'} = `cat $NEXT_ROOT/usr/lib/NextStep/release_name`; # chomp($buildParams{'RC_RELEASE'}); # } else { # $buildParams{'RC_RELEASE'} = 'undefined'; # } if ($release) { $buildParams{'RC_RELEASE'} = $release; } else { $buildParams{'RC_RELEASE'} = 'undefined'; } # this is a production build $ENV{'RC_XBS'} = 'YES'; $buildParams{'RC_XBS'} = "$ENV{'RC_XBS'}"; # this is probably obsolete now $ENV{'RC_JASPER'} = 'YES'; $buildParams{'RC_JASPER'} = "$ENV{'RC_JASPER'}"; # defer setting of NEXT_ROOT env var until here $ENV{'NEXT_ROOT'} = $NEXT_ROOT if $NEXT_ROOT; # # set LD_SEG_ADDR_TABLE # $ENV{'LD_SEG_ADDR_TABLE'} = '/AppleInternal/Developer/seg_addr_table' # if -f '/AppleInternal/Developer/seg_addr_table'; } sub removeDirectory { my($directory) = @_; # first chmod all subdirectories to be writable &executeCommand('find', $directory, '-type', 'd', '-exec', 'chmod', '+w', '{}', ';'); # now remove the directory &executeCommand('rm', '-rf', $directory); } sub writeLogHeader { my($phase) = @_; $plusLine = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; # get cc version open(CC, "cc -v 2>&1 |") or die "**** ERROR **** cc -v failed: $!"; while () { next unless /version/; s/^.*Inc\. //; $ccVersion = $_; chomp($ccVersion); last; } close(CC); # get cctools version my($cctoolsADotOut) = "/tmp/__XX__buildit__cctools.a.out__XX__"; open(CCTOOLS, "as -v < /dev/null -o $cctoolsADotOut 2>&1 |") or die "**** ERROR **** as -v: $!"; while () { next unless /version/; s/^.*Inc\. //; $ccToolsVersion = $_; chomp($ccToolsVersion); last; } close(CCTOOLS); unlink($cctoolsADotOut); # write banner print LOG $plusLine; printf LOG "STARTING $buildit ($phase) of $projectVersion on %s", `date`; print LOG $plusLine; print LOG "Build configuration:\n"; print LOG " Build host: $hostname\n"; print LOG " Build Tool: $buildTool\n"; printf LOG " Build Target: %s\n", ($target ? $target : $phase); print LOG " Target Update: \n"; print LOG " Build Layer: \n"; printf LOG " Search path: %s\n", $ENV{'PATH'}; print LOG " cc version: $ccVersion\n"; print LOG " cctools version: $ccToolsVersion\n"; print LOG $plusLine; # write the various fields print LOG "Build parameters are:\n"; print LOG " SRCROOT: $srcRoot\n"; if ($phase eq 'install') { print LOG " OBJROOT: $objRoot\n"; print LOG " SYMROOT: $symRoot\n"; print LOG " DSTROOT: $dstRoot\n"; } else { print LOG " OBJROOT: $hdrObjRoot\n"; print LOG " SYMROOT: $hdrSymRoot\n"; print LOG " DSTROOT: $hdrDstRoot\n"; } print LOG " RC_ProjectName: $project\n"; print LOG " RC_ProjectSourceVersion: $projectSourceVersion\n"; print LOG " RC_ProjectBuildVersion: 1\n"; print LOG " RC_ReleaseStatus: Development\n"; printf LOG " RC_CFLAGS: %s\n", $buildParams{'RC_CFLAGS'}; printf LOG " RC_NONARCH_CFLAGS: %s\n", $buildParams{'RC_NONARCH_CFLAGS'}; printf LOG " RC_ARCHS: %s\n", $buildParams{'RC_ARCHS'}; printf LOG " RC_hppa: %s\n", $buildParams{'RC_hppa'}; printf LOG " RC_i386: %s\n", $buildParams{'RC_i386'}; printf LOG " RC_m68k: %s\n", $buildParams{'RC_m68k'}; printf LOG " RC_ppc: %s\n", $buildParams{'RC_ppc'}; printf LOG " RC_ppc64: %s\n", $buildParams{'RC_ppc64'}; printf LOG " RC_sparc: %s\n", $buildParams{'RC_sparc'}; printf LOG " RC_RELEASE: %s\n", $buildParams{'RC_RELEASE'}; printf LOG " RC_OS: %s\n", $buildParams{'RC_OS'}; print LOG " RC_DEVROOT: \n"; printf LOG " RC_XBS: %s\n", $buildParams{'RC_XBS'}; printf LOG " RC_JASPER: %s\n", $buildParams{'RC_JASPER'}; printf LOG " NEXT_ROOT: %s\n", $NEXT_ROOT; printf LOG " Prebinding: %s\n", (exists($ENV{'LD_PREBIND'}) ? "YES" : "NO"); printf LOG " Other Args: %s\n", join(' ', @otherArguments); print LOG $plusLine; print LOG "Environment variables:\n"; my($maxLength) = 0; for $envVar ( keys(%ENV) ) { $keyLength = length($envVar); $maxLength = $keyLength if $keyLength > $maxLength; } for $envVar ( sort(keys(%ENV)) ) { printf LOG "%-${maxLength}s \"%s\"\n", $envVar, $ENV{$envVar}; } print LOG $plusLine; } sub writeLogFooter { my($phase) = @_; # write banner print LOG "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; printf LOG "FINISHED $buildit ($phase) of $projectVersion on %s", `date`; print LOG "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; } sub doMake { my($log, $phase) = @_; MAKEFORK: { if ($pid = fork()) { # parent (tee) close(WRITE); while() { print LOG $_; print $_; } waitpid($pid, 0); my($childStatus) = $? / 256; exit($childStatus) if $childStatus; exit(0); } elsif (defined($pid)) { # child (make) close(READ); close(STDOUT); close(STDERR); open(STDOUT, ">&WRITE") || die "Can't dupe STDOUT to WRITE\n"; open(STDERR, ">&WRITE") || die "Can't dupe STDERR to WRITE\n"; &printMakeCommand(); exec(@makeCommand); } elsif ($! =~ /No more process/) { sleep 5; redo MAKEFORK; } else { die "$buildit: Can't fork make process: $!\n"; } } } sub doMerge { my($root, $log) = @_; my($mergeSrc); # skip unless merge option is specified return unless $merge; # print to log open(LOG, ">>$log") || die "Can't open $log: $!\n"; select(LOG); $| = 1; select(STDOUT); # write to log print LOG "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; printf LOG "Merging $root to $merge\n"; print LOG "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; # write to console print "\n*** $buildit ***: Merging $root to $merge\n"; # make sure that merge directory exists &mkdirs($merge); # do the merge # save STDOUT/STDERR, hook them up to the log file, run the command, # then restore STDOUT/STDERR open(SAVEOUT, ">&STDOUT"); open(SAVEERR, ">&STDERR"); open(STDOUT, ">&LOG") || die "Can't open $log: $!\n"; open(STDERR, ">&LOG") || die "Can't open $log: $!\n"; &executeCommand("ditto -V $root $merge"); close(STDOUT); close(STDERR); open(STDOUT, ">&SAVEOUT") || die "Can't open $log: $!\n"; open(STDERR, ">&SAVEERR") || die "Can't open $log: $!\n"; close(SAVEOUT); close(SAVEERR); close(LOG); } sub doTee { my($log, $phase) = @_; TEEFORK: { if ($pid = fork()) { # parent (buildit) waitpid($pid, 0); exit($? / 256) if $?; } elsif (defined($pid)) { # child (tee) pipe(READ, WRITE); &doMake($log, $phase); } elsif ($! =~ /No more process/) { sleep 5; redo TEEFORK; } else { die "$buildit: Can't fork tee process: $!\n"; } } } sub consMakeCommand { my($phase) = @_; # push the command line arguments onto the make command list @makeCommand = (); push(@makeCommand, $buildTool); # if PB or XCode build tool, emit action, target, and build style if ($buildTool =~ /(pb)|(pbx)|(xcode)build$/) { push(@makeCommand, $phase); push(@makeCommand, '-target', $target) if $target; push(@makeCommand, '-buildstyle', $buildstyle) if $buildstyle; } else { # probably a Make-based build if ($phase eq 'install') { push(@makeCommand, ($target ? $target : $phase)); } else { push(@makeCommand, $phase); } } push(@makeCommand, "SRCROOT=$srcRoot"); if ($phase eq 'install') { push(@makeCommand, "OBJROOT=$objRoot"); push(@makeCommand, "SYMROOT=$symRoot"); push(@makeCommand, "DSTROOT=$dstRoot"); } else { push(@makeCommand, "OBJROOT=$hdrObjRoot"); push(@makeCommand, "SYMROOT=$hdrSymRoot"); push(@makeCommand, "DSTROOT=$hdrDstRoot"); } push(@makeCommand, "SUBLIBROOTS=$subRoot"); push(@makeCommand, "RC_XBS=$buildParams{'RC_XBS'}"); push(@makeCommand, "RC_JASPER=$buildParams{'RC_JASPER'}"); push(@makeCommand, "RC_CFLAGS=$buildParams{'RC_CFLAGS'}"); push(@makeCommand, "RC_NONARCH_CFLAGS=$buildParams{'RC_NONARCH_CFLAGS'}"); push(@makeCommand, "RC_ARCHS=$buildParams{'RC_ARCHS'}"); push(@makeCommand, "RC_hppa=$buildParams{'RC_hppa'}"); push(@makeCommand, "RC_i386=$buildParams{'RC_i386'}"); push(@makeCommand, "RC_m68k=$buildParams{'RC_m68k'}"); push(@makeCommand, "RC_ppc=$buildParams{'RC_ppc'}"); push(@makeCommand, "RC_ppc64=$buildParams{'RC_ppc64'}"); push(@makeCommand, "RC_sparc=$buildParams{'RC_sparc'}"); push(@makeCommand, "RC_OS=$buildParams{'RC_OS'}"); push(@makeCommand, "RC_RELEASE=$buildParams{'RC_RELEASE'}"); push(@makeCommand, "RC_ProjectName=$project"); push(@makeCommand, "RC_ProjectSourceVersion=$projectSourceVersion"); push(@makeCommand, "RC_ProjectBuildVersion=1"); push(@makeCommand, "RC_ReleaseStatus=Development"); push(@makeCommand, "NEXT_ROOT=$NEXT_ROOT"); push(@makeCommand, @otherArguments); } sub printMakeCommand { # cough out the make command, quoting whitespaced arguments for ($i = 0; $i <= $#makeCommand; $i++) { if ($makeCommand[$i] =~ /\s/) { print "\"$makeCommand[$i]\" "; } else { print "$makeCommand[$i] "; } } print "\n"; } sub build { my($phase, $log) = @_; print "\n*** $buildit ***: $buildTool $phase ...\n"; # open the log open(LOG, ">$log") || die "Can't open $log: $!\n"; select(LOG); $| = 1; select(STDOUT); # write the log header &writeLogHeader($phase); # construct the make command &consMakeCommand($phase); #fork the tee process &doTee($log, $phase); # write the log footer &writeLogFooter($phase); # close the log close(LOG); } sub executeCommand { system(@_); if ( $? ) { $lowByte = ($? & 0377); if ($lowByte == 0177) { die "Command \"@_\" has been stopped\n"; } elsif ($lowByte) { $sigNum = ($lowByte & 0177); print "Command \"@_\" was terminated by signal $sigNum.\n"; if ($lowByte & 0200) { print "Core dumped.\n"; } exit 1; } $childExitStatus = (($? >> 8) & 0377); die "Command \"@_\" failed with exit status $childExitStatus\n" ; } } sub printenv { foreach (sort keys %ENV) { print "$_: $ENV{$_}\n"; } }