App-Bootstrap-Perl

 view release on metacpan or  search on metacpan

bin/bootstrap-perl  view on Meta::CPAN

#! /usr/bin/perl
# -*- mode: cperl -*-

# PODNAME: bootstrap-perl
# ABSTRACT: Bootstrap Perl inclusive CPAN from git


$|++; # autoflush

use 5.006;
use strict;
use warnings;

use Getopt::Long ":config", "no_ignore_case", "bundling";
use Cwd "getcwd";
use Sys::Hostname;

my $timestamp           = ~~localtime();
my $HOME                = $ENV{HOME} || '/opt';
my $hostname            = hostname();

our $build_path          = "$HOME/.bootstrapperl/bootstrap-perl-build";
my $build_path_perl     = "$build_path/perl";
my $logfile             = "$build_path/bootstrap-perl.log";
my $logcommand          = "$build_path/command.log";
my $giturl              = "git://github.com/Perl/perl5.git";

my $cpucount             = `cat /proc/cpuinfo | grep bogomips | wc -l`; chomp $cpucount;
my $threadcount          = $cpucount + 1;

# getopt
my $dry;
my $prefix;
my $prefixbase          = "$HOME/.bootstrapperl/$hostname";
my $version             = "blead";
our $VERSION;
my $installdeps;
my $sourcetgz           = "/var/tmp/perl.tar.gz";
my $blead               = 0;
my $usethreads          = 1;
my $bit64               = 1;
my $taintsupport        = 1;
my $silentnotaint       = 0;
my $help                = 0;
my $test                = 0;
my $cpan                = 1;
my $cleancpansources    = 0;
my $forcecpancfg        = 0;
my $forcebuildperl      = 0;
my $forcemoduleinstall  = 0;
my $perlformance        = 0;
my $perlformance_local  = 0;
my $perlformance_report = 0;
my $jobs                = $threadcount;
my @mirrors;
my @modules;
my @runscripts          = ();
my @runargs             = ();
my @confargs            = ();
my @exesuffixes         = ();
my $OLDSTDOUT;
my $OLDSTDERR;
my $LOGFILE;
my $COMMAND;
my $USER;

use constant {
    NO_REINSTALL => 0,
    REINSTALL   => 1,
    NO_FORCE     => 0,
    FORCE       => 1,
};

sub caller_line {
        my $i = 0;
        my ($rc, $rc_prev, $func);
        while (1) {
                my @v = caller($i);
                defined($v[0]) or last;
                $rc_prev = $rc;
                ($rc, $func) = @v[2..3];
                $i++;
        };
        $func ne "(eval)"? $rc: $rc_prev;
}

sub setup_user {
        open($USER, ">&", \*STDOUT) || die;
}

sub setup_log {
        open($OLDSTDOUT, ">&", \*STDOUT) || die;
        open($OLDSTDERR, ">&", \*STDERR) || die;
        open($LOGFILE,   ">>" , $logfile) || die;
        open($COMMAND,   ">>",  $logcommand) || die;
}

# Execute a command via system(). Output goes to log file.
sub print_and_system {
        my ($cmd) = @_;
        my $exitcode;
        my $line = caller_line();

        my @cmd = split / +/, $cmd;
        my $bin = $cmd[0];
        my $is_cpan = $bin eq bin_cpan();

        open(STDOUT, ">>&", $LOGFILE) || die;
        open(STDERR, ">>&", $LOGFILE) || die;

        print $COMMAND $cmd, "\n";
        print $LOGFILE $cmd, "\n";
        $exitcode = system ($cmd);

        open(STDOUT, ">&", $OLDSTDOUT) || die;
        open(STDERR, ">&", $OLDSTDERR) || die;

        $exitcode == 0 || warn "$0($line): $cmd ($exitcode>>".($exitcode>>8).")";
}

