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