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