# Execute a command via system(). Output goes to normal stdout/stderr.
sub print_and_system_out {
        my ($cmd) = @_;
        my $line = caller_line();

        print $COMMAND $cmd, "\n";
        print $LOGFILE $cmd, "\n";
        print $USER    $cmd, "\n";
        system ($cmd) == 0 || warn "$0($line): $cmd ($?>>".($?>>8).")";
}

# Execute a command via qx(). Output goes to log file.
sub print_and_qx {
        my ($cmd) = @_;
        my $line = caller_line();
        my $exitcode;
        my $out;

        print $COMMAND $cmd, "\n";
        print $LOGFILE $cmd, "\n";
        $out = qx($cmd 2>&1);
        $exitcode = $?;
        print $LOGFILE $out, "\n";
        $exitcode == 0 || warn "$0($line): $cmd ($exitcode>>".($exitcode>>8).")";

        $out;
}

# Execute a command via qx(). Output goes to normal stdout/stderr.
sub print_and_qx_chomp {
        my $out = print_and_qx @_;
        chomp $out;
        $out;
}

sub _perl_base_name {
        my ($perl_revision, $perl_version, $perl_subversion, $usethreads, $bit64, $taintsupport, $silentnotaint, $gitdescribe, $gitchangeset, $exe_suffixes, $blead) = @_;
        return join("-",
                    ($blead      ? "blead" : "$perl_revision.$perl_version"),
                    ($usethreads ? "" : "no" )."thread",
                    ($bit64      ? "" : "no" )."64bit",
                    ($taintsupport ? "" : (($silentnotaint ? "silent" : "")."no"))."taint",
                    @$exe_suffixes,
                   );
}

sub _perl_exe_name {
        my ($perl_revision, $perl_version, $perl_subversion, $usethreads, $bit64, $taintsupport, $silentnotaint, $gitdescribe, $gitchangeset, $exe_suffixes, $blead) = @_;

        return join("-",
                    "perl",
                    _perl_base_name(@_),
                   );
}

sub _perl_pathprefix {
        my ($perl_revision, $perl_version, $perl_subversion, $usethreads, $bit64, $taintsupport, $silentnotaint, $gitdescribe, $gitchangeset, $exe_suffixes, $blead) = @_;

        return join("-",
                    $prefixbase."/perl",
                    _perl_base_name(@_),
                    $gitdescribe,
                   );
}

# ========== setup user log ==========

setup_user();

# ========== getopt ==========

my $ok = GetOptions (
                     "prefix=s"     => \$prefix,
                     "prefixbase=s" => \$prefixbase,
                     "version|commit|c=s" => \$version,
                     "installdeps=s" => \$installdeps,
                     "sourcetgz=s"  => \$sourcetgz,
                     "blead!"       => \$blead,
                     "usethreads!"  => \$usethreads,
                     "use64bit!"    => \$bit64,
                     "taintsupport!" => \$taintsupport,
                     "silentnotaint!" => \$silentnotaint,
                     "help|h"       => \$help,
                     "jobs|j=i"     => \$jobs,
                     "test|t"       => \$test,
                     "cpan!"        => \$cpan,
                     "cleancpansources!" => \$cleancpansources,
                     "forcecpancfg!" => \$forcecpancfg,
                     "forcebuildperl!" => \$forcebuildperl,
                     "forcemoduleinstall!" => \$forcemoduleinstall,
                     'perlformance' => \$perlformance,
                     'perlformance-local' => \$perlformance_local,
                     'perlformance-report' => \$perlformance_report,
                     'dry|n'        => \$dry,
                     'mirror|m=s@'  => \@mirrors,
                     'module|M=s@'  => \@modules,
                     'run|r=s@'     => \@runscripts,
                     'runargs=s@'   => \@runargs,
                     'confargs=s@'  => \@confargs,
                     'exesuffixes=s@' => \@exesuffixes,
                     'giturl|g=s'   => \$giturl,
                    );

if ($help) {
        exec("perldoc", "bootstrap-perl") or do {
                print $USER "\nPlease see 'perldoc bootstrap-perl' for help.\n";
                exit 0;
        }
}

