From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#!/usr/bin/env perl
#
# Makepp version @VERSION@.
# $Id: makepp,v 1.228 2017/08/06 21:19:08 pfeiffer Exp $
# See @htmldir@/index.html for user documentation.
#
package Mpp;
use 5.008;
use strict;
use Config;
our $datadir;
BEGIN {
#@@setdatadir
#
# Find the location of our data directory that contains the auxiliary files.
# This is normally built into the program by install.pl, but if makepp hasn't
# been installed, then we look in the directory we were run from.
#
$datadir = $0; # Assume it's running from the same place that
# we're running from.
unless( $datadir =~ s@/[^/]+$@@ ) { # No path specified?
# See if we can find ourselves in the path.
foreach( split( /:/, $ENV{'PATH'} ), '.' ) {
# Add '.' to the path in case the user is
# running it with "perl makepp" even if
# . is not in his path.
if( -d "$_/Mpp" ) { # Found something we need?
$datadir = $_;
last;
}
}
}
$datadir or die "makepp: can't find library files\n";
$datadir = eval "use Cwd; cwd . '/$datadir'"
if $datadir =~ /^\./; # Make it absolute, if it's a relative path.
do "$datadir/Mpp/DB.pm" if exists $DB::{sh};
if( $^O =~ /^MSWin/ && $] < 5.008007 ) { # IDENTICAL AS IN t/run_tests.pl
# This is a very bad hack! On earlier Win Active State "lstat 'file'; lstat _ or -l _" is broken.
my $file = "$datadir/Mpp/File.pm";
local $_ = "$file.broken";
unless( -f ) { # Already converted
rename $file, $_;
open my $in, '<', $_;
open my $out, '>', $file;
chmod 07777 & (stat)[2], $file;
while( <$in> ) {
s/\blstat\b/stat/g;
s/-l _/0/g;
print $out $_;
}
}
}
#@@
unshift @INC, $datadir;
}
our $progname;
$Mpp::Text::pod = 'makepp_command';
BEGIN { *MAKEPP = $Mpp::Text::N[1] } # Differentiate from makeppreplay
use POSIX ();
use Mpp;
our $error_found;
our $log_level;
use Mpp::Event qw(when_done wait_for);
our $original_cwd = $CWD_INFO; # Remember the directory we started from.
delete $ENV{ROOT}; # This is our builtin variable.
our $MAKEFLAGS = delete $ENV{MAKEFLAGS};
our $MAKEPPFLAGS = delete $ENV{MAKEPPFLAGS};
our %global_ENV = %ENV; # Make a copy of the environment, because
# we're going to change the environment a lot.
%ENV = %global_ENV; # Some (only Solaris?) versions hang when changing $0 before %ENV
our( $dont_build_dir_flag, $sandbox_warn_flag, $virtual_sandbox_flag, $warn_undef_var );
# This is set if we set ASSUME_UNCHANGED /
# DONT_BUILD on any directory. Optimizations
# can be made if they aren't set.
our $dry_run; # Display commands but don't actually
# execute them.
our $n_phony_messages = 0; # Number of times we've had to warn about
# phony targets.
#
# Default settings for various command line options:
#
our $environment_override = 0; # Variables in environment do not override
# variables in the makefile by default.
our $implicitly_load_makefiles = 1; # Load a makefile from every directory
# that contains a dependency or is searched
# by a wildcard.
our $final_rule_only = 0; # Unconditionally run only the top-level rules
our $rm_stale_files = 0; # Ignore stale files, removing them if
# necessary, instead of treating them as
# source files.
our @makepp_include_path = file_info $datadir, $original_cwd;
# The default directories to search for
# include files supplied with makepp.
our $remake_makefiles = 1; # Default to making the makefiles automatically.
our $stop_on_race = 0; # Stop if a race is detected.
# my $handle = build($objinfo);
# Starts building the specified object. When build() returns, the object
# may not have been built yet; build() returns a handle (see Mpp/Event.pm for
# details) which you can wait on if you want to wait until the build is
# actually done. The usual idiom is
# wait_for build($objinfo);
# If you do this, of course, you make it harder for makepp to build targets in
# parallel because the makepp process will not spawn any other build requests
# until this one is done.
# When any targets are built, the global variable $n_files_changed is
# updated.
sub build {
exists $_[0]{BUILD_HANDLE} and return $_[0]{BUILD_HANDLE};
# Don't start a rebuild if we're already
# trying to build it, or if we've tried
# before.
return if $error_found; # If we're quitting because of an error, don't
# start any new builds (but still propagate
# errors from existing builds).
#
# Find a rule for this object.
#
my $oinfo = $_[0]; # Name the arguments.
if( &Mpp::File::dont_read ) {
warn "error: Not building inexistent `" . &absolute_filename . "' because it's marked for dont-read\n";
return 1;
}
if( &Mpp::File::dont_build ) {
if( exists $_[0]{xPHONY} || &file_exists ) {
Mpp::log BUILD_NOT => $oinfo
if $log_level;
} else {
warn "info: Not building inexistent `" . &absolute_filename . "' because it's marked for dont-build\n";
return 1 if &Mpp::File::dont_build == 2;
}
return undef $_[0]{BUILD_HANDLE};
}
Mpp::log TRY => $oinfo
if $log_level;
# NOTE: We can't unlink the target just yet (even if it's from a repository),
# because then we couldn't use its cached scanner info. Instead, that
# happens just before (and only if) it's built.
my $rule = Mpp::File::get_rule( $oinfo ) || # Do we already have a rule for it?
#
# Make up a dummy rule if there is no rule. This makes it much easier to
# handle the case where there is a target with no action but some
# dependencies, like this:
#
# all: my_program my_other_program
#
# This is quite common in makefiles. Another more sinister idiom is like
# this:
#
# y.tab.c: y.tab.h
# y.tab.h: parse.y
# yacc -d $<
#
# This despicable idiom is the only way to tell the traditional make that the
# yacc command produces both output files. Not only is this ugly, but if the
# rule containing the action looks up-to-date but the side-effect is not, then
# the side-effect doesn't get updated. This isn't easy to fix, because by the
# time we build the side effect file, the action rule could have already been
# checked.
#
new Mpp::DefaultRule($oinfo);
# By the time we're done getting the rule, the target might have already
# been built, so just return its handle in that case. Otherwise we'll try
# to build it a second time after its rule has been nuked, and that won't
# work.
exists $oinfo->{BUILD_HANDLE} and return $oinfo->{BUILD_HANDLE};
# If we find that the rule for this target is in the middle of being
# processed, then just return as if the file were already built and hope
# for the best. (This is currently the best we can do with circular
# dependencies.)
return if exists $rule->{xSCANNING};
# If the file still looks stale (now that we've tried to load its rule), and
# we're not treating stale files as source files, then we need to error out
# here, because something specifically requested a stale file.
if( $rm_stale_files && &Mpp::File::is_stale ) {
warn 'error: Not building ', $oinfo, " because it's a stale generated file\n";
&Mpp::File::unlink;
++$Mpp::failed_count; # Wouldn't get counted, since there's no rule
return 1;
}
#
# If a makefile contains $(MAKE), then we turn off implicit loading of other
# makefiles, assuming that the makefile is constructed to load them manually.
# This is a bit tricky, since the implicit loading flag must be passed in a
# global variable. This local modification will not apply, however, to the
# routines activated by when_done.
#
local $implicitly_load_makefiles if $implicitly_load_makefiles &&
$rule->{MAKEFILE} && exists $rule->{MAKEFILE}{xRECURSIVE_MAKE};
undef $oinfo->{BUILD_HANDLE}; # Indicate that we're trying to build.
# This will be replaced later by a real
# build handle. This must be done after
# get_rule because we may attempt to load a
# makefile in get_rule, and we don't want
# to print out a warning message about
# encountering the rule after we already tried
# to build the file.
local $rule->{xSCANNING}; # Ditto for other targets of this rule that
# we might not know about yet.
Mpp::log USE => $rule
if $log_level;
# In --final-rule-only mode, ignore the dependencies and implicit targets
# of the rule unless the target is phony.
my $dont_scan = $final_rule_only && !UNIVERSAL::isa( $rule, 'Mpp::DefaultRule' ) &&
!exists $oinfo->{xPHONY};
warn 'info: Ignoring dependencies of ', &absolute_filename,
" because --final-rule-only is specified\n"
if $dont_scan;
my( $all_targets, $all_dependencies, $command_string, $env ) =
$rule->find_all_targets_dependencies( $oinfo, $dont_scan );
# Find out everything that's needed for this
# command, and everything that it changes.
Mpp::log DEPEND => $all_targets, $all_dependencies
if $log_level;
#
# Build each of the dependencies:
#
my @build_handles;
if( exists $rule->{SCAN_FAILED} ) {
# If we couldn't even figure out what the dependencies are, then
# propagate error status instead of running the rule. But if -k is
# specified, then build the dependencies that we already know about.
die unless $rule->{SCAN_FAILED};
push @build_handles, $rule->{SCAN_FAILED};
my $reason = '';
if( UNIVERSAL::isa $rule->{SCAN_FAILED}, 'Mpp::File' ) {
my $finfo = $rule->{SCAN_FAILED};
my( $handle, $next );
while( $handle = $finfo->{BUILD_HANDLE} and
exists $handle->{STATUS} and
$next = $handle->{STATUS} and
UNIVERSAL::isa $next, 'Mpp::File'
) {
$finfo = $next;
}
$reason = ', because ' . absolute_filename( $finfo ) . ' failed to build';
}
warn 'error: Not building ' . &absolute_filename . " because the dependencies could not be computed $reason\n";
}
my $handle;
if( @$all_dependencies and $Mpp::keep_going || ! exists $rule->{SCAN_FAILED} ) {
local $Mpp::indent_level = $Mpp::indent_level + 2;
# Temporarily change the indentation level
# when building dependencies.
foreach my $dep (@$all_dependencies) {
next if exists $dep->{BUILD_HANDLE} && !defined $dep->{BUILD_HANDLE};
# Don't wait for what's already been done.
$handle = when_done build( $dep ), $Mpp::Text::N[0], ERROR =>
$rm_stale_files ?
sub {
if( grep !(exists $_->{xPHONY} || file_exists $_), @$all_dependencies ) {
for my $finfo ( @$all_targets ) { # Not all deps exist: target is now stale.
Mpp::log DEL_STALE => $finfo
if $log_level;
Mpp::File::unlink $finfo;
CORE::unlink Mpp::File::build_info_fname $finfo;
}
}
$dep;
} :
$dep;
$handle and push @build_handles, $handle;
# Don't bother saving it if it's blank.
}
}
$handle = when_done @build_handles, \&build_dependencies_done,
[$oinfo, $all_targets, $all_dependencies, $command_string, $rule, $env, $dont_scan]
and exists $handle->{STATUS} && !$handle->{STATUS} and
undef $handle; # If the build operation already succeeded,
# don't keep the handle around.
foreach my $target (@$all_targets) { # Tag all targets with the handle
$target->{BUILD_HANDLE} = $handle; # that builds them.
}
#
# If we're not doing a parallel make, wait for the above to finish, because
# it's very confusing if makepp gets ahead of the shell commands.
#
$Mpp::parallel_make or
wait_for $oinfo->{BUILD_HANDLE};
$oinfo->{BUILD_HANDLE};
}
#
# This subroutine is called when all the dependencies for a given target
# are finished building. We make sure the target is up to date, or if it
# isn't, we set off the build.
# Arguments:
# a) A list of all the targets.
# b) A list of all the dependencies, in sorted order.
# c) The command string to execute.
# d) The rule object to execute.
#
# Note that indentation level in the log file is handled automatically by
# the when_done mechanism.
#
sub build_dependencies_done {
my( $oinfo, $all_targets, $all_dependencies, $command_string, $rule, $env, $dont_scan ) = @_;
#
# This routine is only called if no errors were encountered in building the
# dependencies.
#
return if $error_found; # If we're quitting because of an error,
# don't start any new builds. This can happen
# if we're doing a parallel build and we got
# an error in a different, unrelated target.
for my $dep ( @$all_dependencies ) {
Mpp::File::check_for_change $dep; # Restat each dependency. This will slow down
# the build, but it's important for accuracy,
# because if a file has been edited or if
# a command altered a file without specifying
# it as a target, we won't know about it
# otherwise, and thus the build info that
# we store will be wrong. (There's still a
# time window where this can occur, but since
# we restat just before we execute the
# commands, it's much narrower.)
warn 'Target ', &absolute_filename, ' depends on temporary file ',
absolute_filename( $dep ), "\n"
if exists $dep->{xTEMP};
}
#
# Now check all the signatures of each target to see if it is up
# to date.
#
my $is_recursive_make = defined $Mpp::Recursive::command && $command_string =~ /\brecursive_makepp\b/;
# Note that the above will catch invocations
# of the recursive_makepp program, and also
# invocations of makepp when
# --traditional-recursive-make is in effect.
my $rebuild_needed; # Assume we don't need to rebuild.
my $dont_build; # Assume no files marked with --dont-build.
my $out_of_sandbox; # Assume no files marked with --out-of-sandbox.
my @targets_to_get_from_repository; # If we have to get any files from a
# repository, this is where we store them.
my @targets_to_get_from_build_cache; # Targets to get from a build cache.
my $build_cwd = $rule->build_cwd;
my $build_check_method = $rule->build_check_method;
my $sig_method = $rule->signature_method; # Get which signature checking
# algorithm we want to use.
if( $is_recursive_make ) {
$rebuild_needed = 1; # Always reexecute this command.
Mpp::log BUILD_RECURSIVE => $all_targets
if $log_level;
} elsif( $dont_scan ) {
$rebuild_needed = 1; # Always reexecute this command.
Mpp::log BUILD_FINAL => $all_targets
if $log_level;
} else {
target_loop:
foreach my $target (@$all_targets) {
last if $rebuild_needed && $dont_build;
if( Mpp::File::dont_build $target ) {
$dont_build = $target; # Remember not to clobber dont-build targets
next target_loop; # Skip to next target now.
}
unless( Mpp::File::in_sandbox $target ) {
$out_of_sandbox = $target;
$dont_build = $target unless $sandbox_warn_flag;
# Remember not to overstep sandbox
}
next target_loop if $rebuild_needed
or exists $target->{xTEMP};
# Temporary targets don't have to be up-to-date
if( exists $target->{xPHONY} ) { # If this is a phony target, then we always rebuild.
$rebuild_needed = 1;
Mpp::log BUILD_PHONY => $target
if $log_level;
next target_loop; # Keep checking for dont_build.
}
#
# Sometimes when we're using repositories, we may need to execute a rule
# but the output directory does not exist yet. If the output directory
# does not exist, but it does exist in a repository, then go ahead and
# make it. However, if the output directory does not exist in any
# repository, then don't make it, because it's possible that the
# command or the targets are erroneous, and we don't want to scatter
# junk directories around.
#
Mpp::File::mkdir $target->{'..'} # Make it with parents.
if exists $target->{'..'}{xIN_REPOSITORY} && !file_exists $target->{'..'};
if( $build_check_method->build_check( $target, $all_dependencies,
$command_string, $build_cwd,
$sig_method, $env )) { # Need to rebuild?
if( exists $target->{ALTERNATE_VERSIONS} ) { # Possibly copy from repository?
my $is_src_file = UNIVERSAL::isa $rule, 'Mpp::DefaultRule';
for( @{$target->{ALTERNATE_VERSIONS}} ) {
if( $rm_stale_files && $is_src_file && Mpp::File::was_built_by_makepp $_ ) {
Mpp::log REP_SKIP => $_
if $log_level;
next;
}
Mpp::log REP_CHECK => $_
if $log_level;
if( $is_src_file ||
!$build_check_method->build_check_from_build_info( $_, $all_dependencies, $command_string, $build_cwd, $sig_method, $env )) {
push @targets_to_get_from_repository, $target, $_;
# Remember to copy this from repository.
next target_loop; # Move on to next target.
}
}
}
if( defined $Mpp::BuildCache::used and my $build_cache = !$Mpp::BuildCache::write_only && $rule->build_cache ) {
# Do we have a build cache we can look in?
my $bc_key = $build_check_method->
build_cache_key( $target, $all_dependencies, $command_string,
$build_cwd, $sig_method, $env );
if( $bc_key ) { # Got a valid key?
my $bc_entry = $build_cache->lookup_file($bc_key);
if( $bc_entry ) { # Got a candidate entry in cache?
Mpp::log BC_FOUND => $target, $bc_key
if $log_level;
push @targets_to_get_from_build_cache, $bc_entry, $target;
next target_loop; # Move on to next target.
} else {
Mpp::log BC_NONE => $target, $bc_key
if $log_level;
}
} else {
Mpp::log BC_NO_KEY => $target
if $log_level;
}
}
$rebuild_needed = 1; # We'll need to run the command.
next target_loop; # Keep checking for dont_build.
}
}
}
$rebuild_needed ||= Mpp::BuildCache::get( $rule, \@targets_to_get_from_build_cache )
if @targets_to_get_from_build_cache;
# At this point, it appears that we won't have to run the rule.
# However, we could have a problem when we attempt to import from
# the cache, and because there could be concurrent processes operating
# on the build cache, we can't predict ahead of time whether such a
# problem will occur. The solution is to attempt the import first,
# and if it fails then fall back to rebuilding.
if( $rebuild_needed ) {
if( $dont_build ) {
# NOTE: This can trip even if the target(s) that we need are up to date
# if some other target *not* marked for dont-build isn't up to date.
# This usually isn't a problem in practice, and it appears nontrivial
# to fix.
warn 'error: Not building ' . absolute_filename( $oinfo ) .
' because ' . absolute_filename( $dont_build ) .
" (a target of its rule) was marked for dont-build or out-of-sandbox\n";
return 1;
}
warn 'Building ', absolute_filename( $out_of_sandbox ), ", which is outside the sandbox\n"
if $out_of_sandbox;
# Do we actually need to rebuild something?
if( $is_recursive_make ) { # If this is a recursive make invocation,
# we'll have to reexecute a build
# command for it, so don't inhibit that.
foreach my $target (@$all_targets) {
delete $target->{BUILD_HANDLE};
}
}
unless( $is_recursive_make || $dry_run ) {
# For the targets that are about to be built, flush out any potentially
# stale stuff (repository links and build info) from the filesystem.
foreach my $tinfo (@$all_targets) {
++$tinfo->{BUILDING} if $virtual_sandbox_flag;
if( grep $_, Mpp::File::build_info_string $tinfo, qw'FROM_REPOSITORY LINKED_TO_CACHE' or
!exists $tinfo->{xPHONY} && Mpp::File::check_for_change( $tinfo ), exists $tinfo->{xEXISTS} &&
(my $nw = !Mpp::File::is_writable_owner $tinfo) ) { # Compiler can't write?
Mpp::log REMOVE => $tinfo, $nw ? 'not writable' : 'repository or build cache import'
if $log_level;
Mpp::File::unlink $tinfo; # If it's from a repository, get rid of it.
# It might be a bogus file from the past.
# If it's from a build cache, delete it so
# we don't corrupt the file in the build
# cache.
} elsif( exists $tinfo->{BUILD_INFO}{SYMLINK} ) {
$tinfo->{TEMP_BUILD_INFO} = delete $tinfo->{BUILD_INFO};
next;
} elsif( exists $tinfo->{TEMP_BUILD_INFO} ) {
next;
}
Mpp::File::clear_build_info $tinfo;
}
}
$Mpp::critical_sections++;
# TBD: It would be better to mark all the targets as invalid first,
# and then restore them when the command succeeded (and ideally restore
# the ones that weren't touched if it fails). That way, we won't get
# confused if makepp stops very suddenly (e.g. kernel panic), even if
# none of the targets' timestamps changed.
my $execute_handle = $dry_run && !$is_recursive_make ?
$rule->print_command( $command_string ) :
$rule->execute( $command_string, $all_targets, $all_dependencies );
# Start executing the command, or just print it
# if -n is enabled (unless it's recursive make).
shift;
my $handle = when_done $execute_handle, \&build_target_done, \@_,
ERROR => sub {
$error_found = $_[0]
if $_[0] =~ /^signal (?:$Mpp::int_signo|$Mpp::quit_signo)$/os;
$error_found ||= $_[0] # Remember the error status. This will cause us
unless $Mpp::keep_going; # to stop compilation as soon as possible.
$Mpp::failed_count += @$all_targets;
warn 'error: Failed to build target', (@$all_targets>1 ? 's' : ''),
map( ' `'.absolute_filename( $_ )."'", @$all_targets ), " [$_[0]]\n";
Mpp::log RULE_FAILED => $rule
if $log_level; # Sometimes it's hard to tell where the problem
# was if -k was specified unless we mark it
# in the log file.
# TBD: We don't need to invalidate the build info of targets that we know
# weren't touched by the command. We can leverage the code that detects
# when we need to retouch a file (also TBD) to accomplish this.
foreach my $tinfo (@$all_targets) {
next if exists $tinfo->{xPHONY}; # Skip phony targets.
may_have_changed $tinfo; # Invalidate cached info.
# We don't really need to blow away BUILD_INFO,
# because we called clear_build_info earlier,
# but that doesn't slow things down too much.
my $deref = dereference $tinfo;
may_have_changed $deref if $tinfo != $deref;
# Since we don't know whether the command
# modified the link or its referent or both,
# need to invalidate both.
unless( $is_recursive_make || $dry_run || ref($rule) eq 'Mpp::DefaultRule' ) {
# Never store build information on recursive
# makes. They always need to be rerun every
# time we run through the makefile.
Mpp::File::set_build_info_string $tinfo,
BUILD_SIGNATURE => 'FAILED',
COMMAND => "|FAILED|$command_string",
CWD => relative_filename $rule->{MAKEFILE}{CWD}, $tinfo->{'..'};
}
}
&Mpp::File::update_build_infos # Flush the build info to disk.
unless $dry_run;
$Mpp::critical_sections--;
propagate_pending_signals;
return $_[0]; # Propagate the status.
};
return $handle; # Not finished building until the rule has
# finished executing.
}
#
# We didn't actually need to rebuild anything. We may have to copy or link
# a target from the repository, however.
#
Mpp::Repository::get( splice @targets_to_get_from_repository, 0, 2 )
and return 1 # failed
while @targets_to_get_from_repository; # Move or link the targets.
Mpp::log UP_TO_DATE => $all_targets
if $log_level;
# We need to cache the scanner info even if the action wasn't executed,
# because the existing build info may have been generated with
# --nocache_scaninfo. Since Mpp::File::set_build_info is intelligent, this
# shouldn't slow things down noticeably if the scan info didn't change.
if( UNIVERSAL::isa $build_check_method, 'Mpp::BuildCheck::exact_match' ) {
$rule->cache_scaninfo([
grep {
!exists $_->{xPHONY} &&
(Mpp::File::build_info_string $_, 'CWD' or '') eq relative_filename $build_cwd, $_->{'..'}
} @$all_targets
]);
}
foreach my $tinfo (@$all_targets) {
Mpp::File::set_rule $tinfo, undef; # Discard the rule to save memory.
}
undef; # Success, and nothing to wait for.
}
#
# This subroutine is called when a rule's command has finished executing.
# We don't reach this subroutine if an error occurred while the rule was
# executing.
#
# Arguments:
# a) A list of all the targets.
# b) A list of all the dependencies, in sorted order.
# c) The indentation level in the log file.
# d) The command string to execute.
# e) The rule object to execute.
#
# Note that indentation level in the log file is handled automatically by
# the when_done mechanism.
#
#
sub build_target_done {
my( $all_targets, $all_dependencies, $command_string, $rule, $env ) = @_;
my $implicit_phony = 0;
# TBD: We should probably do an eval on a per-target basis, because it's nice
# to clean up after the rest of the targets after one fails to have its build
# info straightened out.
eval {
my $sig_method = $rule->signature_method; # Get which signature checking
# algorithm we want to use.
my $build_cwd = $rule->build_cwd;
my $is_recursive_make = defined $Mpp::Recursive::command && $command_string =~ /\brecursive_makepp\b/;
my $build_cache = defined $Mpp::BuildCache::used && $rule->build_cache;
my $build_check_method = $rule->build_check_method;
Mpp::log SUCCESS => $rule, $all_targets
if $log_level;
#
# Update the stored build information for each target:
#
my @built_targets;
my $include = $rule->{INCLUDE};
my %uniq;
if( $include ) {
if( $rule->{INCLUDE_MD5} ? $rule->{INCLUDE_MD5} ne Mpp::Signature::md5::signature( $include ) : 1 ) {
# If we have a sig, only reread if it changed.
$uniq{sprintf '%x', $_} = $_ for @$all_dependencies;
for my $tinfo (@$all_targets) {
delete $uniq{sprintf '%x', $_} for $rule->expand_additional_deps( $tinfo );
# These may now be outdated.
}
Mpp::log LOAD_INCL => $include, $rule
if $Mpp::log_level;
local $Mpp::Makefile::rule_include = 2;
$rule->{MAKEFILE}->read_makefile( $include );
} else {
undef $include; # No change, forget about it for this time.
}
}
foreach my $tinfo (@$all_targets) {
next if exists $tinfo->{xPHONY}; # Skip phony targets.
if( $include ) { # Do this for all targets, as we don't know which one was in the :include .d
$uniq{sprintf '%x', $_} = $_ for $rule->expand_additional_deps( $tinfo );
}
may_have_changed $tinfo; # Invalidate cached info.
# We don't really need to blow away BUILD_INFO,
# because we called clear_build_info earlier,
# but that doesn't slow things down too much.
my $deref = dereference $tinfo;
may_have_changed $deref if $tinfo != $deref;
# Since we don't know whether the command modified the link
# or its referent or both, need to invalidate both.
push @built_targets, $tinfo;
}
$all_dependencies = $rule->sorted_dependencies( values %uniq )
if keys %uniq;
$rule->cache_scaninfo(\@built_targets);
my $sorted_dep_names = join "\cA", map relative_filename( $_, $build_cwd ), @$all_dependencies;
my $sorted_dep_sigs = join "\cA", map { $sig_method->signature($_) || '' } @$all_dependencies;
foreach my $tinfo (@built_targets) {
unless( $is_recursive_make || $dry_run ) {
# Never store build information on recursive
# makes. They always need to be rerun every
# time we run through the makefile.
if( my $tsig = $sig_method->signature( $tinfo )) { # File actually exists now:
# Store the information on how we built it.
my @extra;
if( %$env ) {
my @deps = sort keys %$env;
@extra = (
ENV_DEPS => join( "\cA", @deps ), ENV_VALS => join( "\cA", @{$env}{@deps} )
);
}
if( $tinfo->{LINK_DEREF} && $tinfo->{LSTAT}[Mpp::File::STAT_NLINK] == 1 ) {
# Assume nlink > 1 to mean action only created
# a link to an already existing symlink.
Mpp::log SYMLINK => $tinfo
if $log_level;
my $link = readlink absolute_filename $tinfo;
my $linkee = file_info $link, $tinfo->{'..'};
$tinfo->{LINK_DEREF} ||= $linkee;
if( grep $_ == $linkee, @$all_dependencies, @$all_targets ) {
# Don't use dereference, as that follows a link to a link.
push @extra, 'SYMLINK', $link;
} else {
warn $rule->source, ': `' . absolute_filename_nolink( $tinfo ) . "' is a symbolic link to `$link',
which is neither a dependency of this rule nor a target.
That means makepp can't guarantee a correct build of that file.
Your rule should in essence follow one of these two schemes:
link: linkee
&ln --force --resolve \$(input) \$(output)
linkee link:
&echo Somehow produce linkee -o \$(output)
&ln -fr \$(outputs)\n";
}
}
Mpp::File::set_build_info_string $tinfo,
SORTED_DEPS => $sorted_dep_names,
DEP_SIGS => $sorted_dep_sigs,
BUILD_SIGNATURE => $tsig,
COMMAND => $command_string,
CWD => relative_filename( $build_cwd, $tinfo->{'..'} ),
ARCH => ARCHITECTURE, # Tag the build with the architecture, too.
@extra;
if( $build_cache && !$Mpp::BuildCache::read_only and # Store this file in a build cache?
my $bc_key = $build_check_method->
build_cache_key( $tinfo, $all_dependencies, $command_string, $build_cwd, $sig_method, $env )) {
# Store the file in cache.
unless( $build_cache->cache_file( $tinfo, $bc_key, \my $reason )) {
my $msg = 'Exporting of ' . absolute_filename( $tinfo ) . " to the build cache failed because $reason\n";
&$Mpp::BuildCache::error_hook( $msg ) if $Mpp::BuildCache::error_hook;
if( !$stop_on_race && $msg =~ s/ \(OK\)$//m ) {
warn "info: ${msg}This might be due to concurrent access, so we'll just skip exporting this target.\n";
} else {
die $msg;
}
}
}
} elsif( !exists $tinfo->{xTEMP} ) {
my $mfile = $rule->{MAKEFILE} || $tinfo->{'..'}{MAKEINFO};
if( $mfile && $mfile->expand_variable( 'makepp_require_phony', $rule->source )) {
$implicit_phony++; # DefaultRule has no MAKEFILE.
} elsif( UNIVERSAL::isa $rule, 'Mpp::DefaultRule' ) {
warn 'error: There is no rule to build inexistent ' . absolute_filename( $tinfo ) . "\n";
} elsif( $n_phony_messages++ ) { # Give a shorter message on other instances.
warn 'Target ' . absolute_filename( $tinfo ) . " is probably also phony.\n";
} else {
warn 'I attempted to build ' . absolute_filename( $tinfo ) . ',
but after successfully executing the commands, the target does not exist.
Perhaps it is a phony target? If so, the rule defining it should be modified
like this:
$(phony ', relative_filename($tinfo, $build_cwd), '): dependencies
actions
or you could also declare it as phony like this:
.PHONY: ', relative_filename( $tinfo, $build_cwd ), "\n";
}
undef $tinfo->{xPHONY};
$tinfo->{SIGNATURE} = 9e99; # Give it a very recent time to
# force rebuilding of everything that depends
# on it even if -m target_newer is in effect.
}
}
undef $tinfo->{xASSUME_NEW} if $dry_run; # If this is a dry run, the file
# didn't actually change, but force any command
# that depends on it to execute.
undef $tinfo->{BUILD_HANDLE} unless $is_recursive_make;
# No need to keep the handle around since
# the build was successful. Just leave a
# flag that we tried to build.
Mpp::File::set_rule( $tinfo, undef ); # Discard the rule to save memory.
}
my $real = ref($rule) ne 'Mpp::DefaultRule';
# Don't count targets for which there was no rule.
for( @$all_targets ) {
if( exists $_->{xPHONY} ) { ++$Mpp::n_phony_targets_built }
elsif( $real ) { ++$Mpp::n_files_changed }
}
};
my $error = $@;
&Mpp::File::update_build_infos # Flush the build info to disk.
unless $dry_run;
if( $implicit_phony ) { # Why must this block come before $critical_sections--?
warn $error if $error;
return -2;
}
$Mpp::critical_sections --;
propagate_pending_signals;
die $error if $error;
undef; # Return a success code.
}
our( $stop, $loop );
sub maybe_stop {
$stop = 1 if $loop and !defined $stop;
if( $stop ) {
$stop = 0; # Only stop once per loop (in case of prebuild)
print "\nmakepp: Stopped before building anything. Kill it; or continue with",
-t() ? " one of:\n\n\tfg\tbg" : ":\n\n",
"\tkill -CONT $$\n\n";
&flush_log;
kill STOP => $$;
}
}
# Find .makepprc upwards and add content to @ARGV
sub makepprc {
return if grep /^-(?:[h?V]|-help|-version)$/, @ARGV; # Avoid loading expensive things like huge repository.
my $finfo = Mpp::Subs::f_find_first_upwards '.makepprc', { CWD => $CWD_INFO }, 'startup', \1;
return if !$finfo or exists $finfo->{xMAKEPPRC}; # Already read?
return unless $finfo->{LSTAT}[Mpp::File::STAT_SIZE]; # Empty file is test dir boundary.
open my $fh, '<', absolute_filename $finfo or die "$0: cannot open `.makepprc'--$!\n";
local $/;
unshift @ARGV, $finfo->{'..'} == $CWD_INFO ?
unquote_split_on_whitespace <$fh> :
('-C', $finfo->{'..'}, unquote_split_on_whitespace( <$fh> ), '-C', $CWD_INFO);
close $fh;
undef $finfo->{xMAKEPPRC}; # Mark as read
}
# parse_command_line(@ARGV, \%environment_hash);
# Parses and executes the given command line. Loads whatever makefiles are
# necessary and builds the appropriate targets, or at least starts off the
# build. (It doesn't wait until the build is finished.) Returns a list of
# build handles.
# The environment must be provided as a reference to a hash. Any rules which
# are executed have the environment set to this value before the shell is
# invoked.
# This parser only accepts options which are valid during recursive makes or
# from the load_makefile command. There are other options which are handled
# by the mainline makepp code which are not accepted here.
# parse_command_line assumes that the current directory is the proper directory
# for executing the command.
my @do_build;
our @ignore_opts;
if( $ENV{MAKEPP_IGNORE_OPTS} ) {
my( @lx, @l, @sx, @s );
for my $opt( split ' ', $ENV{MAKEPP_IGNORE_OPTS} ) {
if( $opt =~ /^--(.+)=/ ) {
push @lx, $1;
} elsif( $opt =~ /^--(.+)/ ) {
push @l, $1;
} elsif( $opt =~ /^-(.)./ ) {
push @sx, $1;
} elsif( $opt =~ /^-(.)/ ) {
push @s, $1;
} else {
die "\$MAKEPP_IGNORE_OPTS: '$opt' not understood\n";
}
}
my $nop;
local $" = '';
if( @lx || @sx ) {
my $lx = @lx ? join '|', @lx : 'DUMMY';
$lx = qr/$lx/;
my $sx = @sx > 1 ? qr/[@sx]/ : $sx[0];
push @ignore_opts, [$sx, $lx, \$nop, 1];
}
if( @l || @s ) {
my $l = @l ? join '|', @l : 'DUMMY';
$l = qr/$l/;
my $s = @s > 1 ? qr/[@s]/ : $s[0];
push @ignore_opts, [$s, $l, \$nop];
}
}
sub parse_command_line(\%@) {
local $_; # Don't mess up caller's $_.
my $this_ENV = shift; # First argument is the environment hash.
my @targets; # Targets we need to make.
my @initial_makefiles;
my @makefiles;
my @include_path = @makepp_include_path;
# Make a copy of the include path so we can
# add to it.
#
# Command line variable settings affect all makefiles regardless of where they
# are specified on the command line.
#
my %command_line_vars;
my $root_makefile;
my $target_cwd = $CWD_INFO;
my $tmp;
&makepprc;
while( @ARGV ) {
Mpp::Text::getopts \%command_line_vars, 1,
@ignore_opts,
@_,
['c', qr/root(?:[-_]?dir(?:ectory)?)?/, \$tmp, undef, sub {
$tmp = Mpp::Makefile::find_root_makefile_upwards $CWD_INFO
or $CWD_INFO->{ROOT}
or die "$0: No RootMakeppfile(.mk) found above `", absolute_filename( $CWD_INFO ), "'\n";
# See if we find a RootMakeppfile from here.
if( $tmp ) {
$root_makefile ||= $tmp; # TODO: Load multiple RootMakeppfiles for targets from multiple trees.
$tmp = $tmp->{'..'};
} else {
$tmp = $CWD_INFO->{ROOT};
}
chdir $tmp; # Switch to that directory.
$target_cwd = $tmp; # It's now the directory relative to which
# further targets are specified.
&makepprc;
}],
[qw(C directory), \$tmp, 1, sub {
if( ref $tmp ) { # Called via makepprc?
chdir $tmp; # Switch to that directory.
return;
}
$tmp = file_info $tmp;
Mpp::File::mkdir $tmp; # Make sure the directory exists.
chdir $tmp; # Switch to that directory.
$root_makefile ||= Mpp::Makefile::find_root_makefile_upwards $tmp;
# See if we find a RootMakeppfile from here.
$target_cwd = $tmp; # It's now the directory relative to which
# further targets are specified.
&makepprc;
}],
[undef, qr/dump[-_]?makep*file/, \$tmp, 1, sub {
open my $fh, '>', $tmp or die "Failed to write $tmp--$!";
push @Mpp::close_fhs, $fh;
$CWD_INFO->{DUMP_MAKEFILE} = $fh;
}],
['e', qr/env(?:ironment)?[-_]?overrides?/, \$environment_override],
['f', qr/(?:make)?file/, \$tmp, 1, sub {
# Load a makefile without changing directory?
if( $tmp eq '-' ) { # Loading expects a real file(_info) so make one.
$tmp = Mpp::Subs::f_mktemp '';
open my $fh, '>', $tmp;
local $/; # Makefiles are small enough to slurp.
syswrite $fh, <>;
close $fh;
}
push @makefiles, [file_info( $tmp ), $CWD_INFO]; # Load makefile later
# when we can specify MAKECMDGOALS.
}],
[qw(F makeppfile), \$tmp, 1, sub {
$tmp = file_info $tmp;
my $mdir = $tmp; # Assume it is actually a directory.
Mpp::File::is_or_will_be_dir $tmp or $mdir = $tmp->{'..'};
# Default directory is the directory the
# makefile is in.
push @makefiles, [$tmp, $mdir]; # Load the makefile later.
Mpp::File::mkdir( $mdir ); # Make sure the directory exists, so we
# can cd into it.
$root_makefile ||= Mpp::Makefile::find_root_makefile_upwards $mdir;
# See if we find a RootMakeppfile from here.
$target_cwd = $mdir; # It's now the directory relative to which
# further targets are specified.
}],
['I', qr/include(?:[-_]?dir)?/, \$tmp, 1, sub {
# Specify more directories for the include
# path?
push @include_path, file_info $tmp;
}],
[undef, qr/load[-_]?make(?:pp)?file/, \$tmp, 1, sub {
$tmp = file_info $tmp;
my $mdir = $tmp; # Assume it is actually a directory.
Mpp::File::is_or_will_be_dir( $tmp ) or $mdir = $tmp->{'..'};
# Default directory is the directory the makefile is in.
push @initial_makefiles, [$tmp, $mdir]; # Load the makefile later.
Mpp::File::mkdir( $mdir ); # Make sure the directory exists, so we can cd into it.
}],
['o', qr/(?:assume[-_]?old|old[-_]?file)/, \$tmp, 1, sub {
$tmp = file_info $tmp;
$tmp->{ASSUME_UNCHANGED} = 1; # Don't pay any attention to this file.
our $assume_unchanged_dir_flag = 1 if is_or_will_be_dir $tmp;
# Remember that we have to check parent dirs
}],
['r', qr/no[-_]?builtin[-_]?rules/, \$command_line_vars{makepp_no_builtin}],
# Pretend this was given as a variable.
[qw(R repository), \$tmp, 1, sub { Mpp::Subs::s_repository $tmp }],
['W', qr/(?:what[-_]?if|assume[-_]?new|new[-_]?file)/, \$tmp, 1, sub {
undef file_info( $tmp )->{xASSUME_NEW}; # Pretend this file has changed.
}],
@Mpp::common_opts;
delete $command_line_vars{makepp_no_builtin} unless defined $command_line_vars{makepp_no_builtin};
# Ref above brought this into existence.
push @targets, file_info shift @ARGV, $target_cwd
if @ARGV;
# Evidently it is a real target. This is a
# hack, because getopts has no way of handling
# normal args immediately. So we operate in
# strict mode, giving the 1st non-opt/non-var,
# at the beginning of @ARGV. Just one extra
# call per target...
}
close DATA; # Only needed for --help.
@_ = (); # Free lots of memory.
unless( defined $MAKEFLAGS ) { # 1st call
my $flags =
($environment_override ? 'e' : '') .
($Mpp::keep_going ? 'k' : '') .
($dry_run ? 'n' : '') .
($Mpp::quiet_flag ? 's' : '') .
($Mpp::log_level == 1 ? 'v' : '');
$MAKEFLAGS = join ' ',
($flags ? $flags : ()),
($Mpp::implicitly_load_makefiles ? () : '--noimplicitload'),
($Mpp::log_level ? () : '--nolog'),
($Mpp::print_directory ? () : '--noprintdirectory'),
($warn_undef_var ? '--warnundef' : ());
# TODO: what about -o -W
}
$root_makefile ||= Mpp::Makefile::find_root_makefile_upwards( $target_cwd );
if( $root_makefile ) {
unshift @initial_makefiles, [$root_makefile, $root_makefile->{'..'}];
my $build_root = $root_makefile->{'..'};
for( @do_build ) {
my $saw_build_root;
$_ = $_->{'..'} unless $_ == $Mpp::File::root;
while( !exists $_->{DONT_BUILD} ) {
$saw_build_root++ if $_ == $build_root;
if( $_ == $Mpp::File::root ) {
if( $saw_build_root ) {
$build_root->{DONT_BUILD} = 1;
# Found a do_build within build tree
# but not within a dont_build dir.
warn "info: --do-build not overriding --dont-build implies not building anything else.\n";
@do_build = ();
}
last;
}
$_ = $_->{'..'};
}
}
$dont_build_dir_flag = 1;
exists $build_root->{DONT_BUILD} or
undef $build_root->{DONT_BUILD};
exists $Mpp::File::root->{DONT_BUILD} or
$Mpp::File::root->{DONT_BUILD} = 2;
# If we have a root, don't build anything
# outside our build tree unless specifically
# requested. Use 2 to mean: implicitly set.
}
my $makecmdgoals = join(' ', map relative_filename( $_ ), @targets);
# Format MAKECMDGOALS.
if(@initial_makefiles) {
my $cwd = $CWD_INFO;
foreach (@initial_makefiles) {
Mpp::Makefile::load($_->[0], $_->[1], \%command_line_vars, $makecmdgoals,
\@include_path, $this_ENV); # Load the initial makefiles
}
chdir $cwd;
}
#
# At this point we have a list of target fileinfo structures in @targets.
# Now load makefiles. The procedure for finding a makefile is:
# 1) Use any explicitly specified on the command line.
# 2) If none, try to find one in the current directory.
# 3) If none and no RootMakeppfile, try all the directories containing targets.
#
{
last if @makefiles || $CWD_INFO->{MAKEINFO}; # last leaves this block
my $finfo = Mpp::Makefile::find_makefile_in( $CWD_INFO );
# Look in the current directory.
$finfo and @makefiles = [$finfo, $CWD_INFO] and last;
$root_makefile and last;
for( @targets ) {
$finfo = Mpp::Makefile::find_makefile_in( $_->{'..'} ) and
# Did we find one in this directory?
push @makefiles, [$finfo, $finfo->{'..'}];
}
@makefiles or @makefiles = [$CWD_INFO, $CWD_INFO];
# Load the default makefile if we still haven't found one.
}
$Mpp::Recursive::depth = # Avoid warning.
$Mpp::Recursive::depth = $command_line_vars{recursive_makepp}
if defined $Mpp::Recursive::traditional || defined $Mpp::Recursive::hybrid;
foreach (@makefiles) {
$_ = Mpp::Makefile::load($_->[0], $_->[1], \%command_line_vars, $makecmdgoals,
\@include_path, $this_ENV); # Load all the makefiles, and store
# the makeinfo structure back in @makefiles.
}
@targets or # No targets specified?
@targets = ($target_cwd->{MAKEINFO} || $makefiles[0] || $CWD_INFO)->{FIRST_TARGET} ||
# Use the first one in the first makefile. If
# we had no makefile in cwd but a
# RootMakeppfile, @makefiles is empty. That's
# why we fall back to $CWD_INFO, just so we
# don't reference undef.
die 'no targets specified and no default target in makefile `' .
absolute_filename( ($target_cwd->{MAKEINFO} || $makefiles[0])->{MAKEFILE} || $CWD_INFO ) .
"'\n";
@targets;
}
###############################################################################
#
# Parse the top level command. This is at the bottom of the file to force
# all the miscellaneous initialization stuff scattered in with the routines
# to be executed.
#
my @other_args; # Arguments that are valid at places other
# than the top level.
if( $MAKEPPFLAGS || $MAKEFLAGS ) {
unshift @ARGV, unquote_split_on_whitespace $MAKEPPFLAGS || $MAKEFLAGS;
# See if we're passed flags from a parent process.
$ARGV[0] =~ /^-/ || $ARGV[0] =~ /=/ or substr $ARGV[0], 0, 0, '-';
# If we're actually called from make, the first
# argument might not have a minus sign.
# However, don't put a minus sign in if it's
# a variable assignment.
}
undef $MAKEFLAGS; # 1st round of parsing shall set standard values.
unshift @ARGV, unquote_split_on_whitespace $ENV{_MAKEPPFLAGS}
if $ENV{_MAKEPPFLAGS};
perform eval {
# Now parse the command line, and handle the
# targets. Do fast exit without freeing all
# variables. Cleaning up the Mpp::File tree
# can take a long time and there's no need to
# do it. (This saves 8% of the run time on a
# Mpp::File tree with > 10^5 files, so it's not
# a trivial savings.)
my $tmp;
parse_command_line %global_ENV,
['b', qr/build[-_]?cache/, \$tmp, 1, sub {
Mpp::Subs::s_build_cache $tmp, undef, $progname, {global => 1};
}],
[undef, qr/build[-_]?check(?:[-_]?method)?/, \$tmp, 1, sub {
Mpp::Subs::s_build_check $tmp, undef, $progname, {global => 1};
}],
[undef, qr/do[-_]?build/, \$tmp, 1, sub {
$tmp = file_info $tmp;
undef $tmp->{DONT_BUILD};
push @do_build, $tmp;
}],
[undef, qr/do[-_]?read/, \$tmp, 1, sub {
undef file_info( $tmp )->{DONT_READ};
}],
[undef, qr/dont[-_]?build/, \$tmp, 1, sub {
$tmp = file_info $tmp;
$tmp->{DONT_BUILD} = 1;
$dont_build_dir_flag = 1 if is_or_will_be_dir $tmp;
# Remember that we have to check parent dirs
}],
[undef, qr/dont[-_]?read/, \$tmp, 1, sub {
$tmp = file_info $tmp;
$tmp->{DONT_READ} = 1;
our $dont_read_dir_flag = 1 if is_or_will_be_dir $tmp;
# Remember that we have to check parent dirs
}],
[undef, qr/final[-_]?rules?[-_]?only/, \$final_rule_only],
[undef, qr/force[-_]?copy[-_]?from[-_]?bc/, eval '\$Mpp::BuildCache::force_copy'],
[undef, qr/force[-_]?rescan/, \our $force_rescan], # Don't use cached scanner results
[undef, 'gullible', \our $gullible], # Trust actions not to touch non-targets
[undef, qr/hybrid(?:[-_]?recurs(?:ion|ive|ive[-_]?make))?/, \$Mpp::Recursive::hybrid],
[undef, qr/implicit[-_]load[-_]?[Mm]akeppfile[-_]?only/, \our $implicit_load_makeppfile_only],
# Never implicitly load makefile and Makefile.
[undef, qr/(?:in(?:side)?[-_]?)?sand[-_]?box/, \$tmp, 1, sub {
$tmp = file_info $tmp;
$tmp->{IN_SANDBOX} = 1;
our $sandbox_enabled_flag = 1; # default is now out-of-sandbox
our $in_sandbox_dir_flag = 1 if is_or_will_be_dir $tmp;
# Remember that we have to check parent dirs
}],
[qw(j jobs), \$Mpp::Event::max_proc, 1, sub {
$Mpp::Event::max_proc =~ /^\d+$/ or die "$progname: invalid argument to -j\n";
if( is_windows > 0 ) {
$Mpp::Event::max_proc = 1;
warn "parallel make (-j) only supported on Cygwin or MinGW Perl\n";
return;
}
if( $Mpp::Event::max_proc != 1 ) { # More than one process?
$Mpp::parallel_make = 1;
# Remember if we're doing a parallel make.
# It's not sufficient to test $max_proc,
# because if we are doing a recursive make,
# we have to increment max_proc, and we don't
# want to switch into parallel make mode.
require File::Copy; # Needed for reemitting collected output.
# Do it once here, so as to not recheck each time.
}
}],
[undef, qr/last[-_]?chance[-_]?rules/, \our $last_chance_rules],
# Flag allowing target pattern only rules for gmake compatibility.
['m', qr/signature(?:[-_]?method)?/, \$tmp, 1, sub {
Mpp::Subs::s_signature $tmp, undef, $progname, {global => 1};
}],
[undef, qr/override[-_]?signature(?:[-_]?method)?/, \$tmp, 1, sub {
Mpp::Subs::s_signature $tmp, undef, $progname, {global => 1, override => 1};
}],
[undef, qr/md5[-_]?(?:check[-_]?)?bc/, eval '\$Mpp::BuildCache::md5check'],
[undef, qr/no[-_]?cache[-_]?scaninfos?/, \our $nocache_scaninfo], # Don't save scanner results
[undef, qr/no[-_]?implicit[-_]?load/, \$implicitly_load_makefiles, undef, 0],
[undef, qr/no[-_]?path[-_]?exe(?:cutable)?[-_]?dep(?:s|endency|endencies)?/, \our $no_path_executable_dependencies],
# Don't add implicit dependencies on executables
# picked up from the command search path.
[undef, qr/no[-_]?populate[-_]?bc/, \$Mpp::BuildCache::read_only],
# don't populate bc with targets, just read from it
[undef, qr/no[-_]?remake[-_]?makefiles/, \$remake_makefiles, undef, 0],
# Don't remake makefiles automatically?
[undef, qr/out[-_]?of[-_]?sand[-_]?box/, \$tmp, 1, sub {
$tmp = file_info $tmp;
undef $tmp->{IN_SANDBOX};
our $in_sandbox_dir_flag = 1 if is_or_will_be_dir $tmp;
# Remember that we have to check parent dirs
}],
[undef, qr/populate[-_]?bc[-_]?only/, \$Mpp::BuildCache::write_only],
[undef, qr/re?m(?:ove)?[-_]?stale(?:[-_]?files)?/, \$rm_stale_files],
[undef, qr/sand[-_]?box[-_]?warn(?:ing)?/, \$sandbox_warn_flag],
[undef, qr/loop|stop(?:[-_]?before[-_]?build(?:ing)?|[-_]?after[-_]?load(?:ing|ed)?)?/, \$loop],
[undef, qr/stop[-_]?(?:on[-_]?)?race/, \$stop_on_race],
[undef, qr/symlink[-_]?in[-_]?rep(?:os(?:itory)?)?[-_]?as[-_]?file/, eval '\$Mpp::Repository::symlink_as_file'],
[undef, qr/traditional(?:[-_]?recurs(?:ion|ive|ive[-_]?make))?/, \$Mpp::Recursive::traditional],
[undef, qr/virtual[-_]?sandbox/, \$virtual_sandbox_flag],
[undef, qr/warn[-_]?undef(?:ined)?(?:[-_]?var(?:iables)?)?/, \$warn_undef_var];
};
__DATA__
[option ...] [VAR=value ...] [target ...]
Valid options are: