1#! perl 2 3# Copyright (C) 2012, Parrot Foundation. 4 5=head1 NAME 6 7tools/release/parrot_github_release.pl - automates the 'parrot.github.com' 8release process 9 10=head1 SYNOPSIS 11 12 $ perl tools/release/parrot_github_release.pl [OPTIONS] 13 14=head1 DESCRIPTION 15 16This script automates the packaging of the 'parrot.github.com' repository and 17the archiving of the previous documentation release into the appropriate 18'parrot-docsx' repository. 19 20In short, this script automates Section X of the Release Manager Guide 21(F<docs/project/release_manager_guide.pod>) as outlined in the Release Parrot 22Github Guide (F<docs/project/release_parrot_github_guide.pod>). 23 24=head1 OPTIONS 25 26=over 4 27 28=item B<--docs>=[/path_to/previous/docs/] 29 30The path to the directory which contains the previous documentation release. 31Specifically, the 'docs/' directory of the previous release of parrot. 32 33=item B<-h>, B<--help> 34 35Displays this help message and exits. 36 37=item B<-v>, B<--version> 38 39Displays the version and copyright information and exits. 40 41=back 42 43=head1 LIMITATIONS 44 451. As written, this script will execute only on *nix (and related) systems. 46 472. You must execute this script from the parrot root directory, I<i.e.>, 48'./parrot'; otherwise, it will fail. 49 50=head1 NOTES 51 521. This script assumes you are the Release Manager, working on Section X of 53the Release Manger Guide, and have, therefore, already cut the new Parrot 54release. 55 562. You must use a fully qualified path for the '--docs' option. 57 58For example, if the path to the previous version of the documentation is 59contained in F</home/user/git-work/parrot/docs/'>, you I<must> specify the 60complete path to the 'docs/' directory and may not use "shell expansion" as 61the name of your home directory, I<i.e.,> you may not use 62C<~/git-work/parrot/docs/>. To do otherwise means the script will fail. 63 64=head1 HISTORY 65 66* [2012-03-21] Initial version written by Alvis Yardley <ac.yardley@gmail.com> 67 68* [2012-07-13] Made the script more robust Alvis Yardley <ac.yardley@gmail.com> 69 70=head1 SEE ALSO 71 72F<docs/project/release_manager_guide.pod> 73 74F<docs/project/release_parrot_github_guide.pod> 75 76=cut 77 78use strict; 79use warnings; 80 81use Getopt::Long; 82use Pod::Usage; 83use System::Command; 84use lib qw( ./lib ); 85use Parrot::Config; 86use Cwd; 87 88# Switches 89my $docs; # Path to the previous docs release 90my $help; # Displays help message 91my $version; # Displays version and copyright information 92 93my $result = GetOptions('docs=s' => \$docs, 94 'h|help' => \$help, 95 'v|version' => \$version); 96 97my $repos; # Path to where to store, temporarily, the repositories 98 99# Catch unrecognized switches 100pod2usage() unless $result; 101 102# Display help message if '-h' was given 103pod2usage(0) if $help; 104 105# Display version and copyright information if '-v' was given 106version() && exit(0) if $version; 107 108# Get temporary directory defined in 'Parrot::Config::Generated.pm' 109get_repo_directory(); 110 111# Get 'docs/' directory if not supplied 112get_docs_directory() unless $docs; 113 114# Test 'docs/' directory to ensure it's a valid 'docs/' directory. 115tst_docs_directory(); 116 117# Get VERSION 118open my $FH, '<', 'VERSION' or stop("Unable to open 'VERSION' file"); 119chomp($version = <$FH>); 120close $FH; 121 122# Parse version number 123my ($major, $minor, $patch); # Quiet perlcritic 124($major, $minor, $patch) = ($1, $2, $3) if $version =~ /^(\d+)\.(\d+)\.(\d+)$/; 125stop("There is some problem with the major or the minor release numbers") 126 unless $major and $minor; 127 128# Set to the previous release version 129if ($minor == 0) { 130 $minor = 11; 131 $major -= 1; 132} 133else { 134 $minor -= 1; 135} 136 137# Get the current working directory 138my $parrot_dir = getcwd(); 139 140# Release process 141get_parrot_github(); 142get_parrot_docsx(); 143archive_parrot_docsx(); 144update_parrot_github(); 145delete_repos(); 146exit(0); 147 148########################## 149# Subroutine definitions # 150########################## 151 152# Get the temporary directory, contained in '%PConfig', in which to clone 153# the repos 154sub get_repo_directory { 155 $repos = $PConfig{tempdir}; 156 if (!defined $repos) { 157 print "\'\$PConfig{\'tempdir\'}\' is undefined. This variable must ", 158 "be defined and defined with a readable and a writeable directory ", 159 "to execute, successfuly, this script.\n"; 160 print "Did you, perhaps, fail to configure parrot?\n"; 161 exit(1); 162 } 163 164 $repos .= '/'; 165 166 # Test '$repo' directory to ensure we can read and write to it. 167 my $tstfile = $repos . 'parrot_github_release.out'; 168 my $outstring = "A simple test string: parrot_github_release.out"; 169 open my $OUT, '+>', $tstfile or 170 stop("Unable to open file for output in $repos directory."); 171 print $OUT $outstring; 172 close $OUT or stop("Unable to close file in $repos directory"); 173 174 open my $IN, '<', $tstfile or 175 stop("Unable to open file for input in $repos directory."); 176 my $instring = <$IN>; 177 close $IN or stop("Unable to close $tstfile"); 178 stop("Unable to read and to write to $repos directory") 179 unless $instring eq $outstring; 180 181 unlink $tstfile or warn "Unable to delete $tstfile: $!"; 182} 183 184# Get 'docs/' directory 185sub get_docs_directory { 186 while (1) { 187 print "Please specify the path to the previous documentation release? "; 188 $docs = <>; 189 chomp $docs; 190 191 last if -d $docs; 192 } 193 194 $docs .= '/' if $docs =~ /[a-zA-Z0-9]$/; 195} 196 197# Test whether or not we actually have a valid 'docs/' directory. 198sub tst_docs_directory { 199 my $parrot_dir = getcwd(); 200 my $filename = 'parrothist.pod'; # This one's likely to stick around. 201 202 $docs .= '/' if $docs =~ /[a-zA-Z0-9]$/; 203 chdir $docs; 204 205 stop("Unable to access the $docs directory") 206 unless (-f $filename && -s $filename); 207 208 chdir $parrot_dir; 209} 210 211# Clone a local copy of 'parrot.github.com' 212sub get_parrot_github { 213 chdir $repos; 214 215 print "\n== CLONING 'PARROT.GITHUB.COM' ==\n"; 216 system('git', 'clone', 'git@github.com:parrot/parrot.github.com.git') == 0 217 or stop("Unable to clone 'parrot.github.com'"); 218 219 chdir $parrot_dir; 220} 221 222# Clone a local copy of 'parrot-docsx' 223sub get_parrot_docsx { 224 my $parrot_docsx = 'git@github.com:parrot/parrot-docs' . $major . '.git'; 225 chdir $repos; 226 227 print "\n== CLONING 'PARROT-DOCSX' ==\n"; 228 system('git', 'clone', $parrot_docsx) == 0 or 229 stop("Unable to clone the appropriate 'parrot-docsx' repo."); 230 231 chdir $parrot_dir; 232} 233 234# Archive the previous documentation release to the 'parrot-docsx' repository 235sub archive_parrot_docsx { 236 my $parrot_docsx = $repos . 'parrot-docs' . $major . '/'; 237 chdir $parrot_docsx; 238 239 print "\n== CHECKING OUT GH-PAGES BRANCH ==\n"; 240 system('git', 'checkout', 'gh-pages') == 0 or 241 stop("Unable to switch to the 'gh-pages' branch"); 242 243 my $previous = $major . '.' . $minor . '.' . $patch; 244 my $copy_to = $parrot_docsx . $previous . '/'; 245 my $copy_from = $docs . '*'; 246 247 print "\n== MAKING NEW DIRECTORY IN 'PARROT-DOCSX' ==\n"; 248 mkdir($copy_to) or stop("Unable to make new directory in 'parrot-docsx'"); 249 250 print "\n== COPYING DOCS TO 'PARROT-DOCSX' ==\n"; 251 # Use shell globbing, for convenience. (Should I rewrite this?) 252 system("cp -r --target-directory=$copy_to $copy_from") == 0 or 253 stop("Unable to copy 'docs/' to 'parrot-docsx'"); 254 255 print "\n== GIT ADD ('PARROT-DOCSX') ==\n"; 256 system('git', 'add', '.') == 0 or 257 stop("Unable to add to 'parrot-docsx'"); 258 259 print "\n== GIT COMMIT ('PARROT-DOCSX') ==\n"; 260 system('git', 'commit', '-m', "'Archiving documentation release'") == 0 or 261 stop("Unable to commit to 'parrot-docsx'"); 262 263 print "\n== CHECKING OUT MASTER ==\n"; 264 system('git', 'checkout', 'master') == 0 or 265 stop("Unable to switch to 'master'"); 266 267 print "\n== PUSHING 'PARROT-DOCSX' ==\n"; 268 system('git', 'push', 'origin', 'gh-pages') == 0 or 269 stop("Unable to push updates to 'parrot-docsx'"); 270 271 chdir $parrot_dir; 272} 273 274# Update parrot.github.com with present release docs 275sub update_parrot_github { 276 my $parrot_github = $repos . 'parrot.github.com' . '/'; 277 chdir $parrot_github; 278 279 my $tmp = $PConfig{tempdir}; 280 281 print "\n== SAVING KEY 'PARROT.GITHUB.COM' FILES ==\n"; 282 system('cp', "--target-directory=$tmp", 'README.md') == 0 or 283 stop("Unable to save 'README.md'"); 284 system('cp', "--target-directory=$tmp", 'index.html') == 0 or 285 stop("Unable to save 'index.html'"); 286 system('cp', "--target-directory=$tmp", 'releases.html') == 0 or 287 stop("Unable to save 'releases.html'"); 288 289 print "\n== GIT RM ('PARROT.GITHUB.COM') ==\n"; 290 system('git', 'rm', '-rf', '*') == 0 or 291 stop("Unable to remove files from 'parrot.github.com'"); 292 293 print "\n== GIT ADD ('PARROT.GITHUB.COM') ==\n"; 294 system('git', 'add', '-A') == 0 or 295 stop("Unable to add to 'parrot.github.com'"); 296 297 print "\n== GIT COMMIT ('PARROT.GITHUB.COM') ==\n"; 298 system('git', 'commit', '-m', "'Removed files from 'parrot.github.com'") == 0 or 299 stop("Unable to commit to 'parrot.github.com'"); 300 301 print "\n== RESTORING KEY 'PARROT.GITHUB.COM' FILES ==\n"; 302 system('cp', "$tmp/README.md", '.') == 0 or 303 stop("Unable to restore 'README.md'"); 304 system('cp', "$tmp/index.html", '.') == 0 or 305 stop("Unable to restore 'index.html'"); 306 system('cp', "$tmp/releases.html", '.') == 0 or 307 stop("Unable to restore 'releases.html'"); 308 309 update_index_html(); 310 update_releases_html(); 311 312 my $parrot_docs = $parrot_dir . '/' . 'docs/*'; 313 print "\n== COPYING 'DOCS/' TO 'PARROT.GITHUB.COM' ==\n"; 314 # Here I am, relying on the shell, again. <sigh> 315 system("cp -r $parrot_docs .") == 0 or 316 stop("Unable to copy 'docs/' to 'parrot.github.com'"); 317 318 print "\n== GIT ADD ('PARROT.GITHUB.COM') ==\n"; 319 system('git', 'add', '.') == 0 or 320 stop("Unable to add to 'parrot.github.com'"); 321 322 print "\n== GIT COMMIT ('PARROT.GITHUB.COM') ==\n"; 323 system('git', 'commit', '-m', "'Updated 'parrot.github.com'") == 0 or 324 stop("Unable to commit to 'parrot.github.com'"); 325 326 print "\n== PUSHING ('PARROT.GITHUB.COM') ==\n"; 327 system('git', 'push', 'origin', 'master') == 0 or 328 stop("Unable to push updates to 'parrot.github.com' master"); 329 330 chdir $parrot_dir; 331} 332 333# Update the link to 'Previous Parrot Documentation Releases' in 'index.html' 334sub update_index_html { 335 my $buffer = ''; 336 open my $FH, '+<', 'index.html' or stop("Unable to open 'index.html'"); 337 while (<$FH>) { 338 s/$1/$version/ if /Parrot ($major\.$minor\.$patch)-devel - Home/; 339 s/$1/$version/ if /Parrot version ($major\.$minor\.$patch)-devel/; 340 s/$1/$major\.$minor\.$patch/ 341 if /Previous Parrot Documentation Releases \((\d.\d.\d) - 0.1.1\)/; 342 $buffer .= $_; 343 } 344 seek($FH, 0, 0) or stop("Unable to seek start of 'index.html'"); 345 print $FH $buffer or stop("Unable to print out 'index.html'"); 346 truncate($FH, tell($FH)) or stop("Unable to truncate 'index.html'"); 347 close $FH or stop("Unable to close 'index.html'"); 348} 349 350# Update 'releases.html' to point to the newly archived documents in 351# 'parrot-docsx' 352sub update_releases_html { 353 my $buffer = ''; 354 my $ul = '<ul>'; 355 my $li = '<li><a href="./parrot-docs' . $major . '/' . $major . '.' . 356 $minor . '.' . $patch . '/html/index.html">Release ' . $major . '.' . 357 $minor . '.' . $patch . '</a></li>' . "\n"; 358 359 open my $FH, '+<', 'releases.html' or stop("Unable to open 'releases.html'"); 360 while (<$FH>) { 361 s/$1/$version/ if /Parrot ($major\.$minor\.$patch)-devel - Home/; 362 s/$1/$version/ if /Parrot version ($major\.$minor\.$patch)-devel/; 363 if (/<ul>/) { 364 $buffer .= $_; 365 $buffer .= $li; 366 next; # So we don't duplicate the '<ul>' tag. 367 } 368 $buffer .= $_; 369 } 370 seek($FH, 0, 0) or stop("Unable to seek to start of 'releases.html'"); 371 print $FH $buffer or stop("Unable to print out 'releases.html'"); 372 truncate($FH, tell($FH)) or stop("Unable to truncate 'releases.html'"); 373 close $FH or stop("Unable to close 'releases.html'"); 374} 375 376# Delete the downloaded repositories 377sub delete_repos { 378 my $parrot_docsx = $repos . 'parrot-docs' . $major . '/'; 379 my $parrot_github = $repos . 'parrot.github.com' . '/'; 380 381 print "\n== DELETING PARROT-DOCSX ==\n"; 382 system('rm', '-rf', $parrot_docsx) == 0 or 383 stop("Unable to delete the 'parrot-docsx' repo"); 384 385 print "\n== DELETING PARROT.GITHUB.COM ==\n"; 386 system('rm', '-rf', $parrot_github) == 0 or 387 stop("Unable to delete the 'parrot.github.com' repo"); 388} 389 390# Customized version of die() for more consistent diagnostics 391sub stop { 392 if (@_ == 0) { 393 print '[FATAL ERROR] '; 394 395 if ($!) { 396 print $!; 397 } 398 else { 399 print 'An unrecoverable error has occurred'; 400 } 401 } 402 else { 403 my $msg = shift; 404 print "[ERROR] $msg"; 405 406 if ($!) { 407 print ": $!"; 408 } 409 } 410 411 print "!\n"; 412 413 exit 1; 414} 415 416sub version { 417 (my $version = <<VERSION_END) =~ s/^\S+//gm; 418 This is parrot_github_release.pl, v1.0 419 420 Copyright (C) 2001-2012, Parrot Foundation. 421 422VERSION_END 423 print "\n$version"; 424} 425 426################### 427# Signal handlers # 428################### 429 430$SIG{'INT'} = sub { stop('SIGINT received. Stopping') }; 431 432# Local Variables: 433# mode: cperl 434# cperl-indent-level: 4 435# fill-column: 100 436# End: 437# vim: expandtab shiftwidth=4: 438