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 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 |
|
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)\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? |
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 |
} |