Commit | Line | Data |
---|---|---|
adfe19db MHM |
1 | #!/usr/bin/perl -w |
2 | ################################################################################ | |
3 | # | |
4 | # buildperl.pl -- build various versions of perl automatically | |
5 | # | |
6 | ################################################################################ | |
7 | # | |
b2049988 | 8 | # Version 3.x, Copyright (C) 2004-2013, Marcus Holland-Moritz. |
adfe19db MHM |
9 | # Version 2.x, Copyright (C) 2001, Paul Marquess. |
10 | # Version 1.x, Copyright (C) 1999, Kenneth Albanowski. | |
11 | # | |
12 | # This program is free software; you can redistribute it and/or | |
13 | # modify it under the same terms as Perl itself. | |
14 | # | |
15 | ################################################################################ | |
16 | ||
17 | use strict; | |
18 | use Getopt::Long; | |
19 | use Pod::Usage; | |
20 | use File::Find; | |
21 | use File::Path; | |
22 | use Data::Dumper; | |
23 | use IO::File; | |
24 | use Cwd; | |
25 | ||
0d0f8426 MHM |
26 | # TODO: - extra arguments to Configure |
27 | ||
679ad62d MHM |
28 | # |
29 | # --test-archives=1 check if archives can be read | |
30 | # --test-archives=2 like 1, but also extract archives | |
31 | # --test-archives=3 like 2, but also apply patches | |
32 | # | |
33 | ||
adfe19db | 34 | my %opt = ( |
1d175cda MHM |
35 | prefix => '/tmp/perl/install/<config>/<perl>', |
36 | build => '/tmp/perl/build/<config>', | |
37 | source => '/tmp/perl/source', | |
38 | force => 0, | |
39 | test => 0, | |
40 | install => 1, | |
41 | oneshot => 0, | |
42 | configure => 0, | |
cac25305 | 43 | 'test-archives' => 0, |
adfe19db MHM |
44 | ); |
45 | ||
46 | my %config = ( | |
4a582685 | 47 | default => { |
b2049988 | 48 | config_args => '-des', |
adfe19db | 49 | }, |
4a582685 | 50 | thread => { |
b2049988 MHM |
51 | config_args => '-des -Dusethreads', |
52 | masked_versions => [ qr/^5\.00[01234]/ ], | |
adfe19db | 53 | }, |
4a582685 | 54 | thread5005 => { |
b2049988 MHM |
55 | config_args => '-des -Duse5005threads', |
56 | masked_versions => [ qr/^5\.00[012345]|^5\.(9|\d\d)|^5\.8\.9/ ], | |
adfe19db | 57 | }, |
4a582685 | 58 | debug => { |
b2049988 | 59 | config_args => '-des -Doptimize=-g', |
adfe19db MHM |
60 | }, |
61 | ); | |
62 | ||
63 | my @patch = ( | |
64 | { | |
65 | perl => [ | |
0d0f8426 | 66 | qr/^5\.00[01234]/, |
adfe19db | 67 | qw/ |
0d0f8426 MHM |
68 | 5.005 |
69 | 5.005_01 | |
70 | 5.005_02 | |
71 | 5.005_03 | |
adfe19db MHM |
72 | /, |
73 | ], | |
74 | subs => [ | |
75 | [ \&patch_db, 1 ], | |
76 | ], | |
77 | }, | |
78 | { | |
79 | perl => [ | |
b2049988 | 80 | qw/ |
0d0f8426 MHM |
81 | 5.6.0 |
82 | 5.6.1 | |
83 | 5.7.0 | |
84 | 5.7.1 | |
85 | 5.7.2 | |
86 | 5.7.3 | |
87 | 5.8.0 | |
b2049988 | 88 | /, |
adfe19db MHM |
89 | ], |
90 | subs => [ | |
91 | [ \&patch_db, 3 ], | |
92 | ], | |
93 | }, | |
94 | { | |
95 | perl => [ | |
679ad62d | 96 | qr/^5\.004_0[1234]$/, |
adfe19db MHM |
97 | ], |
98 | subs => [ | |
99 | [ \&patch_doio ], | |
100 | ], | |
101 | }, | |
679ad62d MHM |
102 | { |
103 | perl => [ | |
104 | qw/ | |
105 | 5.005 | |
106 | 5.005_01 | |
107 | 5.005_02 | |
108 | /, | |
109 | ], | |
110 | subs => [ | |
111 | [ \&patch_sysv, old_format => 1 ], | |
112 | ], | |
113 | }, | |
114 | { | |
115 | perl => [ | |
116 | qw/ | |
117 | 5.005_03 | |
118 | 5.005_04 | |
119 | /, | |
120 | qr/^5\.6\.[0-2]$/, | |
121 | qr/^5\.7\.[0-3]$/, | |
122 | qr/^5\.8\.[0-8]$/, | |
123 | qr/^5\.9\.[0-5]$/ | |
124 | ], | |
125 | subs => [ | |
126 | [ \&patch_sysv ], | |
127 | ], | |
128 | }, | |
49ef49fe CBW |
129 | { |
130 | perl => [ | |
131 | qr/^5\.004_05$/, | |
132 | qr/^5\.005(?:_0[1-4])?$/, | |
133 | qr/^5\.6\.[01]$/, | |
134 | ], | |
135 | subs => [ | |
136 | [ \&patch_configure ], | |
137 | [ \&patch_makedepend_lc ], | |
138 | ], | |
139 | }, | |
140 | { | |
141 | perl => [ | |
142 | '5.8.0', | |
143 | ], | |
144 | subs => [ | |
145 | [ \&patch_makedepend_lc ], | |
146 | ], | |
147 | }, | |
adfe19db MHM |
148 | ); |
149 | ||
150 | my(%perl, @perls); | |
151 | ||
152 | GetOptions(\%opt, qw( | |
153 | config=s@ | |
154 | prefix=s | |
0d0f8426 | 155 | build=s |
adfe19db MHM |
156 | source=s |
157 | perl=s@ | |
158 | force | |
0d0f8426 MHM |
159 | test |
160 | install! | |
679ad62d | 161 | test-archives=i |
1d175cda MHM |
162 | patch! |
163 | oneshot | |
adfe19db MHM |
164 | )) or pod2usage(2); |
165 | ||
1d175cda MHM |
166 | my %current; |
167 | ||
168 | if ($opt{patch} || $opt{oneshot}) { | |
169 | @{$opt{perl}} == 1 or die "Exactly one --perl must be given with --patch or --oneshot\n"; | |
170 | my $perl = $opt{perl}[0]; | |
171 | patch_source($perl) if !exists $opt{patch} || $opt{patch}; | |
172 | if (exists $opt{oneshot}) { | |
173 | eval { require String::ShellQuote }; | |
174 | die "--oneshot requires String::ShellQuote to be installed\n" if $@; | |
175 | %current = (config => 'oneshot', version => $perl); | |
176 | $config{oneshot} = { config_args => String::ShellQuote::shell_quote(@ARGV) }; | |
177 | build_and_install($perl{$perl}); | |
178 | } | |
179 | exit 0; | |
180 | } | |
181 | ||
adfe19db MHM |
182 | if (exists $opt{config}) { |
183 | for my $cfg (@{$opt{config}}) { | |
184 | exists $config{$cfg} or die "Unknown configuration: $cfg\n"; | |
185 | } | |
186 | } | |
187 | else { | |
188 | $opt{config} = [sort keys %config]; | |
189 | } | |
190 | ||
191 | find(sub { | |
49ef49fe | 192 | /^(perl-?(5\..*))\.tar\.(gz|bz2|lzma)$/ or return; |
0d0f8426 | 193 | $perl{$1} = { version => $2, source => $File::Find::name, compress => $3 }; |
adfe19db MHM |
194 | }, $opt{source}); |
195 | ||
196 | if (exists $opt{perl}) { | |
197 | for my $perl (@{$opt{perl}}) { | |
198 | my $p = $perl; | |
199 | exists $perl{$p} or $p = "perl$perl"; | |
200 | exists $perl{$p} or $p = "perl-$perl"; | |
201 | exists $perl{$p} or die "Cannot find perl: $perl\n"; | |
202 | push @perls, $p; | |
203 | } | |
204 | } | |
205 | else { | |
206 | @perls = sort keys %perl; | |
207 | } | |
208 | ||
cac25305 MHM |
209 | if ($opt{'test-archives'}) { |
210 | my $test = 'test'; | |
211 | my $cwd = cwd; | |
212 | -d $test or mkpath($test); | |
213 | chdir $test or die "chdir $test: $!\n"; | |
214 | for my $perl (@perls) { | |
215 | eval { | |
216 | my $d = extract_source($perl{$perl}); | |
679ad62d MHM |
217 | if ($opt{'test-archives'} > 2) { |
218 | my $cwd2 = cwd; | |
219 | chdir $d or die "chdir $d: $!\n"; | |
220 | patch_source($perl{$perl}{version}); | |
221 | chdir $cwd2 or die "chdir $cwd2:$!\n" | |
222 | } | |
cac25305 MHM |
223 | rmtree($d) if -e $d; |
224 | }; | |
225 | warn $@ if $@; | |
226 | } | |
227 | chdir $cwd or die "chdir $cwd: $!\n"; | |
228 | print STDERR "cleaning up\n"; | |
229 | rmtree($test); | |
230 | exit 0; | |
231 | } | |
232 | ||
adfe19db MHM |
233 | for my $cfg (@{$opt{config}}) { |
234 | for my $perl (@perls) { | |
235 | my $config = $config{$cfg}; | |
0d0f8426 | 236 | %current = (config => $cfg, perl => $perl, version => $perl{$perl}{version}); |
adfe19db | 237 | |
0d0f8426 | 238 | if (is($config->{masked_versions}, $current{version})) { |
adfe19db MHM |
239 | print STDERR "skipping $perl for configuration $cfg (masked)\n"; |
240 | next; | |
241 | } | |
242 | ||
243 | if (-d expand($opt{prefix}) and !$opt{force}) { | |
244 | print STDERR "skipping $perl for configuration $cfg (already installed)\n"; | |
245 | next; | |
246 | } | |
247 | ||
248 | my $cwd = cwd; | |
249 | ||
250 | my $build = expand($opt{build}); | |
251 | -d $build or mkpath($build); | |
252 | chdir $build or die "chdir $build: $!\n"; | |
253 | ||
254 | print STDERR "building $perl with configuration $cfg\n"; | |
255 | buildperl($perl, $config); | |
256 | ||
257 | chdir $cwd or die "chdir $cwd: $!\n"; | |
258 | } | |
259 | } | |
260 | ||
261 | sub expand | |
262 | { | |
263 | my $in = shift; | |
264 | $in =~ s/(<(\w+)>)/exists $current{$2} ? $current{$2} : $1/eg; | |
265 | return $in; | |
266 | } | |
267 | ||
268 | sub is | |
269 | { | |
270 | my($s1, $s2) = @_; | |
4a582685 | 271 | |
adfe19db MHM |
272 | defined $s1 != defined $s2 and return 0; |
273 | ||
274 | ref $s2 and ($s1, $s2) = ($s2, $s1); | |
275 | ||
276 | if (ref $s1) { | |
277 | if (ref $s1 eq 'ARRAY') { | |
278 | is($_, $s2) and return 1 for @$s1; | |
279 | return 0; | |
280 | } | |
281 | return $s2 =~ $s1; | |
282 | } | |
283 | ||
284 | return $s1 eq $s2; | |
285 | } | |
286 | ||
287 | sub buildperl | |
288 | { | |
289 | my($perl, $cfg) = @_; | |
290 | ||
291 | my $d = extract_source($perl{$perl}); | |
292 | chdir $d or die "chdir $d: $!\n"; | |
293 | ||
0d0f8426 | 294 | patch_source($perl{$perl}{version}); |
adfe19db MHM |
295 | |
296 | build_and_install($perl{$perl}); | |
297 | } | |
298 | ||
299 | sub extract_source | |
300 | { | |
1d175cda MHM |
301 | eval { require Archive::Tar }; |
302 | die "Archive processing requires Archive::Tar to be installed\n" if $@; | |
303 | ||
adfe19db | 304 | my $perl = shift; |
adfe19db | 305 | |
cac25305 MHM |
306 | my $what = $opt{'test-archives'} ? 'test' : 'read'; |
307 | print "${what}ing $perl->{source}\n"; | |
adfe19db | 308 | |
0d0f8426 | 309 | my $target; |
adfe19db | 310 | |
0d0f8426 MHM |
311 | for my $f (Archive::Tar->list_archive($perl->{source})) { |
312 | my($t) = $f =~ /^([^\\\/]+)/ or die "ooops, should always match...\n"; | |
313 | die "refusing to extract $perl->{source}, as it would not extract to a single directory\n" | |
314 | if defined $target and $target ne $t; | |
315 | $target = $t; | |
316 | } | |
adfe19db | 317 | |
cac25305 MHM |
318 | if ($opt{'test-archives'} == 0 || $opt{'test-archives'} > 1) { |
319 | if (-d $target) { | |
320 | print "removing old build directory $target\n"; | |
321 | rmtree($target); | |
322 | } | |
adfe19db | 323 | |
cac25305 | 324 | print "extracting $perl->{source}\n"; |
0d0f8426 | 325 | |
cac25305 MHM |
326 | Archive::Tar->extract_archive($perl->{source}) |
327 | or die "extract failed: " . Archive::Tar->error() . "\n"; | |
0d0f8426 | 328 | |
cac25305 MHM |
329 | -d $target or die "oooops, $target not found\n"; |
330 | } | |
adfe19db MHM |
331 | |
332 | return $target; | |
333 | } | |
334 | ||
335 | sub patch_source | |
336 | { | |
0d0f8426 | 337 | my $version = shift; |
adfe19db MHM |
338 | |
339 | for my $p (@patch) { | |
0d0f8426 | 340 | if (is($p->{perl}, $version)) { |
adfe19db MHM |
341 | for my $s (@{$p->{subs}}) { |
342 | my($sub, @args) = @$s; | |
343 | $sub->(@args); | |
344 | } | |
345 | } | |
346 | } | |
347 | } | |
348 | ||
349 | sub build_and_install | |
350 | { | |
351 | my $perl = shift; | |
352 | my $prefix = expand($opt{prefix}); | |
353 | ||
49ef49fe CBW |
354 | run_or_die(q{sed -i -e "s:\\*/\\*) finc=\\"-I\\`echo \\$file | sed 's#/\\[^/\\]\\*\\$##\\`\\" ;;:*/*) finc=\\"-I\\`echo \\$file | sed 's#/[^/]\\*\\$##'\\`\\" ;;:" makedepend.SH}); |
355 | ||
adfe19db MHM |
356 | print "building perl $perl->{version} ($current{config})\n"; |
357 | ||
358 | run_or_die("./Configure $config{$current{config}}{config_args} -Dusedevel -Uinstallusrbinperl -Dprefix=$prefix"); | |
b2049988 MHM |
359 | if (-f "x2p/makefile") { |
360 | run_or_die("sed -i -e '/^.*<builtin>/d' -e '/^.*<built-in>/d' -e '/^.*<command line>/d' -e '/^.*<command-line>/d' makefile x2p/makefile"); | |
361 | } | |
adfe19db | 362 | run_or_die("make all"); |
0d0f8426 MHM |
363 | run("make test") if $opt{test}; |
364 | if ($opt{install}) { | |
365 | run_or_die("make install"); | |
366 | } | |
367 | else { | |
368 | print "\n*** NOT INSTALLING PERL ***\n\n"; | |
369 | } | |
adfe19db MHM |
370 | } |
371 | ||
372 | sub patch_db | |
373 | { | |
374 | my $ver = shift; | |
679ad62d | 375 | print "patching ext/DB_File/DB_File.xs\n"; |
adfe19db MHM |
376 | run_or_die("sed -i -e 's/<db.h>/<db$ver\\/db.h>/' ext/DB_File/DB_File.xs"); |
377 | } | |
378 | ||
379 | sub patch_doio | |
380 | { | |
679ad62d | 381 | patch(<<'END'); |
adfe19db MHM |
382 | --- doio.c.org 2004-06-07 23:14:45.000000000 +0200 |
383 | +++ doio.c 2003-11-04 08:03:03.000000000 +0100 | |
384 | @@ -75,6 +75,16 @@ | |
385 | # endif | |
386 | #endif | |
4a582685 | 387 | |
adfe19db MHM |
388 | +#if _SEM_SEMUN_UNDEFINED |
389 | +union semun | |
390 | +{ | |
391 | + int val; | |
392 | + struct semid_ds *buf; | |
393 | + unsigned short int *array; | |
394 | + struct seminfo *__buf; | |
395 | +}; | |
396 | +#endif | |
397 | + | |
398 | bool | |
399 | do_open(gv,name,len,as_raw,rawmode,rawperm,supplied_fp) | |
400 | GV *gv; | |
401 | END | |
402 | } | |
403 | ||
679ad62d MHM |
404 | sub patch_sysv |
405 | { | |
406 | my %opt = @_; | |
407 | ||
408 | # check if patching is required | |
409 | return if $^O ne 'linux' or -f '/usr/include/asm/page.h'; | |
410 | ||
411 | if ($opt{old_format}) { | |
412 | patch(<<'END'); | |
413 | --- ext/IPC/SysV/SysV.xs.org 1998-07-20 10:20:07.000000000 +0200 | |
414 | +++ ext/IPC/SysV/SysV.xs 2007-08-12 10:51:06.000000000 +0200 | |
415 | @@ -3,9 +3,6 @@ | |
416 | #include "XSUB.h" | |
417 | ||
418 | #include <sys/types.h> | |
419 | -#ifdef __linux__ | |
420 | -#include <asm/page.h> | |
421 | -#endif | |
422 | #if defined(HAS_MSG) || defined(HAS_SEM) || defined(HAS_SHM) | |
423 | #include <sys/ipc.h> | |
424 | #ifdef HAS_MSG | |
425 | END | |
426 | } | |
427 | else { | |
428 | patch(<<'END'); | |
429 | --- ext/IPC/SysV/SysV.xs.org 2007-08-11 00:12:46.000000000 +0200 | |
430 | +++ ext/IPC/SysV/SysV.xs 2007-08-11 00:10:51.000000000 +0200 | |
431 | @@ -3,9 +3,6 @@ | |
432 | #include "XSUB.h" | |
433 | ||
434 | #include <sys/types.h> | |
435 | -#ifdef __linux__ | |
436 | -# include <asm/page.h> | |
437 | -#endif | |
438 | #if defined(HAS_MSG) || defined(HAS_SEM) || defined(HAS_SHM) | |
439 | #ifndef HAS_SEM | |
440 | # include <sys/ipc.h> | |
441 | END | |
442 | } | |
443 | } | |
444 | ||
49ef49fe CBW |
445 | sub patch_configure |
446 | { | |
447 | patch(<<'END'); | |
448 | --- Configure | |
449 | +++ Configure | |
450 | @@ -3380,6 +3380,18 @@ | |
451 | test "X$gfpthkeep" != Xy && gfpth="" | |
452 | EOSC | |
453 | ||
454 | +# gcc 3.1 complains about adding -Idirectories that it already knows about, | |
455 | +# so we will take those off from locincpth. | |
456 | +case "$gccversion" in | |
457 | +3*) | |
458 | + echo "main(){}">try.c | |
459 | + for incdir in `$cc -v -c try.c 2>&1 | \ | |
460 | + sed '1,/^#include <\.\.\.>/d;/^End of search list/,$d;s/^ //'` ; do | |
461 | + locincpth=`echo $locincpth | sed s!$incdir!!` | |
462 | + done | |
463 | + $rm -f try try.* | |
464 | +esac | |
465 | + | |
466 | : What should the include directory be ? | |
467 | echo " " | |
468 | $echo $n "Hmm... $c" | |
469 | END | |
470 | } | |
471 | ||
472 | sub patch_makedepend_lc | |
473 | { | |
474 | patch(<<'END'); | |
475 | --- makedepend.SH | |
476 | +++ makedepend.SH | |
477 | @@ -58,6 +58,10 @@ case $PERL_CONFIG_SH in | |
478 | ;; | |
479 | esac | |
480 | ||
481 | +# Avoid localized gcc/cc messages | |
482 | +LC_ALL=C | |
483 | +export LC_ALL | |
484 | + | |
485 | # We need .. when we are in the x2p directory if we are using the | |
486 | # cppstdin wrapper script. | |
487 | # Put .. and . first so that we pick up the present cppstdin, not | |
488 | END | |
489 | } | |
490 | ||
adfe19db MHM |
491 | sub patch |
492 | { | |
679ad62d MHM |
493 | my($patch) = @_; |
494 | print "patching $_\n" for $patch =~ /^\+{3}\s+(\S+)/gm; | |
495 | my $diff = 'tmp.diff'; | |
adfe19db MHM |
496 | write_or_die($diff, $patch); |
497 | run_or_die("patch -s -p0 <$diff"); | |
498 | unlink $diff or die "unlink $diff: $!\n"; | |
499 | } | |
500 | ||
501 | sub write_or_die | |
502 | { | |
503 | my($file, $data) = @_; | |
504 | my $fh = new IO::File ">$file" or die "$file: $!\n"; | |
505 | $fh->print($data); | |
506 | } | |
507 | ||
508 | sub run_or_die | |
509 | { | |
510 | # print "[running @_]\n"; | |
511 | system "@_" and die "@_: $?\n"; | |
512 | } | |
513 | ||
514 | sub run | |
515 | { | |
516 | # print "[running @_]\n"; | |
517 | system "@_" and warn "@_: $?\n"; | |
518 | } | |
0d0f8426 MHM |
519 | |
520 | __END__ | |
521 | ||
522 | =head1 NAME | |
523 | ||
524 | buildperl.pl - build/install perl distributions | |
525 | ||
526 | =head1 SYNOPSIS | |
527 | ||
528 | perl buildperl.pl [options] | |
529 | ||
530 | --help show this help | |
531 | ||
532 | --source=directory directory containing source tarballs | |
533 | [default: /tmp/perl/source] | |
534 | ||
535 | --build=directory directory used for building perls [EXPAND] | |
536 | [default: /tmp/perl/build/<config>] | |
537 | ||
538 | --prefix=directory use this installation prefix [EXPAND] | |
5cde1e48 KW |
539 | [default: |
540 | /tmp/perl/install/<config>/<perl>] | |
0d0f8426 MHM |
541 | |
542 | --config=configuration build this configuration [MULTI] | |
543 | [default: all possible configurations] | |
544 | ||
545 | --perl=version build this version of perl [MULTI] | |
546 | [default: all possible versions] | |
547 | ||
5cde1e48 KW |
548 | --force rebuild and install already installed |
549 | versions | |
0d0f8426 MHM |
550 | |
551 | --test run test suite after building | |
552 | ||
553 | --noinstall don't install after building | |
554 | ||
5cde1e48 KW |
555 | --patch only patch the perl source in the current |
556 | directory | |
1d175cda | 557 | |
5cde1e48 KW |
558 | --oneshot build from the perl source in the current |
559 | directory (extra arguments are passed to | |
560 | Configure) | |
1d175cda | 561 | |
0d0f8426 MHM |
562 | options tagged with [MULTI] can be given multiple times |
563 | ||
564 | options tagged with [EXPAND] expand the following items | |
565 | ||
566 | <perl> versioned perl directory (e.g. 'perl-5.6.1') | |
567 | <version> perl version (e.g. '5.6.1') | |
568 | <config> name of the configuration (e.g. 'default') | |
569 | ||
570 | =head1 EXAMPLES | |
571 | ||
572 | The following examples assume that your Perl source tarballs are | |
573 | in F</tmp/perl/source>. If they are somewhere else, use the C<--source> | |
574 | option to specify a different source directory. | |
575 | ||
576 | To build a default configuration of perl5.004_05 and install it | |
577 | to F</opt/perl5.004_05>, you would say: | |
578 | ||
579 | buildperl.pl --prefix='/opt/<perl>' --perl=5.004_05 --config=default | |
580 | ||
581 | To build debugging configurations of all perls in the source directory | |
582 | and install them to F</opt>, use: | |
583 | ||
584 | buildperl.pl --prefix='/opt/<perl>' --config=debug | |
585 | ||
586 | To build all configurations for perl-5.8.5 and perl-5.8.6, test them | |
587 | and don't install them, run: | |
588 | ||
589 | buildperl.pl --perl=5.8.5 --perl=5.8.6 --test --noinstall | |
590 | ||
1d175cda MHM |
591 | To build and install a single version of perl with special configuration |
592 | options, use: | |
593 | ||
5cde1e48 KW |
594 | buildperl.pl --perl=5.6.0 --prefix=/opt/p560ld --oneshot -- -des \ |
595 | -Duselongdouble | |
1d175cda | 596 | |
0d0f8426 MHM |
597 | =head1 COPYRIGHT |
598 | ||
b2049988 | 599 | Copyright (c) 2004-2013, Marcus Holland-Moritz. |
0d0f8426 MHM |
600 | |
601 | This program is free software; you can redistribute it and/or | |
602 | modify it under the same terms as Perl itself. | |
603 | ||
604 | =head1 SEE ALSO | |
605 | ||
ba120f6f | 606 | See L<Devel::PPPort> and L<HACKERS>. |