my $DISTROPREFSREPO     = "git://github.com/renormalist/cpanpm-distroprefs.git";
my $DISTROPREFSSRCBASE  = "$build_path";
my $DISTROPREFSSRC1     = "$DISTROPREFSSRCBASE/cpanpm-distroprefs/cpanpm/distroprefs";
my $DISTROPREFSSRC2     = "$DISTROPREFSSRCBASE/cpanpm-distroprefs/renormalist/distroprefs";
my $DISTROPREFSDIR      = $prefixbase."/cpan/prefs";
my $SOURCESDIR          = $prefixbase."/cpan/sources";
my $CPANBUILDDIR        = $prefixbase."/cpan/build";

# defaults
my @default_runscripts = ();
my @default_confargs   = ();
my @default_runargs    = ();
my @default_modules    = ();
my @default_mirrors    = (qw( http://search.cpan.org/CPAN/ ));

return if defined($::{"no_run"});
print $USER "\n";
print $USER "============================================================\n";
print $USER "Bootstrap Perl - $timestamp\n";
print $USER "============================================================\n";

# giturl "." means clone from local dir
if ($giturl eq '.') {
        $giturl = getcwd;
        my $firstcommit = qx!git log --oneline 8d063cd8450e59ea1c611a2f4f5a21059a2804f1 2> /dev/null!;
        if ($firstcommit !~ /a "replacement" for awk and sed/) {
                print $USER "Local git repo does not look like it is Perl's.\n";
                exit 1;
        }
}

# version "." is replaced with current repo's HEAD

bin/bootstrap-perl  view on Meta::CPAN

                print_and_system_out "cd $build_path && tar xzf $sourcetgz";
        } else {
                print_and_system_out "cd $build_path && git clone $giturl perl";
        }
}

chdir "$build_path_perl"; # important, I don't remember for what below
print_and_system "cd $build_path_perl && rm -f $build_path_perl/.git/index.lock";
print_and_system "cd $build_path_perl && git reset --hard";
print_and_system "cd $build_path_perl && git clean -dxf";
print_and_system "cd $build_path_perl && git checkout blead";
print_and_system "cd $build_path_perl && git stash";
print_and_system "cd $build_path_perl && git pull";
print_and_system "cd $build_path_perl && git stash";
print_and_system "cd $build_path_perl && git checkout $version";
print_and_system "cd $build_path_perl && git pull";

# ========= version =========

my $gitdescribe;
$gitdescribe = print_and_qx_chomp qq!cd $build_path_perl && git describe --all!;
$gitdescribe = print_and_qx_chomp qq!cd $build_path_perl && git describe! if ($gitdescribe =~ m,/, && $gitdescribe !~ m,tags/,);
$gitdescribe =~ s|^tags/||g;
my $gitchangeset = print_and_qx_chomp qq!cd $build_path_perl && git rev-parse HEAD!;
my ($perl_revision, $perl_version, $perl_subversion) = $gitdescribe =~ m/^\D*(\d+)[._](\d+)(?:[._](\d+))?.*$/g;
$perl_version    = sprintf("%02d", $perl_version);
$perl_subversion ||= 0;
$VERSION = eval("v$perl_revision.$perl_version.$perl_subversion");

# ========= cherry-picks  =========

# cherry pick several bugfixes in 5.8/5.9 that break building
if (v5.8.0 le $VERSION && $VERSION le v5.9.4) {
        print $LOGFILE "*** cherry-pick building fixes for older perl versions\n";
        # FIX: <command-line>
        print_and_system qq!cd $build_path_perl && git cherry-pick -n cf14425e2bd169ce935f76d359f3253b51e441a0!;
        # FIX: rebuild / gcc -g
        print_and_system qq!cd $build_path_perl && git cherry-pick -n a3d1be69db4c082c1b9cef14f51a6e7fe4a9dac9!;
        # FIX: makedepend.SH
        print_and_system qq!cd $build_path_perl && git cherry-pick -n 96a8704c85b1e14bb3d63195b0b720f2fd5b8182!;
        # FIX: create missing link (5.8.8)
        print_and_system q'sudo ln -sf `find  /usr/src -name page.h | grep arch/x86/include/asm/page.h | tail -1` /usr/include/asm/'  if ! -e "/usr/include/asm/page.h";
}

