#!/usr/bin/perl # # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Patcher 2, a patch generator for the AUS2 system. # # The Initial Developer of the Original Code is # Mozilla Corporation # # Portions created by the Initial Developer are Copyright (C) 2006 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Chase Phillips (chase@mozilla.org) # J. Paul Reed (preed@mozilla.com) # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** # use strict; use Getopt::Long; use Data::Dumper; use Cwd; use English; use IO::Handle; use POSIX qw(strftime); use File::Path; use File::Copy qw(move copy); use File::Spec::Functions; use File::Basename; use MozAUSConfig; use MozAUSLib qw(CreatePartialMarFile GetAUS2PlatformStrings EnsureDeliverablesDir ValidateToolsDirectory SubstitutePath GetSnippetDirFromChannel CachedHashFile $OBJDIR PrepopulateHashCache); use MozBuild::Util qw(MkdirWithPath RunShellCommand DownloadFile); $Data::Dumper::Indent = 1; autoflush STDOUT 1; autoflush STDERR 1; ## ## CONSTANTS ## use vars qw($PID_FILE $DEFAULT_HASH_TYPE $DEFAULT_CVSROOT $DEFAULT_HGROOT $DEFAULT_SCHEMA_VERSION @SCHEMA_2_OPTIONAL_ATTR @COMPUTED_URLS); $PID_FILE = catfile(getcwd(), 'patcher2.pid'); $DEFAULT_HASH_TYPE = 'SHA512'; $DEFAULT_CVSROOT = ':pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot'; $DEFAULT_HGROOT = 'http://hg.mozilla.org/mozilla-central'; $DEFAULT_SCHEMA_VERSION = 2; # URLS which will have %platform%, %locale%, and %version% expanded # from schema 1: details, license # from schema 2: billboardURL, openURL, notificationURL, alertURL @COMPUTED_URLS = qw(details license billboardURL openURL notificationURL alertURL); @SCHEMA_2_OPTIONAL_ATTR = qw(showPrompt showNeverForVersion showSurvey actions); sub main { Startup(); my (%args, %move_args); my $config = new MozAUSConfig(); PrintUsage(exitCode => 1) if ($config eq undef); if (not $config->useChecksums() and not ($config->RequestedStep('build-tools') or $config->RequestedStep('build-tools-hg')) and not ValidateToolsDirectory(toolsDir => $config->GetToolsDir())) { my $badDir = $config->GetToolsDir(); print STDERR <<__END_TOOLS_ERROR__; ERROR: $badDir doesn't contain the required build tools and --build-tools wasn't requested; bailing... __END_TOOLS_ERROR__ PrintUsage(exitCode => 1); } my $startdir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); BuildTools(config => $config, fromHg => 0) if $config->RequestedStep('build-tools'); BuildTools(config => $config, fromHg => 1) if $config->RequestedStep('build-tools-hg'); run_download_complete_patches(config => $config) if $config->RequestedStep('download'); if ($config->useChecksums()) { PrepopulateHashCache(config => $config); } #printf("PRE-REMOVE-BROKEN-UPDATES:\n\n%s", Data::Dumper::Dumper($config)); $config->RemoveBrokenUpdates(); #printf("POST-REMOVE-BROKEN:\n\n%s", Data::Dumper::Dumper($config)); if ($config->RequestedStep('create-patches')) { if (!$config->useChecksums()) { CreatePartialPatches(config => $config); CreateCompletePatches(config => $config); } } if ($config->RequestedStep('(create-patches|create-patchinfo)')) { CreatePartialPatchinfo(config => $config); CreateCompletePatchinfo(config => $config); CreatePastReleasePatchinfo(config => $config); } Shutdown(); } ################# sub PrintUsage { my %args = @_; my $exitCode = $args{'exitCode'}; print STDERR <<__END_USAGE__; Usage: patcher2.pl --config=[FILE] --app=[APP] [MODE] Generate updates for Mozilla applications. Example: patcher2.pl --config=moz19-branch-patcher2.cfg --app=firefox --download Required global settings: --config=FILE Specify config file --app=APPNAME Application to build as specified in config file Patcher can be run in one of the following modes: --build-tools Checkout and build the tools needed for updates (From CVS) --build-tools-hg Checkout and build the tools needed for updates (From mozilla-central) --download Download "to" and "from" complete MAR files --create-patches Create partial MAR files NOTE - create-patches calls create-patchinfo --create-patchinfo Create AUS configuration file "snippets" Options: --partial-patchlist-file=FILE Use cache file; puts patcher into "fast mode" Only applicable when in "create-patches" mode --tools-revision=TAG Specify tag to use for build-tools checkout Only applicable when in "build-tools" mode --use-checksums Use checksums files instead of MAR files. __END_USAGE__ exit($exitCode) if (defined($exitCode)); } sub BuildTools { my %args = @_; my $config = $args{'config'}; my $fromHg = $args{'fromHg'}; my $codir = $config->GetToolsDir(); my $toolsRevision = $config->GetToolsRevision(); my $startdir = getcwd(); # Handle the cases where we shouldn't/can't proceed. if ( -e $codir and ! -d $codir ) { die "ERROR: $codir exists and isn't a directory"; } # Make the parent path. MkdirWithPath(dir => $codir, mask => 0751) or die "ERROR: MkdirWithPath($codir) FAILED"; chdir($codir); # Handle the cases where we shouldn't/can't proceed. if (-e "$codir/mozilla") { die "ERROR: $codir/mozilla exists. Please move it away before continuing!"; } if ($fromHg) { { # When we're building out of Mercurial this pulls the entire # repository. my $hgRoot = $ENV{'HGROOT'} || $DEFAULT_HGROOT; printf("Cloning $hgRoot and updating to $toolsRevision\n"); my $cloneArgs = ["clone", $hgRoot, "mozilla"]; run_shell_command(cmd => 'hg', cmdArgs => $cloneArgs, timeout => 3600); my $updateArgs = ["-R", "mozilla", "update", "-r", $toolsRevision]; run_shell_command(cmd => 'hg', cmdArgs => $updateArgs, timeout => 1800); printf("\n\nPull complete.\n"); } } else { { # When we're building out of CVS this *only* pulls client.mk printf("Checking out 'client.mk' from $toolsRevision... \n"); my $cvsroot = $ENV{'CVSROOT'} || $DEFAULT_CVSROOT; my $checkoutArgs = ["-d$cvsroot", 'co', '-r' . $toolsRevision, 'mozilla/client.mk']; run_shell_command(cmd => 'cvs', cmdArgs => $checkoutArgs); printf("\n\nCheckout complete.\n"); } # Checkout 'client.mk'. } my $mozDir = catfile($codir, 'mozilla'); # The checkout directory should exist but doesn't. if (not -e $mozDir) { die "ERROR: Couldn't create checkout directory $mozDir"; } { # Checkout and build mozilla dependencies and tools. my $mozconfig; # TODO - fix this to refer to the update-tools pull-target when # bug 329686 gets fixed. $mozconfig = "mk_add_options MOZ_CO_PROJECT=all\n"; $mozconfig .= "mk_add_options MOZ_CO_TAG=$toolsRevision\n"; $mozconfig .= "ac_add_options --enable-application=tools/update-packaging\n"; # these aren't required and introduce more dependencies $mozconfig .= "ac_add_options --disable-dbus\n"; $mozconfig .= "ac_add_options --disable-svg\n"; # On our *prometheus-vm machines we must use gtk2 as the default toolkit # On any other machines, they will be able to use the default, # cairo-gtk2, without issue. if (system("pkg-config --atleast-version=1.6.0 pango") != 0 || system("pkg-config --atleast-version=1.6.0 pangoft2") != 0 || system("pkg-config --atleast-version=1.6.0 pangoxft") != 0) { $mozconfig .= "ac_add_options --enable-default-toolkit=gtk2"; } open(MOZCFG, '>' . catfile($mozDir, '.mozconfig')) or die "ERROR: Opening .mozconfig for writing failed: $!"; print MOZCFG $mozconfig; close(MOZCFG); my $makeArgs = ['-f', './client.mk', "MOZ_OBJDIR=$OBJDIR"]; # When we're building out of Mercurial this goes straight to compiling # the tools. When we're building out of CVS this checks outo the # rest of the repository, and *then* builds the tools. run_shell_command(cmd => 'make', cmdArgs => $makeArgs, dir => $mozDir, timeout => 0); } # Checkout and build mozilla dependencies and tools. if (not ValidateToolsDirectory(toolsDir => $codir)) { die "BuildTools(): Couldn't find the tools after a BuildTools() run; something's wrong... bailing...\n"; } # Change directory to the starting directory. chdir($startdir); return 1; } sub run_download_complete_patches { my %args = @_; my $config = $args{'config'}; my $total = download_complete_patches(config => $config); download_complete_patches(total => $total, config => $config); } sub download_complete_patches { my %args = @_; my $config = $args{'config'}; my $i = 0; my $total = $args{'total'}; my $calculate_total = 0; if (defined($total)) { printf("Downloading complete patches - $total to download\n"); } else { $calculate_total = 1; } my $startdir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); chdir($deliverableDir); my $fromReleaseVersion = $config->GetCurrentUpdate()->{'from'}; my $toReleaseVersion = $config->GetCurrentUpdate()->{'to'}; my $r_config = $config->{'mAppConfig'}->{'release'}; my @releases = ($fromReleaseVersion, $toReleaseVersion); if ($config->useChecksums()){ @releases = ($toReleaseVersion); } for my $r (@releases) { my $rl_config = $r_config->{$r}; my $rlp_config = $rl_config->{'platforms'}; my @platforms = sort(keys(%{$rlp_config})); for my $p (@platforms) { my $platform_locales = $rlp_config->{$p}->{'locales'}; for my $l (@$platform_locales) { chdir($deliverableDir); my $relPath = catfile($r, 'ftp'); MkdirWithPath(dir => $relPath, mask => 0751) or die "MkdirWithPath($relPath) FAILED\n"; chdir($relPath); my $download_url = $rl_config->{'completemarurl'}; if ($config->useChecksums()){ $download_url = $rl_config->{'checksumsurl'}; } $download_url = SubstitutePath(path => $download_url, platform => $p, version => $r, locale => $l); my $output_filename = $MozAUSConfig::DEFAULT_MAR_NAME; if ($config->useChecksums()){ $output_filename = $MozAUSConfig::DEFAULT_CHECKSUMS_NAME; } my $output_filename = SubstitutePath( path => $output_filename, platform => $p, locale => $l, version => $r, app => lc($config->GetApp())); next if -e $output_filename; $i++; next if $calculate_total; my $path = "."; if ( $output_filename =~ m/^(.*)\/([^\/]*)$/ ) { $path = $1; } MkdirWithPath(dir => $path, mask => 0751) or die "Failed to mkpath($path)"; chdir($path); my $download_url_s = $download_url; my $output_filename_s = $output_filename; #next if -e $output_filename; $download_url_s =~ s/^.*(.{57})$/...$1/ if (length($download_url_s) > 60); $output_filename_s =~ s/^.*(.{57})$/...$1/ if (length($output_filename_s) > 60); my $start_time = time(); PrintProgress(total => $total, current => $i, string => $output_filename_s); if (exists($rl_config->{'completemaruser'}) and exists($rl_config->{'completemarpasswd'})) { DownloadFile(url => $download_url, dest => $output_filename, user => $rl_config->{'completemaruser'}, password => $rl_config->{'completemarpasswd'} ); } else { DownloadFile(url => $download_url, dest => $output_filename ); } chdir(catfile($deliverableDir, $r, 'ftp')); my $end_time = time(); my $total_time = $end_time - $start_time; if ( -f $output_filename ) { printf("done (" . $total_time . "s)\n"); } else { printf("failed (" . $total_time . "s)\n"); } select(undef, undef, undef, 0.5); } } } chdir($startdir); if (defined($total)) { printf("Finished downloading complete patches.\n"); } return $i; } # download_complete_patches sub PrintProgress { my %args = @_; my $currentStep = $args{'current'}; my $totalSteps = $args{'total'}; my $stepString = $args{'string'}; my $length = length($totalSteps); my $format = "[%${length}s/%${length}s]"; my $progressStr = sprintf($format, $currentStep, $totalSteps); print "\t$progressStr $stepString... "; } sub CreateCompletePatches { my %args = @_; my $config = $args{'config'}; my $update = $config->GetCurrentUpdate(); my $i = 0; my $total = 0; foreach my $plat (keys(%{$update->{'platforms'}})) { $total += scalar(keys(%{$update->{'platforms'}->{$plat}->{'locales'}})); } printf("Complete patches - $total to create\n"); my $startdir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); chdir($deliverableDir); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = sort keys %$u_config; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $complete = $u_config->{$u}->{'complete'}; my $complete_path = $complete->{'path'}; my $complete_url = $complete->{'url'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; my $to_name = $u_config->{$u}->{'to'}; my $gen_complete_path = $complete_path; $gen_complete_path = SubstitutePath(path => $complete_path, platform => $p, version => $to->{'appv'}, locale => $l); my $gen_complete_url = $complete_url; $gen_complete_url = SubstitutePath(path => $complete_url, platform => $p, version => $to->{'appv'}, locale => $l); #printf("%s", Data::Dumper::Dumper($to)); my $complete_pathname = catfile($u, 'ftp', $gen_complete_path); # Go to next iteration if this partial patch already exists. next if -e $complete_pathname; if ( -f $to_path and ! -e $complete_pathname ) { my $start_time = time(); # copy complete to the expected result PrintProgress(total => $total, current => ++$i, string => "$u/$p/$l"); $complete_pathname =~ m/^(.*)\/[^\/]*/; my $parentdir = $1; MkdirWithPath(dir => $parentdir, mask => 0751) or die "Failed to mkpath($parentdir)"; system("rsync -a $to_path $complete_pathname"); my $end_time = time(); my $total_time = $end_time - $start_time; printf("done (" . $total_time . "s)\n"); } #last if $i > 2; #$i++; select(undef, undef, undef, 0.5); } #last; } #last; } #printf("%s", Data::Dumper::Dumper($u_config)); chdir($startdir); if (defined($total)) { printf("\n"); } return $i; } # create_complete_patches sub CreatePartialPatches { my %args = @_; my $config = $args{'config'}; my $useFastPatcher = defined($config->{'mPartialPatchlistFile'}); if ($useFastPatcher) { print STDERR "fast patcher on!\n"; open(PARTIAL_PATCHLIST_FILE, ">$config->{'mPartialPatchlistFile'}") or die "open() of $config->{'mPartialPatchlistFile'} failed: $!"; } my $update = $config->GetCurrentUpdate(); my $total = 0; my $i = 0; foreach my $plat (keys(%{$update->{'platforms'}})) { $total += scalar(keys(%{$update->{'platforms'}->{$plat}->{'locales'}})); } printf("Partial patches - $total to create\n"); my $startdir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); chdir($deliverableDir); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = sort keys %$u_config; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $partial = $u_config->{$u}->{'partial'}; my $partial_path = $partial->{'path'}; my $partial_url = $partial->{'url'}; my $forcedUpdateList = $u_config->{$u}->{'force'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; my $to_name = $u_config->{$u}->{'to'}; my $gen_partial_path = $partial_path; $gen_partial_path = SubstitutePath(path => $partial_path, platform => $p, version => $to->{'appv'}, locale => $l ); my $gen_partial_url = $partial_url; $gen_partial_url = SubstitutePath(path => $partial_url, platform => $p, version => $to->{'appv'}, locale => $l ); #printf("%s", Data::Dumper::Dumper($to)); my $partial_pathname = catfile($u, 'ftp', $gen_partial_path); # Go to next iteration if this partial patch already exists. next if -e $partial_pathname; $i++; if ( -f $from_path and -f $to_path and ! -e $partial_pathname ) { if ($useFastPatcher) { $partial_pathname =~ m/^(.*)\/[^\/]*$/g; print PARTIAL_PATCHLIST_FILE getcwd() . '/' . $from_path . ',' . getcwd() . '/' . $to_path . ',' . getcwd() . '/' . $partial_pathname . ',' . join('|', @{$forcedUpdateList}) . "\n"; } else { my $start_time = time(); PrintProgress(total => $total, current => $i, string => "$u/$p/$l"); my $rv = CreatePartialMarFile(from => $from_path, to => $to_path, mozdir => $config->GetToolsDir(), outputDir => getcwd(), outputFile => 'partial.mar', force => $forcedUpdateList); if ($rv <= 0) { die 'Partial mar creation failed (see error above?); ' . 'aborting.'; } print $partial_pathname."\n\n"; # rename partial.mar to the expected result $partial_pathname =~ m/^(.*)\/[^\/]*$/g; my $partial_pathname_parent = $1; MkdirWithPath(dir => $partial_pathname_parent) or die "ASSERT: MkdirWithPath($partial_pathname_parent) FAILED\n"; move('partial.mar', $partial_pathname) or die "ASSERT: move(partial.mar, $partial_pathname) FAILED\n"; my $end_time = time(); my $total_time = $end_time - $start_time; printf("done (" . $total_time . "s)\n"); } } } } } #printf("%s", Data::Dumper::Dumper($u_config)); chdir($startdir); if ($useFastPatcher) { close(PARTIAL_PATCHLIST_FILE); # -u turns of output buffering so we get real-time updates my $fastIncrementalUpdateBinary = catfile($config->GetToolsDir(), 'mozilla', $MozAUSLib::FAST_INCREMENTAL_UPDATE_BIN); my $args = ['-u', $fastIncrementalUpdateBinary, '-f', $config->{'mPartialPatchlistFile'}]; $ENV{'PATH'} = catfile($config->GetToolsDir(), 'mozilla', $OBJDIR, 'dist', 'host', 'bin') . ':' . $ENV{'PATH'}; run_shell_command(cmd => 'python', cmdArgs => $args, timeout => 10800); } if (defined($total)) { printf("\n"); } return $i; } # create_partial_patches sub get_aus_platforms { my $short_platform = shift; my %aus_platform_strings = GetAUS2PlatformStrings(); my $aus_platforms = $aus_platform_strings{$short_platform}; if (not defined($aus_platforms)) { die "get_aus_platforms(): Unknown short platform: $short_platform"; } return $aus_platforms; } sub CreateCompletePatchinfo { my %args = @_; my $config = $args{'config'}; my $i = 0; my $total = 0; my $update = $config->GetCurrentUpdate(); my $startdir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); chdir($deliverableDir); my $u_config = $config->GetAppConfig()->{'update_data'}; my @updates = sort keys %$u_config; foreach my $u (@updates) { my @channels = @{$u_config->{$u}->{'all_channels'}}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; foreach my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; foreach my $l (@locales) { foreach my $c (@channels) { $total++; } } } } printf("Complete patch info - $total to create\n"); #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $complete = $u_config->{$u}->{'complete'}; my $complete_path = $complete->{'path'}; my $complete_url = $complete->{'url'}; my $currentUpdateRcInfo = $u_config->{$u}->{'rc'}; my @channels = @{$u_config->{$u}->{'all_channels'}}; my $channel = $u_config->{$u}->{'channel'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; my $to_name = $u_config->{$u}->{'to'}; # Build patch info my $from_aus_app = ucfirst($config->GetApp()); my $from_aus_version = $from->{'appv'}; my @from_aus_platforms = @{ get_aus_platforms($p) }; my $from_aus_buildid = $from->{'build_id'}; my $gen_complete_path = $complete_path; $gen_complete_path = SubstitutePath(path => $complete_path, platform => $p, version => $to->{'appv'}, locale => $l ); my $complete_pathname = "$u/ftp/$gen_complete_path"; my $gen_complete_url = SubstitutePath(path => $complete_url, platform => $p, version => $to->{'appv'}, locale => $l ); my $computed_urls = {}; foreach my $attr (@COMPUTED_URLS) { if (defined($u_config->{$u}->{$attr})) { $computed_urls->{$attr} = SubstitutePath( path => $u_config->{$u}->{$attr}, locale => $l, platform => $p, version => $to->{'appv'}); } } my $updateType = $config->GetCurrentUpdate()->{'updateType'}; for my $c (@channels) { my $snippetDir = GetSnippetDirFromChannel( config => $config->GetCurrentUpdate(), channel => $c); my $snippetToAppVersion = $to->{'appv'}; my $prettySnippetToAppVersion = $to->{'prettyAppv'}; foreach my $channel (keys(%{$currentUpdateRcInfo})) { if ($c eq $channel) { $snippetToAppVersion = $to->{'appv'} . 'build' . $currentUpdateRcInfo->{$channel}; $prettySnippetToAppVersion = $to->{'prettyAppv'} . ' (build ' . $currentUpdateRcInfo->{$channel} . ')'; last; } } for my $from_aus_platform (@from_aus_platforms) { my $aus_prefix = catfile($u, $snippetDir, $from_aus_app, $from_aus_version, $from_aus_platform, $from_aus_buildid, $l, $c); my $complete_patch = $ul_config->{$l}->{'complete_patch'}; $complete_patch->{'info_path'} = catfile($aus_prefix, 'complete.txt'); next if ( -e $complete_patch->{'info_path'} or (! $config->useChecksums() && ! -e $complete_pathname ) ); $i++; #printf("partial = %s", Data::Dumper::Dumper($partial_patch)); #printf("complete = %s", Data::Dumper::Dumper($complete_patch)); # This is just prettyfication for the PrintProgress() call, # so we know which channels have had rc's added to their # appv's. my $progressVersion = $u; if ($snippetToAppVersion ne $to->{'appv'}) { $progressVersion =~ s/\-$to->{'appv'}/-$snippetToAppVersion/; } PrintProgress(total => $total, current => $i, string => "$progressVersion/$p/$l/$c"); $complete_patch->{'patch_path'} = $to_path; $complete_patch->{'type'} = 'complete'; my $hash_type = $DEFAULT_HASH_TYPE; $complete_patch->{'hash_type'} = $hash_type; $complete_patch->{'hash_value'} = CachedHashFile( file => $to_path, type => $hash_type); $complete_patch->{'hash_value'} =~ s/^(\S+)\s+.*$/$1/g; $complete_patch->{'build_id'} = $to->{'build_id'}; $complete_patch->{'appv'} = $prettySnippetToAppVersion; $complete_patch->{'extv'} = $to->{'extv'}; $complete_patch->{'size'} = CachedHashFile( file => $to_path, type => 'size'); my $channelSpecificUrlKey = $c . '-url'; if (exists($complete->{$channelSpecificUrlKey})) { $complete_patch->{'url'} = SubstitutePath( path => $complete->{$channelSpecificUrlKey}, platform => $p, version => $to->{'appv'}, locale => $l); } else { $complete_patch->{'url'} = $gen_complete_url; } foreach my $attr (keys %{$computed_urls}) { $complete_patch->{$attr} = $computed_urls->{$attr}; } foreach my $attr (@SCHEMA_2_OPTIONAL_ATTR) { if (defined($u_config->{$u}->{$attr})) { $complete_patch->{$attr} = $u_config->{$u}->{$attr}; } } $complete_patch->{'updateType'} = $updateType; write_patch_info(patch => $complete_patch, schemaVer => $to->{'schema'}); if (defined($u_config->{$u}->{'testchannel'})) { # Deep copy this data structure, since it's a copy of # $ul_config->{$l}->{'complete_patch'}; my $testPatch = {}; foreach my $key (keys(%{$complete_patch})) { $testPatch->{$key} = $complete_patch->{$key}; } # XXX - BUG: this shouldn't be run inside the # foreach my $channels loop; it causes us to re-create # the test channel snippets at least once through. # I think I did it this way because all the info I # needed was already all built up in complete_patch foreach my $testChan (split(/[\s,]+/, $u_config->{$u}->{'testchannel'})) { $snippetToAppVersion = $to->{'appv'}; $prettySnippetToAppVersion = $to->{'prettyAppv'}; foreach my $channel (keys(%{$currentUpdateRcInfo})) { if ($testChan eq $channel) { $snippetToAppVersion = $to->{'appv'} . 'build' . $currentUpdateRcInfo->{$channel}; $prettySnippetToAppVersion = $to->{'prettyAppv'} . ' (build ' . $currentUpdateRcInfo->{$channel} . ')'; last; } } # Note that we munge the copy here, but below # we use $to->{appv} in the SubstitutePath() call # to handle the URL; we do this because we only # want the snippet to have the rc value for its # appv, but not for any of the other places where # we use version. $testPatch->{'appv'} = $prettySnippetToAppVersion; my $snippetDir = GetSnippetDirFromChannel( config => $u_config->{$u}, channel => $testChan); $testPatch->{'info_path'} = catfile($u, $snippetDir, $from_aus_app, $from_aus_version, $from_aus_platform, $from_aus_buildid, $l, $testChan, 'complete.txt'); my $testUrlKey = $testChan . '-url'; if (exists($complete->{$testUrlKey})) { $testPatch->{'url'} = SubstitutePath( path => $complete->{$testUrlKey}, platform => $p, version => $to->{'appv'}, locale => $l ); } else { $testPatch->{'url'} = $gen_complete_url; } write_patch_info(patch => $testPatch, schemaVer => $to->{'schema'}); } } } print("done\n"); } } } } chdir($startdir); printf("\n"); return $i; } # create_complete_patch_info sub CreatePastReleasePatchinfo { my %args = @_; my $config = $args{'config'}; my $patchInfoFilesCreated = 0; my $totalPastUpdates = 0; my $startDir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); chdir($deliverableDir); my $update = $config->GetCurrentUpdate(); my $prefixStr = "$update->{'from'}-$update->{'to'}"; foreach my $pastUpd (@{$config->GetPastUpdates()}) { my $fromRelease = $config->GetAppRelease($pastUpd->{'from'}); my @pastFromPlatforms = sort(keys(%{$fromRelease->{'platforms'}})); foreach my $fromPlatform (@pastFromPlatforms) { foreach my $locale (@{$fromRelease->{'platforms'}->{$fromPlatform}->{'locales'}}) { foreach my $channel (@{$pastUpd->{'channels'}}) { $totalPastUpdates++; } } } } # Multiply by two for the partial and the complete... # DISABLED IN BUG 514040 #$totalPastUpdates *= 2; printf("Past release patch info - $totalPastUpdates to create\n"); foreach my $pastUpd (@{$config->GetPastUpdates()}) { my $fromRelease = $config->GetAppRelease($pastUpd->{'from'}); my $currentRelease = $config->GetAppRelease($config->GetCurrentUpdate()->{'to'}); my @pastFromPlatforms = sort(keys(%{$fromRelease->{'platforms'}})); my $currentReleaseRcInfo = $config->GetCurrentUpdate()->{'rc'}; my $complete = $config->GetCurrentUpdate()->{'complete'}; my $completePath = $complete->{'path'}; my $completeUrl = $complete->{'url'}; foreach my $fromPlatform (@pastFromPlatforms) { # XXX - This is a hack, solely to support the fact that "mac" # now means "universal binaries," but for 1.5.0.2 and 1.5.0.3, there # was "mac" which meant PPC and "unimac," which meant universal # binaries. Unfortunately, we can't just make > 1.5.0.4 use a # platform of "unimac," because all the filenames are foo.mac.dmg, # not foo.unimac.dmg. Le sigh. # # So, what this does is checks if $patchPlatformNode is null AND # our platform is macppc; we want all the old macppc builds to # update to the universal builds; so, we can get the proper locales # and build IDs by grabbing the proper patchPlatformNode using # the a key of 'mac' to generate the right strings. # # But, that's not all; we need to support this concept of "platform # transformations," so now we have a "fromPlatform" and a # "toPlatform" which, MOST of the time, will be the same, but # for this specific case, won't be. my $toPlatform = $fromPlatform; my $patchPlatformNode = $update->{'platforms'}->{$toPlatform}; if ($patchPlatformNode eq undef && $fromPlatform eq 'macppc') { $toPlatform = 'mac'; $patchPlatformNode = $update->{'platforms'}->{$toPlatform}; } foreach my $locale (@{$fromRelease->{'platforms'}->{$fromPlatform}->{'locales'}}) { my $patchLocaleNode = $patchPlatformNode->{'locales'}->{$locale}->{'to'}; if ($patchLocaleNode eq undef) { print STDERR "No known patch for locale $locale, $fromRelease->{'version'} -> $currentRelease->{'version'}; skipping...\n"; next; } my $to_path = $patchLocaleNode->{'path'}; # Build patch info my $fromAusApp = ucfirst($config->GetApp()); my $fromAusVersion = $fromRelease->{'version'}; my @fromAusPlatforms = @{ get_aus_platforms($fromPlatform) }; my $fromAusBuildId = $fromRelease->{'platforms'}->{$fromPlatform}->{'build_id'}; my $genCompletePath = SubstitutePath(path => $completePath, platform => $toPlatform, version => $patchLocaleNode->{'appv'}, locale => $locale ); my $completePathname = "$prefixStr/ftp/$genCompletePath"; my $genCompleteUrl = SubstitutePath(path => $completeUrl, platform => $toPlatform, version => $patchLocaleNode->{'appv'}, locale => $locale ); my $computed_urls = {}; foreach my $attr (@COMPUTED_URLS) { if (defined($config->GetCurrentUpdate()->{$attr})) { $computed_urls->{$attr} = SubstitutePath( path => $config->GetCurrentUpdate()->{$attr}, locale => $locale, platform => $fromPlatform, version => $patchLocaleNode->{'appv'}); } } my $updateType = $config->GetCurrentUpdate()->{'updateType'}; foreach my $channel (@{$pastUpd->{'channels'}}) { my $ausDir = GetSnippetDirFromChannel(config => $config->GetCurrentUpdate(), channel => $channel); my $snippetToAppVersion = $patchLocaleNode->{'appv'}; my $prettySnippetToAppVersion = $patchLocaleNode->{'prettyAppv'}; foreach my $rcChan (keys(%{$currentReleaseRcInfo})) { if ($rcChan eq $channel) { $snippetToAppVersion = $patchLocaleNode->{'appv'} . 'build' . $currentReleaseRcInfo->{$channel}; $prettySnippetToAppVersion = $patchLocaleNode->{'prettyAppv'} . ' (build ' . $currentReleaseRcInfo->{$channel} . ')'; last; } } for my $fromAusPlatform (@fromAusPlatforms) { my $ausPrefix = catfile($prefixStr, $ausDir, $fromAusApp, $fromAusVersion, $fromAusPlatform, $fromAusBuildId, $locale, $channel); my $completePatch = {}; $completePatch ->{'info_path'} = catfile($ausPrefix, 'complete.txt'); my $prettyPrefix = "$pastUpd->{'from'}-$update->{'to'}"; if ($snippetToAppVersion ne $update->{'to'}) { $prettyPrefix = "$pastUpd->{'from'}-$snippetToAppVersion"; } PrintProgress(total => $totalPastUpdates, current => ++$patchInfoFilesCreated, string => "$prettyPrefix/$fromAusPlatform/$locale/$channel/complete"); # Go to next iteration if this partial patch already exists. #next if ( -e $complete_patch->{'info_path'} or ! -e $complete_pathname ); $completePatch->{'patch_path'} = $to_path; $completePatch->{'type'} = 'complete'; my $hash_type = $DEFAULT_HASH_TYPE; $completePatch->{'hash_type'} = $hash_type; $completePatch->{'hash_value'} = CachedHashFile( file => $to_path, type => $hash_type); $completePatch->{'build_id'} = $patchLocaleNode->{'build_id'}; $completePatch->{'appv'} = $prettySnippetToAppVersion; $completePatch->{'extv'} = $patchLocaleNode->{'extv'}; $completePatch->{'size'} = CachedHashFile( file => $to_path, type => 'size'); my $channelSpecificUrlKey = $channel . '-url'; if (exists($complete->{$channelSpecificUrlKey})) { $completePatch->{'url'} = SubstitutePath( path => $complete->{$channelSpecificUrlKey}, platform => $toPlatform, version => $patchLocaleNode->{'appv'}, locale => $locale); } else { $completePatch->{'url'} = $genCompleteUrl; } foreach my $attr (keys %{$computed_urls}) { $completePatch->{$attr} = $computed_urls->{$attr}; } foreach my $attr (@SCHEMA_2_OPTIONAL_ATTR) { if (defined($config->GetCurrentUpdate()->{$attr})) { $completePatch->{$attr} = $config->GetCurrentUpdate()->{$attr}; } } $completePatch->{'updateType'} = $updateType; write_patch_info(patch => $completePatch, schemaVer => $patchLocaleNode->{'schema'}); print("done\n"); # Now, write the same information as a partial, since # for now, we publish the "partial" and "complete" updates # as pointers to the complete. # DISABLED IN BUG 514040 #$completePatch->{'type'} = 'partial'; #$completePatch->{'info_path'} = "$ausPrefix/partial.txt"; #PrintProgress(total => $totalPastUpdates, # current => ++$patchInfoFilesCreated, # string => "$prettyPrefix/$fromAusPlatform/$locale/$channel/partial"); #write_patch_info(patch => $completePatch, # schemaVer => $patchLocaleNode->{'schema'}); } print("done\n"); } } } } chdir($startDir); printf("\n"); } sub CreatePartialPatchinfo { my %args = @_; my $config = $args{'config'}; my $i = 0; my $total = 0; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); my $startdir = getcwd(); my $deliverableDir = EnsureDeliverablesDir(config => $config); chdir($deliverableDir); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = sort keys %$u_config; # TODO - This could be cleaner. foreach my $u (@updates) { my @channels = @{$u_config->{$u}->{'all_channels'}}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; foreach my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; foreach my $l (@locales) { foreach my $c (@channels) { $total++; } } } } printf("Partial patch info - $total to create\n"); #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $partial = $u_config->{$u}->{'partial'}; my $partial_path = $partial->{'path'}; my $partial_url = $partial->{'url'}; # We get the information about complete patches for the case where # we're in an rc channel, and the rc is > 2; in that case, we need # to serve completes as partials on those channels, because we're # not going to re-create partial updates from every rc build we do. # We track this throughout this horrid function via # $serveCompleteUpdateToRcs (further down below) my $complete = $u_config->{$u}->{'complete'}; my $complete_path = $complete->{'path'}; my $complete_url = $complete->{'url'}; my $currentUpdateRcInfo = $u_config->{$u}->{'rc'}; # Used in the case where we never released rc1, so rc2 or 3 or 4 is # actually the "first" release rc. my $disableCompleteJumpForRcs = exists($u_config->{$u}->{'DisableCompleteJump'}) && int($u_config->{$u}->{'DisableCompleteJump'}); my @channels = @{$u_config->{$u}->{'all_channels'}}; my $channel = $u_config->{$u}->{'channel'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; #my $to_name = $u_config->{$u}->{'to'}; # Build patch info my $from_aus_app = ucfirst($config->GetApp()); my $from_aus_version = $from->{'appv'}; my @from_aus_platforms = @{ get_aus_platforms($p) }; my $from_aus_buildid = $from->{'build_id'}; my $gen_partial_path = $partial_path; $gen_partial_path = SubstitutePath(path => $partial_path, platform => $p, version => $to->{'appv'}, locale => $l ); my $partial_pathname = "$u/ftp/$gen_partial_path"; my $gen_partial_url = SubstitutePath(path => $partial_url, platform => $p, version => $to->{'appv'}, locale => $l ); my $partialPatchHash = CachedHashFile(file => $partial_pathname, type => $DEFAULT_HASH_TYPE); my $partialPatchSize = CachedHashFile( file => $partial_pathname, type => 'size'); my $gen_complete_path = SubstitutePath(path => $complete_path, platform => $p, version => $to->{'appv'}, locale => $l ); my $complete_pathname = "$u/ftp/$gen_complete_path"; my $gen_complete_url = SubstitutePath(path => $complete_url, platform => $p, version => $to->{'appv'}, locale => $l ); my $completePatchHash = CachedHashFile( file => $complete_pathname, type => $DEFAULT_HASH_TYPE); my $completePatchSize = CachedHashFile( file => $complete_pathname, type => 'size'); my $computed_urls = {}; foreach my $attr (@COMPUTED_URLS) { if (defined($u_config->{$u}->{$attr})) { $computed_urls->{$attr} = SubstitutePath( path => $u_config->{$u}->{$attr}, locale => $l, platform => $p, version => $to->{'appv'}); } } my $updateType = $u_config->{$u}->{'updateType'}; for my $c (@channels) { my $serveCompleteUpdateToRcs = 0; my $snippetPathname = $partial_pathname; my $snippetUrl = $gen_partial_url; my $snippetDir = GetSnippetDirFromChannel(config => $u_config->{$u}, channel => $c); my $snippetToAppVersion = $to->{'appv'}; my $prettySnippetToAppVersion = $to->{'prettyAppv'}; foreach my $channel (keys(%{$currentUpdateRcInfo})) { if ($c eq $channel) { $snippetToAppVersion = $to->{'appv'} . 'build' . $currentUpdateRcInfo->{$channel}; $prettySnippetToAppVersion = $to->{'prettyAppv'} . ' (build ' . $currentUpdateRcInfo->{$channel} . ')'; $serveCompleteUpdateToRcs = (!$disableCompleteJumpForRcs) && (int($currentUpdateRcInfo->{$channel}) > 1); if ($serveCompleteUpdateToRcs) { $snippetPathname = $complete_pathname; $snippetUrl = $gen_complete_url; } last; } } for my $from_aus_platform (@from_aus_platforms) { my $aus_prefix = catfile($u, $snippetDir, $from_aus_app, $from_aus_version, $from_aus_platform, $from_aus_buildid, $l, $c); my $partial_patch = $ul_config->{$l}->{'partial_patch'}; $partial_patch->{'info_path'} = catfile($aus_prefix, 'partial.txt'); next if ( -e $partial_patch->{'info_path'} or (! $config->useChecksums() && ! -e $snippetPathname) ); $i++; # This is just prettyfication for the PrintProgress() call, # so we know which channels have had rc's added to their # appv's. my $progressVersion = $u; if ($snippetToAppVersion ne $to->{'appv'}) { $progressVersion =~ s/\-$to->{'appv'}/-$snippetToAppVersion/; } PrintProgress(total => $total, current => $i, string => "$progressVersion/$p/$l/$c"); $partial_patch->{'patch_path'} = $snippetPathname; $partial_patch->{'type'} = 'partial'; $partial_patch->{'hash_type'} = $DEFAULT_HASH_TYPE; $partial_patch->{'hash_value'} = $serveCompleteUpdateToRcs ? $completePatchHash : $partialPatchHash; $partial_patch->{'build_id'} = $to->{'build_id'}; $partial_patch->{'appv'} = $prettySnippetToAppVersion; $partial_patch->{'extv'} = $to->{'extv'}; $partial_patch->{'size'} = $serveCompleteUpdateToRcs ? $completePatchSize : $partialPatchSize; my $channelSpecificUrlKey = $c . '-url'; if ($serveCompleteUpdateToRcs && (exists($complete->{$channelSpecificUrlKey}))) { $partial_patch->{'url'} = SubstitutePath( path => $complete->{$channelSpecificUrlKey}, platform => $p, version => $to->{'appv'}, locale => $l); $partial_patch->{'hash_value'} = $completePatchHash; $partial_patch->{'size'} = $completePatchSize; } elsif (exists($partial->{$channelSpecificUrlKey})) { $partial_patch->{'url'} = SubstitutePath( path => $partial->{$channelSpecificUrlKey}, platform => $p, version => $to->{'appv'}, locale => $l); } else { $partial_patch->{'url'} = $snippetUrl; } foreach my $attr (keys %{$computed_urls}) { $partial_patch->{$attr} = $computed_urls->{$attr}; } foreach my $attr (@SCHEMA_2_OPTIONAL_ATTR) { if (defined($u_config->{$u}->{$attr})) { $partial_patch->{$attr} = $u_config->{$u}->{$attr}; } } $partial_patch->{'updateType'} = $updateType; write_patch_info(patch => $partial_patch, schemaVer => $to->{'schema'}); # XXX - BUG: this shouldn't be run inside the # foreach my $channels loop; it causes us to re-create # the test channel snippets at least once through. # I think I did it this way because all the info I # needed was already all built up in complete_patch if (defined($u_config->{$u}->{'testchannel'})) { # Deep copy this data structure, since it's a copy of # $ul_config->{$l}->{'complete_patch'}; my $testPatch = {}; foreach my $key (keys(%{$partial_patch})) { $testPatch->{$key} = $partial_patch->{$key}; } # We store these values here so we can restore them # each time in the loop if they get munged because # the rc logic kicks in and we have to serve these # channels a complete. my $testPatchSize = $testPatch->{'size'}; my $testPatchHash = $testPatch->{'hash_value'}; foreach my $testChan (split(/[\s,]+/, $u_config->{$u}->{'testchannel'})) { $testPatch->{'size'} = $testPatchSize; $testPatch->{'hash_value'} = $testPatchHash; $snippetToAppVersion = $to->{'appv'}; $prettySnippetToAppVersion = $to->{'prettyAppv'}; foreach my $channel (keys(%{$currentUpdateRcInfo})) { if ($testChan eq $channel) { $snippetToAppVersion = $to->{'appv'} . 'build' . $currentUpdateRcInfo->{$channel}; $prettySnippetToAppVersion = $to->{'prettyAppv'} . ' (build ' . $currentUpdateRcInfo->{$channel} . ')'; last; } } # Note that we munge the copy here, but below # we use $to->{appv} in the SubstitutePath() call # to handle the URL; we do this because we only # want the snippet to have the rc value for its # appv, but not for any of the other places where # we use version. $testPatch->{'appv'} = $prettySnippetToAppVersion; my $snippetDir = GetSnippetDirFromChannel(config => $u_config->{$u}, channel => $testChan); $testPatch->{'info_path'} = catfile($u, $snippetDir, $from_aus_app, $from_aus_version, $from_aus_platform, $from_aus_buildid, $l, $testChan, 'partial.txt'); my $testChanKey = $testChan . '-url'; if ($serveCompleteUpdateToRcs && (exists($complete->{$testChanKey}))) { $testPatch->{'url'} = SubstitutePath( path => $complete->{$testChanKey}, version => $to->{'appv'}, platform => $p, locale => $l ); $testPatch->{'hash_value'} = $completePatchHash; $testPatch->{'size'} = $completePatchSize; } elsif (exists($partial->{$testChanKey})) { $testPatch->{'url'} = SubstitutePath( path => $partial->{$testChanKey}, version => $to->{'appv'}, platform => $p, locale => $l ); } else { $testPatch->{'url'} = $gen_partial_url; } write_patch_info(patch => $testPatch, schemaVer => $to->{'schema'}); } } } printf("done\n"); } } } } chdir($startdir); if (defined($total)) { printf("\n"); } return $i; } # create_partial_patch_info sub write_patch_info { my %args = @_; my $patch = $args{'patch'}; my $schemaVersion = $args{'schemaVer'} || $DEFAULT_SCHEMA_VERSION; my $info_path = $patch->{'info_path'}; my $info_path_parent = dirname($patch->{'info_path'}); my $text; if (0 == $schemaVersion) { $text = "$patch->{'type'}\n"; $text .= "$patch->{'url'}\n"; $text .= "$patch->{'hash_type'}\n"; $text .= "$patch->{'hash_value'}\n"; $text .= "$patch->{'size'}\n"; $text .= "$patch->{'build_id'}\n"; $text .= "$patch->{'appv'}\n"; $text .= "$patch->{'extv'}\n"; if (defined($patch->{'details'})) { $text .= "$patch->{'details'}\n"; } if (defined($patch->{'license'})) { $text .= "$patch->{'license'}\n"; } if (defined($patch->{'updateType'})) { $text .= "$patch->{'updateType'}\n"; } } elsif (1 == $schemaVersion) { $text = "version=1\n"; $text .= "type=$patch->{'type'}\n"; $text .= "url=$patch->{'url'}\n"; $text .= "hashFunction=$patch->{'hash_type'}\n"; $text .= "hashValue=$patch->{'hash_value'}\n"; $text .= "size=$patch->{'size'}\n"; $text .= "build=$patch->{'build_id'}\n"; $text .= "appv=$patch->{'appv'}\n"; $text .= "extv=$patch->{'extv'}\n"; if (defined($patch->{'details'})) { $text .= "detailsUrl=$patch->{'details'}\n"; } if (defined($patch->{'license'})) { $text .= "licenseUrl=$patch->{'license'}\n"; } if (defined($patch->{'updateType'})) { $text .= "updateType=$patch->{'updateType'}\n"; } } elsif (2 == $schemaVersion) { $text = "version=2\n"; $text .= "type=$patch->{'type'}\n"; $text .= "url=$patch->{'url'}\n"; $text .= "hashFunction=$patch->{'hash_type'}\n"; $text .= "hashValue=$patch->{'hash_value'}\n"; $text .= "size=$patch->{'size'}\n"; $text .= "build=$patch->{'build_id'}\n"; $text .= "displayVersion=$patch->{'appv'}\n"; $text .= "appVersion=$patch->{'extv'}\n"; $text .= "platformVersion=$patch->{'extv'}\n"; foreach my $attr ((@COMPUTED_URLS, @SCHEMA_2_OPTIONAL_ATTR)) { if (defined($patch->{$attr})) { if ($attr eq 'details') { $text .= "detailsUrl=$patch->{'details'}\n"; } elsif ($attr eq 'license') { $text .= "licenseUrl=$patch->{'license'}\n"; } else { $text .= "$attr=$patch->{$attr}\n"; } } } if (defined($patch->{'updateType'})) { $text .= "updateType=$patch->{'updateType'}\n"; } } else { die "ASSERT: Invalid schema version: $schemaVersion\n"; } MkdirWithPath(dir => $info_path_parent) or die "MkdirWithPath($info_path_parent) FAILED"; open(PATCHINFO, ">$patch->{'info_path'}") or die "ERROR: Couldn't open $patch->{'info_path'} for writing!"; print PATCHINFO $text; close(PATCHINFO); } # write_patch_info sub Startup { # A bunch of assumptions are made that this is NOT Win32; assert that... die "ASSERT: Can not currently run on Win32.\n" if ($OSNAME eq 'MSWin32'); expire_or_win(); } sub Shutdown { print STDERR "IN SHUTDOWN...\n"; my $rv = unlink($PID_FILE); # We should probably die in this condition, but... since we're shutting # down, who cares? print STDERR "Failed to remove $PID_FILE: $ERRNO\n" if (not $rv); } # Contest subroutine that will either die or, if it returns, we have authority # over other instances of this script to proceed. sub expire_or_win { # Create the pid file before continuing. system("touch $PID_FILE"); # Open the pid file for update (modes read and write). open(FH, "+<$PID_FILE") or die "Cannot open $PID_FILE for update: $!\n"; # Obtain a file lock on the handle. flock(FH, 2); # Gather existing data from the file. my $existing_data; { local($/) = undef; $existing_data = ; } chomp($existing_data); # If the existing data is a process ID that is already alive, die. if ( length($existing_data) > 0 and process_is_alive($existing_data) ) { die("System already running with PID $existing_data!\n"); } # Otherwise, we reset the file handle and truncate the pid file, then write # our PID into it. seek(FH, 0, 0); truncate(FH, 0); print FH "$PID\n"; # All done with the pid file. close(FH); } sub process_is_alive { my ($pid) = @_; my $psout = `ps -A | grep $pid | grep -v grep | awk "{if (\\\$1 == $pid) print}"`; length($psout) > 0 ? 1 : 0; } sub run_shell_command { my %args = @_; my $cmd = $args{'cmd'}; my $cmdArgs = exists($args{'cmdArgs'}) ? $args{'cmdArgs'} : []; my $dir = $args{'dir'}; my $timeout = exists($args{'timeout'}) ? $args{'timeout'} : '600'; if (ref($cmdArgs) ne 'ARRAY') { die("ASSERT: run_shell_command(): cmdArgs is not an array ref\n"); } my %runShellCommandArgs = (command => $cmd, args => $cmdArgs, timeout => $timeout, output => 1); if ($dir) { $runShellCommandArgs{'dir'} = $dir; } print('Running shell command' . (defined($dir) ? " in $dir" : '') . ':' . "\n"); print(' arg0: ' . $cmd . "\n"); my $argNum = 1; foreach my $arg (@{$cmdArgs}) { print(' arg' . $argNum . ': ' . $arg . "\n"); $argNum += 1; } print('Starting time is ' . strftime("%T %D", localtime()) . "\n"); print('Timeout: ' . $timeout . "\n"); my $rv = RunShellCommand(%runShellCommandArgs); print('Ending time is ' . strftime("%T %D", localtime()) . "\n"); my $exitValue = $rv->{'exitValue'}; my $timedOut = $rv->{'timedOut'}; my $signalNum = $rv->{'signalNum'}; my $dumpedCore = $rv->{'dumpedCore'}; if ($timedOut) { print("output: $rv->{'output'}\n") if $rv->{'output'}; die('FAIL shell call timed out after ' . $timeout . ' seconds'); } if ($signalNum) { print('WARNING shell recieved signal ' . $signalNum . "\n"); } if ($dumpedCore) { print("output: $rv->{'output'}\n") if $rv->{'output'}; die("FAIL shell call dumped core"); } if ($exitValue) { if ($exitValue != 0) { print("output: $rv->{'output'}\n") if $rv->{'output'}; die("shell call returned bad exit code: $exitValue"); } } } ## ## ENTRY POINT (Yes, aaaalll the way down here...) ## $SIG{'__DIE__'} = \&Shutdown; $SIG{'INT'} = \&Shutdown; main();