xref: /openbsd-src/gnu/usr.bin/perl/cpan/ExtUtils-MakeMaker/t/lib/MakeMaker/Test/Utils.pm (revision eac174f2741a08d8deb8aae59a7f778ef9b5d770)
1package MakeMaker::Test::Utils;
2
3use File::Spec;
4use strict;
5use warnings;
6use Config;
7use Cwd qw(getcwd);
8use Carp qw(croak);
9use File::Path;
10use File::Basename;
11
12require Exporter;
13our @ISA = qw(Exporter);
14
15our $Is_VMS     = $^O eq 'VMS';
16our $Is_MacOS   = $^O eq 'MacOS';
17our $Is_FreeBSD = $^O eq 'freebsd';
18
19our @EXPORT = qw(which_perl perl_lib makefile_name makefile_backup
20                 make make_run run make_macro calibrate_mtime
21                 have_compiler slurp
22                 $Is_VMS $Is_MacOS
23                 run_ok
24                 hash2files
25                 in_dir
26                );
27
28
29# Setup the code to clean out %ENV
30{
31    # Environment variables which might effect our testing
32    my @delete_env_keys = qw(
33        PERL_MM_OPT
34        PERL_MM_USE_DEFAULT
35        HARNESS_TIMER
36        HARNESS_OPTIONS
37        HARNESS_VERBOSE
38        PREFIX
39        MAKEFLAGS
40        PERL_INSTALL_QUIET
41    );
42
43    my %default_env_keys;
44
45    # Inform the BSDPAN hacks not to register modules installed for testing.
46    $default_env_keys{PORTOBJFORMAT} = 1 if $Is_FreeBSD;
47
48    # https://github.com/Perl-Toolchain-Gang/ExtUtils-MakeMaker/issues/65
49    $default_env_keys{ACTIVEPERL_CONFIG_SILENT} = 1;
50
51    # Remember the ENV values because on VMS %ENV is global
52    # to the user, not the process.
53    my %restore_env_keys;
54
55    sub clean_env {
56        for my $key (keys %default_env_keys) {
57            $ENV{$key} = $default_env_keys{$key} unless $ENV{$key};
58        }
59
60        for my $key (@delete_env_keys) {
61            if( exists $ENV{$key} ) {
62                $restore_env_keys{$key} = delete $ENV{$key};
63            }
64            else {
65                delete $ENV{$key};
66            }
67        }
68    }
69
70    END {
71        while( my($key, $val) = each %restore_env_keys ) {
72            $ENV{$key} = $val;
73        }
74    }
75}
76clean_env();
77
78
79=head1 NAME
80
81MakeMaker::Test::Utils - Utility routines for testing MakeMaker
82
83=head1 SYNOPSIS
84
85  use MakeMaker::Test::Utils;
86
87  my $perl     = which_perl;
88  perl_lib;
89
90  my $makefile      = makefile_name;
91  my $makefile_back = makefile_backup;
92
93  my $make          = make;
94  my $make_run      = make_run;
95  make_macro($make, $targ, %macros);
96
97  my $mtime         = calibrate_mtime;
98
99  my $out           = run($cmd);
100
101  my $have_compiler = have_compiler();
102
103  my $text          = slurp($filename);
104
105
106=head1 DESCRIPTION
107
108A consolidation of little utility functions used throughout the
109MakeMaker test suite.
110
111=head2 Functions
112
113The following are exported by default.
114
115=over 4
116
117=item B<which_perl>
118
119  my $perl = which_perl;
120
121Returns a path to perl which is safe to use in a command line, no
122matter where you chdir to.
123
124=cut
125
126sub which_perl {
127    my $perl = $^X;
128    $perl ||= 'perl';
129
130    # VMS should have 'perl' aliased properly
131    return $perl if $Is_VMS;
132
133    $perl .= $Config{exe_ext} unless $perl =~ m/$Config{exe_ext}$/i;
134
135    my $perlpath = File::Spec->rel2abs( $perl );
136    unless( $Is_MacOS || -x $perlpath ) {
137        # $^X was probably 'perl'
138
139        # When building in the core, *don't* go off and find
140        # another perl
141        die "Can't find a perl to use (\$^X=$^X), (\$perlpath=$perlpath)"
142          if $ENV{PERL_CORE};
143
144        foreach my $path (File::Spec->path) {
145            $perlpath = File::Spec->catfile($path, $perl);
146            last if -x $perlpath;
147        }
148    }
149    $perlpath = qq{"$perlpath"}; # "safe... in a command line" even with spaces
150
151    return $perlpath;
152}
153
154=item B<perl_lib>
155
156  perl_lib;
157
158Sets up environment variables so perl can find its libraries.
159
160=cut
161
162my $old5lib = $ENV{PERL5LIB};
163my $had5lib = exists $ENV{PERL5LIB};
164sub perl_lib {
165    my $basecwd = (File::Spec->splitdir(getcwd))[-1];
166    croak "Basename of cwd needs to be 't' but is '$basecwd'\n"
167        unless $basecwd eq 't';
168                               # perl-src/t/
169    my $lib =  $ENV{PERL_CORE} ? qq{../lib}
170                               # ExtUtils-MakeMaker/t/
171                               : qq{../blib/lib};
172    $lib = File::Spec->rel2abs($lib);
173    my @libs = ($lib);
174    push @libs, $ENV{PERL5LIB} if exists $ENV{PERL5LIB};
175    $ENV{PERL5LIB} = join($Config{path_sep}, @libs);
176    unshift @INC, $lib;
177}
178
179END {
180    if( $had5lib ) {
181        $ENV{PERL5LIB} = $old5lib;
182    }
183    else {
184        delete $ENV{PERL5LIB};
185    }
186}
187
188
189=item B<makefile_name>
190
191  my $makefile = makefile_name;
192
193MakeMaker doesn't always generate 'Makefile'.  It returns what it
194should generate.
195
196=cut
197
198sub makefile_name {
199    return $Is_VMS ? 'Descrip.MMS' : 'Makefile';
200}
201
202=item B<makefile_backup>
203
204  my $makefile_old = makefile_backup;
205
206Returns the name MakeMaker will use for a backup of the current
207Makefile.
208
209=cut
210
211sub makefile_backup {
212    my $makefile = makefile_name;
213    return $Is_VMS ? "$makefile".'_old' : "$makefile.old";
214}
215
216=item B<make>
217
218  my $make = make;
219
220Returns a good guess at the make to run.
221
222=cut
223
224sub make {
225    my $make = $Config{make};
226    $make = $ENV{MAKE} if exists $ENV{MAKE};
227
228    return $Is_VMS ? $make : qq{"$make"};
229}
230
231=item B<make_run>
232
233  my $make_run = make_run;
234
235Returns the make to run as with make() plus any necessary switches.
236
237=cut
238
239sub make_run {
240    my $make = make;
241    $make .= ' -nologo' if $make eq 'nmake';
242
243    return $make;
244}
245
246=item B<make_macro>
247
248    my $make_cmd = make_macro($make, $target, %macros);
249
250Returns the command necessary to run $make on the given $target using
251the given %macros.
252
253  my $make_test_verbose = make_macro(make_run(), 'test',
254                                     TEST_VERBOSE => 1);
255
256This is important because VMS's make utilities have a completely
257different calling convention than Unix or Windows.
258
259%macros is actually a list of tuples, so the order will be preserved.
260
261=cut
262
263sub make_macro {
264    my($make, $target) = (shift, shift);
265
266    my $is_mms = $make =~ /^MM(K|S)/i;
267
268    my @macros;
269    while( my($key,$val) = splice(@_, 0, 2) ) {
270        push @macros, qq{$key=$val};
271    }
272    my $macros = '';
273    if (scalar(@macros)) {
274        if ($is_mms) {
275            map { $_ = qq{"$_"} } @macros;
276            $macros = '/MACRO=(' . join(',', @macros) . ')';
277        }
278        else {
279            $macros = join(' ', @macros);
280        }
281    }
282
283    return $is_mms ? "$make$macros $target" : "$make $target $macros";
284}
285
286=item B<calibrate_mtime>
287
288  my $mtime = calibrate_mtime;
289
290When building on NFS, file modification times can often lose touch
291with reality.  This returns the mtime of a file which has just been
292touched.
293
294=cut
295
296sub calibrate_mtime {
297    my $file = "calibrate_mtime-$$.tmp";
298    open(FILE, ">$file") || die $!;
299    print FILE "foo";
300    close FILE;
301    my($mtime) = (stat($file))[9];
302    unlink $file;
303    return $mtime;
304}
305
306=item B<run>
307
308  my $out = run($command);
309  my @out = run($command);
310
311Runs the given $command as an external program returning at least STDOUT
312as $out.  If possible it will return STDOUT and STDERR combined as you
313would expect to see on a screen.
314
315=cut
316
317sub run {
318    my $cmd = shift;
319
320    use ExtUtils::MM;
321
322    # Unix, modern Windows and OS/2 from 5.005_54 up can handle 2>&1
323    # This makes our failure diagnostics nicer to read.
324    if (MM->can_redirect_error) {
325        return `$cmd 2>&1`;
326    }
327    else {
328        return `$cmd`;
329    }
330}
331
332
333=item B<run_ok>
334
335  my @out = run_ok($cmd);
336
337Like run() but it tests that the result exited normally.
338
339The output from run() will be used as a diagnostic if it fails.
340
341=cut
342
343sub run_ok {
344    my $tb = Test::Builder->new;
345
346    my @out = run(@_);
347
348    $tb->cmp_ok( $?, '==', 0, "run(@_)" ) || $tb->diag(@out);
349
350    return wantarray ? @out : join "", @out;
351}
352
353=item have_compiler
354
355  $have_compiler = have_compiler;
356
357Returns true if there is a compiler available for XS builds.
358
359=cut
360
361sub have_compiler {
362    return 1 if $ENV{PERL_CORE};
363
364    my $have_compiler = 0;
365
366    in_dir(sub {
367        eval {
368            require ExtUtils::CBuilder;
369            my $cb = ExtUtils::CBuilder->new(quiet=>1);
370            $have_compiler = $cb->have_compiler;
371        };
372    });
373
374    return $have_compiler;
375}
376
377=item slurp
378
379  $contents = slurp($filename);
380
381Returns the $contents of $filename.
382
383Will die if $filename cannot be opened.
384
385=cut
386
387sub slurp {
388    my $filename = shift;
389
390    local $/ = undef;
391    open my $fh, $filename or die "Can't open $filename for reading: $!";
392    my $text = <$fh>;
393    close $fh;
394
395    return $text;
396}
397
398=item hash2files
399
400  hash2files('dirname', { 'filename' => 'some content' });
401
402Goes through given hash-ref, treating each key as a /-separated filename
403under the specified directory, and writing the value into it. Will create
404any necessary directories.
405
406Will die if errors occur.
407
408=cut
409
410sub hash2files {
411    my ($prefix, $hashref) = @_;
412    while(my ($file, $text) = each %$hashref) {
413        # Convert to a relative, native file path.
414        $file = File::Spec->catfile(File::Spec->curdir, $prefix, split m{\/}, $file);
415        my $dir = dirname($file);
416        mkpath $dir;
417        my $utf8 = ("$]" < 5.008 or !$Config{useperlio}) ? "" : ":utf8";
418        open(FILE, ">$utf8", $file) || die "Can't create $file: $!";
419        print FILE $text;
420        close FILE;
421        # ensure file at least 1 second old for makes that assume
422        # files with the same time are out of date.
423        my $time = calibrate_mtime();
424        utime $time, $time - 1, $file;
425    }
426}
427
428=item in_dir
429
430  $retval = in_dir(\&coderef);
431  $retval = in_dir(\&coderef, $specified_dir);
432  $retval = in_dir { somecode(); };
433  $retval = in_dir { somecode(); } $specified_dir;
434
435Does a C<chdir> to either a directory. If none is specified, one is
436created with L<File::Temp> and then automatically deleted after. It ends
437by C<chdir>ing back to where it started.
438
439If the given code throws an exception, it will be re-thrown after the
440re-C<chdir>.
441
442Returns the return value of the given code.
443
444=cut
445
446sub in_dir(&;$) {
447    my $code = shift;
448    require File::Temp;
449    my $dir = shift || File::Temp::tempdir(TMPDIR => 1, CLEANUP => 1);
450    # chdir to the new directory
451    my $orig_dir = getcwd();
452    chdir $dir or die "Can't chdir to $dir: $!";
453    # Run the code, but trap the error so we can chdir back
454    my $return;
455    my $ok = eval { $return = $code->(); 1; };
456    my $err = $@;
457    # chdir back
458    chdir $orig_dir or die "Can't chdir to $orig_dir: $!";
459    # rethrow if necessary
460    die $err unless $ok;
461    return $return;
462}
463
464=back
465
466=head1 AUTHOR
467
468Michael G Schwern <schwern@pobox.com>
469
470=cut
471
4721;
473