1#!/opt/bin/perl 2use strict; 3use warnings; 4 5use Cwd; 6use Getopt::Std; 7use File::Basename; 8use FindBin; 9 10my $Opts = {}; 11getopts( 'r:p:e:c:vudn', $Opts ); 12 13my $Cwd = cwd(); 14my $Verbose = 1; 15my $ExcludeRe = $Opts->{e} ? qr/$Opts->{e}/i : undef; 16my $Debug = $Opts->{v} || 0; 17my $RunDiff = $Opts->{d} || 0; 18my $PkgDir = $Opts->{p} || cwd(); 19my $Repo = $Opts->{r} or die "Need repository!\n". usage(); 20my $Changes = $Opts->{c} || 'Changes ChangeLog'; 21my $NoBranch = $Opts->{n} || 0; 22 23### strip trailing slashes; 24$Repo =~ s|/$||; 25 26my $CPV = $Debug ? '-v' : ''; 27my $TestBin = 'ptardiff'; 28my $PkgDirRe = quotemeta( $PkgDir .'/' ); 29my $BranchName = basename( $PkgDir ) . '.' . $$; 30my $OrigRepo = $Repo; 31 32### establish working directory, either branch or full copy 33if ( $NoBranch ) { 34 ### create a copy of the repo directory 35 my $RepoCopy = "$Repo-$BranchName"; 36 print "Copying repository to $RepoCopy ..." if $Verbose; 37 38 ### --archive == -dPpR, but --archive is not portable, and neither 39 ### is -d, so settling for -PpR 40 system( "cp -PpR -f $Repo $RepoCopy" ) 41 and die "Copying master repo to $RepoCopy failed: $?"; 42 43 ### Going forward, use the copy in place of the original repo 44 $Repo = $RepoCopy; 45 46 print "done\n" if $Verbose; 47} 48else { 49 ### create a git branch for the new package 50 print "Setting up a branch from blead called '$BranchName'..." if $Verbose; 51 chdir $Repo or die "Could not chdir to $Repo: $!"; 52 unless ( -d '.git' ) { 53 die "\n$Repo is not a git repository\n"; 54 } 55 my $status = `git status`; 56 unless ( $status =~ /nothing to commit/ims ) { 57 die "\nWorking directory not clean. Stopping.\n"; 58 } 59 system( "git checkout -b $BranchName blead" ) 60 and die "Could not create branch '$BranchName': $?"; 61 62 print "done\n" if $Verbose; 63} 64 65### chdir there 66chdir $PkgDir or die "Could not chdir to $PkgDir: $!"; 67 68### copy over all files under lib/ 69my @LibFiles; 70{ print "Copying libdir..." if $Verbose; 71 die "Can't (yet) copy from a repository (found .git or .svn)" 72 if -d '.git' || -d '.svn'; 73 die "No lib/ directory found\n" unless -d 'lib'; 74 system( "cp -fR $CPV lib $Repo" ) and die "Copy of lib/ failed: $?"; 75 76 @LibFiles = map { chomp; $_ } 77 ### should we get rid of this file? 78 grep { $ExcludeRe && $_ =~ $ExcludeRe 79 ? do { warn "Removing $Repo/$_\n"; 80 system("rm $Repo/$_") and die "rm '$Repo/$_' failed: $?"; 81 undef 82 } 83 : 1 84 } `find lib -type f` 85 or die "Could not detect library files\n"; 86 87 print "done\n" if $Verbose; 88} 89 90### find the directory to put the t/ and bin/ files under 91my $RelTopDir; # topdir from the repo root 92my $TopDir; # full path to the top dir 93my $ModName; # name of the module 94my @ModFiles; # the .PMs in this package 95{ print "Creating top level dir..." if $Verbose; 96 97 ### make sure we get the shortest file, so we don't accidentally get 98 ### a subdir 99 @ModFiles = sort { length($a) <=> length($b) } 100 map { chomp; $_ } 101 grep { $ExcludeRe ? $_ !~ $ExcludeRe : 1 } 102 grep /\.p(?:m|od)$/, 103 `find $PkgDir/lib -type f` 104 or die "No TopDir detected\n"; 105 106 $RelTopDir = $ModFiles[0]; 107 $RelTopDir =~ s/^$PkgDirRe//; 108 $RelTopDir =~ s/\.p(m|od)$//; 109 $TopDir = "$Repo/$RelTopDir"; 110 111 ### create the dir if it's not there yet 112 unless( -d $TopDir ) { 113 system( "mkdir $TopDir" ) and die "Creating dir $TopDir failed: $?"; 114 } 115 116 ### the module name, like Foo::Bar 117 ### slice syntax not elegant, but we need to remove the 118 ### leading 'lib/' entry 119 ### stupid temp vars! stupid perl! it doesn't do @{..}[0..-1] :( 120 { my @list = @{[split '/', $RelTopDir]}; 121 $ModName = join '::', @list[1 .. $#list]; 122 } 123 124 ### the .pm files in this package 125 @ModFiles = map { s|^$PkgDirRe||; $_ } @ModFiles 126 or die "Could not detect modfiles\n"; 127 128 print "done\n" if $Verbose; 129} 130 131my $TopDirRe = quotemeta( $TopDir . '/' ); 132 133### copy over t/ and bin/ directories to the $TopDir 134my @TestFiles; 135{ print "Copying t/* files to $TopDir..." if $Verbose; 136 137 -d 't' 138 ? system( "cp -fR $CPV t $TopDir" ) && die "Copy of t/ failed: $?" 139 : warn "No t/ directory found\n"; 140 141 @TestFiles = map { chomp; s|^$TopDirRe||; s|//|/|g; $_ } 142 ### should we get rid of this file? 143 grep { $ExcludeRe && $_ =~ $ExcludeRe 144 ? do { warn "Removing $_\n"; 145 system("rm $TopDir/$_") and die "rm '$_' failed: $?"; 146 undef 147 } 148 : 1 149 } `find t -type f` 150 or die "Could not detect testfiles\n"; 151 152 print "done\n" if $Verbose; 153} 154 155my $BinDir; 156my @BinFiles; 157my $TopBinDir; 158BIN: { 159 $BinDir = -d 'bin' ? 'bin' : 160 -d 'scripts' ? 'scripts' : undef ; 161 unless ($BinDir) { 162 print "No bin/ or scripts/ directory found\n" if $Verbose; 163 last BIN; 164 } 165 my $TopBinDir = "$TopDir/$BinDir/"; 166 print "Copying $BinDir/* files to $TopBinDir..." if $Verbose; 167 168 my $CopyCmd = "cp -fR $CPV $BinDir $TopDir"; 169 print "Running '$CopyCmd'..." if $Verbose; 170 171 system($CopyCmd) && die "Copy of $BinDir failed: $?"; 172 173 @BinFiles = map { chomp; s|^$TopDirRe||; s|//|/|g; $_ } 174 ### should we get rid of this file? 175 grep { $ExcludeRe && $_ =~ $ExcludeRe 176 ? do { warn "Removing $_\n"; 177 system("rm $TopDir/$_") and die "rm '$_' failed: $?"; 178 undef 179 } 180 : 1 181 } `find $BinDir -type f` 182 or die "Could not detect binfiles\n"; 183 184 print "done\n" if $Verbose; 185} 186 187### copy over change log 188my @Changes; 189foreach my $cl (split m/\s+/ => $Changes) { 190 -f $cl or next; 191 push @Changes, $cl; 192 print "Copying $cl files to $TopDir..." if $Verbose; 193 194 system( "cp -f $CPV $cl $TopDir" ) 195 and die "Copy of $cl failed: $?"; 196} 197 198 199### add files where they are required 200my @NewFiles; 201my @ChangedFiles; 202{ for my $bin ( map { basename( $_ ) } @BinFiles ) { 203 print "Registering $bin with system files...\n"; 204 205 ### fix installperl, so these files get installed by other utils 206 ### ./installperl: return if $name =~ 207 ### /^(?:cpan|instmodsh|prove|corelist|ptar|ptardiff|config_data)\z/; 208 { my $file = 'installperl'; 209 210 ### not there already? 211 unless( `grep $TestBin $Repo/$file| grep $bin` ) { 212 print " Adding $bin to $file..." if $Verbose; 213 214 ### double \\| required --> once for in this script, once 215 ### for the cli 216 system("$^X -pi -e 's/($TestBin\\|)/$bin|\$1/' $Repo/$file") 217 and die "Could not add $bin to $file: $?"; 218 print "done\n" if $Verbose; 219 push @ChangedFiles, $file; 220 } else { 221 print " $bin already mentioned in $file\n" if $Verbose; 222 } 223 } 224 225 ### fix utils.lst, so the new tools are mentioned 226 { my $file = 'utils.lst'; 227 228 ### not there already? 229 unless( `grep $bin $Repo/$file` ) { 230 print " Adding $bin to $file..." if $Verbose; 231 232 ### double \\| required --> once for in this script, once 233 ### for the cli 234 system("$^X -pi -e 's!($TestBin)!\$1\nutils/$bin!' $Repo/$file") 235 and die "Could not add $bin to $file: $?"; 236 print "done\n" if $Verbose; 237 push @ChangedFiles, $file; 238 } else { 239 print " $bin already mentioned in $file\n" if $Verbose; 240 } 241 } 242 243 ### make a $bin.PL file and fix it up 244 { my $src = "utils/${TestBin}.PL"; 245 my $file = "utils/${bin}.PL"; 246 247 ### not there already? 248 unless( -e "$Repo/$file" ) { 249 print " Creating $file..." if $Verbose; 250 251 ### important part of the template looks like this 252 ### (we'll need to change it): 253 # my $script = File::Spec->catfile( 254 # File::Spec->catdir( 255 # File::Spec->updir, qw[lib Archive Tar bin] 256 # ), "module-load.pl"); 257 258 ### copy another template file 259 system( "cp -f $Repo/$src $Repo/$file" ) 260 and die "Could not create $file from $src: $?"; 261 262 ### change the 'updir' path 263 ### make sure to escape the \[ character classes 264 my $updir = join ' ', (split('/', $RelTopDir), $BinDir); 265 system( "$^X -pi -e'". 266 's/^(.*?File::Spec->updir, qw\[).+?(\].*)$/'. 267 "\$1 $updir \$2/' $Repo/$file" 268 ) and die "Could not fix updir for $bin in $file: $?"; 269 270 271 ### change the name of the file from $TestBin to $bin 272 system( "$^X -pi -e's/$TestBin/$bin/' $Repo/$file" ) 273 and die "Could not update $file with '$bin' as name: $?"; 274 275 print "done\n" if $Verbose; 276 277 } else { 278 print " $file already exists\n" if $Verbose; 279 } 280 281 ### we've may just have created a new file, it will have to 282 ### go into the manifest 283 push @NewFiles, $file; 284 } 285 286 ### add an entry to utils/Makefile.PL for $bin 287 { my $file = "utils/Makefile.PL"; 288 289 ### not there already? 290 unless( `grep $bin $Repo/$file` ) { 291 print " Adding $bin entries to $file..." if $Verbose; 292 293 ### $bin appears on 4 lines in this file, so replace all 4 294 ### first, pl = 295 system( "$^X -pi -e'/^pl\\s+=/ && s/(${TestBin}.PL)/". 296 "\$1 ${bin}.PL/' $Repo/$file" 297 ) and die "Could not add $bin to the pl = entry: $?"; 298 299 ### next, plextract = 300 system( "$^X -pi -e'/^plextract\\s+=/ " . 301 "&& s/(${TestBin})/\$1 $bin/' $Repo/$file" 302 ) and die "Could not add $bin to the plextract = entry: $?"; 303 304 ### third, plextractexe = 305 system( "$^X -pi -e'/^plextractexe\\s+=/ " . 306 "&& s!(\./${TestBin})!\$1 ./$bin!' $Repo/$file" 307 ) and die "Could not add $bin to the plextractexe = entry: $?"; 308 309 ### last, the make directive $bin: 310 system( "$^X -pi -e'/^(${TestBin}:.+)/; \$x=\$1 or next;" . 311 "\$x =~ s/$TestBin/$bin/g;" . '$_.=$/.$x.$/;' . 312 "' $Repo/$file" 313 ) and die "Could not add $bin as a make directive: $?"; 314 315 push @ChangedFiles, $file; 316 print "done\n" if $Verbose; 317 } else { 318 print " $bin already added to $file\n" if $Verbose; 319 } 320 } 321 322 ### add entries to win32/Makefile and win32/makefile.mk 323 ### they contain the following lines: 324 # ./win32/makefile.mk: ..\utils\ptardiff \ 325 # ./win32/makefile.mk: xsubpp instmodsh prove ptar ptardiff 326 for my $file ( qw[win32/Makefile win32/makefile.mk] ) { 327 unless ( `grep $bin $Repo/$file` ) { 328 print " Adding $bin entries to $file..." if $Verbose; 329 330 system( "$^X -pi -e'/^(.+?utils.${TestBin}.+)/;". 331 '$x=$1 or next;' . 332 "\$x =~ s/$TestBin/$bin/g;" . '$_.=$x.$/;' . 333 "' $Repo/$file" 334 ) and die "Could not add $bin to UTILS section in $file: $?\n"; 335 336 system( "$^X -pi -e's/( $TestBin)/\$1 $bin/' $Repo/$file" ) 337 and die "Could not add $bin to $file: $?\n"; 338 339 push @ChangedFiles, $file; 340 print "done\n" if $Verbose; 341 } else { 342 print " $bin already added to $file\n" if $Verbose; 343 } 344 } 345 346 ### we need some entries in a vms specific file as well.. 347 ### except, I don't understand how it works or what it does, and it 348 ### looks all a bit odd... so lets just print a warning... 349 ### the entries look something like this: 350 # ./vms/descrip_mms.template:utils4 = [.utils]enc2xs.com 351 # [.utils]piconv.com [.utils]cpan.com [.utils]prove.com 352 # [.utils]ptar.com [.utils]ptardiff.com [.utils]shasum.com 353 # ./vms/descrip_mms.template:[.utils]ptardiff.com : [.utils]ptardiff.PL 354 # $(ARCHDIR)Config.pm 355 { my $file = 'vms/descrip_mms.template'; 356 357 unless( `grep $bin $Repo/$file` ) { 358 print $/.$/; 359 print " WARNING! You should add entries like the following\n" 360 . " to $file (Using $TestBin as an example)\n" 361 . " Unfortunately I don't understand what these entries\n" 362 . " do, so I won't change them automatically:\n\n"; 363 364 print `grep -nC1 $TestBin $Repo/$file`; 365 print $/.$/; 366 367 } else { 368 print " $bin already added to $file\n" if $Verbose; 369 } 370 } 371 } 372} 373 374### update the manifest 375{ my $file = $Repo . '/MANIFEST'; 376 my @manifest; 377 { open my $fh, "<$file" or die "Could not open $file: $!"; 378 @manifest = <$fh>; 379 close $fh; 380 } 381 382 ### fill it with files from our package 383 my %pkg_files; 384 for ( @ModFiles ) { 385 $pkg_files{$_} = "$_\t$ModName\n"; 386 } 387 388 for ( @TestFiles ) { 389 $pkg_files{"$RelTopDir/$_"} = "$RelTopDir/$_\t$ModName tests\n" 390 } 391 392 for ( @BinFiles ) { 393 $pkg_files{"$RelTopDir/$_"} = "$RelTopDir/$_\tthe ". 394 basename($_) ." utility\n"; 395 } 396 397 for ( @Changes ) { 398 $pkg_files{"$RelTopDir/$_"} = "$RelTopDir/$_\t$ModName change log\n"; 399 } 400 401 for ( @NewFiles ) { 402 $pkg_files{$_} = "$_\tthe ". 403 do { m/(.+?)\.PL$/; basename($1) } . 404 " utility\n" 405 } 406 407 ### remove all the files that are already in the manifest; 408 delete $pkg_files{ [split]->[0] } for @manifest; 409 410 print "Adding the following entries to the MANIFEST:\n" if $Verbose; 411 print "\t$_" for sort values %pkg_files; 412 print $/.$/; 413 414 push @manifest, values %pkg_files; 415 416 { chmod 0644, $file; 417 open my $fh, ">$file" or die "Could not open $file for writing: $!"; 418 #print $fh sort { lc $a cmp lc $b } @manifest; 419 ### XXX stolen from pod/buildtoc:sub do_manifest 420 print $fh 421 map { $_->[0] } 422 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] } 423 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] } 424 @manifest; 425 426 close $fh; 427 } 428 push @ChangedFiles, 'MANIFEST'; 429} 430 431 432### would you like us to show you a diff? 433if( $RunDiff ) { 434 if ( $NoBranch ) { 435 436 my $diff = $Repo; $diff =~ s/$$/patch/; 437 438 ### weird RV ;( 439 my $master = basename( $OrigRepo ); 440 my $repo = basename( $Repo ); 441 my $chdir = dirname( $OrigRepo ); 442 443 ### the .patch file is added by an rsync from the APC 444 ### but isn't actually in the p4 repo, so exclude it 445 my $cmd = "cd $chdir; diff -ruN --exclude=.patch $master $repo > $diff"; 446 447 print "Running: '$cmd'\n"; 448 449 print "Generating diff..." if $Verbose; 450 451 system( $cmd ); 452 #and die "Could not write diff to '$diff': $?"; 453 die "Could not write diff to '$diff'" unless -e $diff && -s _; 454 455 print "done\n" if $Verbose; 456 print "\nDiff can be applied with patch -p1 in $OrigRepo\n\n"; 457 print " Diff written to: $diff\n\n" if $Verbose; 458 } 459 else { 460 my $diff = "$Repo/$BranchName"; $diff =~ s/$$/patch/; 461 my $cmd = "cd $Repo; git diff > $diff"; 462 463 print "Running: '$cmd'\n"; 464 465 print "Generating diff..." if $Verbose; 466 467 system( $cmd ); 468 #and die "Could not write diff to '$diff': $?"; 469 die "Could not write diff to '$diff'" unless -e $diff && -s _; 470 471 print "done\n" if $Verbose; 472 print " Diff written to: $diff\n\n" if $Verbose; 473 } 474} 475 476 477# add files to git index 478unless ( $NoBranch ) { 479 chdir $Repo; 480 system( "git add $CPV $_" ) 481 for ( @LibFiles, @NewFiles, @ChangedFiles, 482 map { "$RelTopDir/$_" } @TestFiles, @BinFiles, @Changes ); 483} 484 485# return to original directory 486chdir $Cwd; 487 488sub usage { 489 my $me = basename($0); 490 return qq[ 491 492Usage: $me -r PERL_REPO_DIR [-p PACKAGE_DIR] [-v] [-d] [-e REGEX] 493 494Options: 495 -r Path to perl-core git repository 496 -v Run verbosely 497 -c File containing changelog (default 'Changes' or 'ChangeLog') 498 -e Perl regex matching files that shouldn't be included 499 -d Create a diff as patch file 500 -p Path to the package to add. Defaults to cwd() 501 -n No branching; repository is not a git repo 502 503 \n]; 504 505} 506