if ($VERSION lt v5.12.4) {
        print $LOGFILE "*** cherry-pick: fix search libs in configure\n";
        print_and_system qq!cd $build_path_perl && git cherry-pick -n 40f026236b9959b7ad3260fedc6c66cd30bb7abc!;
        print_and_system qq!cd $build_path_perl && git cherry-pick -n bcab1245a0693be445ce018352f6fbe4abc26e88!;
}

# ========== prepare paths ==========

unless (defined $perl_revision && defined $perl_version)
{
        no warnings 'uninitialized';
        print $USER "# Bummer! Unrecognized version schema ($gitdescribe).\n";
        print $USER "# ($perl_revision, $perl_version, $perl_subversion)\n";
        exit 1;
}

my $codespeed_executable =            _perl_exe_name  ($perl_revision, $perl_version, $perl_subversion, $usethreads, $bit64, $taintsupport, $silentnotaint, $gitdescribe, $gitchangeset, \@EXESUFFIXES, $blead);
my $PREFIX               = $prefix || _perl_pathprefix($perl_revision, $perl_version, $perl_subversion, $usethreads, $bit64, $taintsupport, $silentnotaint, $gitdescribe, $gitchangeset, \@EXESUFFIXES, $blead);
my $USETHREADS           = $usethreads ? "-Dusethreads"  : "";
my $BIT64                = $bit64      ? "-Duse64bitall" : "";
my $TAINTSUPPORT         = $taintsupport ? "" : ($silentnotaint ? "-Accflags=-DSILENT_NO_TAINT_SUPPORT" : "-Accflags=-DNO_TAINT_SUPPORT");

print $USER "\n";
print $USER "============================================================\n\n";
print $USER "  Bootstrap Perl\n";
print $USER "  --------------\n\n";
print $USER "  version:        $version\n\n";
print $USER "  git-describe:   $gitdescribe\n\n";
print $USER "  git-changeset:  $gitchangeset\n\n";
print $USER "  codespeed name: $codespeed_executable\n\n";
print $USER "  PREFIX:         $PREFIX\n\n";
print $USER "  configureargs:  ".join("\n                  ", @confargs)."\n\n";
print $USER "  CPAN mirrors:   ".join("\n                  ", @CPANMIRRORS)."\n\n";
print $USER "  modules:        ".join("\n                  ", @MODULES)."\n\n";
print $USER "  scripts:        ".join("\n                  ", map { $_ ." ".join(" ", @RUNARGS) } @RUNSCRIPTS)."\n\n";
print $USER "============================================================\n\n";

my @in_blead = grep { / blead$/ }
               map  { chomp ; $_ }
               print_and_qx qq!git branch --contains $gitchangeset!;
if ($blead && !@in_blead) {
        print $USER "# Bummer!\n";
        print $USER "#   Use '--blead' only with commits in blead branch.\n";
        exit 1;
}
$ENV{PERL_AUTOINSTALL} = "--defaultdeps";
$ENV{TEST_JOBS}        = $threadcount;
$ENV{TWMC_TEST_PORT}   = "9876";

my $perl_exe = "$PREFIX/bin/perl";

my $THREADS = $jobs ? "-j $jobs" : "-j $threadcount";
my $DRY = $dry ? "-n" : "";

# ========== build ==========

