| 1 | #!/opt/bin/perl |
| 2 | use strict; |
| 3 | use warnings; |
| 4 | |
| 5 | use Cwd; |
| 6 | use Getopt::Std; |
| 7 | use File::Basename; |
| 8 | use FindBin; |
| 9 | |
| 10 | my $Opts = {}; |
| 11 | getopts( 'r:p:e:c:vudn', $Opts ); |
| 12 | |
| 13 | my $Cwd = cwd(); |
| 14 | my $Verbose = 1; |
| 15 | my $ExcludeRe = $Opts->{e} ? qr/$Opts->{e}/i : undef; |
| 16 | my $Debug = $Opts->{v} || 0; |
| 17 | my $RunDiff = $Opts->{d} || 0; |
| 18 | my $PkgDir = $Opts->{p} || cwd(); |
| 19 | my $Repo = $Opts->{r} or die "Need repository!\n". usage(); |
| 20 | my $Changes = $Opts->{c} || 'Changes ChangeLog'; |
| 21 | my $NoBranch = $Opts->{n} || 0; |
| 22 | |
| 23 | ### strip trailing slashes; |
| 24 | $Repo =~ s|/$||; |
| 25 | |
| 26 | my $CPV = $Debug ? '-v' : ''; |
| 27 | my $TestBin = 'ptardiff'; |
| 28 | my $PkgDirRe = quotemeta( $PkgDir .'/' ); |
| 29 | my $BranchName = basename( $PkgDir ) . '.' . $$; |
| 30 | my $OrigRepo = $Repo; |
| 31 | |
| 32 | ### establish working directory, either branch or full copy |
| 33 | if ( $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 | } |
| 48 | else { |
| 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 |
| 66 | chdir $PkgDir or die "Could not chdir to $PkgDir: $!"; |
| 67 | |
| 68 | ### copy over all files under lib/ |
| 69 | my @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 |
| 91 | my $RelTopDir; # topdir from the repo root |
| 92 | my $TopDir; # full path to the top dir |
| 93 | my $ModName; # name of the module |
| 94 | my @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 dont 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 | |
| 131 | my $TopDirRe = quotemeta( $TopDir . '/' ); |
| 132 | |
| 133 | ### copy over t/ and bin/ directories to the $TopDir |
| 134 | my @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 | |
| 155 | my $BinDir; |
| 156 | my @BinFiles; |
| 157 | my $TopBinDir; |
| 158 | BIN: { |
| 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 |
| 188 | my @Changes; |
| 189 | foreach 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 |
| 200 | my @NewFiles; |
| 201 | my @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.SH for $bin |
| 287 | { my $file = "utils/Makefile.SH"; |
| 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 dont 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 dont understand what these entries\n" |
| 362 | . " do, so I wont 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? |
| 433 | if( $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 |
| 478 | unless ( $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 |
| 486 | chdir $Cwd; |
| 487 | |
| 488 | sub usage { |
| 489 | my $me = basename($0); |
| 490 | return qq[ |
| 491 | |
| 492 | Usage: $me -r PERL_REPO_DIR [-p PACKAGE_DIR] [-v] [-d] [-e REGEX] |
| 493 | |
| 494 | Options: |
| 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 | } |