The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
# $Id: makeppreplay,v 1.21 2013/02/16 15:11:37 pfeiffer Exp $

package Mpp;

use strict;

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 "makeppreplay: can't find library files\n";

  $datadir = eval "use Cwd; cwd . '/$datadir'"
    if $datadir =~ /^\./;	# Make it absolute, if it's a relative path.
#@@
  unshift @INC, $datadir;
}


use Mpp::Utils;
use Mpp::Text ();
use POSIX ();
use Mpp;

use Mpp::Event qw(wait_for);

our $progname;

my( $sed, $temporary );

use Mpp::File;
use Mpp::FileOpt;
use Mpp::Cmds;
use Mpp::Makefile;
use Mpp::Rule;

my @targets;
my $target_cwd = $CWD_INFO;
Mpp::Makefile::find_root_makefile_upwards $target_cwd;
				# See if we find a RootMakeppfile from here, in case Perl code uses ROOT.
my $modules = '';

my %command_line_vars;
my $tmp;

sub load_build_info_file($) {
  my $build_info = &Mpp::File::load_build_info_file
    or return;			# No build info -- don't create it by following modification.
  unless( exists $build_info->{SIGNATURE} ) { # Out of date build info?
    my $sig = &Mpp::File::signature; # But file exists.
    my $count = keys %$build_info;
    delete @{$build_info}{qw(FROM_REPOSITORY LINKED_TO_CACHE)} unless $sig;
    while( my( $key, $value ) = each %$build_info ) {
      delete $build_info->{$key} # We may recalculate some of these, but not necessarily all.
				# So make sure not to save any outdated ones with new SIGNATURE.
	if $key ne 'DEP_SIGS' && Mpp::Signature::is_content_based $value or
	  $key eq 'LINKED_TO_CACHE' && $_[0]{LSTAT}[Mpp::File::STAT_NLINK] == 1 or
				# Could have more than one link, but none to cache -- how can we know?
	  $key eq 'FROM_REPOSITORY' && (readlink( &absolute_filename_nolink ) || '') ne $value;
    }
    if( $sig ) { # But file exists.
      &Mpp::File::mark_build_info_for_update
	if $count != keys %$build_info; # Found a significant change?
      $build_info->{RESCAN} ||= 1; # If we save this, let makepp double check what we signed.
				# keep value if it was already 2 from last run.
      $build_info->{SIGNATURE} = $sig;
    }
  }
  $build_info;
}

