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