Make t/porting/authors.t pass under LC_ALL=en_GB.UTF-8 PERL_UNICODE=""
[perl.git] / Porting / add-package.pl
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 }