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