Bug 372755: respin support for Bootstrap. Finally! ;-) r=rhelmer,cf
authorpreed@mozilla.com
Thu, 05 Jul 2007 15:58:32 -0700
changeset 3178 8fde5b90e06713edbed43a4cbbb654000fa07822
parent 3177 02ff77f8834cd9ffbdb4cae48eb3ebd25d0d2b24
child 3179 ea6c8ef1f20ef88460ba8368156a32b6bd761fd2
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhelmer, cf
bugs372755
milestone1.9a7pre
Bug 372755: respin support for Bootstrap. Finally! ;-) r=rhelmer,cf
tools/release/Bootstrap/Config.pm
tools/release/Bootstrap/Step.pm
tools/release/Bootstrap/Step/Tag.pm
tools/release/Bootstrap/Step/Tag/Bump.pm
tools/release/Bootstrap/Step/Tag/Mozilla.pm
tools/release/Bootstrap/Step/Tag/Talkback.pm
tools/release/Bootstrap/Step/Tag/l10n.pm
tools/release/MozBuild/Util.pm
--- a/tools/release/Bootstrap/Config.pm
+++ b/tools/release/Bootstrap/Config.pm
@@ -33,23 +33,23 @@ sub new {
 
 sub Parse {
     my $this = shift;
     
     open(CONFIG, "< bootstrap.cfg") 
       || die("Can't open config file bootstrap.cfg");
 
     while (<CONFIG>) {
-        chomp; # no newline
-        s/#.*//; # no comments
+        # no comments or empty lines
+        next if ($_ =~ /^#/ || $_ =~ /^\s*$/);
         s/^\s+//; # no leading white
         s/\s+$//; # no trailing white
-        next unless length; # anything left?
+        chomp $_; # no newline
         my ($var, $value) = split(/\s*=\s*/, $_, 2);
-        $config{$var} = $value;
+        $this->Set(var => $var, value => $value);
     }
     close(CONFIG);
 }
 
 ##
 # Get checks to see if a variable exists and returns it.
 # Returns scalar
 #
@@ -88,16 +88,41 @@ sub Get {
         }
     } elsif ($this->Exists(var => $var)) {
         return $config{$var};
     } else {
         die("No such config variable: $var");
     }
 }
 