if ($forcebuildperl or ! -x $perl_exe )
{
        print $USER "*** BUILD perl\n";

        # ========== metainfo in Perl Config ==========

        # This (mis-)uses a built-in mechanism in Configure.
        #
        # Ultimately we want to inject meta information so it appears
        # later in Perl's %Config.
        #
        # Instead of dumping metadata directly into "config.arch" it
        # generates script code in there that in turn appends variable
        # assignments to "UU/config.sh" which in turn pretend to
        # later Configure steps that there would be configuration data
        # of an earlier session which it then merges into %Config.
        #
        # It should work for any ancient or future Perl 5 version.
        # Credits to Tux for help sorting this out.

        my $config_arch;
        print $LOGFILE "*** CONFIGURE meta information for Perl's \%Config via config.arch\n";
        open ($config_arch, ">>", "$build_path_perl/config.arch") and do {
                print $config_arch qq!\
cat >> UU/config.sh <<EOUUCONFIG

bin/bootstrap-perl  view on Meta::CPAN


=head3 test

Run the perl test suite:

  $ bootstrap-perl --test
  $ bootstrap-perl -t

Default is B<not> to run the tests.

=head3 (re-)build Perl

You can specify whether you want to compile perl even when an
executable C<perl> already exists:

  $ bootstrap-perl --forcebuildperl
  $ bootstrap-perl --noforcebuildperl

Default is B<off>.

=head3 bootstrap CPAN

You can specify whether to bootstrap a full CPAN environment with
distroprefs and dependencies:

  $ bootstrap-perl --cpan
  $ bootstrap-perl --nocpan

Default is B<on>.

=head3 (re-)configure CPAN

You can specify whether to (re-)write a CPAN config
(C<CPAN/Config.pm>, C<CPAN/MyConfig.pm>) even when it already exists:

  $ bootstrap-perl --forcecpancfg
  $ bootstrap-perl --noforcecpancfg

Default is B<on>.

=head3 use threads

Build a threaded Perl (C<-Dusethreads>):

  $ bootstrap-perl --usethreads

which is already the default. To build non-threaded Perl use:

  $ bootstrap-perl --nousethreads

=head3 use 64bit

Build a 64bit enabled Perl (C<-Duse64bitall>):

  $ bootstrap-perl --use64bit

which is already the default. To build Perl without 64bit use:

  $ bootstrap-perl --nouse64bit

=head3 taint support

Perl can be built without taint support (C<-DNO_TAINT_SUPPORT>, C<-DSILENT_NO_TAINT_SUPPORT>).

To build a Perl with taint support:

  $ bootstrap-perl --taintsupport

which is already the default.

To build Perl without taint support use:

  $ bootstrap-perl --notaintsupport

To make the notaintsupport silent, combine it with --silentnotaint:

  $ bootstrap-perl --notaintsupport --silentnotaint

(sic, both are needed).

=head3 version numbers vs. blead

By default the Perl version number is derived from git-describe and
kept for later reference (e.g., for codespeed exe name). However, if you
specify

  $ bootstrap-perl --blead

then the version used in paths or codespeed executables is using
"blead" in order to generate a make it belong to to a common
development line besides the yearly segmented graphs.

It is only possible to use C<--blead> with development versions in
order to create consistent results from only the blead branch.

Note that C<--blead> does not set a version but only modifies the
formats of path prefix and how the version is reported to
Perl::Formance; it is B<not> the same as C<--version blead>.

=head3 configure CPAN and modules

Configure CPAN to use these mirrors:

  $ bootstrap-perl --mirror file:///home/ss5/MINICPAN/
  $ bootstrap-perl -m file:///home/ss5/MINICPAN/ -m ftp://ftp.rub.de/pub/CPAN/
  $ bootstrap-perl -m file:///home/ss5/MINICPAN/,ftp://ftp.rub.de/pub/CPAN/

(Option can be repeated and allow comma separated lists.)

Install these modules from CPAN:

  $ bootstrap-perl --module YAML::Syck
  $ bootstrap-perl --module YAML::Syck --forcemoduleinstall
  $ bootstrap-perl -M YAML::Syck -M Digest::SHA1 -M IO::Tty -M LWP
  $ bootstrap-perl -M YAML::Syck,Digest::SHA1 -M IO::Tty,LWP

(Option can be repeated and allow comma separated lists.)

=head3 run scripts

Run these scripts relative to built <PREFIX>/bin/:

  $ bootstrap-perl --run tapper-testsuite-benchmark-perlformance
  $ bootstrap-perl --run tapper-testsuite-benchmark-perlformance --runargs="--plugins=Fib,FibOO;-vvv"
  $ bootstrap-perl -r tapper-testsuite-benchmark-perlformance -r primes.pl

(Option C<--run> can be repeated and allows comma separated lists.)

(Option C<--runargs> can be repeated and allows semicolon[sic] separated lists.)

=head3 run Perl::Formance benchmarks

To do everything in one go needed for running
Benchmark::Perl::Formance do:

  $ bootstrap-perl --perlformance

This sets defaults equivalent to C<-M Task::PerlFormance> C<-m
http://perlformance.net/PINTO/perlformance/> C<--run
benchmark-perlformance> C<--runargs=--plugins=ALL>.

You can override parts of that like this:

  $ bootstrap-perl --perlformance --runargs="--plugins=Fib,FibOO"

to specify different set ob benchmarks.

To use current subdir as Perl's repo and run only
those benchmarks with no extra dependencies

  $ bootstrap-perl --perlformance-local
  $ bootstrap-perl --perlformance-local -c .
  $ bootstrap-perl --perlformance-local -c .^
  $ bootstrap-perl --perlformance-local -c .~5

=head2 Unified installation prefix schema

It uses a unified naming schema for it's installation PREFIX:

  /$HOME/.bootstrapperl/$HOSTNAME/perl-5.<VERSION>-<(no)?thread>-<(no)?64bit>-<(no)?taint>-<git-describe>

which leads to paths like this:

  /home/ss5/.bootstrapperl/zomtec/perl-5.10-thread-64bit-taint-v5.10.0
  /home/ss5/.bootstrapperl/zomtec/perl-5.15-thread-64bit-taint-v5.15.5-258-ge7d0a3f

This naming schema consist of the major version, basic configuration
and I<git-describe>.

=head1 ABOUT

The script B<bootstrap-perl> bootstraps Perl installations with
complete CPAN environment, inclusive distroprefs, from git.

It was originally developed to be used by
L<Benchmark::Perl::Formance|Benchmark::Perl::Formance> and now lives
on its own.

It should work for Perl versions from 5.8.6 to blead. Occasionally it
cherry-picks a very few patches to fix some known build issues, like
for 5.8.x.

=head1 AUTHOR

Steffen Schwigon <ss5@renormalist.net>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2021 by Steffen Schwigon.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

__DATA__

# Have POD in it so perldoc -l can find its path.

=head1 ABOUT

B<AUTO-GENERATED> -- see L<https://metacpan.org/pod/bootstrap-perl>

This is CPAN.pm's systemwide configuration file. This file provides
defaults for users, and the values can be changed in a per-user
configuration file. The user-config file is being looked for as
~/.cpan/CPAN/MyConfig.pm.

=cut

$CPAN::Config = {
  'applypatch' => q[/usr/bin/applypatch],
  'auto_commit' => q[1],
  'build_cache' => q[10],
  'build_dir' => q[__PREFIXBASE__/cpan/build],
  'build_dir_reuse' => q[0],
  'build_requires_install_policy' => q[yes],
  'bzip2' => q[/bin/bzip2],
  'cache_metadata' => q[0],
  'check_sigs' => q[0],
  'colorize_debug' => q[bold cyan],
  'colorize_output' => q[1],
  'colorize_print' => q[bold blue],
  'colorize_warn' => q[bold red],
  'commandnumber_in_prompt' => q[0],



( run in 0.755 second using v1.01-cache-2.11-cpan-39bf76dae61 )