releasing/cbrtools/perl/CleanLocalArch
changeset 602 3145852acc89
equal deleted inserted replaced
600:6d08f4a05d93 602:3145852acc89
       
     1 #!perl
       
     2 # Copyright (c) 2001-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 # All rights reserved.
       
     4 # This component and the accompanying materials are made available
       
     5 # under the terms of the License "Eclipse Public License v1.0"
       
     6 # which accompanies this distribution, and is available
       
     7 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 # 
       
     9 # Initial Contributors:
       
    10 # Nokia Corporation - initial contribution.
       
    11 # 
       
    12 # Contributors:
       
    13 # 
       
    14 # Description:
       
    15 # 
       
    16 #
       
    17 
       
    18 use strict;
       
    19 use FindBin;
       
    20 use lib "$FindBin::Bin";
       
    21 use Getopt::Long;
       
    22 use IniData;
       
    23 use RelData;
       
    24 use File::Copy;
       
    25 use File::Path;
       
    26 use File::Spec;
       
    27 use File::Basename;
       
    28 use Cleaner;
       
    29 use Utils;
       
    30 use Cwd;
       
    31 
       
    32 #
       
    33 # Globals.
       
    34 #
       
    35 
       
    36 my $verbose = 0;
       
    37 my $overwrite = 0;
       
    38 my $dummyRun = 0;
       
    39 my $descriptionFile;
       
    40 my $iniData = IniData->New();
       
    41 my $cleaner; # object that does most of it
       
    42 my $cleanTo;
       
    43 my $expunge = 0; # don't leave reldatas lying around
       
    44 my $reallyClean;
       
    45 
       
    46 #
       
    47 # Main.
       
    48 #
       
    49 
       
    50 ProcessCommandLine();
       
    51 $cleaner = Cleaner->New($iniData, 0, $verbose, $reallyClean); # 0 = local not remote
       
    52 ParseDescriptionFile($descriptionFile);
       
    53 $cleaner->SetCleaningSubroutine(\&CleaningSubroutine);
       
    54 if (!$expunge) {
       
    55   $cleaner->SetRevertingSubroutine(\&RevertingSubroutine);
       
    56 }
       
    57 $cleaner->Clean();
       
    58 
       
    59 
       
    60 #
       
    61 # Subs.
       
    62 #
       
    63 
       
    64 sub ProcessCommandLine {
       
    65   Getopt::Long::Configure ("bundling");
       
    66   my $help;
       
    67   GetOptions('h' => \$help, 'd' => \$dummyRun, 'v+' => \$verbose, 'o' => \$overwrite, 'r' => \$reallyClean);
       
    68 
       
    69   if ($help) {
       
    70     Usage(0);
       
    71   }
       
    72 
       
    73   $descriptionFile = shift @ARGV;
       
    74 
       
    75   unless ($descriptionFile) {
       
    76     print "Error: Archive cleaning description file not specified\n";
       
    77     Usage(1);
       
    78   }
       
    79 
       
    80   unless ($#ARGV == -1) {
       
    81     print "Error: Invalid number of arguments\n";
       
    82     Usage(1);
       
    83   }
       
    84 
       
    85   if ($dummyRun and not $verbose) {
       
    86     $verbose = 1;
       
    87   }
       
    88 }
       
    89 
       
    90 sub Usage {
       
    91   my $exitCode = shift;
       
    92 
       
    93   Utils::PrintDeathMessage($exitCode, "\nUsage: cleanlocalarch [options] description-file
       
    94 
       
    95 options:
       
    96 
       
    97 -h  help
       
    98 -d  dummy run (don't do anything) - assumes -v
       
    99 -r  really clean (removes corrupt and partially released components)
       
   100 -v  verbose output (-vv very verbose)
       
   101 -o  overwrite destination (delete destination then normal copy)
       
   102 
       
   103 Please note, if you are in the process of publishing components to the archive
       
   104 and specify the -r option you may lose partially released components.\n");
       
   105 
       
   106 }
       
   107 
       
   108 sub ParseDescriptionFile {
       
   109   if ($dummyRun) { print "Running in dummy mode...\n"; }
       
   110   if ($verbose) { print "Parsing \"$descriptionFile\"...\n"; }
       
   111   open (DES, $descriptionFile) or die "Unable to open \"$descriptionFile\" for reading: $!\n";
       
   112 
       
   113   while (my $line = <DES>) {
       
   114     # Remove line feed, white space and comments.
       
   115     chomp($line);
       
   116     $line =~ s/^\s*$//;
       
   117     $line =~ s/#.*//;
       
   118     if ($line eq '') {
       
   119       # Nothing left.
       
   120       next;
       
   121     }
       
   122 
       
   123     my $keyWord;
       
   124     my @operand;
       
   125     if ($line =~ /^(\w+)\s+(.*)/) {
       
   126       $keyWord = $1;
       
   127       @operand = ();
       
   128       if ($2) {
       
   129         @operand = split /\s+/, $2;
       
   130       }
       
   131     } else {
       
   132       $keyWord = $line;
       
   133     }
       
   134 
       
   135     unless (defined $keyWord) {
       
   136       die "Error: Invalid line in \"$descriptionFile\":\n$line\n";
       
   137       next;
       
   138     }
       
   139 
       
   140     if ($cleaner->ProcessDescriptionLine($descriptionFile, $keyWord, @operand)) {
       
   141       # We're happy because Cleaner.pm knows what to do with this line
       
   142     } elsif ($keyWord =~ /^clean_to$/) {
       
   143       unless ($#operand == 0) {
       
   144         die "Error: Incorrect number of arguments to \'$keyWord\' keyword in \"$descriptionFile\"\nSyntax: clean_to <path>\n";
       
   145       }
       
   146       if ($cleanTo) {
       
   147         die "Error: \'$keyWord\' keyword specifed more than once in \"$descriptionFile\"\n";
       
   148       }
       
   149       $cleanTo = $operand[0];
       
   150     } elsif ($keyWord =~ /^delete$/) {
       
   151       if (@operand) {
       
   152         die "Error: Incorrect number of arguments to \'$keyWord\' keyword in \"$descriptionFile\"\nSyntax: delete\n";
       
   153       }
       
   154     } elsif ($keyWord =~ /^expunge$/) {
       
   155       $expunge = 1;
       
   156       $cleaner->{expunge_already_cleaned} = 1;
       
   157     } elsif ($keyWord =~ /^no_prompt$/) {
       
   158       print "Warning: currently, CleanLocalArch does not prompt. 'no_prompt' keyword is redundant.\n";
       
   159     } else {
       
   160       die "Error: Unknown keyword \'$keyWord\' in \"$descriptionFile\"\n";
       
   161     }
       
   162   }
       
   163 
       
   164   close (DES);
       
   165 
       
   166   unless ($cleanTo || $expunge) {
       
   167     die "Error: \"Clean to\" path not specified in \"$descriptionFile\"\n";
       
   168   }
       
   169   if ($cleanTo && $expunge) {
       
   170     die "Error: can't specify both \"clean_to\" and \"expunge\" in \"$descriptionFile\"\n";
       
   171   }
       
   172 
       
   173   if ($verbose > 1) {
       
   174     $cleaner->PrintEnvsToKeep();
       
   175   }
       
   176 }
       
   177 
       
   178 sub CleaningSubroutine {
       
   179   # This actually gets run by Cleaner.pm (it's a callback)
       
   180   my $thisComp = shift;
       
   181   my $thisVer = shift;
       
   182   my $relDir = shift;
       
   183   if ($expunge) {
       
   184     print "Expunging $thisComp $thisVer from $relDir...\n" if ($verbose);
       
   185     return DeleteComp($relDir);
       
   186   }
       
   187   print "Archiving $thisComp $thisVer from $relDir to $cleanTo...\n" if ($verbose);
       
   188   my $cleanDir = "$cleanTo\\$thisComp\\$thisVer";
       
   189   
       
   190   if (CopyComp($relDir, $cleanDir)) {
       
   191     print "Wiping $thisComp $thisVer from $relDir...\n" if ($verbose);
       
   192     if (DeleteComp("$relDir")) {
       
   193       # Check if the remaining dir is empty
       
   194       my ($parent, $file, $ext) = Utils::SplitFileName($relDir);
       
   195       return DeleteCompIfEmpty($parent);
       
   196     }
       
   197     else {
       
   198       # Call the reverting subroutine here because cleaner.pm will only revert clean components
       
   199       RevertingSubroutine($thisComp, $thisVer, $relDir);
       
   200     }
       
   201   }
       
   202   
       
   203   return 0;
       
   204 }
       
   205 
       
   206 sub RevertingSubroutine {
       
   207   # Again, this gets run by Cleaner.pm
       
   208   my $thisComp = shift;
       
   209   my $thisVer = shift;
       
   210   my $relDir = shift;
       
   211   
       
   212   print "Restoring $thisComp $thisVer to $relDir...\n" if ($verbose);
       
   213   
       
   214   # create the reldir if required
       
   215   if(!-d $relDir) {
       
   216     Utils::MakeDir($relDir);
       
   217   }
       
   218   
       
   219   my $fullCleanToPath = File::Spec->catdir($cleanTo, $thisComp, $thisVer);
       
   220   
       
   221   my $dirContents = Utils::ReadDir($fullCleanToPath);
       
   222   foreach my $thisFile (@$dirContents) {
       
   223      copy(File::Spec->catdir($fullCleanToPath, $thisFile), $relDir);
       
   224   }  
       
   225 
       
   226   print "Removing copy of $thisComp $thisVer from $cleanTo...\n" if ($verbose);
       
   227   if (DeleteComp("$cleanTo\\$thisComp\\$thisVer")) {
       
   228     # Check if the remaining dir is empty
       
   229     return DeleteCompIfEmpty("$cleanTo\\$thisComp");
       
   230   }
       
   231   else {
       
   232     # Failed to even delete component
       
   233     return 0;
       
   234   }
       
   235 }
       
   236 
       
   237 sub CopyComp {
       
   238   my $dir = shift;
       
   239   my $destDir = shift;
       
   240 
       
   241   if (-e $destDir) {
       
   242     if ($overwrite) {
       
   243       if ($verbose > 0) { print "Overwriting by deleting \"$destDir\"\n"; }
       
   244       DeleteComp("$destDir");
       
   245     }
       
   246     else {
       
   247       print "Error: Can't copy \"$dir\" to \"$destDir\" because directory \"$destDir\" already exists\n";
       
   248       return 0;
       
   249     }
       
   250   }
       
   251 
       
   252   my $failed = 0;
       
   253   my @copied;
       
   254   eval {
       
   255     Utils::MakeDir($destDir) unless $dummyRun;
       
   256   };
       
   257   if ($@) {
       
   258     print "$@";
       
   259     $failed = 1;
       
   260   }
       
   261 
       
   262   if($failed==0) {
       
   263     my $dirContents = Utils::ReadDir($dir);
       
   264     foreach my $thisFile (@$dirContents) {
       
   265       if ($verbose > 1) { print "\tCopying \"$dir\\$thisFile\" to \"$destDir\"...\n"; }
       
   266       if ($dummyRun) {
       
   267         return 1;
       
   268       }
       
   269       else {
       
   270         if (copy($dir."\\".$thisFile, $destDir)) {
       
   271           push @copied, $thisFile;
       
   272         }
       
   273         else {
       
   274           print "Error: Couldn't copy \"$dir\\$thisFile\" to \"$destDir\": $!\n";
       
   275           $failed = 1;
       
   276           if (-f $destDir."\\".$thisFile) {
       
   277             # Must've part-copied this file
       
   278             push @copied, $thisFile;
       
   279           }
       
   280           last;
       
   281         }
       
   282       }
       
   283     }
       
   284   }
       
   285 
       
   286   if ($failed) {
       
   287     # Revert copied files
       
   288     foreach my $thisFile (@copied) {
       
   289       unlink $destDir."\\".$thisFile or print "Error: Couldn't delete $destDir\\$thisFile when cleaning up\n";
       
   290     }
       
   291     DeleteCompIfEmpty($destDir) or print "Error: Couldn't clean up empty directory $destDir\n";
       
   292   }
       
   293 
       
   294   return ($failed == 0);
       
   295 }
       
   296 
       
   297 sub DeleteComp {
       
   298   my $dir = shift;
       
   299 
       
   300   if (!$dummyRun) {
       
   301     local $SIG{__WARN__} = sub {my $line = shift;
       
   302                                 $line =~ s/ at .*$//;
       
   303                                 print "Error: $line\n";};
       
   304     
       
   305     my $reldataFile = File::Spec->catdir($dir, 'reldata');
       
   306 
       
   307     my $origDir = cwd();
       
   308     chdir(dirname($dir));
       
   309     
       
   310     if (-e $reldataFile) {
       
   311       # Delete the reldata file first, if something goes wrong other tools will identify the archived component
       
   312       # as corrupt by the absence of reldata
       
   313       if (!unlink $reldataFile) {
       
   314         print "Error: Couldn't delete \"$reldataFile\"\n";
       
   315         return 0;
       
   316       }
       
   317     }
       
   318     
       
   319     if (!rmtree($dir, 0, 0) or -d $dir) {
       
   320       print "Error: Couldn't delete \"$dir\"\n";
       
   321       return 0;
       
   322     }
       
   323     else {
       
   324       chdir($origDir);
       
   325       return 1;
       
   326     }
       
   327   }
       
   328   else {
       
   329     return 1;
       
   330   }
       
   331 }
       
   332 
       
   333 sub DeleteCompIfEmpty {
       
   334   my $dir = shift;
       
   335 
       
   336   if (!$dummyRun) {
       
   337     if (opendir(DIR, $dir)) {
       
   338       my @files = grep( !/\.\.?$/, readdir DIR);
       
   339       if (!closedir(DIR)) {
       
   340         die "Error: Couldn't close '$dir' after reading. Aborting\n";
       
   341       }
       
   342       if (scalar(@files) == 0) {
       
   343         print "Tidying $dir...\n" if ($verbose);
       
   344         return DeleteComp("$dir");
       
   345 
       
   346       }
       
   347       else {
       
   348         return 1; # Nothing to do
       
   349       }
       
   350     }
       
   351     else {
       
   352       print "Warning: Couldn't open '$dir' directory for reading. An empty directory may have been left behind.\n";
       
   353       return 1; # Warning only
       
   354     }
       
   355   }
       
   356   else {
       
   357     return 1; # Dummy run
       
   358   }
       
   359 }
       
   360 
       
   361 __END__
       
   362 
       
   363 =head1 NAME
       
   364 
       
   365 CleanLocalArch - Cleans unwanted releases from the local release archive.
       
   366 
       
   367 =head1 SYNOPSIS
       
   368 
       
   369   cleanlocalarch [options] <description_file>
       
   370 
       
   371 options:
       
   372 
       
   373   -h  help
       
   374   -d  dummy run (don't do anything) - assumes -v
       
   375   -r  really clean (removes corrupt and partially released components)
       
   376   -v  verbose output (-vv very verbose)
       
   377   -o  overwrite destination (delete destination then normal copy)
       
   378 
       
   379 Please note, if you are in the process of publishing components to the archive and specify the -r option you may lose partially released components.
       
   380 
       
   381 =head1 DESCRIPTION
       
   382 
       
   383 C<CleanLocalArch> allows releases to be cleaned out of a local archive. This may be useful if a local archive is consuming a large amount of disk space and there are old releases present that are no longer required. Note that releases to be cleaned are normally backed up to a user defined directory before being deleted. This allows the cleaned releases to be permanently archived (to say a writable CDROM) before they are deleted.
       
   384 
       
   385 If C<CleanLocalArch> encounters an error while backing up releases to be cleaned, it will attempt to back out of the change by deleting the backups of any releases already done. If C<CleanLocalArch> encounters errors while backing out of a clean, it has the potential to leave releases in the backup directory. Similarly, if after backing up all releases to delete, it encounters errors while actually deleting them, it may leave releases in the local archive. However the clean can be repeated to a fresh backup directory once the problem has been isolated to get rid of these releases.
       
   386 
       
   387 Before using C<CleanLocalArchive> you must write a plain text file that describes which releases you want to keep etc. The following keywords are supported:
       
   388 
       
   389 =over 4
       
   390 
       
   391 =item keep_env <component> <version>
       
   392 
       
   393 Instructs C<CleanLocalArchive> to keep all the component versions in the environment from which the specified component was released. This keyword may be used multiple times.
       
   394 
       
   395 =item keep_rel <component> <version>
       
   396 
       
   397 Instructs C<CleanLocalArchive> to keep a specific component release. This keyword may be used multiple times.
       
   398 
       
   399 =item keep_recent_env <component> <num_days>
       
   400 
       
   401 Instructs C<CleanLocalArchive> to keep all named component releases, including their environments, where the component release has been made within the specified number of days (since the current time). This keyword may be used multiple times provided it is used for different components each time.
       
   402 
       
   403 =item keep_recent_rel [component] <num_days>
       
   404 
       
   405 Instructs C<CleanLocalArchive> to keep any component releases made within the specified number of days (since the current time). If a component name is specified, C<CleanLocalArchive> will only keep component releases which match that name (and are sufficiently recent). This keyword may be used multiple times if the command is used for different components.
       
   406 
       
   407 =item keep_recent <num_days>
       
   408 
       
   409 B<Depricated:> Equivalent to keep_recent_rel without a component name entered.
       
   410 
       
   411 =item clean_to
       
   412 
       
   413 Specifies where to move release to be cleaned. Use of this keyword is mandatory and may only be used once. There is an alternative, 'expunge', which will actually delete the releases - but this is only intended for test scripts and use on real, important archives is strongly discouraged.
       
   414 
       
   415 =item force
       
   416 
       
   417 This keyword, which takes no operands, specifies that cleanlocalarch should be non-interactive.
       
   418 
       
   419 =back
       
   420 
       
   421 For example:
       
   422 
       
   423  keep_env     pixie alpha
       
   424  keep_env     pixie beta
       
   425  keep_rel     comp1 rel1
       
   426  keep_recent  10
       
   427  clean_to     \\backup\pixie_cleaned_releases
       
   428 
       
   429 C<CleanLocalArch> will work out which component releases need to be kept in order to satisfy the specified keep criteria. All other component releases found in the archive will be moved to the C<clean_to> directory. B<It is therefore extremely important that the list of environments to keep is complete>. It is recommended that this file be controlled using a configuration management tool. It is also recommended that each project has only one description file, and that all users of C<CleanLocalArch> know where to find it.
       
   430 
       
   431 Recommended procedure for using C<CleanLocalArch>:
       
   432 
       
   433 =over 4
       
   434 
       
   435 =item 1
       
   436 
       
   437 Inform all users of the archive that a clean is about to be performed, and that the archive will be unavailable whilst this is happening.
       
   438 
       
   439 =item 2
       
   440 
       
   441 Take the archive off-line or alter directory permissions such that you are the only person that can access it.
       
   442 
       
   443 =item 3
       
   444 
       
   445 Backup the archive.
       
   446 
       
   447 =item 4
       
   448 
       
   449 Run C<CleanLocalArchive> and carefully check the list of components that are about to be cleaned. If you are happy, type 'yes' to continue, otherwise type 'no', modify your description file and re-run C<CleanLocalArchive>.
       
   450 
       
   451 =item 5
       
   452 
       
   453 Backup the C<clean_to> directory.
       
   454 
       
   455 =item 6
       
   456 
       
   457 Bring the archive back on-line.
       
   458 
       
   459 =item 7
       
   460 
       
   461 Inform all users of the archive that it is available for use once more.
       
   462 
       
   463 =back
       
   464 
       
   465 =head1 STATUS
       
   466 
       
   467 Supported. If you find a problem, please report it to us.
       
   468 
       
   469 =head1 COPYRIGHT
       
   470 
       
   471  Copyright (c) 2001-2009 Nokia Corporation and/or its subsidiary(-ies).
       
   472  All rights reserved.
       
   473  This component and the accompanying materials are made available
       
   474  under the terms of the License "Eclipse Public License v1.0"
       
   475  which accompanies this distribution, and is available
       
   476  at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
   477  
       
   478  Initial Contributors:
       
   479  Nokia Corporation - initial contribution.
       
   480  
       
   481  Contributors:
       
   482  
       
   483  Description:
       
   484  
       
   485 
       
   486 =cut