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