+sub Set {
+    my $this = shift;
+
+    my %args = @_;
+
+    die "ASSERT: Config::Set(): null var and/or value\n" if
+     (!exists($args{'var'}) || !exists($args{'value'}));
+
+    die "ASSERT: Config::Set(): Cannot set null var\n" if
+     (!defined($args{'var'}) || 
+     (defined($args{'var'}) && $args{'var'} =~ /^\s*$/));
+
+    my $var = $args{'var'};
+    my $value = $args{'value'};
+    my $force = exists($args{'force'}) ? $args{'force'} : 0;
+
+    die "ASSERT: Config::Set(): $var already exists ($value)\n" if 
+     (!$force && exists($config{$var}));
+
+    die "ASSERT: Config::Set(): Attempt to set null value for var $var\n" if 
+     (!$force && (!defined($value) || $value =~ /^\s*$/));
+
+    return ($config{$var} = $value);
+}
+ 
 sub GetLocaleInfo {
     my $this = shift;
 
     if (! $this->Exists(var => 'localeInfo')) {
         my $localeFileTag = $this->Get(var => 'productTag') . '_RELEASE';
         $config{'localeInfo'} = GetLocaleManifest(
          app => $this->Get(var => 'appName'),
          cvsroot => $this->Get(var => 'mozillaCvsroot'),
--- a/tools/release/Bootstrap/Step.pm
+++ b/tools/release/Bootstrap/Step.pm
@@ -11,37 +11,42 @@ use POSIX qw(strftime);
 use Bootstrap::Config;
 use MozBuild::Util qw(RunShellCommand Email);
 
 use base 'Exporter';
 
 our @EXPORT = qw(catfile);
 
 my $DEFAULT_TIMEOUT = 3600;
+my $DEFAULT_LOGFILE = 'default.log';
 
 sub new {
     my $proto = shift;
     my $class = ref($proto) || $proto;
     my $this = {};
     bless($this, $class);
     return $this;
 }
 
 sub Shell {
     my $this = shift;
     my %args = @_;
     my $cmd = $args{'cmd'};
-    my $cmdArgs = defined($args{'cmdArgs'}) ? $args{'cmdArgs'} : [];
+    my $cmdArgs = exists($args{'cmdArgs'}) ? $args{'cmdArgs'} : [];
     my $dir = $args{'dir'};
-    my $timeout = $args{'timeout'} ? $args{'timeout'} : $DEFAULT_TIMEOUT;
-    my $logFile = $args{'logFile'};
-    my $ignoreExitValue = $args{'ignoreExitValue'};
+    my $timeout = exists($args{'timeout'}) ? $args{'timeout'} :
+     $DEFAULT_TIMEOUT;
+    my $ignoreExitValue = exists($args{'ignoreExitValue'}) ? 
+     $args{'ignoreExitValue'} : 0;
     my $rv = '';
     my $config = new Bootstrap::Config();
 
+    my $logFile = exists($args{'logFile'}) ? $args{'logFile'} : 
+     catfile($config->Get(var => 'logDir'), $DEFAULT_LOGFILE);
+
     if (ref($cmdArgs) ne 'ARRAY') {
         die("ASSERT: Bootstrap::Step::Shell(): cmdArgs is not an array ref\n");
     }
 
     my %runShellCommandArgs = (command => $cmd,
                                args => $cmdArgs,
                                timeout => $timeout,
                                logfile => $logFile);
--- a/tools/release/Bootstrap/Step/Tag.pm
+++ b/tools/release/Bootstrap/Step/Tag.pm
@@ -1,156 +1,378 @@
 #
 # Tag step. Sets up the tagging directory, and checks out the mozilla source.
 # 
 package Bootstrap::Step::Tag;
+
+use Cwd;
+use File::Copy qw(move);
+use POSIX qw(strftime);
+
+use MozBuild::Util qw(MkdirWithPath RunShellCommand);
+use Bootstrap::Util qw(CvsCatfile);
+
 use Bootstrap::Step;
 use Bootstrap::Step::Tag::Bump;
 use Bootstrap::Step::Tag::Mozilla;
 use Bootstrap::Step::Tag::l10n;
 use Bootstrap::Step::Tag::Talkback;
 use Bootstrap::Config;
-use Bootstrap::Util qw(CvsCatfile);
-use File::Copy qw(move);
-use MozBuild::Util qw(MkdirWithPath);
-@ISA = qw(Bootstrap::Step);
+
+use strict;
+
+our @ISA = qw(Bootstrap::Step);
 
-my @subSteps = ('Bump', 'Mozilla', 'l10n', 'Talkback');
+my @TAG_SUB_STEPS = qw( Bump
+                        Mozilla
+                        l10n
+                        Talkback
+                      );
 
 sub Execute {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $productTag = $config->Get(var => 'productTag');
     my $rc = $config->Get(var => 'rc');
+    my $milestone = $config->Get(var => 'milestone');
     my $tagDir = $config->Get(var => 'tagDir');
     my $mozillaCvsroot = $config->Get(var => 'mozillaCvsroot');
     my $branchTag = $config->Get(var => 'branchTag');
     my $pullDate = $config->Get(var => 'pullDate');
     my $logDir = $config->Get(var => 'logDir');
 
-    my $releaseTag = $productTag.'_RELEASE';
-    my $rcTag = $productTag.'_RC'.$rc;
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
     my $releaseTagDir = catfile($tagDir, $releaseTag);
+    my $rcTagDir = catfile($tagDir, $rcTag);
 
     # create the main tag directory
-    if (not -d $releaseTagDir) {
-        MkdirWithPath(dir => $releaseTagDir) 
-          or die("Cannot mkdir $releaseTagDir: $!");
-    }
-
-    # Symlink to to RC dir
-    my $fromLink = catfile($tagDir, $releaseTag);
-    my $toLink   = catfile($tagDir, $rcTag);
-    if (not -e $toLink) {
-        symlink($fromLink, $toLink) 
-          or die("Cannot symlink $fromLink $toLink: $!");
+    if (not -d $rcTagDir) {
+        MkdirWithPath(dir => $rcTagDir) 
+          or die("Cannot mkdir $rcTagDir: $!");
     }
 
     # Tagging area for Mozilla
-    my $cvsrootTagDir = catfile($releaseTagDir, 'cvsroot');
-    if (not -d $cvsrootTagDir) {
-        MkdirWithPath(dir => $cvsrootTagDir) 
-          or die("Cannot mkdir $cvsrootTagDir: $!");
+    my $cvsrootTagDir = catfile($rcTagDir, 'cvsroot');
+    if (-e $cvsrootTagDir) {
+        die "ASSERT: Tag::Execute(): $cvsrootTagDir already exists?";
     }
 
+    MkdirWithPath(dir => $cvsrootTagDir) 
+     or die("Cannot mkdir $cvsrootTagDir: $!");
+
     # Check out Mozilla from the branch you want to tag.
     # TODO this should support running without branch tag or pull date.
 
-    $this->Shell(
-      cmd => 'cvs',
-      cmdArgs => ['-d', $mozillaCvsroot, 
-                  'co', 
-                  '-r', $branchTag, 
-                  '-D', $pullDate, 
-                  CvsCatfile('mozilla', 'client.mk'),
-                 ],
-      dir => $cvsrootTagDir,
-      logFile => catfile($logDir, 'tag_checkout_client_mk.log'),
-    );
+    my $geckoTag = undef;
+
+    if (1 == $rc) {
+        $this->Shell(cmd => 'cvs',
+                     cmdArgs => ['-d', $mozillaCvsroot, 
+                                 'co', 
+                                 '-r', $branchTag, 
+                                 '-D', $pullDate, 
+                                 CvsCatfile('mozilla', 'client.mk'),
+                                ],
+                     dir => $cvsrootTagDir,
+                     logFile => catfile($logDir, 'tag_checkout_client_mk.log'),
+                   );
+
+        $this->CheckLog(log => catfile($logDir, 'tag_checkout_client_mk.log'),
+                       checkForOnly => '^U mozilla/client.mk');
+
+        $this->Shell(cmd => 'gmake',
+                     cmdArgs => ['-f', 'client.mk', 'checkout', 
+                                 'MOZ_CO_PROJECT=all', 
+                                 'MOZ_CO_DATE=' . $pullDate],
+                     dir => catfile($cvsrootTagDir, 'mozilla'),
+                     logFile => catfile($logDir, 'tag_mozilla-checkout.log'));
+
+        $this->CheckLog(
+          log => catfile($logDir, 'tag_mozilla-checkout.log'),
+          checkFor => '^U',
+        );
+
+        $geckoTag = $this->GenerateRelbranchName(milestone => $milestone);
+
+        # The glob seems weird/pointless, but it's because CvsTag requires a 
+        # list of files to operate on in the branch => 1 case. This may (or
+        # may not) be considered a bug, depending on how paranoid you're
+        # feeling about automatically creating branches.
+
+        my $cwd = getcwd();
+        chdir(catfile($cvsrootTagDir, 'mozilla')) or
+         die "Couldn't chdir() to $cvsrootTagDir/mozilla: $!\n";
+        my @topLevelMozCVSFiles = grep(!/^CVS$/, glob('*'));
+        chdir($cwd) or die "Couldn't chdir() home: $!\n";
+
+        $this->CvsTag(tagName => $geckoTag,
+                      branch => 1,
+                      files => \@topLevelMozCVSFiles,
+                      coDir => catfile($cvsrootTagDir, 'mozilla'),
+                      logFile => catfile($logDir, 'tag-relbranch_tag-' . 
+                                         $geckoTag));
+
+        $this->Shell(cmd => 'cvs',
+                     cmdArgs => ['up',
+                                 '-r', $geckoTag],
+                     dir => catfile($cvsrootTagDir, 'mozilla'));
+    } else {
+        # We go through some convoluted hoops here to get the _RELBRANCH
+        # datespec without forcing it to be specified. Because of this,
+        # there's lots of icky CVS parsing.
+
+        my $rcOneTag = $productTag . '_RC1';
+        my $checkoutLog = "tag_rc${rc}_checkout_client_ck.log";
+
+        $this->Shell(cmd => 'cvs',
+                     cmdArgs => ['-d', $mozillaCvsroot,
+                                 'co', 
+                                 '-r', $branchTag, 
+                                 '-D', $pullDate, 
+                                 CvsCatfile('mozilla', 'client.mk')],
+                     dir => $cvsrootTagDir,
+                     logFile => catfile($logDir, $checkoutLog),
+                   );
 
-    $this->CheckLog(
-      log => catfile($logDir, 'tag_checkout_client_mk.log'),
-      checkForOnly => '^U mozilla/client.mk',
-    );
+
+        $this->CheckLog(log => catfile($logDir, $checkoutLog),
+                        checkForOnly => '^U mozilla/client.mk');
 
-    $this->Shell(
-      cmd => 'gmake',
-      cmdArgs => ['-f', 'client.mk', 'checkout', 'MOZ_CO_PROJECT=all', 
-                    'MOZ_CO_DATE=' . $pullDate],
-      dir => catfile($cvsrootTagDir, 'mozilla'),
-      logFile => catfile($logDir, 'tag_mozilla-checkout.log'),
-    );
+        # Use RunShellCommand() here because we need to grab the output,
+        # and Shell() sends the output to a log.
+        my $clientMkInfo = RunShellCommand(command => 'cvs',
+                                           args => ['log', 
+                                                    'client.mk'],
+                                           dir => catfile($cvsrootTagDir, 
+                                                          'mozilla'));
+       
+        if ($clientMkInfo->{'exitValue'} != 0) {
+            die("cvs log call on client.mk failed: " .
+             $clientMkInfo->{'exitValue'} . "\n");
+        }
+   
+        my $inSymbolic = 0;
+        my $inDescription = 0;
+        my $haveRev = 0;
+        my $cvsRev = '';
+        my $cvsDateSpec = '';
+        foreach my $logLine (split(/\n/, $clientMkInfo->{'output'})) {
+            if ($inSymbolic && $logLine =~ /^\s+$rcOneTag:\s([\d\.]+)$/) {
+                $cvsRev = $1;            
+                $inSymbolic = 0;
+                next;
+            } elsif ($inDescription && $logLine =~ /^revision $cvsRev$/) {
+                $haveRev = 1;
+                next;
+            } elsif ($haveRev) {
+                if ($logLine =~ /^date:\s([^;]+);/) {
+                    # Gives us a line like: "2006/12/05 19:12:58" 
+                    $cvsDateSpec = $1;
+                    last;
+                }
+
+                die 'ASSERT: Step::Tag::Execute(): have rev, but did not ' .
+                 'find a datespec?';
+            } elsif ($logLine =~ /^symbolic names:/) {
+                $inSymbolic = 1;
+                next;
+            } elsif ($logLine =~ /^description:/) {
+                $inDescription = 1;
+                next;
+            }
+        }
+
+        # relBranchDateSpec now has something like: "2006/12/05 19:12:58" 
+        my $relBranchDateSpec = $cvsDateSpec;
+        # Strip off the time...
+        $relBranchDateSpec =~ s/^\s*([\d\/]+).*/$1/;
+        # Strip out the /'s; now we have our datespec: 20061205
+        $relBranchDateSpec =~ s/\///g;
+
+        $geckoTag = $this->GenerateRelbranchName(milestone => $milestone,
+         datespec => $relBranchDateSpec);
+
+        $this->Shell(cmd => 'cvs',
+                     cmdArgs => ['-d', $mozillaCvsroot,
+                                 'co',
+                                 '-r', $geckoTag,
+                                 'mozilla'],
+                     dir => $cvsrootTagDir,
+                     logFile => catfile($logDir, 'tag_checkout_client_mk.log'),
+                   );
+
+    }
+
+    $config->Set(var => 'geckoBranchTag', value => $geckoTag);
 
     # Call substeps
-    my $numSteps = scalar(@subSteps);
-    my $currentStep = 0;
-    while ($currentStep < $numSteps) {
-        my $stepName = $subSteps[$currentStep];
+    for (my $curStep = 0; $curStep < scalar(@TAG_SUB_STEPS); $curStep++) {
+        my $stepName = $TAG_SUB_STEPS[$curStep];
         eval {
             $this->Log(msg => 'Tag running substep ' . $stepName);
             my $step = "Bootstrap::Step::Tag::$stepName"->new();
             $step->Execute();
             $step->Verify();
         };
         if ($@) {
             die("Tag substep $stepName died: $@");
         }
-        $currentStep += 1;
     }
 }
 
 sub Verify {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $logDir = $config->Get(var => 'logDir');
 
-    $this->CheckLog(
-      log => catfile($logDir, 'tag_mozilla-checkout.log'),
-      checkFor => '^U',
-    );
+    # This step doesn't really do anything now, because the verification it used
+    # to do (which wasn't much) is now done in the Execute() method, since the
+    # biz logic for rc 1 vs. rc > 1 is different.
 }
 
 sub CvsTag {
     my $this = shift;
     my %args = @_;
 
-    my $config = new Bootstrap::Config();
+    # All the required args first, followed by the optional ones...
+    die "ASSERT: Bootstrap::Step::Tag::CvsTag(): null tagName" if 
+     (!exists($args{'tagName'})); 
     my $tagName = $args{'tagName'};
+
+    die "ASSERT: Bootstrap::Step::Tag::CvsTag(): null coDir" if 
+     (!exists($args{'coDir'})); 
     my $coDir = $args{'coDir'};
-    my $branch = $args{'branch'};
-    my $files = $args{'files'};
-    my $force = $args{'force'};
+
+    die "ASSERT: Bootstrap::Step::Tag::CvsTag(): invalid files data" if 
+     (exists($args{'files'}) && ref($args{'files'}) ne 'ARRAY');
+
+    die "ASSERT: Bootstrap::Step::Tag::CvsTag(): null logFile"
+     if (!exists($args{'logFile'}));
     my $logFile = $args{'logFile'};
    
+    my $branch = exists($args{'branch'}) ? $args{'branch'} : 0;
+    my $files = exists($args{'files'}) ? $args{'files'} : [];
+    my $force = exists($args{'force'}) ? $args{'force'} : 0;
+
+    my $config = new Bootstrap::Config();
     my $logDir = $config->Get(var => 'logDir');
 
     # only force or branch specific files, not the whole tree
-    if ($force and scalar(@{$files}) <= 0 ) {
+    if ($force && scalar(@{$files}) <= 0) {
         die("ASSERT: Bootstrap::Step::Tag::CvsTag(): Cannot specify force without files");
-    } elsif ($branch and scalar(@{$files}) <= 0) {
+    } elsif ($branch && scalar(@{$files}) <= 0) {
         die("ASSERT: Bootstrap::Step::Tag::CvsTag(): Cannot specify branch without files");
-    } elsif ($branch and $force) {
+    } elsif ($branch && $force) {
         die("ASSERT: Bootstrap::Step::Tag::CvsTag(): Cannot specify both branch and force");
-    } elsif (not $tagName) {
-        die("ASSERT: Bootstrap::Step::Tag::CvsTag(): tagName must be specified");
-    } elsif (not $logFile) {
-        die("ASSERT: Bootstrap::Step::Tag::CvsTag(): logFile must be specified");
     }
 
     my @cmdArgs;
-    push(@cmdArgs, '-q');
     push(@cmdArgs, 'tag');
     push(@cmdArgs, '-F') if ($force);
     push(@cmdArgs, '-b') if ($branch);
     push(@cmdArgs, $tagName);
-    push(@cmdArgs, @$files) if defined($files);
+    push(@cmdArgs, @{$files}) if (scalar(@{$files}) > 0);
 
     $this->Shell(
       cmd => 'cvs',
       cmdArgs => \@cmdArgs,
       dir => $coDir,
       logFile => $logFile,
     );
 }
 
+#
+# Give me some information, I'll give you the GECKO$version_$datespec_RELBRANCH
+# tag back; utility function, so we can centralize creation of this string.
+#
+# It has two modes; if you give it a branch (always required), you'll get a new
+# (current) _RELBRANCH tag; if you give it a datespec, you'll get a _RELBRANCH
+# tag based on that datespec (which, of course, may or may not exist.
+#
+# You can override all of this logic by setting "RelbranchOverride" in the 
+# bootstrap.cfg.
+#
+sub GenerateRelbranchName {
+    my $this = shift;
+    my %args = @_;
+    
+    die "ASSERT: GenerateRelbranchName(): null milestone" if 
+     (!exists($args{'milestone'}));
+
+    my $config = new Bootstrap::Config();
+
+    if ($config->Exists(var => 'RelbranchOverride')) {
+        return $config->Get(var => 'RelbranchOverride');
+    }
+
+    # Convert milestone (1.8.1.x) into "181"; we assume we should always have
+    # three digits for now (180, 181, 190, etc.)
+
+    my $geckoVersion = $args{'milestone'};
+    $geckoVersion =~ s/\.//g;
+    $geckoVersion =~ s/^(\d{3}).*$/$1/;
+
+    die "ASSERT: GenerateRelbranchName(): Gecko version should be only " .
+     "numbers by now" if ($geckoVersion !~ /^\d{3}$/);
+
+    my $geckoDateSpec = exists($args{'datespec'}) ? $args{'datespec'} : 
+     strftime('%Y%m%d', localtime());
+
+    # This assert()ion has a Y21k (among other) problem(s)...
+    die "ASSERT: GenerateRelbranchName(): invalid datespec" if 
+     ($geckoDateSpec !~ /^20\d{6}$/);
+
+    return 'GECKO' . $geckoVersion . '_' . $geckoDateSpec . '_RELBRANCH';
+}
+
+sub GetDiffFileList {
+    my $this = shift;
+    my %args = @_;
+
+    foreach my $requiredArg (qw(cvsDir prevTag newTag)) {
+        if (!exists($args{$requiredArg})) {
+            die "ASSERT: MozBuild::Util::GetDiffFileList(): null arg: " .
+             $requiredArg;
+        }
+    }
+
+    my $cvsDir = $args{'cvsDir'};
+    my $firstTag = $args{'prevTag'};
+    my $newTag = $args{'newTag'};
+
+    my $rv = RunShellCommand(command => 'cvs',
+                             args => ['diff', '-uN',
+                                      '-r', $firstTag,
+                                      '-r', $newTag],
+                             dir => $cvsDir,
+                             timeout => 3600);
+
+    # Gah. So, the shell return value of "cvs diff" is dependent on whether or
+    # not there were diffs, NOT whether or not the command succeeded. (Thanks,
+    # CVS!) So, we can't really check exitValue here, since it could be 1 or
+    # 0, depending on whether or not there were diffs (and both cases are valid
+    # for this function). Maybe if there's an error it returns a -1? Or 2?
+    # Who knows.
+    #
+    # So basically, we check that it's not 1 or 0, which... isn't a great test.
+    #
+    # TODO - check to see if timedOut, dumpedCore, or sigNum are set.
+    if ($rv->{'exitValue'} != 1 && $rv->{'exitValue'} != 0) {
+        die("ASSERT: MozBuild::Util::GetDiffFileList(): cvs diff returned " .
+         $rv->{'exitValue'});
+    }
+
+    my @differentFiles = ();
+
+    foreach my $line (split(/\n/, $rv->{'output'})) {
+        if ($line =~ /^Index:\s(.+)$/) {
+            push(@differentFiles, $1);
+        }
+    }
+
+
+    return \@differentFiles;
+}
+
 1;
--- a/tools/release/Bootstrap/Step/Tag/Bump.pm
+++ b/tools/release/Bootstrap/Step/Tag/Bump.pm
@@ -1,90 +1,89 @@
 #
 # Tag::Bump substep. Bumps version files for Mozilla appropriately.
 # 
 package Bootstrap::Step::Tag::Bump;
+
+use strict;
+
+use File::Copy qw(move);
+
+use MozBuild::Util qw(MkdirWithPath);
+
+use Bootstrap::Util qw(CvsCatfile);
 use Bootstrap::Step;
 use Bootstrap::Config;
 use Bootstrap::Step::Tag;
-use Bootstrap::Util qw(CvsCatfile);
-use File::Copy qw(move);
-use MozBuild::Util qw(MkdirWithPath);
-@ISA = ("Bootstrap::Step::Tag");
+
+our @ISA = ("Bootstrap::Step::Tag");
 
 sub Execute {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $product = $config->Get(var => 'product');
     my $productTag = $config->Get(var => 'productTag');
     my $branchTag = $config->Get(var => 'branchTag');
     my $pullDate = $config->Get(var => 'pullDate');
     my $version = $config->Get(var => 'version');
+    my $rc = int($config->Get(var => 'rc'));
     my $milestone = $config->Exists(var => 'milestone') ? 
      $config->Get(var => 'milestone') : undef;
     my $appName = $config->Get(var => 'appName');
     my $logDir = $config->Get(var => 'logDir');
     my $mozillaCvsroot = $config->Get(var => 'mozillaCvsroot');
     my $tagDir = $config->Get(var => 'tagDir');
+    my $geckoBranchTag = $config->Get(var => 'geckoBranchTag');
 
-    my $minibranchTag = $productTag.'_MINIBRANCH';
-    my $releaseTag = $productTag.'_RELEASE';
-    my $releaseTagDir = catfile($tagDir, $releaseTag);
-    my $cvsrootTagDir = catfile($releaseTagDir, 'cvsroot');
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
+
+    my $rcTagDir = catfile($tagDir, $rcTag);
+    my $cvsrootTagDir = catfile($rcTagDir, 'cvsroot');
+ 
+    ## TODO - we need to handle the case here where we're in security firedrill
+    ## mode, and we need to bump versions on the GECKO_ branch, but they
+    ## won't have "pre" in them. :-o
+    #
+    # We only do the bump step for rc1
+
+    if ($rc > 1) {
+        $this->Log(msg => "Skipping Tag::Bump::Execute substep for RC $rc.");
+        return;
+    }
 
     # pull version files
-    my $moduleVer = catfile($appName, 'app', 'module.ver');
-    my $versionTxt = catfile($appName, 'config', 'version.txt');
-    my $milestoneTxt = catfile('config', 'milestone.txt');
+    my $moduleVer = CvsCatfile($appName, 'app', 'module.ver');
+    my $versionTxt = CvsCatfile($appName, 'config', 'version.txt');
+    my $milestoneTxt = CvsCatfile('config', 'milestone.txt');
 
     my @bumpFiles = ('client.mk', $moduleVer, $versionTxt);
 
     # only bump milestone if it's defined in the config
     if (defined($milestone)) {
         @bumpFiles = (@bumpFiles, $milestoneTxt);
     }
 
     # Check out Mozilla from the branch you want to tag.
     # TODO this should support running without branch tag or pull date.
     $this->Shell(
       cmd => 'cvs',
       cmdArgs => ['-d', $mozillaCvsroot, 
                   'co', 
-                  '-r', $branchTag, 
-                  '-D', $pullDate, 
+                  '-r', $geckoBranchTag, 
                   CvsCatfile('mozilla', 'client.mk'),
                   CvsCatfile('mozilla', $appName, 'app', 'module.ver'),
                   CvsCatfile('mozilla', $appName, 'config', 'version.txt'),
                   CvsCatfile('mozilla', 'config', 'milestone.txt'),
                  ],
       dir => $cvsrootTagDir,
       logFile => catfile($logDir, 'tag-bump_checkout.log'),
     );
 
-    # Create a minibranch for the pull scripts so we can change them without
-    # changing anything on the original branch.
-    $this->CvsTag(
-      tagName => $minibranchTag,
-      branch => '1',
-      files => \@bumpFiles,
-      coDir => catfile($cvsrootTagDir, 'mozilla'),
-      logFile => catfile($logDir, 'tag-bump_cvsroot_tag-' . $minibranchTag . '.log'),
-    );
-
-    # pull version files from the version bump minibranch
-    $this->Shell(
-      cmd => 'cvs',
-      cmdArgs => ['up', '-r', $minibranchTag, 
-                  @bumpFiles,
-                 ],
-      dir => catfile($cvsrootTagDir, 'mozilla'),
-      logFile => catfile($logDir, 'tag-bump-pull_minibranch.log'),
-    );
-
     ### Perform version bump
 
     my $parentDir = catfile($cvsrootTagDir, 'mozilla');
     foreach my $fileName (@bumpFiles) {
         my $found = 0;
 
         my $file = catfile($parentDir, $fileName);
 
@@ -131,37 +130,43 @@ sub Execute {
         }
 
         if (not move("$file.tmp",
                      "$file")) {
             die("Cannot rename $file.tmp to $file: $!");
         }
     }
 
-    my $bumpCiMsg = 'Automated version bump, remove pre tag for ' 
+    my $bumpCiMsg = 'Automated checkin: version bump, remove pre tag for ' 
                         . $product . ' ' . $version . ' release on ' 
-                        . $minibranchTag;
+                        . $geckoBranchTag;
     $this->Shell(
       cmd => 'cvs',
       cmdArgs => ['commit', '-m', $bumpCiMsg, 
                   @bumpFiles,
                  ],
-      dir => catfile($releaseTagDir, 'cvsroot', 'mozilla'),
+      dir => catfile($rcTagDir, 'cvsroot', 'mozilla'),
       logFile => catfile($logDir, 'tag-bump_checkin.log'),
     );
 }
 
 sub Verify {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $logDir = $config->Get(var => 'logDir');
     my $appName = $config->Get(var => 'appName');
     my $milestone = $config->Exists(var => 'milestone') ? 
      $config->Get(var => 'milestone') : undef;
+    my $rc = $config->Get(var => 'rc');
+
+    if ($rc > 1) {
+        $this->Log(msg => "Skipping Tag::Bump::Verify substep for RC $rc.");
+        return;
+    }
 
     my $moduleVer = catfile($appName, 'app', 'module.ver');
     my $versionTxt = catfile($appName, 'config', 'version.txt');
     my $milestoneTxt = catfile('config', 'milestone.txt');
     my @bumpFiles = ('client.mk', $moduleVer, $versionTxt);
 
     # only bump milestone if it's defined in the config
     if (defined($milestone)) {
--- a/tools/release/Bootstrap/Step/Tag/Mozilla.pm
+++ b/tools/release/Bootstrap/Step/Tag/Mozilla.pm
@@ -1,62 +1,111 @@
 #
 # Tag Mozilla substep. Applies appropriate tags to Mozilla source code.
 # 
 package Bootstrap::Step::Tag::Mozilla;
-use Bootstrap::Step;
+
+use File::Copy qw(move);
+use File::Spec::Functions;
+
+use MozBuild::Util qw(MkdirWithPath);
+
 use Bootstrap::Config;
 use Bootstrap::Step::Tag;
-use File::Copy qw(move);
-use MozBuild::Util qw(MkdirWithPath);
-@ISA = ("Bootstrap::Step::Tag");
+
+use strict;
+
+our @ISA = ("Bootstrap::Step::Tag");
 
 sub Execute {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
-    my $product = $config->Get(var => 'product');
     my $productTag = $config->Get(var => 'productTag');
-    my $branchTag = $config->Get(var => 'branchTag');
-    my $pullDate = $config->Get(var => 'pullDate');
-    my $rc = $config->Get(var => 'rc');
-    my $version = $config->Get(var => 'version');
-    my $appName = $config->Get(var => 'appName');
+    my $rc = int($config->Get(var => 'rc'));
     my $logDir = $config->Get(var => 'logDir');
     my $mozillaCvsroot = $config->Get(var => 'mozillaCvsroot');
     my $tagDir = $config->Get(var => 'tagDir');
 
-    my $releaseTag = $productTag.'_RELEASE';
-    my $rcTag = $productTag.'_RC'.$rc;
-    my $releaseTagDir = catfile($tagDir, $releaseTag);
-    my $cvsrootTagDir = catfile($releaseTagDir, 'cvsroot');
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
+    my $rcTagDir = catfile($tagDir, $rcTag);
+    my $cvsrootTagDir = catfile($rcTagDir, 'cvsroot');
+
+    # Create the RC tag
+    $this->CvsTag(
+      tagName => $rcTag,
+      coDir => catfile($cvsrootTagDir, 'mozilla'),
+      logFile => catfile($logDir, 
+                         'tag-mozilla_cvsroot_tag-' . $rcTag . '.log'),
+    );
+
+    # Create or move the RELEASE tag
+    #
+    # This is for the Verify() method; we assume that we actually set (or reset,
+    # in the case of rc > 1) the _RELEASE tag; if that's not the case, we reset
+    # this value below.
+    $config->Set(var => 'tagModifyMozillaReleaseTag', value => 1);
 
-    # Create the RELEASE and RC tags
-    foreach my $tag ($releaseTag, $rcTag) {
+    if ($rc > 1) {
+        my $previousRcTag = $productTag . '_RC' . ($rc - 1);
+        my $diffFileList = $this->GetDiffFileList(cvsDir => 
+                                                   catfile($cvsrootTagDir,
+                                                           'mozilla'),
+                                                  prevTag => $previousRcTag,
+                                                  newTag => $rcTag);
+
+        if (scalar(@{$diffFileList}) > 0) {
+            $this->CvsTag(
+              tagName => $releaseTag,
+              coDir => catfile($cvsrootTagDir, 'mozilla'),
+              force => 1,
+              files => $diffFileList,
+              logFile => catfile($logDir, 
+                                 'tag-mozilla_cvsroot_tag-' . $releaseTag . 
+                                 '.log'),
+              );
+        } else {
+            $config->Set(var => 'tagModifyMozillaReleaseTag', value => 0,
+             force => 1);
+            $this->Log(msg => "No diffs found in cvsroot for RC $rc; NOT " .
+             "modifying $releaseTag");
+        }
+    } else {
         $this->CvsTag(
-          tagName => $tag,
+          tagName => $releaseTag,
           coDir => catfile($cvsrootTagDir, 'mozilla'),
           logFile => catfile($logDir, 
-                             'tag-mozilla_cvsroot_tag-' . $tag . '.log'),
+                             'tag-mozilla_cvsroot_tag-' . $releaseTag . '.log'),
         );
     }
+
 }
 
 sub Verify {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $productTag = $config->Get(var => 'productTag');
     my $logDir = $config->Get(var => 'logDir');
     my $rc = $config->Get(var => 'rc');
 
-    my $releaseTag = $productTag.'_RELEASE';
-    my $rcTag = $productTag.'_RC'.$rc;
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
+
+    my @checkTags = ($rcTag);
 
-    foreach my $tag ($releaseTag, $rcTag) {
+    # If RC > 1 and we took no changes in cvsroot for that RC, the _RELEASE
+    # tag won't have changed, so we shouldn't attempt to check it.
+    if ($config->Get(var => 'tagModifyMozillaReleaseTag')) {
+        push(@checkTags, $releaseTag);      
+    }
+
+    # TODO: should this complain about W's?
+    foreach my $tag (@checkTags) {
         $this->CheckLog(
           log => catfile($logDir, 'tag-mozilla_cvsroot_tag-' . $tag . '.log'),
           checkFor => '^T',
         );
     }
 }
 
 1;
--- a/tools/release/Bootstrap/Step/Tag/Talkback.pm
+++ b/tools/release/Bootstrap/Step/Tag/Talkback.pm
@@ -1,34 +1,48 @@
 #
 # Tag step. Applies a CVS tag to the appropriate repositories.
 # 
 package Bootstrap::Step::Tag::Talkback;
+
+use strict;
+
+use File::Copy qw(move);
+
+use MozBuild::Util qw(MkdirWithPath);
+
+use Bootstrap::Util qw(CvsCatfile);
 use Bootstrap::Step;
 use Bootstrap::Config;
 use Bootstrap::Step::Tag;
-use Bootstrap::Util qw(CvsCatfile);
-use File::Copy qw(move);
-use MozBuild::Util qw(MkdirWithPath);
-@ISA = ("Bootstrap::Step::Tag");
+
+our @ISA = ("Bootstrap::Step::Tag");
 
 sub Execute {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
-    my $product = $config->Get(var => 'product');
     my $productTag = $config->Get(var => 'productTag');
     my $branchTag = $config->Get(var => 'branchTag');
+    my $rc = int($config->Get(var => 'rc'));
     my $pullDate = $config->Get(var => 'pullDate');
     my $logDir = $config->Get(var => 'logDir');
     my $mofoCvsroot = $config->Get(var => 'mofoCvsroot');
     my $tagDir = $config->Get(var => 'tagDir');
 
-    my $releaseTag = $productTag.'_RELEASE';
-    my $releaseTagDir = catfile($tagDir, $releaseTag);
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
+    my $releaseTagDir = catfile($tagDir, $rcTag);
+
+    # Since talkback so seldom changes, we don't include it in our fancy
+    # respin logic; we only need to tag it for RC 1.
+    if ($rc > 1) {
+        $this->Log(msg => "Not tagging Talkback repo for RC $rc.");
+        return;
+    }
 
     # Create the mofo tag directory.
     my $mofoDir = catfile($releaseTagDir, 'mofo');
     if (not -d $mofoDir) {
         MkdirWithPath(dir => $mofoDir) 
           or die("Cannot mkdir $mofoDir: $!");
     }
 
@@ -51,18 +65,24 @@ sub Execute {
 }
 
 sub Verify {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $logDir = $config->Get(var => 'logDir');
     my $productTag = $config->Get(var => 'productTag');
+    my $rc = $config->Get(var => 'rc');
 
-    my $releaseTag = $productTag.'_RELEASE';
+    if ($rc > 1) {
+        $this->Log(msg => "Not verifying Talkback repo for RC $rc.");
+        return;
+    }
+
+    my $releaseTag = $productTag . '_RELEASE';
 
     $this->CheckLog(
       log => catfile($logDir, 
                      'tag-talkback_mofo-tag-' . $releaseTag . '.log'),
       checkFor => '^T',
     );
 }
 
--- a/tools/release/Bootstrap/Step/Tag/l10n.pm
+++ b/tools/release/Bootstrap/Step/Tag/l10n.pm
@@ -1,91 +1,168 @@
 #
 # Tag step. Applies a CVS tag to the appropriate repositories.
 # 
 package Bootstrap::Step::Tag::l10n;
 
+use Cwd;
 use File::Copy qw(move);
+use File::Spec::Functions;
+
+use MozBuild::Util qw(MkdirWithPath);
 
 use Bootstrap::Step;
 use Bootstrap::Config;
 use Bootstrap::Step::Tag;
 use Bootstrap::Util qw(CvsCatfile);
 
-use MozBuild::Util qw(MkdirWithPath);
+use strict;
 
-@ISA = ("Bootstrap::Step::Tag");
+our @ISA = ("Bootstrap::Step::Tag");
 
 sub Execute {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $product = $config->Get(var => 'product');
     my $productTag = $config->Get(var => 'productTag');
     my $branchTag = $config->Get(var => 'branchTag');
     my $l10n_pullDate = $config->Get(var => 'l10n_pullDate');
-    my $rc = $config->Get(var => 'rc');
+    my $rc = int($config->Get(var => 'rc'));
     my $appName = $config->Get(var => 'appName');
     my $logDir = $config->Get(var => 'logDir');
     my $l10nCvsroot = $config->Get(var => 'l10nCvsroot');
     my $tagDir = $config->Get(var => 'tagDir');
 
-    my $releaseTag = $productTag.'_RELEASE';
-    my $rcTag = $productTag.'_RC'.$rc;
-    my $releaseTagDir = catfile($tagDir, $releaseTag);
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
+    my $releaseTagDir = catfile($tagDir, $rcTag);
 
     # Create the l10n tag directory.
-    my $l10nDir = catfile($releaseTagDir, 'l10n');
-    if (not -d $l10nDir) {
-        MkdirWithPath(dir => $l10nDir) or die("Cannot mkdir $l10nDir: $!");
+    my $l10nTagDir = catfile($releaseTagDir, 'l10n');
+
+    if (not -d $l10nTagDir) {
+        MkdirWithPath(dir => $l10nTagDir) or
+         die("Cannot mkdir $l10nTagDir: $!");
     }
 
     # Grab list of shipped locales
     #
     # Note: GetLocaleInfo() has a dependency on the $releaseTag above already
-    # being set; it should be when the l10n tagging step gets run, though.
+    # being set; it should be by when the l10n tagging step gets run, though.
 
     my $localeInfo = $config->GetLocaleInfo();
 
+    # Config::Set() for us by Step::Tag::Execute() 
+    my $geckoTag = $config->Get(var => 'geckoBranchTag');
+
     # Check out the l10n files from the branch you want to tag.
-    for my $locale (keys(%{$localeInfo})) {
+
+    my @l10nCheckoutArgs = (1 == $rc) ?
+     # For rc1, pull by date on the branch
+     ('-r', $branchTag, '-D', $l10n_pullDate) :
+     # For rc(N > 1), pull the _RELBRANCH tag and tag that
+     ('-r', $geckoTag);
+
+    for my $locale (sort(keys(%{$localeInfo}))) {
         # skip en-US; it's kept in the main repo
         next if ($locale eq 'en-US');
 
         $this->Shell(
             cmd => 'cvs',
-            cmdArgs => ['-d', $l10nCvsroot, 'co', '-r', $branchTag, '-D',
-                        $l10n_pullDate, CvsCatfile('l10n', $locale)],
-            dir => catfile($releaseTagDir, 'l10n'),
+            cmdArgs => ['-d', $l10nCvsroot, 'co', @l10nCheckoutArgs, 
+                        CvsCatfile('l10n', $locale)],
+            dir => $l10nTagDir,
             logFile => catfile($logDir, 'tag-l10n_checkout.log'),
         );
     }
 
-    # Create the l10n RELEASE and RC tags.
-    foreach my $tag ($releaseTag, $rcTag) {
-        $this->CvsTag(
-          tagName => $tag,
-          coDir => catfile($releaseTagDir, 'l10n', 'l10n'),
-          logFile => catfile($logDir, 'tag-l10n_tag_' . $tag. '.log'),
-        );
+    my $cwd = getcwd();
+    chdir(catfile($l10nTagDir, 'l10n')) or
+     die "chdir() to $releaseTagDir/l10n failed: $!\n";
+    my @topLevelFiles = grep(!/^CVS$/, glob('*'));
+    chdir($cwd) or die "Couldn't chdir() home: $!\n";
+
+    if (1 == $rc) {
+        $this->CvsTag(tagName => $geckoTag,
+                      branch => 1,
+                      files => \@topLevelFiles,
+                      coDir => catfile($l10nTagDir, 'l10n'),
+                      logFile => catfile($logDir, 'tag-l10n_relbranch_tag_' . 
+                                         $geckoTag));
+
+    
+        $this->Shell(cmd => 'cvs',
+                     cmdArgs => ['up',
+                                 '-r', $geckoTag],
+                     dir => catfile($l10nTagDir, 'l10n'));
+    }
+
+    # Create the l10n RC tag
+    $this->CvsTag(
+      tagName => $rcTag,
+      coDir => catfile($l10nTagDir, 'l10n'),
+      logFile => catfile($logDir, 'tag-l10n_tag_' . $rcTag. '.log'),
+    );
+
+    # Create the l10n RELEASE tag
+    my %releaseTagArgs = (tagName => $releaseTag,
+                          coDir => catfile($l10nTagDir, 'l10n'),
+                          logFile => catfile($logDir, 'tag-l10n_tag_' . 
+                           $releaseTag. '.log'));
+
+    # This is for the Verify() method; we assume that we actually set (or reset,
+    # in the case of rc > 1) the _RELEASE tag; if that's not the case, we reset
+    # this value below.
+    $config->Set(var => 'tagModifyl10nReleaseTag', value => 1);
+
+    # If we're retagging rc(N > 1), we need to tag -F
+    if ($rc > 1) {
+        my $previousRcTag = $productTag . '_RC' . ($rc - 1);
+        my $diffFileList = $this->GetDiffFileList(cvsDir => catfile($l10nTagDir,
+                                                                'l10n'),
+                                              prevTag => $previousRcTag,
+                                              newTag => $rcTag);
+
+        if (scalar(@{$diffFileList}) > 0) {
+            $releaseTagArgs{'force'} = 1;
+            $releaseTagArgs{'files'} = $diffFileList;
+            $this->CvsTag(%releaseTagArgs); 
+        } else {
+            $this->Log(msg => "No diffs found in l10n for RC $rc; NOT " .
+             "modifying $releaseTag");
+            $config->Set(var => 'tagModifyl10nReleaseTag', value => 0,
+              force => 1);
+        }
+    } else {
+        # If we're RC 1, we obviously need to apply the _RELEASE tag...
+        $this->CvsTag(%releaseTagArgs); 
     }
 }
 
 sub Verify {
     my $this = shift;
 
     my $config = new Bootstrap::Config();
     my $logDir = $config->Get(var => 'logDir');
     my $productTag = $config->Get(var => 'productTag');
     my $rc = $config->Get(var => 'rc');
 
-    my $releaseTag = $productTag.'_RELEASE';
-    my $rcTag = $productTag.'_RC'.$rc;
+    my $releaseTag = $productTag . '_RELEASE';
+    my $rcTag = $productTag . '_RC' . $rc;
+
+    my @checkTags = ($rcTag);
 
-    foreach my $tag ($releaseTag, $rcTag) {
+    # If RC > 1 and we took no changes in cvsroot for that RC, the _RELEASE
+    # tag won't have changed, so we shouldn't attempt to check it.
+    if ($config->Get(var => 'tagModifyl10nReleaseTag')) {
+        push(@checkTags, $releaseTag);
+    }
+
+    foreach my $tag (@checkTags) {
         $this->CheckLog(
           log => catfile($logDir, 'tag-l10n_tag_' . $tag . '.log'),
           checkFor => '^T',
         );
     }
 }
 
 1;
--- a/tools/release/MozBuild/Util.pm
+++ b/tools/release/MozBuild/Util.pm
@@ -130,17 +130,17 @@ sub RunShellCommand {
         push(@execCommand, @{$commandArgs}) if (defined($commandArgs) && 
          scalar(@{$commandArgs} > 0));
     
         my $childIn = new IO::Handle();
         my $childOut = new IO::Handle();
         my $childErr = new IO::Handle();
 
         alarm($timeout);
-        $childStartedTime = localtime();
+        $childStartedTime = time();
 
         $childPid = open3($childIn, $childOut, $childErr, @execCommand);
         $childIn->close();
 
         if ($args{'background'}) {
             alarm(0);
 
             # Restore external state
@@ -206,17 +206,17 @@ sub RunShellCommand {
 
                 $output .= $line;
                 print STDOUT $line if ($printOutputImmediately);
                 print LOGFILE $line if (defined($logfile));
             }
 
             if (!$childReaped && (waitpid($childPid, WNOHANG) > 0)) {
                 alarm(0);
-                $childEndedTime = localtime();
+                $childEndedTime = time();
                 $exitValue = WEXITSTATUS($?);
                 $signalNum = WIFSIGNALED($?) && WTERMSIG($?);
                 $dumpedCore = WIFSIGNALED($?) && ($? & 128);
                 $childReaped = 1;
             }
         }
 
         die('ASSERT: RunShellCommand(): stdout handle not empty')