1#!/usr/bin/perl -w 2################################################################################ 3# 4# buildperl.pl -- build various versions of perl automatically 5# 6################################################################################ 7# 8# Version 3.x, Copyright (C) 2004-2013, Marcus Holland-Moritz. 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 17use strict; 18use Getopt::Long; 19use Pod::Usage; 20use File::Find; 21use File::Path; 22use Data::Dumper; 23use IO::File; 24use Cwd; 25 26# TODO: - extra arguments to Configure 27 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 34my %opt = ( 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, 43 'test-archives' => 0, 44); 45 46my %config = ( 47 default => { 48 config_args => '-des', 49 }, 50 thread => { 51 config_args => '-des -Dusethreads', 52 masked_versions => [ qr/^5\.00[01234]/ ], 53 }, 54 thread5005 => { 55 config_args => '-des -Duse5005threads', 56 masked_versions => [ qr/^5\.00[012345]|^5\.(9|\d\d)|^5\.8\.9/ ], 57 }, 58 debug => { 59 config_args => '-des -Doptimize=-g', 60 }, 61); 62 63my @patch = ( 64 { 65 perl => [ 66 qr/^5\.00[01234]/, 67 qw/ 68 5.005 69 5.005_01 70 5.005_02 71 5.005_03 72 /, 73 ], 74 subs => [ 75 [ \&patch_db, 1 ], 76 ], 77 }, 78 { 79 perl => [ 80 qw/ 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 88 /, 89 ], 90 subs => [ 91 [ \&patch_db, 3 ], 92 ], 93 }, 94 { 95 perl => [ 96 qr/^5\.004_0[1234]$/, 97 ], 98 subs => [ 99 [ \&patch_doio ], 100 ], 101 }, 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 }, 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 }, 148); 149 150my(%perl, @perls); 151 152GetOptions(\%opt, qw( 153 config=s@ 154 prefix=s 155 build=s 156 source=s 157 perl=s@ 158 force 159 test 160 install! 161 test-archives=i 162 patch! 163 oneshot 164)) or pod2usage(2); 165 166my %current; 167 168if ($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 182if (exists $opt{config}) { 183 for my $cfg (@{$opt{config}}) { 184 exists $config{$cfg} or die "Unknown configuration: $cfg\n"; 185 } 186} 187else { 188 $opt{config} = [sort keys %config]; 189} 190 191find(sub { 192 /^(perl-?(5\..*))\.tar\.(gz|bz2|lzma)$/ or return; 193 $perl{$1} = { version => $2, source => $File::Find::name, compress => $3 }; 194}, $opt{source}); 195 196if (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} 205else { 206 @perls = sort keys %perl; 207} 208 209if ($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}); 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 } 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 233for my $cfg (@{$opt{config}}) { 234 for my $perl (@perls) { 235 my $config = $config{$cfg}; 236 %current = (config => $cfg, perl => $perl, version => $perl{$perl}{version}); 237 238 if (is($config->{masked_versions}, $current{version})) { 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 261sub expand 262{ 263 my $in = shift; 264 $in =~ s/(<(\w+)>)/exists $current{$2} ? $current{$2} : $1/eg; 265 return $in; 266} 267 268sub is 269{ 270 my($s1, $s2) = @_; 271 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 287sub buildperl 288{ 289 my($perl, $cfg) = @_; 290 291 my $d = extract_source($perl{$perl}); 292 chdir $d or die "chdir $d: $!\n"; 293 294 patch_source($perl{$perl}{version}); 295 296 build_and_install($perl{$perl}); 297} 298 299sub extract_source 300{ 301 eval { require Archive::Tar }; 302 die "Archive processing requires Archive::Tar to be installed\n" if $@; 303 304 my $perl = shift; 305 306 my $what = $opt{'test-archives'} ? 'test' : 'read'; 307 print "${what}ing $perl->{source}\n"; 308 309 my $target; 310 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 } 317 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 } 323 324 print "extracting $perl->{source}\n"; 325 326 Archive::Tar->extract_archive($perl->{source}) 327 or die "extract failed: " . Archive::Tar->error() . "\n"; 328 329 -d $target or die "oooops, $target not found\n"; 330 } 331 332 return $target; 333} 334 335sub patch_source 336{ 337 my $version = shift; 338 339 for my $p (@patch) { 340 if (is($p->{perl}, $version)) { 341 for my $s (@{$p->{subs}}) { 342 my($sub, @args) = @$s; 343 $sub->(@args); 344 } 345 } 346 } 347} 348 349sub build_and_install 350{ 351 my $perl = shift; 352 my $prefix = expand($opt{prefix}); 353 354 run_or_die(q{sed -i -e "s:\\*/\\*) finc=\\"-I\\`echo \\$file | sed 's#/\\[^/\\]\\*\\$##\\`\\" ;;:*/*) finc=\\"-I\\`echo \\$file | sed 's#/[^/]\\*\\$##'\\`\\" ;;:" makedepend.SH}); 355 356 print "building perl $perl->{version} ($current{config})\n"; 357 358 run_or_die("./Configure $config{$current{config}}{config_args} -Dusedevel -Uinstallusrbinperl -Dprefix=$prefix"); 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 } 362 run_or_die("make all"); 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 } 370} 371 372sub patch_db 373{ 374 my $ver = shift; 375 print "patching ext/DB_File/DB_File.xs\n"; 376 run_or_die("sed -i -e 's/<db.h>/<db$ver\\/db.h>/' ext/DB_File/DB_File.xs"); 377} 378 379sub patch_doio 380{ 381 patch(<<'END'); 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 387 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; 401END 402} 403 404sub 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 425END 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> 441END 442 } 443} 444 445sub 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" 469END 470} 471 472sub 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 488END 489} 490 491sub patch 492{ 493 my($patch) = @_; 494 print "patching $_\n" for $patch =~ /^\+{3}\s+(\S+)/gm; 495 my $diff = 'tmp.diff'; 496 write_or_die($diff, $patch); 497 run_or_die("patch -s -p0 <$diff"); 498 unlink $diff or die "unlink $diff: $!\n"; 499} 500 501sub 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 508sub run_or_die 509{ 510 # print "[running @_]\n"; 511 system "@_" and die "@_: $?\n"; 512} 513 514sub run 515{ 516 # print "[running @_]\n"; 517 system "@_" and warn "@_: $?\n"; 518} 519 520__END__ 521 522=head1 NAME 523 524buildperl.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] 539 [default: 540 /tmp/perl/install/<config>/<perl>] 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 548 --force rebuild and install already installed 549 versions 550 551 --test run test suite after building 552 553 --noinstall don't install after building 554 555 --patch only patch the perl source in the current 556 directory 557 558 --oneshot build from the perl source in the current 559 directory (extra arguments are passed to 560 Configure) 561 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 572The following examples assume that your Perl source tarballs are 573in F</tmp/perl/source>. If they are somewhere else, use the C<--source> 574option to specify a different source directory. 575 576To build a default configuration of perl5.004_05 and install it 577to F</opt/perl5.004_05>, you would say: 578 579 buildperl.pl --prefix='/opt/<perl>' --perl=5.004_05 --config=default 580 581To build debugging configurations of all perls in the source directory 582and install them to F</opt>, use: 583 584 buildperl.pl --prefix='/opt/<perl>' --config=debug 585 586To build all configurations for perl-5.8.5 and perl-5.8.6, test them 587and don't install them, run: 588 589 buildperl.pl --perl=5.8.5 --perl=5.8.6 --test --noinstall 590 591To build and install a single version of perl with special configuration 592options, use: 593 594 buildperl.pl --perl=5.6.0 --prefix=/opt/p560ld --oneshot -- -des \ 595 -Duselongdouble 596 597=head1 COPYRIGHT 598 599Copyright (c) 2004-2013, Marcus Holland-Moritz. 600 601This program is free software; you can redistribute it and/or 602modify it under the same terms as Perl itself. 603 604=head1 SEE ALSO 605 606See L<Devel::PPPort> and L<HACKERS>. 607