package Moz::BuildUtils; require 5.004; require Exporter; # Package that contains build util functions specific to the Mozilla build # process. use strict; use Exporter; use Cwd; use File::Path; use File::Basename; use Mac::Events; use Mac::StandardFile; use Moz::Moz; use Moz::BuildFlags; use Moz::MacCVS; #use Moz::ProjectXML; #optional; required for static build only use vars qw(@ISA @EXPORT); @ISA = qw(Exporter); @EXPORT = qw( SetupDefaultBuildOptions SetupBuildRootDir StartBuildModule EndBuildModule GetBinDirectory BuildOneProjectWithOutput BuildOneProject BuildProject BuildProjectClean BuildIDLProject BuildFolderResourceAliases AskAndPersistFile DelayFor TimeStart TimeEnd EmptyTree SetupBuildLog SetBuildNumber SetTimeBomb UpdateConfigHeader ); #//-------------------------------------------------------------------------------------------------- #// SetupDefaultBuildOptions #//-------------------------------------------------------------------------------------------------- sub SetupDefaultBuildOptions($$$) { my($debug, $bin_dir, $config_header_file_name) = @_; # Here we set up defaults for the various build flags. # If you want to override any of these, it's best to do # so via the relevant preferences file, which lives in # System Folder:Preferences:Mozilla build prefs:{build prefs file}. # For the name of the prefs file, see the .pl script that you # run to start this build. The prefs files are created when # you run the build, and contain some documentation. #------------------------------------------------------------- # configuration variables that globally affect what is built #------------------------------------------------------------- $main::DEBUG = $debug; $main::PROFILE = 0; $main::RUNTIME = 0; # turn on to just build runtime support and NSPR projects $main::GC_LEAK_DETECTOR = 0; # turn on to use GC leak detection $main::MOZILLA_OFFICIAL = 0; # generate build number $main::LOG_TO_FILE = 0; # write perl output to a file #------------------------------------------------------------- # configuration variables that affect the manner of building, # but possibly affecting the outcome. #------------------------------------------------------------- $main::ALIAS_SYM_FILES = $main::DEBUG; $main::CLOBBER_LIBS = 1; # turn on to clobber existing libs and .xSYM files before # building each project # The following two options will delete all dist files (if you have $main::build{dist} turned on), # but leave the directory structure intact. $main::CLOBBER_DIST_ALL = 1; # turn on to clobber all aliases/files inside dist (headers/xsym/libs) $main::CLOBBER_DIST_LIBS = 0; # turn on to clobber only aliases/files for libraries/sym files in dist $main::CLOBBER_IDL_PROJECTS = 0; # turn on to clobber all IDL projects. $main::CLOBBER_PROJECTS = 0; # turn on to remove object code from each project before building it $main::UNIVERSAL_INTERFACES_VERSION = 0x0320; #------------------------------------------------------------- # configuration variables that are preferences for the build, # style and do not affect what is built. #------------------------------------------------------------- $main::CLOSE_PROJECTS_FIRST = 0; # 1 = close then make (for development), # 0 = make then close (for tinderbox). $main::USE_TIMESTAMPED_LOGS = 0; $main::USE_BUILD_PROGRESS = 1; # track build progress for restartable builds #------------------------------------------------------------- # END OF CONFIG SWITCHES #------------------------------------------------------------- $main::BIN_DIRECTORY = $bin_dir; $main::DEFINESOPTIONS_FILE = $config_header_file_name; } #//-------------------------------------------------------------------------------------------------- #// SetupBuildRootDir #//-------------------------------------------------------------------------------------------------- sub SetupBuildRootDir($) { my($rel_path_to_script) = @_; my($cur_dir) = cwd(); $cur_dir =~ s/$rel_path_to_script$//; chdir($cur_dir) || die "Error: failed to set build root directory to '$cur_dir'.\nYou probably need to put 'mozilla' one level down (in a folder).\n"; $main::MOZ_SRC = cwd(); } #//-------------------------------------------------------------------------------------------------- #// StartBuildModule #//-------------------------------------------------------------------------------------------------- sub StartBuildModule($) { my($module) = @_; print("---- Start of $module ----\n"); } #//-------------------------------------------------------------------------------------------------- #// EndBuildModule #//-------------------------------------------------------------------------------------------------- sub EndBuildModule($) { my($module) = @_; WriteBuildProgress($module); print("---- End of $module ----\n"); } #-------------------------------------------------------------------------------------------------- # GetBinDirectory #-------------------------------------------------------------------------------------------------- sub GetBinDirectory() { if ($main::BIN_DIRECTORY eq "") { die "Dist directory not set\n"; } return $main::BIN_DIRECTORY; } #-------------------------------------------------------------------------------------------------- # AskAndPersistFile stores the information about the user pick inside # the file $session_storage #-------------------------------------------------------------------------------------------------- sub AskAndPersistFile($) { my ($sessionStorage) = @_; my $cvsfile; if (( -e $sessionStorage) && open( SESSIONFILE, $sessionStorage )) { # Read in the path if available $cvsfile = ; chomp $cvsfile; close SESSIONFILE; if ( ! -e $cvsfile ) { print STDERR "$cvsfile has disappeared\n"; undef $cvsfile; } } unless (defined ($cvsfile)) { # make sure that MacPerl is a front process ActivateApplication('McPL'); MacPerl::Answer("Could not find your MacCVS session file. Please choose one", "OK"); # prompt user for the file name, and store it my $macFile = StandardGetFile( 0, "McvD"); if ( $macFile->sfGood() ) { $cvsfile = $macFile->sfFile(); # save the choice if we can if ( open (SESSIONFILE, ">" . $sessionStorage)) { printf SESSIONFILE $cvsfile, "\n"; close SESSIONFILE; } else { print STDERR "Could not open storage file $sessionStorage for saving $cvsfile\n"; } } } return $cvsfile; } #-------------------------------------------------------------------------------------------------- # BuildIDLProject # #-------------------------------------------------------------------------------------------------- sub BuildIDLProject($$) { my ($project_path, $module_name) = @_; if ($main::CLOBBER_IDL_PROJECTS) { my (@suffix_list) = (".mcp", ".xml"); my ($project_name, $project_dir, $suffix) = fileparse($project_path, @suffix_list); if ($suffix eq "") { die "Error: Project, $project_path must end in .xml or .mcp\n"; } my($datafolder_path); if ($suffix eq ".xml") { $datafolder_path = $project_dir . "_" . $project_name . " Data:"; } else { $datafolder_path = $project_dir . $project_name . " Data:"; } print STDERR "Deleting IDL data folder: $datafolder_path\n"; EmptyTree($datafolder_path); } BuildOneProject($project_path, "headers", 0, 0, 0); BuildOneProject($project_path, $module_name.".xpt", 1, 0, 1); } #-------------------------------------------------------------------------------------------------- # CreateStaticLibTargets # #-------------------------------------------------------------------------------------------------- sub CreateXMLStaticLibTargets($) { my($xml_path) = @_; my (@suffix_list) = (".xml"); my ($project_name, $project_dir, $suffix) = fileparse($xml_path, @suffix_list); if ($suffix eq "") { die "XML munging: $xml_path must end in .xml\n"; } #sniff the file to see if we need to fix up broken Pro5-exported XML print "Parsing $xml_path\n"; my $ide_version = Moz::ProjectXML::SniffProjectXMLIDEVersion($xml_path); if ($ide_version eq "4.0") { my $new_file = $project_dir.$project_name."2.xml"; print "Cleaning up Pro 5 xml to $new_file\n"; Moz::ProjectXML::CleanupPro5XML($xml_path, $new_file); unlink $xml_path; rename ($new_file, $xml_path); } my $doc = Moz::ProjectXML::ParseXMLDocument($xml_path); my @target_list = Moz::ProjectXML::GetTargetsList($doc); my $target; my %target_hash; # for easy lookups below foreach $target (@target_list) { $target_hash{$target} = 1; } foreach $target (@target_list) { if ($target =~ /(.+).shlb$/) # if this is a shared lib target { my $target_base = $1; my $static_target = $target_base.".o"; # ensure that this does not exist already if ($target_hash{$static_target}) { print "Static target $static_target already exists in project. Not making\n"; next; } print "Making static target '$static_target' from target '$target'\n"; Moz::ProjectXML::CloneTarget($doc, $target, $static_target); Moz::ProjectXML::SetAsStaticLibraryTarget($doc, $static_target, $static_target); } } print "Writing XML file to $xml_path\n"; my $temp_path = $project_dir."_".$project_name.".xml"; Moz::ProjectXML::WriteXMLDocument($doc, $temp_path, $ide_version); Moz::ProjectXML::DisposeXMLDocument($doc); if (-e $temp_path) { unlink $xml_path; rename ($temp_path, $xml_path); } else { die "Error: Failed to add new targets to XML project\n"; } } #//-------------------------------------------------------------------------------------------------- #// ProcessProjectXML #// #// Helper routine to allow for XML pre-processing. This should read in the XML, process it, #// and replace the original file with the processed version. #//-------------------------------------------------------------------------------------------------- sub ProcessProjectXML($) { my($xml_path) = @_; # we need to manually load Moz::ProjectXML, becaues not everyone will have the # required perl modules in their distro. my($cur_dir) = cwd(); chdir(dirname($0)); # change to the script dir eval "require Moz::ProjectXML"; if ($@) { die "Error: could not do Project XML munging because you do not have the correct XML modules installed. Error is:\n################\n $@################"; } chdir($cur_dir); CreateXMLStaticLibTargets($xml_path); } #//-------------------------------------------------------------------------------------------------- #// Build one project, and make the alias. Parameters are project path, target name, shared library #// name, make shlb alias (boolean), make xSYM alias (boolean), and is component (boolean). #//-------------------------------------------------------------------------------------------------- sub BuildOneProjectWithOutput($$$$$$) { my ($project_path, $target_name, $output_name, $alias_lib, $alias_xSYM, $component) = @_; unless ($project_path =~ m/^$main::BUILD_ROOT.+/) { return; } my (@suffix_list) = (".mcp", ".xml"); my ($project_name, $project_dir, $suffix) = fileparse($project_path, @suffix_list); if ($suffix eq "") { die "Error: Project, $project_path must end in .xml or .mcp\n"; } my($dist_dir) = GetBinDirectory(); # Put libraries in "Essential Files" folder, Components in "Components" folder my($output_dir) = $component ? "Components:" : "Essential Files:"; my($output_path) = $dist_dir.$output_dir; if ($main::options{static_build}) { if ($output_name =~ /\.o$/ || $output_name =~ /\.[Ll]ib$/) { $alias_xSYM = 0; $alias_lib = 1; $output_path = $main::DEBUG ? ":mozilla:dist:static_libs_debug:" : ":mozilla:dist:static_libs:"; } } # if the flag is on to export projects to XML, export and munge them if ($main::EXPORT_PROJECTS && !($project_path =~ /IDL\.mcp$/)) { my $xml_out_path = $project_path; $xml_out_path =~ s/\.mcp$/\.xml/; # only do this if project is newer? if (! -e $xml_out_path) { ExportProjectToXML(full_path_to($project_path), full_path_to($xml_out_path)); ProcessProjectXML($xml_out_path); } } # if the flag is set to use XML projects, default to XML if the file # is present. if ($main::USE_XML_PROJECTS && !($project_path =~ /IDL\.mcp$/)) { my $xml_project_path = $project_dir.$project_name.".xml"; if (-e $xml_project_path) { $project_path = $xml_project_path; $suffix = ".xml"; } } if ($suffix eq ".xml") { my($xml_path) = $project_path; # Prepend an "_" onto the name of the generated project file so it doesn't conflict $project_path = $project_dir . "_" . $project_name . ".mcp"; my($project_modtime) = (-e $project_path ? GetFileModDate($project_path) : 0); my($xml_modtime) = (-e $xml_path ? GetFileModDate($xml_path) : 0); if ($xml_modtime > $project_modtime) { print("Importing $project_path from $project_name.xml.\n"); unlink($project_path); # Might want to delete the "xxx.mcp Data" dir ??? ImportXMLProject(full_path_to($xml_path), full_path_to($project_path)); } } if ($main::CLOBBER_LIBS) { unlink "$project_dir$output_name"; # it's OK if these fail unlink "$project_dir$output_name.xSYM"; } DoBuildProject($project_path, $target_name, $main::CLOBBER_PROJECTS); $alias_lib ? MakeAlias("$project_dir$output_name", "$output_path") : 0; $alias_xSYM ? MakeAlias("$project_dir$output_name.xSYM", "$output_path") : 0; } #//-------------------------------------------------------------------------------------------------- #// For compatiblity with existing scripts, BuildOneProject now just calls #// BuildOneProjectWithOutput, with the output name and target name identical. #// Note that this routine assumes that the target name and the shared libary name #// are the same. #//-------------------------------------------------------------------------------------------------- sub BuildOneProject($$$$$) { my ($project_path, $target_name, $alias_lib, $alias_xSYM, $component) = @_; BuildOneProjectWithOutput($project_path, $target_name, $target_name, $alias_lib, $alias_xSYM, $component); } #//-------------------------------------------------------------------------------------------------- #// For compatiblity with existing scripts, BuildProject now just calls #// BuildOneProjectWithOutput, with the output name and target name identical. #// Note that this routine assumes that the target name and the shared libary name #// are the same. No aliases of the output are made. #//-------------------------------------------------------------------------------------------------- sub BuildProject($$) { my ($project_path, $target_name) = @_; BuildOneProjectWithOutput($project_path, $target_name, $target_name, 0, 0, 0); } #//-------------------------------------------------------------------------------------------------- #// Identical to BuildProject but clobbers the project before building it. #//-------------------------------------------------------------------------------------------------- sub BuildProjectClean($$) { my ($project_path, $target_name) = @_; my ($save_clobber_flag) = $main::CLOBBER_PROJECTS; $main::CLOBBER_PROJECTS = 1; BuildOneProjectWithOutput($project_path, $target_name, $target_name, 0, 0, 0); $main::CLOBBER_PROJECTS = $save_clobber_flag; } #//-------------------------------------------------------------------------------------------------- #// Make resource aliases for one directory #//-------------------------------------------------------------------------------------------------- sub BuildFolderResourceAliases($$) { my($src_dir, $dest_dir) = @_; # get a list of all the resource files opendir(SRCDIR, $src_dir) || die("can't open $src_dir"); my(@resource_files) = readdir(SRCDIR); closedir(SRCDIR); # make aliases for each one into the dest directory print("Placing aliases to all files from $src_dir in $dest_dir\n"); for ( @resource_files ) { next if $_ eq "CVS"; #print(" Doing $_\n"); if (-l $src_dir.$_) { print(" $_ is an alias\n"); next; } my($file_name) = $src_dir . $_; MakeAlias($file_name, $dest_dir); } } #//-------------------------------------------------------------------------------------------------- #// DelayFor #// #// Delay for the given number of seconds, allowing the script to be cancelled #//-------------------------------------------------------------------------------------------------- sub DelayFor($) { my($delay_secs) = @_; STDOUT->autoflush(1); my($end_time) = time() + $delay_secs; my($last_time) = 0; my($cur_time) = time(); while ($cur_time < $end_time) { $cur_time = time(); if ($cur_time > $last_time) { print "."; $last_time = $cur_time; } WaitNextEvent(); } print "\n"; STDOUT->autoflush(0); } #//-------------------------------------------------------------------------------------------------- #// TimeStart #//-------------------------------------------------------------------------------------------------- sub TimeStart() { return time(); } #//-------------------------------------------------------------------------------------------------- #// TimeEnd #//-------------------------------------------------------------------------------------------------- sub TimeEnd($$) { use integer; my($start_time, $operation_name) = @_; my($end_time) = time(); my($tot_sec) = $end_time - $start_time; my($seconds) = $tot_sec; my($hours) = $seconds / (60 * 60); $seconds -= $hours * (60 * 60); my($minutes) = $seconds / 60; $seconds -= $minutes * 60; print "$operation_name took $hours hours $minutes minutes and $seconds seconds\n"; } #//-------------------------------------------------------------------------------------------------- #// Remove all files from a tree, leaving directories intact (except "CVS"). #//-------------------------------------------------------------------------------------------------- sub EmptyTree($) { my ($root) = @_; #print "EmptyTree($root)\n"; opendir(DIR, $root); my $sub; foreach $sub (readdir(DIR)) { my $fullpathname = $root.$sub; # -f, -d only work on full paths # Don't call empty tree for the alias of a directory. # -d returns true for the alias of a directory, false for a broken alias) if (-d $fullpathname) { if (-l $fullpathname) # delete aliases { unlink $fullpathname; next; } EmptyTree($fullpathname.":"); if ($sub eq "CVS") { #print "rmdir $fullpathname\n"; rmdir $fullpathname; } } else { unless (unlink $fullpathname) { die "Failed to delete $fullpathname\n"; } } } closedir(DIR); } #//-------------------------------------------------------------------------------------------------- #// Recurse through a directory hierarchy, looking for MANIFEST files. #// Currently unused. #//-------------------------------------------------------------------------------------------------- sub ScanForManifestFiles($$$$) { my($dir, $theme_root, $theme_name, $dist_dir) = @_; opendir(DIR, $dir) or die "Cannot open dir $dir\n"; my @files = readdir(DIR); closedir DIR; my $file; foreach $file (@files) { my $filepath = $dir.":".$file; if (-d $filepath) { # print "Looking for MANIFEST files in $filepath\n"; ScanForManifestFiles($filepath, $theme_root, $theme_name, $dist_dir); } elsif ($file eq "MANIFEST") { # print "Doing manifest file $filepath\n"; # Get the dest path from the first line of the file open(MANIFEST, $filepath) || die "Could not open file $file"; # Read in the path if available my($dest_line) = ; chomp $dest_line; close MANIFEST; $dest_line =~ s|^#!dest[\t ]+|| || die "No destination line found in $filepath\n"; my($dest_path) = $dist_dir."chrome:skins:$theme_name:$dest_line"; # print " Destination is $dest_path\n"; InstallResources($filepath, "$dest_path", 0); } } } #----------------------------------------------- # SetupBuildLog #----------------------------------------------- sub SetupBuildLog($$) { my($logfile_path, $timestamped_log) = @_; my($logdir) = ""; my($logfile) = $logfile_path; if ($logfile_path =~ /(.+?:)([^:]+)$/) # ? for non-greedy match { $logdir = $1; $logfile = $2; mkpath($logdir); } if ($timestamped_log) { #Use time-stamped names so that you don't clobber your previous log file! my $now = localtime(); while ($now =~ s@:@.@) {} # replace all colons by periods OpenErrorLog("${logdir}${now}"); } else { OpenErrorLog("${logdir}${logfile}"); } } #----------------------------------------------- # SetBuildNumber #----------------------------------------------- sub SetBuildNumber($$) { my($build_num_file, $files_to_touch) = @_; # Make sure we add the config dir to search, to pick up mozBDate.pm # Need to do this dynamically, because this module can be used before # mozilla/config has been checked out. my ($inc_path) = $0; # $0 is the path to the parent script $inc_path =~ s/:build:mac:build_scripts:.+$/:config/; push(@INC, $inc_path); require mozBDate; mozBDate::UpdateBuildNumber($build_num_file, $main::MOZILLA_OFFICIAL); my($file); foreach $file (@$files_to_touch) { print "Writing build number to $file from ${file}.in\n"; mozBDate::SubstituteBuildNumber($file, $build_num_file, "${file}.in"); } } #----------------------------------------------- # SetTimeBomb #----------------------------------------------- sub SetTimeBomb($$) { my ($warn_days, $bomb_days) = @_; system("perl :mozilla:config:mac-set-timebomb.pl $warn_days $bomb_days"); } #//-------------------------------------------------------------------------------------------------- #// Regenerate a configuration header file if necessary #//-------------------------------------------------------------------------------------------------- sub UpdateConfigHeader($) { my($config_path) = @_; my($config, $oldconfig) = ("", ""); my($define, $definevalue, $defines); my($k, $l,); foreach $k (keys(%main::options)) { if ($main::options{$k}) { foreach $l (keys(%{$main::optiondefines{$k}})) { $my::defines{$l} = $main::optiondefines{$k}{$l}; print "Setting up my::defines{$l}\n"; } } } my $config_headerfile = current_directory().$config_path; if (-e $config_headerfile) { open(CONFIG_HEADER, "< $config_headerfile") || die "$config_headerfile: $!\n"; my($line); while ($line = ) { if ($line =~ m/#define\s+([^\s]*)\s+([^\s]*)\s*\n/) { $define = $1; $definevalue = $2; #canonicalize so that whitespace changes are not significant my $canon_value = "#define " . $define . " " . $definevalue . "\n"; $oldconfig .= $canon_value; if (exists ($my::defines{$define}) and ($my::defines{$define} == $definevalue)) { delete $my::defines{$define}; $config .= $canon_value; } } } close(CONFIG_HEADER); } if (%my::defines) { foreach $k (keys(%my::defines)) { $config .= "#define " . $k . " " . $my::defines{$k} . "\n"; } } my $file_name = basename($config_headerfile); if (($config ne $oldconfig) || (!-e $config_headerfile)) { printf("Writing new configuration header $file_name\n"); open(CONFIG_HEADER, "> $config_headerfile") || die "$config_headerfile: $!\n"; print(CONFIG_HEADER "/* This file is auto-generated based on build options. Do not edit. */\n"); print CONFIG_HEADER ($config); close(CONFIG_HEADER); MacPerl::SetFileInfo("CWIE", "TEXT", $config_headerfile); } else { printf("Configuration header $file_name is up-to-date\n"); } } 1;