@ARGV = '.' unless @ARGV;
my $nothing_in_dir;
my $found_in_dir = 0;
eval {
  while( @ARGV) {
    Mpp::Text::getopts \%command_line_vars, 1,
      ['c', qr/root(?:[-_]?dir(?:ectory)?)?/, \$tmp, undef, sub {
	 Mpp::Makefile::find_root_makefile_upwards $target_cwd;
	 $target_cwd = $target_cwd->{ROOT}
	   or die "$0: No RootMakeppfile(.mk) found above `", absolute_filename( $target_cwd ), "'.\n";
				  # See if we find a RootMakeppfile from here.
	 chdir $target_cwd;	# Switch to that directory.
       }],
      [qw(C directory), \$tmp, 1, sub {
	 $target_cwd = file_info $tmp, $target_cwd;
	 chdir $target_cwd;	# Switch to that directory.
	 Mpp::Makefile::find_root_makefile_upwards $target_cwd;
				  # See if we find a RootMakeppfile from here.
       }],
      ['I', qr/include(?:[-_]?dir)?/, \$tmp, 1, sub { unshift @INC, absolute_filename file_info $tmp }],
      [qw(M module), \$tmp, 1, sub { $tmp =~ s/=(.*)/ qw($1)/ and $tmp =~ tr/,/ /; $modules .= "use $tmp;" }],
      [qw(s sed), \$sed, 1],
      [qw(t temporary), \$temporary],

      @Mpp::common_opts;

    @ARGV = '.' unless @ARGV || @targets;
    my $finfo = file_info shift, $target_cwd;
    if( is_dir $finfo ) {
      Mpp::log RULE_ALL => $finfo
	if $Mpp::log_level;
      my $build_info_subdir = file_info relative_filename( $finfo ) . "/$Mpp::File::build_info_subdir";
      $nothing_in_dir = absolute_filename $finfo;
      unshift @ARGV, grep {
	$_ = relative_filename $_;
	s!$Mpp::File::build_info_subdir/!! &&
	  s!\.mk$!! &&
	  ++$found_in_dir;
      } Mpp::Glob::zglob_fileinfo '*.mk', $build_info_subdir;
      next;
    }

    my $build_info = $finfo->{BUILD_INFO} = load_build_info_file $finfo;
    if( $found_in_dir ) {	# Not an explicit target?
      $found_in_dir--;
      next unless exists $build_info->{COMMAND}; # Not built by makepp
      undef $nothing_in_dir;
    } else {
      die "$progname: No files built by makepp in `$nothing_in_dir'.\n"
	if $nothing_in_dir;
      die "$progname: Nothing known about `" . absolute_filename( $finfo ) . "'.\n"
	unless defined $build_info;
      die "$progname: File `" . absolute_filename( $finfo ) . "' not built by makepp.\n"
	unless exists $build_info->{COMMAND};
    }

    (my $cmd = $build_info->{COMMAND}) =~ s/\A\|FAILED\|//;
    $cmd =~ tr/\cC/\n/;
    if( $sed ) {
      my $ocmd = $cmd;
      eval $sed for $cmd;
      die "--sed: $@" if $@;
      undef $finfo->{xSED} if $ocmd ne $cmd; # Cmd might now be changing other files.  Treat this as a change outside of mpp.
    }
    (my $deps = $build_info->{SORTED_DEPS} || '') =~ tr/\cA/ /;

    my $dinfo = file_info $build_info->{CWD}, $finfo->{'..'};
    my $makefile = $dinfo->{MAKEINFO};
    unless( $makefile ) {
      $makefile = Mpp::Makefile::load $dinfo, $dinfo, \%command_line_vars, '', [], \%ENV;
      if( $modules ) {
	$makefile->cd;		# Evaluate in the correct directory.
	Mpp::Subs::eval_or_die $modules, $makefile, absolute_filename( $dinfo ) . ':0';
      }
    }
    $makefile->{EXPORTS} ||= {};

    my $target = relative_filename $finfo, $dinfo;
    my $rule_cache = "$deps\01$cmd"; # Try to group targets that came from same rule.
    if( $dinfo->{$rule_cache} ) {
      Mpp::log RULE_EXTEND => $finfo, $dinfo->{$rule_cache}{TARGETS}
	if $Mpp::log_level;
      $dinfo->{$rule_cache}{TARGET_STRING} .= " $target";
      push @{$dinfo->{$rule_cache}{TARGETS}}, $finfo;
    } else {
      Mpp::log RULE_NEW => $finfo
	if $Mpp::log_level;
      $dinfo->{$rule_cache} = new Mpp::Rule $target, $deps, $cmd, $makefile, Mpp::File::build_info_fname( $finfo ) . ':0';
      push @targets, $finfo;
      $dinfo->{$rule_cache}{TARGETS} = [$finfo];
      @{$dinfo->{$rule_cache}{ENV_DEPS}}{split "\cA", $build_info->{ENV_DEPS}} = split "\cA", $build_info->{ENV_VALS}
	if exists $build_info->{ENV_DEPS};
    }
    $finfo->{RULE} = $dinfo->{$rule_cache};
  }
  @Mpp::common_opts = ();
  close DATA;
  die "$progname: No files built by makepp in `$nothing_in_dir'.\n"
    if $nothing_in_dir;

  for my $target ( @targets ) {	# Should use Mpp::build et al. here!
    my $build_info = $target->{BUILD_INFO};
    if( delete $build_info->{FROM_REPOSITORY} || delete $build_info->{LINKED_TO_CACHE} ) {
				# If these survived load_build_info_file, the file is linked.
      Mpp::log REMOVE => $target
	if $Mpp::log_level;
      Mpp::File::unlink $target;
    }
    my $rule = $target->{RULE};
    my $n_files = @{$rule->{TARGETS}};
    local $rule->{MAKEFILE}{EXPORTS} = $rule->{ENV_DEPS}
      if exists $rule->{ENV_DEPS};
    if( $Mpp::dry_run ) {
      $rule->print_command( $rule->{COMMAND_STRING} );
    } elsif( my $status = wait_for $rule->execute(
	$rule->{COMMAND_STRING},
	$rule->{TARGETS},
	[map file_info( $_, $target->{'..'} ), split ' ', $rule->{DEPENDENCY_STRING}] ) ) {
      print_error "Failed to build target", (@{$rule->{TARGETS}}>1 ? 's' : ''),
	map( ' `'.absolute_filename( $_ )."'", @{$rule->{TARGETS}} ), " [$status]";
      Mpp::log RULE_FAILED => $rule
	if $Mpp::log_level;
      $Mpp::error_found = $status
	if $status =~ /^signal (?:$Mpp::int_signo|$Mpp::quit_signo)$/os;
      $Mpp::error_found ||= $status # Remember the error status.  This will
	unless $Mpp::keep_going;	# cause us to stop compilation as soon as
				# possible.
      $rule->{TARGET_STRING} =~ s/ /' `/g;
      $Mpp::failed_count += $n_files;
      unless( $temporary || $build_info->{COMMAND} =~ /\A\|FAILED\|/ ) {
	for my $tinfo ( @{$rule->{TARGETS}} ) {
	  next if exists $tinfo->{xSED};
	  substr $tinfo->{BUILD_INFO}{COMMAND}, 0, 0, '|FAILED|';
	  Mpp::File::mark_build_info_for_update $tinfo;
	}
	&Mpp::File::update_build_infos;
      }
      last unless $Mpp::keep_going;
    } else {
      Mpp::log SUCCESS => $rule, $rule->{TARGETS}
	if $Mpp::log_level;
      $Mpp::n_files_changed += $n_files;
      next if $temporary;
      my $sig = $build_info->{SIG_METHOD_NAME}; # All targets have the same.
      $sig &&= $rule->set_signature_class( $sig );
      my $dep_sigs = '';	# Precalculate this for the targets loop
      my $sep = '';
      for my $dep ( split /\cA/, $build_info->{SORTED_DEPS} || '' ) {
	$dep = path_file_info $dep, $rule->{MAKEFILE}{CWD};
	$dep->{BUILD_INFO} ||= load_build_info_file $dep;
	$dep_sigs .= $sep .
	  (($sig ? $sig->signature( $dep ) : Mpp::File::signature $dep) || '');
	$sep = "\cA";
      }
      for my $tinfo ( @{$rule->{TARGETS}} ) {
	next if exists $tinfo->{xSED};
	$build_info = $tinfo->{BUILD_INFO};
	delete $tinfo->{LSTAT};
	$build_info->{SIGNATURE} = Mpp::File::signature $tinfo;

	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.
	  $build_info->{SYMLINK} = readlink absolute_filename $tinfo;
	} else {
	  delete $build_info->{SYMLINK}; # Unlikely to have changed, but just in case.
	}
	$build_info->{DEP_SIGS} = $dep_sigs;
	$build_info->{BUILD_SIGNATURE} = $sig ? $sig->signature( $tinfo ) : $build_info->{SIGNATURE};
	$build_info->{RESCAN} = 2; # Let makepp double check what we built.
	Mpp::File::mark_build_info_for_update $tinfo;
      }
      &Mpp::File::update_build_infos;
    }
  }
};
perform;			# Use function w/o args only for final diagnostic.

__DATA__
[option ...] [VAR=value ...] target ...

Valid options are: