File Coverage

Build
Criterion Covered Total %
statement 1 1 100.0
branch n/a
condition n/a
subroutine n/a
pod n/a
total 1 1 100.0


line stmt bran cond sub pod time code
1             #! /usr/local/bin/perl
2             # MD5: 873538fe50c94f2fa500692513e4b01d
3              
4 1         64198 $Build_PL::ARGV = []
5             ;
6              
7             # line 1 "Build.PL"
8             #!/usr/bin/perl -w
9             # -*- perl -*-
10              
11             #
12             # Author: Slaven Rezic
13             #
14             # Copyright (C) 2017,2018,2019,2020,2021,2022,2023,2024,2025 Slaven Rezic. All rights reserved.
15             # This program is free software; you can redistribute it and/or
16             # modify it under the same terms as Perl itself.
17             #
18             # WWW: https://github.com/eserte/Doit
19             #
20              
21             use strict;
22             use FindBin;
23             use lib "$FindBin::RealBin/lib";
24              
25             use Cwd qw(realpath getcwd);
26             use Digest::MD5 qw(md5_hex);
27             use File::Basename;
28             use Doit;
29             use Doit::Log;
30             use Doit::Util qw(in_directory get_os_release);
31              
32             my $doit = Doit->init;
33              
34             my $Build_PL_file_contents = do {
35             open my $fh, '<', 'Build.PL'
36             or error "Error opening Build.PL: $!";
37             local $/ = undef;
38             <$fh>;
39             };
40             my $Build_PL_md5hex = md5_hex $Build_PL_file_contents;
41              
42             for my $argv_i (0 .. $#ARGV) {
43             my $argv = $ARGV[$argv_i];
44             if ($argv =~ /^[^-].*=.*/ && ($argv_i == 0 || $ARGV[$argv_i-1] !~ /^--?config$/)) { # looks like oldfashioned option without leading dash (e.g. "installdirs=vendor"), but not if following --config
45             $argv =~ s/^/--/;
46             }
47             }
48              
49             my %action_with_arg_handling;
50              
51             if (basename($0) eq 'Build.PL') {
52             _Build_PL_mode();
53             }
54              
55             # Check if Build is up-to-date (md5 check, no timestamp check)
56             {
57             open my $fh, '<', $0
58             or error "Can't open $0: $!";
59             my $shebang = <$fh>;
60             my $md5_line = <$fh>;
61             if (my($old_md5hex) = $md5_line =~ m{^# MD5: (\S+)}) {
62             if ($old_md5hex ne $Build_PL_md5hex) {
63             my $perl;
64             if (($perl) = $shebang =~ m{^#!\s*(.*)}) {
65             # parsed it
66             } else {
67             warning "Cannot parse perl interpreter path out of '$shebang', fallback to 'perl'";
68             $perl = "perl";
69             }
70             error "Build.PL changed, please run '$perl Build.PL' again";
71             }
72             } else {
73             error "Unexpected: no MD5 found in '$md5_line'";
74             }
75             }
76              
77             require Getopt::Long;
78             my %opt = (verbose => 0, uninst => 0, destdir => '', create_packlist => 1);
79             $Build_PL::ARGV=$Build_PL::ARGV if 0; # cease -w
80             @ARGV = (@$Build_PL::ARGV, @ARGV);
81             Getopt::Long::GetOptions(
82             \%opt,
83             'allow_mb_mismatch=i',
84             'config=s%',
85             'create_packlist=i',
86             'destdir=s',
87             'installdirs=s',
88             'install_base=s',
89             'install_path=s%',
90             'jobs=i',
91             'prefix=s',
92             'pureperl-only:1',
93             'uninst:1',
94             'verbose:1',
95             'versionlib=s',
96             'version=s',
97             # The following are just ignored, but currently set by cover
98             'extra_compiler_flags=s',
99             'extra_linker_flags=s',
100             )
101             or error "usage: $0 [options]";
102              
103             my $action = shift || 'build';
104             $action =~ s/-/_/g;
105             if (!$action_with_arg_handling{$action}) {
106             if (@ARGV) {
107             error "No arguments allowed for action '$action'";
108             }
109             }
110              
111             tie my %Config, 'CustomConfig', $opt{config};
112              
113             {
114             no strict 'refs';
115             &$action;
116             }
117              
118             sub build {
119             build_libs();
120             manifypods();
121             }
122              
123             sub build_libs {
124             $doit->make_path('blib/lib');
125             $doit->make_path('blib/arch'); # not used, but keep ExtUtils::Install quiet
126             require File::Find;
127             my @pm_files;
128             File::Find::find(sub { no warnings 'once'; push @pm_files, $File::Find::name if /\.(pm|pod)$/ && -f $_ }, "lib");
129             my %seen_blib;
130             for my $file (@pm_files) {
131             my $dest = 'blib/'.$file;
132             if (!-e $dest || -M $dest > -M $file) {
133             $doit->make_path(dirname($dest));
134             $doit->copy($file, $dest);
135             }
136             $seen_blib{$dest} = 1;
137             }
138             File::Find::find
139             ({
140             wanted => sub {
141             if (-f $_ && !$seen_blib{$File::Find::name}) {
142             warning "Stray file $File::Find::name found, removing...";
143             $doit->unlink($File::Find::name);
144             }
145             },
146             no_chdir => 1,
147             },
148             "blib/lib", "blib/arch",
149             );
150             }
151              
152             sub manifypods {
153             # Handles only Pods in .pod files (which is the case in the Doit distribution)
154             require File::Find;
155             require File::Glob;
156             require Pod::Man;
157             my $mansep = $^O =~ m{^(MSWin32|cygwin)$} ? '.' : '::';
158             my %seen_blib_man;
159             File::Find::find
160             ({
161             wanted => sub {
162             if (-f $_ && /\.pod$/) {
163             no warnings 'once';
164             my $pod = $File::Find::name;
165             my $section = 3; # no scripts yet, so we can hardcode here
166             my %options = (section => $section);
167             (my $man = $pod) =~ s{^lib.}{};
168             $man =~ s{/+}{$mansep}g;
169             $man =~ s{\.pod$}{.$Config{"man${section}ext"}};
170             $man = "blib/man$section/$man";
171             if (!-e $man || -M $man > -M $pod || -M $man > -M "Build") {
172             $doit->make_path(dirname($man));
173             my $parser = Pod::Man->new(%options);
174             if ($doit->is_dry_run) {
175             info "$pod -> $man (dry-run)";
176             } else {
177             info "$pod -> $man";
178             $parser->parse_from_file($pod, $man)
179             or error "Could not install $man";
180             }
181             $doit->chmod(0644, $man); # XXX should this be changeable? like $PERM_RW in Makefile.PL?
182             }
183             $seen_blib_man{$man} = 1;
184             }
185             },
186             no_chdir => 1,
187             },
188             "lib");
189             File::Find::find
190             ({
191             wanted => sub {
192             if (-f $_ && !$seen_blib_man{$File::Find::name}) {
193             warning "Stray file $File::Find::name found, removing...";
194             $doit->unlink($File::Find::name);
195             }
196             },
197             no_chdir => 1,
198             },
199             File::Glob::bsd_glob("blib/man*")
200             );
201             }
202              
203             sub clean {
204             $doit->remove_tree('blib');
205             }
206              
207             sub realclean { &clean }
208              
209             BEGIN { $action_with_arg_handling{'test'} = 1 }
210             sub test {
211             my $usage = sub { error(($_[0] ? "$_[0]\n" : "") . "usage: $0 test -- [--no-build] [file...]") };
212             my $do_build = 1;
213             Getopt::Long::GetOptions(
214             'build!' => \$do_build,
215             ) or $usage->();
216             my @test_files = @ARGV; @ARGV = ();
217              
218             build() if $do_build;
219              
220             local $ENV{PERL_DL_NONLAZY} = 1;
221             require File::Glob;
222             require File::Spec;
223             local $ENV{HARNESS_OPTIONS} = $ENV{HARNESS_OPTIONS};
224             if ($opt{jobs} && (!defined $ENV{HARNESS_OPTIONS} || $ENV{HARNESS_OPTIONS} !~ /(^|:)j\d+($|:)/)) {
225             $doit->setenv(HARNESS_OPTIONS => (defined $ENV{HARNESS_OPTIONS} && $ENV{HARNESS_OPTIONS} ne '' ? $ENV{HARNESS_OPTIONS} . ':' : '') . "j$opt{jobs}")
226             }
227             if (0) {
228             $doit->system(_prove_path(), '-b', 't', @test_files); # use right perl?
229             } else {
230             if (!@test_files) {
231             @test_files = sort { lc $a cmp lc $b } File::Glob::bsd_glob(File::Spec->catfile("t", "*.t"));
232             }
233             if (0) {
234             $doit->system($^X, "-MExtUtils::Command::MM", "-MTest::Harness", "-e", 'undef *Test::Harness::Switches; test_harness(0, "blib/lib", "blib/arch")', @test_files);
235             } else {
236             require Test::Harness;
237             if ($doit->is_dry_run) {
238             info "test @test_files (dry-run)";
239             } else {
240             local @INC = @INC;
241             unshift @INC, map { File::Spec->rel2abs($_) } qw(blib/lib blib/arch);
242             info "test @test_files";
243             Test::Harness::runtests(@test_files);
244             }
245             }
246             }
247             }
248              
249             sub test_xt {
250             $doit->system(_prove_path(), '-b', ($opt{jobs} ? ('--jobs', $opt{jobs}) : ()), 'xt'); # use right perl?
251             }
252              
253             sub test_installed {
254             my $t_dir = "$FindBin::RealBin/t";
255             chdir "/"
256             or error "Cannot chdir to /: $!";
257             $doit->system(_prove_path(), $t_dir);
258             }
259              
260             BEGIN { $action_with_arg_handling{'test_in_docker'} = 1 }
261             sub test_in_docker {
262             my $usage = sub { error(($_[0] ? "$_[0]\n" : "") . "usage: $0 test_in_docker -- [--more-testing] dockerimage:tag") };
263             Getopt::Long::GetOptions(
264             "more-testing!" => \my $more_testing,
265             "invalidate-cache!" => \my $do_invalidate_cache,
266             ) or $usage->();
267             my $distro_spec = shift @ARGV or $usage->("dockerimage:tag missing");
268             @ARGV and $usage->("too many arguments");
269             if (!$distro_spec || $distro_spec !~ m{^.*:.*$}) {
270             $usage->("Please set dockerimage:tag argument to something like 'debian:bookworm'");
271             }
272             Doit::Log::set_label("[$distro_spec" . ($more_testing ? " (more)" : "") . "]");
273             for my $tool (qw(docker)) {
274             $doit->which($tool) or error "$tool is missing";
275             }
276             require File::Temp;
277             my $dir = File::Temp::tempdir("Doit_test_in_docker_XXXXXXXX", TMPDIR => 1, CLEANUP => 1);
278             my $dockerfile = <<"EOF";
279             FROM $distro_spec
280              
281             EOF
282             if ($do_invalidate_cache) {
283             _add_Dockerfile_invalidate_cmd(\$dockerfile);
284             }
285              
286             my $add_opts = {};
287             $dockerfile .= _fix_Dockerfile_for_dist($distro_spec, $add_opts);
288             _docker_add_distro_specific_files(dir => $dir, distro_spec => $distro_spec);
289             if (-e "$dir/.distro_support") {
290             $dockerfile .= <<'EOF';
291             COPY .distro_support .distro_support
292             RUN .distro_support/run.sh
293             EOF
294             }
295              
296             if ($distro_spec =~ m{^(centos|rockylinux|fedora):}) {
297             ######################################################################
298             # CentOS
299             # From https://fedoraproject.org/wiki/EPEL/FAQ#How_can_I_install_the_packages_from_the_EPEL_software_repository.3F
300             if ($distro_spec eq 'centos:6') {
301             $dockerfile .= <<"EOF";
302             RUN rpm -Uvh https://dl.fedoraproject.org/pub/archive/epel/6/x86_64/epel-release-6-8.noarch.rpm
303             EOF
304             } elsif ($distro_spec eq 'centos:7') {
305             $dockerfile .= <<"EOF";
306             RUN rpm -Uvh https://dl.fedoraproject.org/pub/archive/epel/7/x86_64/Packages/e/epel-release-7-14.noarch.rpm
307             EOF
308             } elsif ($distro_spec eq 'rockylinux:8') {
309             $dockerfile .= <<"EOF";
310             RUN yum -y install epel-release
311             EOF
312             } else {
313             info "No EPEL support for $distro_spec";
314             }
315             $dockerfile .= <<"EOF";
316             RUN yum -y install \\
317             sudo \\
318             rsync \\
319             "perl(Digest::MD5)" \\
320             "perl(ExtUtils::MakeMaker)" \\
321             "perl(FindBin)" \\
322             "perl(Hash::Util)" \\
323             "perl(Sys::Hostname)" \\
324             "perl(Test::More)" \\
325             "perl(Tie::File)" \\
326             make \\
327             git
328              
329             EOF
330             if ($distro_spec eq 'centos:8') {
331             # Requires also PowerTools repo additionally to EPEL
332             $dockerfile .= <<"EOF";
333             RUN dnf -y install 'dnf-command(config-manager)'
334             RUN yum config-manager --set-enabled PowerTools
335             EOF
336             } elsif ($distro_spec eq 'rockylinux:8') {
337             $dockerfile .= <<"EOF";
338             RUN dnf -y --enablerepo=powertools install perl-IPC-Run
339             EOF
340             } elsif ($distro_spec eq 'rockylinux:9') {
341             $dockerfile .= <<"EOF";
342             RUN dnf -y --enablerepo=crb install perl-IPC-Run
343             EOF
344             }
345             $dockerfile .= <<"EOF";
346             RUN yum -y install \\
347             "perl(IPC::Run)"
348             EOF
349             if ($distro_spec ne 'centos:8' && $distro_spec ne 'rockylinux:8' && $distro_spec ne 'rockylinux:9') {
350             # Currently not available in CentOS8 and Rocky
351             $dockerfile .= <<"EOF";
352             RUN yum -y install \\
353             "perl(Net::OpenSSH)"
354             EOF
355             }
356             if ($distro_spec eq 'centos:6') {
357             $dockerfile .= <<"EOF";
358             RUN curl -L https://cpanmin.us | perl - App::cpanminus
359              
360             # Not available as an RPM
361             RUN cpanm --quiet --notest CPAN::Meta
362             EOF
363             } else {
364             $dockerfile .= <<"EOF";
365             RUN yum -y install \\
366             "perl(CPAN::Meta)"
367             EOF
368             }
369             if ($more_testing) {
370             $dockerfile .= <<"EOF";
371             RUN yum -y install \\
372             "perl(Capture::Tiny)" \\
373             "perl(Term::ANSIColor)" \\
374             "perl(BSD::Resource)" \\
375             "perl(CPAN::Meta::Validator)" \\
376             "perl(LWP::UserAgent)" \\
377             "perl(Config::IniFiles)" \\
378             file
379              
380             ENV GITHUB_ACTIONS 1
381             ENV DOIT_TEST_WITH_SUDO 1
382              
383             EOF
384             if ($distro_spec eq 'centos:6') {
385             $dockerfile .= <<"EOF";
386             # Term::ANSIColor exists, but is too old (no colorstrip function)
387             RUN cpanm --quiet --notest "Term::ANSIColor~>=2.01"
388              
389             RUN yum -y install python34-pip
390              
391             EOF
392             } else {
393             $dockerfile .= <<"EOF";
394             RUN yum -y install python3-pip
395             EOF
396             }
397             }
398             } elsif ($distro_spec =~ m{^alpine(:|$)}) {
399             $dockerfile .= <<"EOF";
400             RUN apk update
401             # perl-utils for prove
402             # shadow for useradd
403             RUN apk add sudo rsync git perl perl-utils make shadow
404             EOF
405             if ($more_testing) {
406             $dockerfile .= <<"EOF";
407             RUN apk add py3-pip
408              
409             #ENV GITHUB_ACTIONS 1
410             ENV DOIT_TEST_WITH_SUDO 1
411              
412             EOF
413             }
414              
415             } else {
416             ######################################################################
417             # Debian-like (Debian, Ubuntu, LinuxMint ...)
418             my $packages_spec;
419             {
420             my @packages;
421             {
422             push @packages, (
423             'libipc-run-perl',
424             ($distro_spec eq 'ubuntu:precise' ? () : 'libnet-openssh-perl'),
425             'sudo',
426             'rsync',
427             'git',
428             );
429             }
430             if ($more_testing) {
431             push @packages, (
432             'libbsd-resource-perl',
433             'libcapture-tiny-perl',
434             'libconfig-inifiles-perl',
435             'libcpan-meta-perl',
436             'libdevel-hide-perl',
437             'libwww-perl',
438             'perl-modules',
439             'locales',
440             'file',
441             ($distro_spec eq 'ubuntu:precise' ? () : 'python3-pip'),
442             );
443             }
444             $packages_spec = join(" \\\n", map { " $_" } @packages);
445             }
446              
447             if ($distro_spec =~ m{^(ubuntu:precise)$}) {
448             # See https://gist.github.com/dergachev/f5da514802fcbbb441a1
449             $dockerfile .= <<'EOF'
450             RUN sed -i.bak -r 's/(archive|security).ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list
451             EOF
452             }
453             $dockerfile .= <<"EOF";
454             ENV DEBIAN_FRONTEND noninteractive
455              
456             RUN apt-get$add_opts->{apt_get_update_opts} update && apt-get install -y --no-install-recommends$add_opts->{apt_get_install_opts} \\
457             $packages_spec
458              
459             EOF
460             if ($more_testing) {
461             $dockerfile .= <<"EOF";
462             ENV GITHUB_ACTIONS 1
463             ENV DOIT_TEST_WITH_SUDO 1
464              
465             EOF
466             }
467             }
468              
469             for my $env_key (qw(HARNESS_OPTIONS)) {
470             if (defined $ENV{$env_key}) {
471             $dockerfile .= <<"EOF";
472             ENV $env_key "$ENV{$env_key}"
473             EOF
474             }
475             }
476              
477             $doit->write_binary("$dir/Dockerfile", $dockerfile);
478             in_directory {
479             (my $tag = $distro_spec) =~ s{:}{-}g;
480             $tag = 'doit-test-' . $tag;
481             $doit->system(qw(docker build -t), $tag, qw(.));
482             $doit->system(
483             qw(docker run), '-v', "$FindBin::RealBin:/data:ro", $tag,
484             'sh', '-c', 'rsync -a --no-owner --no-group --exclude=blib "--exclude=Doit-*.tar.gz" /data/ /tmp/Doit/ && cd /tmp/Doit && perl Build.PL && ./Build && ./Build generate_META_json && ./Build test && ./Build test_xt && ./Build dist_install_and_test'
485             );
486             } $dir;
487              
488             Doit::Log::set_label(undef);
489             }
490              
491             BEGIN { $action_with_arg_handling{'test_package_in_docker'} = 1 }
492             sub test_package_in_docker { # XXX Currently only support for deb packages
493             my $usage = sub { error(($_[0] ? "$_[0]\n" : "") . "usage: $0 test_package_in_docker -- --package package.deb dockerimage:tag") };
494             Getopt::Long::GetOptions(
495             "package=s" => \my $package,
496             ) or $usage->();
497             $package or $usage->("--package is mandatory");
498             my $distro_spec = shift @ARGV or $usage->("dockerimage:tag missing");
499             @ARGV and $usage->("too many arguments");
500             if (!$distro_spec || $distro_spec !~ m{^.*:.*$}) {
501             $usage->("Please set dockerimage:tag argument to something like 'debian:bookworm'");
502             }
503             Doit::Log::set_label("[$distro_spec]");
504             for my $tool (qw(docker)) {
505             $doit->which($tool) or error "$tool is missing";
506             }
507             require File::Temp;
508             my $dir = File::Temp::tempdir("Doit_test_in_docker_XXXXXXXX", TMPDIR => 1, CLEANUP => 1);
509             $doit->copy($package, $dir);
510             my $package_basename = basename($package);
511             my $dockerfile = <<"EOF";
512             FROM $distro_spec
513              
514             COPY $package_basename /tmp/$package_basename
515             EOF
516             my $add_opts = {};
517             $dockerfile .= _fix_Dockerfile_for_dist($distro_spec, $add_opts);
518              
519             $dockerfile .= <<"EOF";
520             ENV DEBIAN_FRONTEND noninteractive
521             RUN apt-get$add_opts->{apt_get_update_opts} update && \\
522             apt-get install -y --no-install-recommends$add_opts->{apt_get_install_opts} /tmp/$package_basename && \\
523             apt-get install -y --no-install-recommends$add_opts->{apt_get_install_opts} rsync
524             EOF
525              
526             $doit->write_binary("$dir/Dockerfile", $dockerfile);
527             in_directory {
528             $doit->system(qw(docker build -t doit-test .));
529             $doit->system(
530             qw(docker run), '-v', "$FindBin::RealBin:/data:ro", 'doit-test',
531             'sh', '-c', 'rsync -a --no-owner --no-group --exclude=blib "--exclude=Doit-*.tar.gz" /data/ /tmp/Doit/ && cd /tmp/Doit && perl Build.PL && ./Build test'
532             );
533             } $dir;
534              
535             Doit::Log::set_label(undef);
536             }
537              
538             # Run docker-based tests on a number of Linux distributions:
539             # the stable ones of Debian, Ubuntu and CentOS, and some
540             # older versions of these distributions.
541             sub test_standard {
542             Doit::Log::set_label("[local]");
543             $doit->system($^X, 'Build.PL');
544             $doit->system('./Build');
545             $doit->system('./Build', 'test');
546             Doit::Log::set_label(undef);
547             my @results;
548             for my $distro_spec ('debian:bookworm', 'ubuntu:jammy', 'debian:bullseye', 'ubuntu:focal', 'debian:buster', 'ubuntu:bionic', 'centos:7', 'debian:stretch', 'debian:jessie', 'ubuntu:xenial', 'ubuntu:precise', 'centos:6', 'alpine:latest') {
549             for my $more_testing (0, 1) {
550             my $t0 = time; # currently not hires, should be fine for now
551             $doit->system('./Build', 'test_in_docker', '--', ($more_testing ? '--more-testing' : ()), $distro_spec);
552             my $t1 = time;
553             push @results, { distro => $distro_spec, more_testing => $more_testing, result => 'pass', time => ($t1-$t0)."s" };
554             }
555             }
556              
557             my $report = "test_standard results\n";
558             my @cols = qw(distro more_testing result time);
559             my @col_widths;
560             for my $col (@cols) {
561             my $max_width;
562             for my $result (@results) {
563             my $width = length $result->{$col};
564             $max_width = $width if !defined $max_width || $max_width < $width;
565             }
566             push @col_widths, $max_width;
567             }
568             for my $result (@results) {
569             for my $col_i (0 .. $#cols) {
570             $report .= sprintf "%-${col_widths[$col_i]}s ", $result->{$cols[$col_i]};
571             }
572             $report .= "\n";
573             }
574             info $report;
575             }
576              
577             sub test_kwalitee (;$) {
578             my($distdir) = @_;
579             # If cwd is used then more tests fail,
580             # because META files are not available here.
581             $distdir = $FindBin::RealBin if !defined $distdir;
582              
583             if (eval { require Test::Kwalitee; 1 }) {
584             in_directory {
585             local $ENV{RELEASE_TESTING} = 1;
586             eval { $doit->system($^X, '-MTest::More', '-MTest::Kwalitee=kwalitee_ok', '-e', 'kwalitee_ok(qw(-has_manifest -has_meta_yml)); done_testing') };
587             } $distdir;
588             } else {
589             warning "Test::Kwalitee is not installed";
590             }
591             }
592              
593             sub test_pod (;$) {
594             my($distdir) = @_;
595             $distdir = $FindBin::RealBin if !defined $distdir;
596              
597             if (eval { require Test::Pod; 1 }) {
598             in_directory {
599             eval { $doit->system($^X, '-MTest::Pod', '-e', 'all_pod_files_ok()') };
600             } $distdir;
601             } else {
602             warning "Test::Pod is not installed";
603             }
604             }
605              
606             sub test_cpan_versions {
607             my($distdir) = @_;
608              
609             if ($doit->which("cpan_check_versions")) {
610             in_directory {
611             $doit->system('cpan_check_versions', '-remote');
612             } $distdir;
613             } else {
614             warning <
615             No cpan_check_versions found in PATH, cannot check for properly
616             incremented VERSIONs. Please check git://github.com/eserte/srezic-misc.git
617             EOF
618             }
619             }
620              
621             sub git_checks {
622             _check_clean_git();
623              
624             my $Doit_VERSION = _get_Doit_VERSION();
625              
626             my $out = $doit->info_qx(qw(git tag -l), $Doit_VERSION);
627             if (defined $out && $out ne '') {
628             error "A git tag $Doit_VERSION already exists";
629             }
630             }
631              
632             sub distdir {
633             my(%options) = @_;
634             my $temporary = delete $options{temporary};
635             error "Unhandled options: " . join(" ", %options) if %options;
636              
637             require File::Temp;
638             my $tempdir = File::Temp::tempdir("Doit_distdir_XXXXXXXX", TMPDIR => 1, CLEANUP => $temporary);
639             my $Doit_VERSION = _get_Doit_VERSION();
640             my $distdir = "$tempdir/Doit-$Doit_VERSION";
641             $doit->mkdir($distdir);
642             for my $line (split /\0/, $doit->info_qx({quiet=>1}, 'git', 'ls-files', '-z')) {
643             next if ($line =~ m{^( \.travis\.yml
644             | \.?appveyor\.yml
645             | \.github/.*
646             | \.gitignore
647             )$}x);
648             # XXX maybe implement also MANIFEST.SKIP?
649             my $dirname = dirname $line;
650             if ($dirname ne '.') {
651             $doit->make_path("$distdir/$dirname");
652             }
653             $doit->copy($line, "$distdir/$dirname/");
654             }
655              
656             generate_META_json("$distdir/META.json");
657             generate_META_yml("$distdir/META.yml" );
658              
659             test_kwalitee($distdir);
660             test_pod($distdir);
661              
662             # If not temporary, then it is assumed that the
663             # interactive distdir action is called, and
664             # provide information to the user.
665             if (!$temporary) {
666             info "Distribution directory is $distdir";
667             }
668              
669             $distdir;
670             }
671              
672             sub dist {
673             if (!$ENV{DOIT_TEST_SKIP_SOME_CHECKS}) {
674             git_checks();
675             }
676             # else skip because Doit version is usually not incremented in
677             # this phase and the git tag already exists
678              
679              
680             my $tarfile = _get_tarfilename();
681             if (-e $tarfile) {
682             error "$tarfile already exists";
683             }
684             my $distdir = distdir(temporary => 1);
685              
686             if (!$ENV{DOIT_TEST_SKIP_SOME_CHECKS}) {
687             test_cpan_versions($distdir);
688             }
689             # else because versions of all Doit modules are usually not
690             # incremented in this phase
691              
692             in_directory {
693             # Note: tar cfvz C:/... ... does not seem to work on Windows
694             # (for all or some tar versions?). Error message is:
695             # tar (child): Cannot connect to C: resolve failed
696             # So create in cwd first, and move to final location.
697             $doit->system('tar', 'cfvz', basename($tarfile), basename($distdir));
698             $doit->move(basename($tarfile), $tarfile);
699             } "$distdir/..";
700             }
701              
702             sub dist_install_and_test {
703             local $ENV{DOIT_TEST_SKIP_SOME_CHECKS} = 1; # XXX is there a better interface?
704             my $tarfile = _get_tarfilename();
705             if (!-e $tarfile) {
706             dist();
707             }
708             require File::Temp;
709             my $dir = File::Temp::tempdir("Doit_dist_install_and_test_XXXXXXXX", TMPDIR => 1, CLEANUP => 1);
710             my $Doit_VERSION = _get_Doit_VERSION();
711             in_directory {
712             # Note: see above for tar under Windows problems
713             $doit->move($tarfile, basename($tarfile));
714             $doit->system('tar', 'xfz', basename($tarfile));
715             in_directory {
716             $doit->system($^X, 'Build.PL');
717             my @Build = $^O eq 'MSWin32' ? ($^X, 'Build') : ('./Build');
718             my @sudo = $^O eq 'MSWin32' ? () : ('sudo');
719             $doit->system(@Build);
720             $doit->system(@Build, 'test');
721             $doit->system(@sudo, @Build, 'install');
722             $doit->system(@Build, 'test-installed');
723             } "Doit-$Doit_VERSION";
724             } $dir;
725             }
726              
727             sub dist_install_with_cpanm {
728             my $tarfile = _get_tarfilename();
729             if (!-e $tarfile) {
730             dist();
731             }
732             $doit->system('cpanm', $tarfile);
733             test_installed();
734             }
735              
736             sub look {
737             my $distdir = distdir(temporary => 1);
738             in_directory {
739             info "Spawning $ENV{SHELL} in $distdir...";
740             local $ENV{DOIT_BUILD_SHELL_LEVEL} = $ENV{DOIT_BUILD_SHELL_LEVEL};
741             $ENV{DOIT_BUILD_SHELL_LEVEL}++;
742             $doit->system($ENV{SHELL});
743             } $distdir;
744             }
745              
746             BEGIN { $action_with_arg_handling{'cover'} = 1 }
747             sub cover {
748             my(@files) = @ARGV;
749             if (@files) {
750             for my $file (@files) {
751             $doit->system($^X, '-MDevel::Cover', $file);
752             }
753             } else {
754             $doit->system(_cover_path(), '--test');
755             }
756             $doit->unlink('MYMETA.json.lock'); # https://github.com/pjcj/Devel--Cover/issues/263
757             }
758              
759             BEGIN { $action_with_arg_handling{'show_cover'} = 1 }
760             sub show_cover {
761             Getopt::Long::GetOptions(
762             'show-only' => \my $show_only,
763             ) or error "usage: $0 show_cover -- --show-only [file ...]";
764             my @files = @ARGV;
765             if (@files && $show_only) { error "Cannot specify --show-only together with list of files" }
766             if ($show_only) {
767             $doit->system(_cover_path());
768             } else {
769             cover(@files);
770             }
771             my $browser = $^O eq 'darwin' ? 'open' : $doit->which('xdg-open') ? 'xdg-open' : 'firefox';
772             $doit->system($browser, "$FindBin::RealBin/cover_db/coverage.html");
773             }
774              
775             sub install {
776             build();
777             # XXX check if test suite was run?
778              
779             # MOD_INSTALL = $(ABSPERLRUN) -MExtUtils::Install -e 'install([ from_to => {@ARGV}, verbose => '\''$(VERBINST)'\'', uninstall_shadows => '\''$(UNINST)'\'', dir_mode => '\''$(PERM_DIR)'\'' ]);' --
780             # $(MOD_INSTALL) \
781              
782             # for a perl install:
783             # read "$(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist" \
784             # write "$(DESTINSTALLARCHLIB)/auto/$(FULLEXT)/.packlist" \
785             # "$(INST_LIB)" "$(DESTINSTALLPRIVLIB)" \
786             # "$(INST_ARCHLIB)" "$(DESTINSTALLARCHLIB)" \
787             # "$(INST_BIN)" "$(DESTINSTALLBIN)" \
788             # "$(INST_SCRIPT)" "$(DESTINSTALLSCRIPT)" \
789             # "$(INST_MAN1DIR)" "$(DESTINSTALLMAN1DIR)" \
790             # "$(INST_MAN3DIR)" "$(DESTINSTALLMAN3DIR)"
791              
792             # for a site install:
793             # read "$(SITEARCHEXP)/auto/$(FULLEXT)/.packlist" \
794             # write "$(DESTINSTALLSITEARCH)/auto/$(FULLEXT)/.packlist" \
795             # "$(INST_LIB)" "$(DESTINSTALLSITELIB)" \
796             # "$(INST_ARCHLIB)" "$(DESTINSTALLSITEARCH)" \
797             # "$(INST_BIN)" "$(DESTINSTALLSITEBIN)" \
798             # "$(INST_SCRIPT)" "$(DESTINSTALLSITESCRIPT)" \
799             # "$(INST_MAN1DIR)" "$(DESTINSTALLSITEMAN1DIR)" \
800             # "$(INST_MAN3DIR)" "$(DESTINSTALLSITEMAN3DIR)"
801            
802             require Data::Dumper;
803             require ExtUtils::Install;
804             my $FULLEXT = 'Doit';
805             my $INST_LIB = 'blib/lib';
806             my $INST_ARCHLIB = 'blib/arch';
807             my $INST_BIN = 'blib/bin';
808             my $INST_SCRIPT = 'blib/script';
809             my $INST_MAN1DIR = 'blib/man1';
810             my $INST_MAN3DIR = 'blib/man3';
811             my $PERM_DIR = '755';
812             my @euii_args = (
813             from_to => {
814             (
815             $opt{install_base}
816             ?
817             (
818             read => "$Config{sitearchexp}/auto/$FULLEXT/.packlist",
819             ($opt{create_packlist} ? (write => "$opt{install_base}/lib/perl5/$Config{archname}/auto/$FULLEXT/.packlist") : ()),
820             $INST_LIB => "$opt{install_base}/lib/perl5",
821             $INST_ARCHLIB => "$opt{install_base}/lib/perl5",
822             $INST_BIN => "$opt{install_base}/bin",
823             $INST_SCRIPT => "$opt{install_base}/bin",
824             $INST_MAN1DIR => "$opt{install_base}/man/man1",
825             $INST_MAN3DIR => "$opt{install_base}/man/man3",
826             )
827             :
828             ($opt{installdirs}||'') eq 'core'
829             ?
830             (
831             read => "$Config{archlib}/auto/$FULLEXT/.packlist",
832             ($opt{create_packlist} ? (write => "$opt{destdir}$Config{installarchlib}/auto/$FULLEXT/.packlist") : ()),
833             $INST_LIB => "$opt{destdir}$Config{installprivlib}",
834             $INST_ARCHLIB => "$opt{destdir}$Config{installarchlib}",
835             $INST_BIN => "$opt{destdir}$Config{installbin}",
836             $INST_SCRIPT => "$opt{destdir}$Config{installscript}",
837             ($Config{installman1dir} ? ($INST_MAN1DIR => "$opt{destdir}$Config{installman1dir}") : ()),
838             ($Config{installman3dir} ? ($INST_MAN3DIR => "$opt{destdir}$Config{installman3dir}") : ()),
839             )
840             :
841             ($opt{installdirs}||'') eq 'vendor'
842             ?
843             (
844             read => "$Config{vendorlib}/auto/$FULLEXT/.packlist",
845             ($opt{create_packlist} ? (write => "$opt{destdir}$Config{installvendorarch}/auto/$FULLEXT/.packlist") : ()),
846             $INST_LIB => "$opt{destdir}$Config{installvendorlib}",
847             $INST_ARCHLIB => "$opt{destdir}$Config{installvendorarch}",
848             $INST_BIN => "$opt{destdir}$Config{installvendorbin}",
849             $INST_SCRIPT => "$opt{destdir}$Config{installvendorscript}",
850             ($Config{installvendorman1dir} ? ($INST_MAN1DIR => "$opt{destdir}$Config{installvendorman1dir}") : ()),
851             ($Config{installvendorman3dir} ? ($INST_MAN3DIR => "$opt{destdir}$Config{installvendorman3dir}") : ()),
852             )
853             : # default is site
854             (
855             read => "$Config{sitearchexp}/auto/$FULLEXT/.packlist",
856             ($opt{create_packlist} ? (write => "$opt{destdir}$Config{installsitearch}/auto/$FULLEXT/.packlist") : ()),
857             $INST_LIB => "$opt{destdir}$Config{installsitelib}",
858             $INST_ARCHLIB => "$opt{destdir}$Config{installsitearch}",
859             $INST_BIN => "$opt{destdir}$Config{installsitebin}",
860             $INST_SCRIPT => "$opt{destdir}$Config{installsitescript}",
861             ($Config{installsiteman1dir} ? ($INST_MAN1DIR => "$opt{destdir}$Config{installsiteman1dir}") : ()),
862             ($Config{installsiteman3dir} ? ($INST_MAN3DIR => "$opt{destdir}$Config{installsiteman3dir}") : ()),
863             )
864             )
865             },
866             verbose => $opt{verbose},
867             uninstall_shadows => $opt{uninst},
868             dir_mode => $PERM_DIR,
869             dry_run => $doit->is_dry_run,
870             );
871             my $euii_args_dump = Data::Dumper->new([{ @euii_args }],[qw()])->Indent(1)->Useqq(1)->Sortkeys(1)->Terse(1)->Dump;
872             info "Run ExtUtils::Install::install with parameters\n" . $euii_args_dump;
873             ExtUtils::Install::install([@euii_args]);
874             if ($doit->is_dry_run) {
875             info "(this was dry-run mode)";
876             }
877             }
878              
879             {
880             my $Doit_VERSION;
881              
882             sub _get_Doit_VERSION () {
883             return $Doit_VERSION if defined $Doit_VERSION;
884              
885             {
886             my $Doit_pm = $INC{"Doit.pm"};
887             open my $fh, $Doit_pm or die "Cannot open $Doit_pm: $!";
888             while(<$fh>) {
889             if (/\$VERSION\s*=\s*'(.*)'/) {
890             $Doit_VERSION = $1;
891             last;
892             }
893             }
894             if (!defined $Doit_VERSION) {
895             error "Fatal: Cannot find \$VERSION in $Doit_pm";
896             }
897             }
898             if ($Doit_VERSION !~ m{^\d+\.[\d_]+$}) {
899             error "Doit.pm VERSION $Doit_VERSION does not look as expected";
900             }
901             # Check also for numerical value of version
902             if (!defined $Doit::VERSION) {
903             error "Unexpected: cannot find \$VERSION in loaded Doit.pm";
904             }
905             {
906             (my $Doit_numerical_version = $Doit_VERSION) =~ s{_}{}g;
907             if ($Doit_numerical_version != $Doit::VERSION) {
908             error "Unexpected: parsed version $Doit_VERSION is not numerically equal to loaded version $Doit::VERSION";
909             }
910             }
911             $Doit_VERSION;
912             }
913             }
914              
915             sub _get_tarfilename () {
916             my $Doit_VERSION = _get_Doit_VERSION();
917             getcwd . "/Doit-" . $Doit_VERSION . ".tar.gz";
918             }
919              
920             sub _generate_META ($;$) {
921             my($destfile, $meta_version) = @_;
922              
923             require CPAN::Meta;
924             require ExtUtils::MakeMaker;
925             my $meta = CPAN::Meta->new({
926             name => 'Doit',
927             author => 'Slaven Rezic ',
928             abstract => 'a scripting framework',
929             license => 'perl',
930             version => MM->parse_version("$FindBin::RealBin/lib/Doit.pm"),
931             dynamic_config => 0,
932             prereqs => {
933             configure => {
934             requires => {
935             'Digest::MD5' => 0,
936             'Exporter' => 5.57, # use Exporter "import"
937             'File::Path' => 2.0, # make_path
938             },
939             },
940             runtime => {
941             recommends => {
942             'IPC::Run' => 0,
943             'Net::OpenSSH' => 0,
944             },
945             requires => {
946             'perl' => 5.006,
947             'Exporter' => 5.57, # use Exporter "import"
948             'File::Path' => 2.07, # make_path
949             },
950             },
951             test => {
952             requires => {
953             'Test::More' => 0,
954             },
955             },
956             },
957             resources => {
958             repository => { url => 'git://github.com/eserte/Doit' },
959             },
960             generated_by => "Doit version " . _get_Doit_VERSION(),
961             });
962             if ($doit->is_dry_run) {
963             info "Would create $destfile" . (defined $meta_version ? " (version $meta_version)" : "") . " (dry-run)";
964             } else {
965             $meta->save($destfile, (defined $meta_version ? { version => $meta_version } : ()));
966             }
967             }
968              
969             sub generate_META_json (;$) {
970             my $destfile = shift || 'META.json';
971             _generate_META $destfile;
972             }
973              
974             sub generate_META_yml (;$) {
975             my $destfile = shift || 'META.yml';
976             _generate_META $destfile, 1.4;
977             }
978              
979             # XXX the debian package build functionality should go into
980             # a separate Doit component
981              
982             sub debian_package {
983             _debian_package('--depends' => 'perl, libnet-openssh-perl, libipc-run-perl', '--add-distro-version' => '1');
984             }
985              
986             sub _debian_package {
987             my(%opts) = @_;
988              
989             for my $tool (qw(dh-make-perl)) { # git is checked if needed
990             $doit->which($tool) or error "$tool is missing";
991             }
992              
993             my $distdir = distdir(temporary => 1);
994              
995             my $version = delete $opts{'--version'};
996             my $add_distro_version = delete $opts{'--add-distro-version'};
997             if (!defined $version) {
998             $version = _basic_debian_version();
999             if ($add_distro_version) {
1000             my $osr = get_os_release();
1001             if (!$osr) {
1002             error 'Cannot read /etc/os-release --- please specify --version manually';
1003             }
1004             my $dist_id = $osr->{ID};
1005             my $rel = $osr->{VERSION_ID};
1006             if ($dist_id eq 'debian') {
1007             $rel =~ s{^(\d+).*}{$1};
1008             $version .= "+deb${rel}u${add_distro_version}";
1009             } elsif ($dist_id eq 'linuxmint') {
1010             $version .= "+linuxmint${rel}u${add_distro_version}";
1011             } elsif ($dist_id eq 'ubuntu') {
1012             $version .= "~ubuntu${rel}.${add_distro_version}";
1013             } else {
1014             error "No distro version support for '$dist_id'";
1015             }
1016             }
1017             }
1018              
1019             my $deb;
1020             in_directory {
1021             $doit->system(qw(dh-make-perl --build --vcs none), (defined $version ? ('--version', $version) : ()), %opts);
1022             require File::Glob;
1023             my(@debs) = File::Glob::bsd_glob("$distdir/../*.deb");
1024             if (@debs != 1) {
1025             error "Expecting exactly one generated .deb, but got: <@debs>\n";
1026             }
1027             $deb = $debs[0];
1028             } $distdir;
1029             $doit->copy($deb, basename($deb));
1030             print basename($deb), "\n";
1031             }
1032              
1033             sub _basic_debian_version {
1034             my $version;
1035             $doit->which('git') or error 'git is missing --- please install it or alternatively specify --version manually';
1036             chomp(my $git_describe = eval { $doit->info_qx('git', 'describe') }); # XXX what if this fails? Optionally made fatal?
1037             if (defined $git_describe) {
1038             if ($git_describe =~ m{^([0-9\._]+)$}) {
1039             $version = $1;
1040             } elsif ($git_describe =~ m{^([0-9\._]+)-(\d+)-g(.*)}) {
1041             $version = $1."+git".$2."+".$3."-1"; # XXX make "1" configurable?
1042             } else {
1043             error "Cannot parse output from git describe: '$git_describe'";
1044             }
1045             $version =~ s{_}{-}; # replace "devel" version specifier
1046             } else {
1047             error "No information using git-describe available. Please specify --version manually.";
1048             }
1049             $version;
1050             }
1051              
1052             BEGIN { $action_with_arg_handling{'debian_package_with_docker'} = 1 }
1053             sub debian_package_with_docker {
1054             my $usage = sub { error(($_[0] ? "$_[0]\n" : "") . "usage: $0 debian_package_with_docker -- [--use-workdir | --tag ...] dockerimage:tag") };
1055             Getopt::Long::GetOptions(
1056             "use-workdir!" => \my $use_workdir,
1057             "tag=s" => \my $tag,
1058             ) or $usage->();
1059             $tag && $use_workdir and $usage->("Cannot use --use-workdir together with --tag");
1060             my $distro_spec = shift @ARGV or $usage->("dockerimage:tag missing");
1061             @ARGV and $usage->("too many arguments");
1062             if ($distro_spec eq 'self') {
1063             $distro_spec = join ":", @{ get_os_release() }{qw(ID VERSION_CODENAME)};
1064             }
1065             if (!$distro_spec || $distro_spec !~ m{^.*:.*$}) {
1066             $usage->("Please set dockerimage:tag argument to something like 'debian:bookworm' or 'self'");
1067             }
1068             for my $tool (qw(docker)) {
1069             $doit->which($tool) or error "$tool is missing";
1070             }
1071              
1072             # Cannot access directories outside of /Users when working
1073             # with docker-machine @ Mac, see for an explanation:
1074             # https://stackoverflow.com/questions/37673140/docker-volume-located-in-tmp-on-osx-empty
1075             my $limited_volume_availability = $^O eq 'darwin';
1076              
1077             require File::Temp;
1078             my $dir = File::Temp::tempdir("Doit_debian_package_docker_XXXXXXXX", TMPDIR => !$limited_volume_availability, CLEANUP => 1);
1079             $dir = realpath $dir; # docker volumes have to be specified with absolute paths
1080              
1081             my $pkgdir = $limited_volume_availability ? "$FindBin::RealBin/pkg" : "/tmp";
1082             if (!-d $pkgdir) {
1083             $doit->mkdir($pkgdir);
1084             }
1085              
1086             if ($use_workdir) {
1087             # no --cvs-exclude --- git-describe has to work
1088             $doit->system('rsync', '-a', '--exclude=blib', '.', $dir);
1089             } else {
1090             $doit->add_component('git');
1091             $doit->git_repo_update(
1092             repository => $FindBin::RealBin,
1093             directory => $dir,
1094             ($tag ? (branch => $tag) : ()),
1095             # no: we need the full history for correct git version calculation: clone_opts => [qw(--depth=1)],
1096             );
1097             }
1098             in_directory {
1099             my $dockerfile = <<"EOF";
1100             FROM $distro_spec
1101             EOF
1102             my $add_opts = {};
1103             $dockerfile .= _fix_Dockerfile_for_dist($distro_spec, $add_opts);
1104             $dockerfile .= <<"EOF";
1105              
1106             ENV DEBIAN_FRONTEND noninteractive
1107              
1108             RUN apt-get$add_opts->{apt_get_update_opts} update && apt-get$add_opts->{apt_get_install_opts} install -y --no-install-recommends \\
1109             dh-make-perl \\
1110             git
1111              
1112             # XXX This is depending on the current perl module and should be configurable
1113             RUN apt-get$add_opts->{apt_get_install_opts} install -y --no-install-recommends \\
1114             libipc-run-perl \\
1115             libnet-openssh-perl
1116              
1117             EOF
1118             for my $env_key (qw(DEBFULLNAME DEBEMAIL EMAIL)) {
1119             if ($ENV{$env_key}) {
1120             $dockerfile .= <<"EOF";
1121             ENV $env_key "$ENV{$env_key}"
1122             EOF
1123             }
1124             }
1125             $doit->write_binary("Dockerfile", $dockerfile);
1126              
1127             (my $label = $distro_spec) =~ s{:}{-}g;
1128             $label = "doit-deb-$label";
1129             $doit->system(qw(docker build -t), $label, qw(.));
1130             $doit->system(
1131             qw(docker run), '-v', "$dir:/data", '-v', "$pkgdir:/pkg", $label,
1132             'sh', '-c', 'git config --global --add safe.directory /data && cd /data && perl Build.PL && ./Build debian_package && cp *.deb /pkg'
1133             );
1134             } $dir;
1135             info "Package is available in $pkgdir";
1136             }
1137              
1138             sub release {
1139             my $Doit_VERSION = _get_Doit_VERSION();
1140             for my $existing_tag (split /\n/, $doit->info_qx({quiet=>1}, 'git', 'tag')) {
1141             if ($Doit_VERSION eq $existing_tag) {
1142             error "The version $Doit_VERSION is already tagged --- probably the release was already done";
1143             }
1144             }
1145              
1146             FIND_VERSION: {
1147             open my $cfh, '<', 'Changes'
1148             or error "Can't open Changes: $!";
1149             while(<$cfh>) {
1150             if (/^\Q$Doit_VERSION\E\s/) {
1151             last FIND_VERSION;
1152             }
1153             }
1154             error "Cannot find version $Doit_VERSION in Changes";
1155             }
1156              
1157             $doit->system('git', 'fetch');
1158              
1159             $doit->add_component('git');
1160             my $git_status = $doit->git_short_status;
1161             if ($git_status ne '' && $git_status ne '<') {
1162             error "Please check the git status";
1163             }
1164              
1165             # XXX TODO: check first if the current version already exists at CPAN
1166             my $tarfile = _get_tarfilename();
1167             if (!-e $tarfile) {
1168             dist();
1169             } else {
1170             info "Use existing tarball $tarfile";
1171             }
1172              
1173             print STDERR "Upload $tarfile? (y/n) ";
1174             if (!y_or_n()) {
1175             error "exiting release process";
1176             }
1177              
1178             $doit->system('cpan-upload', basename($tarfile));
1179              
1180             $doit->system('git', 'tag', '-a', '-m', "* $Doit_VERSION", $Doit_VERSION);
1181             $doit->system('git', 'push', 'origin', 'master', $Doit_VERSION);
1182             }
1183              
1184             sub ci_precheck {
1185             my @ci_systems = ('github-actions', 'appveyor');
1186              
1187             _check_clean_git();
1188              
1189             for my $ci_system (@ci_systems) {
1190             $doit->system('git', 'push', 'origin', "+HEAD:XXX-${ci_system}");
1191             }
1192              
1193             # check-ci is available here: https://github.com/eserte/srezic-misc/blob/master/scripts/check-ci
1194             $doit->system('check-ci', (map { "--$_" } @ci_systems));
1195             }
1196              
1197             sub _prove_path {
1198             my @directory_candidates = ($Config{bin}, dirname(realpath $^X));
1199             my @basename_candidates = ('prove', "prove$Config{version}");
1200             my @candidates = map {
1201             my $basename = $_;
1202             map {
1203             "$_/$basename";
1204             } @directory_candidates;
1205             } @basename_candidates;
1206             if ($^O eq 'MSWin32') {
1207             unshift @candidates, map { "$_.bat"} @candidates;
1208             }
1209             for my $candidate (@candidates) {
1210             if (-x $candidate) {
1211             return $candidate;
1212             }
1213             }
1214             error "No 'prove' for the current perl found";
1215             }
1216              
1217             sub _cover_path {
1218             my @candidates = ("$Config{bin}/cover");
1219             if ($^O eq 'MSWin32') {
1220             unshift @candidates, map { "$_.bat"} @candidates;
1221             }
1222             for my $candidate (@candidates) {
1223             if (-x $candidate) {
1224             return $candidate;
1225             }
1226             }
1227             error "No 'cover' for the current perl found";
1228             }
1229              
1230             sub _Build_PL_mode {
1231             tie my %Config, 'CustomConfig'; # no options here!
1232              
1233             if ($ENV{PERL_MB_OPT}) {
1234             require Text::ParseWords;
1235             push @ARGV, Text::ParseWords::shellwords($ENV{PERL_MB_OPT});
1236             }
1237              
1238             require Data::Dumper;
1239             my $argv_serialized = "\n" . '$Build_PL::ARGV = ' . Data::Dumper->new([\@ARGV], [])->Indent(1)->Useqq(1)->Sortkeys(1)->Terse(1)->Dump . ";\n\n";
1240             {
1241             if (-l "Build") { # formerly this used to be a symlink
1242             $doit->unlink("Build");
1243             }
1244             my $preamble = <<"EOF";
1245             #! $Config{perlpath}
1246             # MD5: $Build_PL_md5hex
1247             EOF
1248             $preamble .= $argv_serialized;
1249             $doit->write_binary({quiet=>1}, 'Build', $preamble . qq{# line 1 "Build.PL"\n} . $Build_PL_file_contents);
1250             $doit->chmod(0755, 'Build');
1251              
1252             eval {
1253             generate_META_json("MYMETA.json");
1254             generate_META_yml("MYMETA.yml" );
1255             };
1256             warning "Failure while generating MYMETA.* files, continue without.\nError was:\n$@" if $@;
1257             }
1258             exit;
1259             }
1260              
1261             # Return a string for adding to Dockerfile after the FROM line. Also
1262             # the 2nd parameter (which has to be an empty hashref) is filled with
1263             # further instructions to amend the Dockerfile (currently only
1264             # apt_get_update_opts).
1265             sub _fix_Dockerfile_for_dist {
1266             my($distro_spec, $add_opts_ref) = @_;
1267             if ($distro_spec =~ m{^debian:(wheezy|jessie|stretch|buster|7|8|9|10)$}) {
1268             my $codename = $1;
1269             if ($codename eq '7') { $codename = 'wheezy' }
1270             elsif ($codename eq '8') { $codename = 'jessie' }
1271             elsif ($codename eq '9') { $codename = 'stretch' }
1272             elsif ($codename eq '10') { $codename = 'buster' }
1273             # https://unix.stackexchange.com/questions/508724/failed-to-fetch-jessie-backports-repository
1274             $add_opts_ref->{apt_get_update_opts} = ' -o Acquire::Check-Valid-Until=false';
1275             # https://askubuntu.com/questions/74345/how-do-i-bypass-ignore-the-gpg-signature-checks-of-apt
1276             $add_opts_ref->{apt_get_install_opts} = ' -o APT::Get::AllowUnauthenticated=true';
1277             <
1278             RUN echo 'deb [check-valid-until=no] http://archive.debian.org/debian $codename main' > /etc/apt/sources.list
1279             RUN echo 'deb [check-valid-until=no] http://archive.debian.org/debian-security/ $codename/updates main' >> /etc/apt/sources.list
1280             EOF
1281             } elsif ($distro_spec =~ m{^perl:(5\.18\.\d+|.*stretch)$}) { # XXX add more perl versions based on jessie/stretch/... containers
1282             # docker perl images are based on debian base images; older
1283             # ones need patching
1284             $add_opts_ref->{apt_get_install_opts} = ' -o APT::Get::AllowUnauthenticated=true';
1285             <<'EOF';
1286             RUN perl -i -pe \
1287             's{deb http://(?:http.debian.net|deb.debian.org)/debian (\S+) main}{deb [check-valid-until=no] http://archive.debian.org/debian $1 main}; \
1288             s{deb http://security.debian.org.* ([^/]+)/updates main}{deb [check-valid-until=no] http://archive.debian.org/debian-security/ $1/updates main}; \
1289             $_ = "" if m{^deb .*-updates main}; \
1290             ' /etc/apt/sources.list
1291             EOF
1292             } else {
1293             $add_opts_ref->{apt_get_update_opts} = '';
1294             $add_opts_ref->{apt_get_install_opts} = '';
1295             '';
1296             }
1297             }
1298              
1299             sub _add_Dockerfile_invalidate_cmd {
1300             my $dockerfile_ref = shift;
1301             require POSIX;
1302             $$dockerfile_ref .= <<"EOF";
1303             # Just a hack to make sure that the following lines
1304             # are executed at least once a day
1305             RUN echo @{[ POSIX::strftime("%F", localtime) ]}
1306             EOF
1307             }
1308              
1309             sub _docker_add_distro_specific_files {
1310             my %opts = @_;
1311             my $dir = delete $opts{dir} || die 'Please specify dir';
1312             my $distro_spec = delete $opts{distro_spec} || die 'Please specify distro_spec';
1313             die 'Unhandled options: ' . join(' ', %opts) if %opts;
1314              
1315             if ($distro_spec eq 'centos:6' || $distro_spec eq 'centos:7') {
1316             $doit->mkdir("$dir/.distro_support");
1317             # Instructions from https://gcore.de/de/hilfe/linux/centos6-repo-nach-eol-anpassen.php
1318             if ($distro_spec eq 'centos:6') {
1319             $doit->write_binary("$dir/.distro_support/CentOS.repo", <<'EOF');
1320             [base]
1321             name=CentOS-6.10 - Base
1322             baseurl=http://vault.centos.org/6.10/os/$basearch/
1323             gpgcheck=1
1324             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
1325             enabled=1
1326             metadata_expire=never
1327              
1328             #released updates
1329             [updates]
1330             name=CentOS-6.10 - Updates
1331             baseurl=http://vault.centos.org/6.10/updates/$basearch/
1332             gpgcheck=1
1333             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
1334             enabled=1
1335             metadata_expire=never
1336              
1337             # additional packages that may be useful
1338             [extras]
1339             name=CentOS-6.10 - Extras
1340             baseurl=http://vault.centos.org/6.10/extras/$basearch/
1341             gpgcheck=1
1342             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
1343             enabled=1
1344             metadata_expire=never
1345              
1346             # additional packages that extend functionality of existing packages
1347             [centosplus]
1348             name=CentOS-6.10 - CentOSPlus
1349             baseurl=http://vault.centos.org/6.10/centosplus/$basearch/
1350             gpgcheck=1
1351             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
1352             enabled=0
1353             metadata_expire=never
1354              
1355             #contrib - packages by Centos Users
1356             [contrib]
1357             name=CentOS-6.10 - Contrib
1358             baseurl=http://vault.centos.org/6.10/contrib/$basearch/
1359             gpgcheck=1
1360             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
1361             enabled=0
1362             metadata_expire=never
1363             EOF
1364             $doit->write_binary("$dir/.distro_support/epel.repo", <<'EOF');
1365             [epel]
1366             name=Extra Packages for Enterprise Linux 6 - $basearch
1367             baseurl=https://archives.fedoraproject.org/pub/archive/epel/6/$basearch
1368             enabled=1
1369             gpgcheck=1
1370             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
1371             metadata_expire=never
1372              
1373             [epel-debuginfo]
1374             name=Extra Packages for Enterprise Linux 6 - $basearch - Debug
1375             baseurl=https://archives.fedoraproject.org/pub/archive/epel/6/$basearch/debug
1376             enabled=0
1377             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
1378             gpgcheck=1
1379             metadata_expire=never
1380              
1381             [epel-source]
1382             name=Extra Packages for Enterprise Linux 6 - $basearch - Source
1383             baseurl=https://archives.fedoraproject.org/pub/archive/epel/6/SRPMS
1384             enabled=0
1385             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
1386             gpgcheck=1
1387             metadata_expire=never
1388             EOF
1389             } elsif ($distro_spec eq 'centos:7') {
1390             $doit->write_binary("$dir/.distro_support/CentOS.repo", <<'EOF');
1391             [base]
1392             name=CentOS-$releasever - Base
1393             baseurl=https://vault.centos.org/7.9.2009/os/$basearch/
1394             gpgcheck=1
1395             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
1396              
1397             [updates]
1398             name=CentOS-$releasever - Updates
1399             baseurl=https://vault.centos.org/7.9.2009/updates/$basearch/
1400             gpgcheck=1
1401             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
1402              
1403             [extras]
1404             name=CentOS-$releasever - Extras
1405             baseurl=https://vault.centos.org/7.9.2009/extras/$basearch/
1406             gpgcheck=1
1407             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
1408             EOF
1409             $doit->write_binary("$dir/.distro_support/epel.repo", <<'EOF');
1410             [epel]
1411             name=Extra Packages for Enterprise Linux 7 - $basearch
1412             baseurl=https://archives.fedoraproject.org/pub/archive/epel/7/$basearch
1413             enabled=1
1414             gpgcheck=1
1415             gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
1416             EOF
1417             } else {
1418             error "Should never happen";
1419             }
1420             $doit->write_binary("$dir/.distro_support/run.sh", <<'EOF');
1421             #! /bin/sh
1422             set -ex
1423             rm -f /etc/yum.repos.d/CentOS*.repo
1424             rm -f /etc/yum.repos.d/epel.repo
1425             cp .distro_support/CentOS.repo /etc/yum.repos.d/
1426             cp .distro_support/epel.repo /etc/yum.repos.d/
1427             yum clean all
1428             EOF
1429             $doit->chmod(0755, "$dir/.distro_support/run.sh");
1430             }
1431             }
1432              
1433             sub _check_clean_git {
1434             $doit->add_component('git');
1435              
1436             my $status = $doit->git_short_status;
1437             if ($status eq '<<') {
1438             error 'Working directory has uncomitted changes: ' . `git status`;
1439             }
1440             if ($status eq '*') {
1441             error 'Working directory has files not under git control (and not in .gitignore or .git/info/exclude): ' . `git status`;
1442             }
1443             }
1444              
1445             # REPO BEGIN
1446             # REPO NAME y_or_n /home/eserte/src/srezic-repository
1447             # REPO MD5 146cfcf8f954555fe0117a55b0ddc9b1
1448              
1449             #=head2 y_or_n
1450             #
1451             #Accept user input. Return true on 'y', return false on 'n', otherwise
1452             #ask again.
1453             #
1454             #A default may be supplied as an optional argument:
1455             #
1456             # y_or_n 'y';
1457             # y_or_n 'n';
1458             #
1459             #=cut
1460              
1461             sub y_or_n (;$) {
1462             my $default = shift;
1463             while () {
1464             chomp(my $yn = );
1465             if ($yn eq '' && defined $default) {
1466             $yn = $default;
1467             }
1468             if (lc $yn eq 'y') {
1469             return 1;
1470             } elsif (lc $yn eq 'n') {
1471             return 0;
1472             } else {
1473             print STDERR "Please answer y or n: ";
1474             }
1475             }
1476             }
1477             # REPO END
1478              
1479             {
1480             package CustomConfig;
1481             use Config '%Config';
1482             use Tie::Hash;
1483             our @ISA = qw(Tie::StdHash);
1484              
1485             sub TIEHASH {
1486             my($class, $custom_options) = @_;
1487             bless { custom_options => $custom_options }, $class;
1488             }
1489              
1490             sub FETCH {
1491             my($self, $key) = @_;
1492             if (exists $self->{custom_options}->{$key}) {
1493             return $self->{original_hash}->{$key};
1494             } else {
1495             return $Config::Config{$key};
1496             }
1497             }
1498             }
1499              
1500             1;
1501             __END__