1#!/usr/bin/perl -w
2# $Id: makeppreplay,v 1.15 2012/02/07 22:26:15 pfeiffer Exp $
3
4package Mpp;
5
6use strict;
7
8our $datadir;
9BEGIN {
10  our $VERSION = '@VERSION@';
11
12#@@setdatadir
13#
14# Find the location of our data directory that contains the auxiliary files.
15# This is normally built into the program by install.pl, but if makepp hasn't
16# been installed, then we look in the directory we were run from.
17#
18  $datadir = $0;		# Assume it's running from the same place that
19				# we're running from.
20  unless( $datadir =~ s@/[^/]+$@@ ) { # No path specified?
21				# See if we can find ourselves in the path.
22    foreach( split( /:/, $ENV{'PATH'} ), '.' ) {
23				# Add '.' to the path in case the user is
24				# running it with "perl makepp" even if
25				# . is not in his path.
26      if( -d "$_/Mpp" ) {	# Found something we need?
27	$datadir = $_;
28	last;
29      }
30    }
31  }
32  $datadir or die "makeppreplay: can't find library files\n";
33
34  $datadir = eval "use Cwd; cwd . '/$datadir'"
35    if $datadir =~ /^\./;	# Make it absolute, if it's a relative path.
36#@@
37  unshift @INC, $datadir;
38}
39
40
41use Mpp::Utils;
42use Mpp::Text ();
43use POSIX ();
44use Mpp;
45
46use Mpp::Event qw(wait_for);
47
48#@@useoldmodules
49#@@
50
51our $progname;
52
53my $temporary;
54
55use Mpp::File;
56use Mpp::FileOpt;
57use Mpp::Cmds;
58use Mpp::Makefile;
59use Mpp::Rule;
60
61my @targets;
62my $target_cwd = $CWD_INFO;
63Mpp::Makefile::find_root_makefile_upwards $target_cwd;
64				# See if we find a RootMakeppfile from here, in case Perl code uses ROOT.
65my $modules = '';
66
67my %command_line_vars;
68my $tmp;
69
70sub load_build_info_file($) {
71  my $build_info = &Mpp::File::load_build_info_file
72    or return;			# No build info -- don't create it by following modification.
73  unless( exists $build_info->{SIGNATURE} ) { # Out of date build info?
74    my $sig = &Mpp::File::signature; # But file exists.
75    my $count = keys %$build_info;
76    delete @{$build_info}{qw(FROM_REPOSITORY LINKED_TO_CACHE)} unless $sig;
77    while( my( $key, $value ) = each %$build_info ) {
78      delete $build_info->{$key} # We may recalculate some of these, but not necessarily all.
79				# So make sure not to save any outdated ones with new SIGNATURE.
80	if $key ne 'DEP_SIGS' && Mpp::Signature::is_content_based $value or
81	  $key eq 'LINKED_TO_CACHE' && $_[0]{LSTAT}[Mpp::File::STAT_NLINK] == 1 or
82				# Could have more than one link, but none to cache -- how can we know?
83	  $key eq 'FROM_REPOSITORY' && (readlink( &absolute_filename_nolink ) || '') ne $value;
84    }
85    if( $sig ) { # But file exists.
86      &Mpp::File::mark_build_info_for_update
87	if $count != keys %$build_info; # Found a significant change?
88      $build_info->{RESCAN} ||= 1; # If we save this, let makepp double check what we signed.
89				# keep value if it was already 2 from last run.
90      $build_info->{SIGNATURE} = $sig;
91    }
92  }
93  $build_info;
94}
95
96@ARGV = '.' unless @ARGV;
97my $nothing_in_dir;
98my $found_in_dir = 0;
99perform eval {
100  while( @ARGV) {
101    Mpp::Text::getopts \%command_line_vars, 1,
102      ['c', qr/root(?:[-_]?dir(?:ectory)?)?/, \$tmp, undef, sub {
103	 Mpp::Makefile::find_root_makefile_upwards $target_cwd;
104	 $target_cwd = $target_cwd->{ROOT}
105	   or die "$0: No RootMakeppfile(.mk) found above `", absolute_filename( $target_cwd ), "'.\n";
106				  # See if we find a RootMakeppfile from here.
107	 chdir $target_cwd;	# Switch to that directory.
108       }],
109      [qw(C directory), \$tmp, 1, sub {
110	 $target_cwd = file_info $tmp, $target_cwd;
111	 chdir $target_cwd;	# Switch to that directory.
112	 Mpp::Makefile::find_root_makefile_upwards $target_cwd;
113				  # See if we find a RootMakeppfile from here.
114       }],
115      ['I', qr/include(?:[-_]?dir)?/, \$tmp, 1, sub { unshift @INC, absolute_filename file_info $tmp }],
116      [qw(M module), \$tmp, 1, sub { $tmp =~ s/=(.*)/ qw($1)/ and $tmp =~ tr/,/ /; $modules .= "use $tmp;" }],
117      [qw(t temporary), \$temporary],
118
119      @Mpp::common_opts;
120
121    @ARGV = '.' unless @ARGV || @targets;
122    my $finfo = file_info shift, $target_cwd;
123    if( is_dir $finfo ) {
124      Mpp::log RULE_ALL => $finfo
125	if $Mpp::log_level;
126      my $build_info_subdir = file_info relative_filename( $finfo ) . "/$Mpp::File::build_info_subdir";
127      $nothing_in_dir = absolute_filename $finfo;
128      unshift @ARGV, grep {
129	$_ = relative_filename $_;
130	s!$Mpp::File::build_info_subdir/!! &&
131	  s!\.mk$!! &&
132	  ++$found_in_dir;
133      } Mpp::Glob::zglob_fileinfo '*.mk', $build_info_subdir;
134      next;
135    }
136
137    my $build_info = $finfo->{BUILD_INFO} = load_build_info_file $finfo;
138    if( $found_in_dir ) {	# Not an explicit target?
139      $found_in_dir--;
140      next unless exists $build_info->{COMMAND}; # Not built by makepp
141      undef $nothing_in_dir;
142    } else {
143      die "$progname: No files built by makepp in `$nothing_in_dir'.\n"
144	if $nothing_in_dir;
145      die "$progname: Nothing known about `" . absolute_filename( $finfo ) . "'.\n"
146	unless defined $build_info;
147      die "$progname: File `" . absolute_filename( $finfo ) . "' not built by makepp.\n"
148	unless exists $build_info->{COMMAND};
149    }
150
151    (my $cmd = $build_info->{COMMAND}) =~ s/\A\|FAILED\|//;
152    $cmd =~ tr/\cC/\n/;
153    (my $deps = $build_info->{SORTED_DEPS} || '') =~ tr/\cA/ /;
154
155    my $dinfo = file_info $build_info->{CWD}, $finfo->{'..'};
156    my $makefile = $dinfo->{MAKEINFO};
157    unless( $makefile ) {
158      $makefile = Mpp::Makefile::load $dinfo, $dinfo, \%command_line_vars, '', [], \%ENV;
159      if( $modules ) {
160	$makefile->cd;		# Evaluate in the correct directory.
161	Mpp::Subs::eval_or_die $modules, $makefile, absolute_filename( $dinfo ) . ':0';
162      }
163    }
164    $makefile->{EXPORTS} ||= {};
165
166    my $target = relative_filename $finfo, $dinfo;
167    my $rule_cache = "$deps\01$cmd"; # Try to group targets that came from same rule.
168    if( $dinfo->{$rule_cache} ) {
169      Mpp::log RULE_EXTEND => $finfo, $dinfo->{$rule_cache}{TARGETS}
170	if $Mpp::log_level;
171      $dinfo->{$rule_cache}{TARGET_STRING} .= " $target";
172      push @{$dinfo->{$rule_cache}{TARGETS}}, $finfo;
173    } else {
174      Mpp::log RULE_NEW => $finfo
175	if $Mpp::log_level;
176      $dinfo->{$rule_cache} = new Mpp::Rule $target, $deps, $cmd, $makefile, Mpp::File::build_info_fname( $finfo ) . ':0';
177      push @targets, $finfo;
178      $dinfo->{$rule_cache}{TARGETS} = [$finfo];
179      @{$dinfo->{$rule_cache}{ENV_DEPS}}{split "\cA", $build_info->{ENV_DEPS}} = split "\cA", $build_info->{ENV_VALS}
180	if exists $build_info->{ENV_DEPS};
181    }
182    $finfo->{RULE} = $dinfo->{$rule_cache};
183  }
184  @Mpp::common_opts = ();
185  close DATA;
186  die "$progname: No files built by makepp in `$nothing_in_dir'.\n"
187    if $nothing_in_dir;
188
189  for my $target ( @targets ) {	# Should use Mpp::build et al. here!
190    my $build_info = $target->{BUILD_INFO};
191    if( delete $build_info->{FROM_REPOSITORY} || delete $build_info->{LINKED_TO_CACHE} ) {
192				# If these survived load_build_info_file, the file is linked.
193      Mpp::log REMOVE => $target
194	if $Mpp::log_level;
195      Mpp::File::unlink $target;
196    }
197    my $rule = $target->{RULE};
198    my $n_files = @{$rule->{TARGETS}};
199    local $rule->{MAKEFILE}{EXPORTS} = $rule->{ENV_DEPS}
200      if exists $rule->{ENV_DEPS};
201    if( $Mpp::dry_run ) {
202      $rule->print_command( $rule->{COMMAND_STRING} );
203    } elsif( my $status = wait_for $rule->execute(
204	$rule->{COMMAND_STRING},
205	$rule->{TARGETS},
206	[map file_info( $_, $target->{'..'} ), split ' ', $rule->{DEPENDENCY_STRING}] ) ) {
207      print_error "Failed to build target", (@{$rule->{TARGETS}}>1 ? 's' : ''),
208	map( ' `'.absolute_filename( $_ )."'", @{$rule->{TARGETS}} ), " [$status]";
209      Mpp::log RULE_FAILED => $rule
210	if $Mpp::log_level;
211      $Mpp::error_found = $status
212	if $status =~ /^signal (?:$Mpp::int_signo|$Mpp::quit_signo)$/os;
213      $Mpp::error_found ||= $status # Remember the error status.  This will
214	unless $Mpp::keep_going;	# cause us to stop compilation as soon as
215				# possible.
216      $rule->{TARGET_STRING} =~ s/ /' `/g;
217      $Mpp::failed_count += $n_files;
218      unless( $temporary || $build_info->{COMMAND} =~ /\A\|FAILED\|/ ) {
219	for my $tinfo ( @{$rule->{TARGETS}} ) {
220	  substr $tinfo->{BUILD_INFO}{COMMAND}, 0, 0, '|FAILED|';
221	  Mpp::File::mark_build_info_for_update $tinfo;
222	}
223	&Mpp::File::update_build_infos;
224      }
225      last unless $Mpp::keep_going;
226    } else {
227      Mpp::log SUCCESS => $rule, $rule->{TARGETS}
228	if $Mpp::log_level;
229      $Mpp::n_files_changed += $n_files;
230      next if $temporary;
231      my $sig = $build_info->{SIG_METHOD_NAME}; # All targets have the same.
232      $sig &&= $rule->set_signature_class( $sig );
233      my $dep_sigs = '';	# Precalculate this for the targets loop
234      my $sep = '';
235      for my $dep ( split /\cA/, $build_info->{SORTED_DEPS} || '' ) {
236	$dep = path_file_info $dep, $rule->{MAKEFILE}{CWD};
237	$dep->{BUILD_INFO} ||= load_build_info_file $dep;
238	$dep_sigs .= $sep .
239	  (($sig ? $sig->signature( $dep ) : Mpp::File::signature $dep) || '');
240	$sep = "\cA";
241      }
242      for my $tinfo ( @{$rule->{TARGETS}} ) {
243	$build_info = $tinfo->{BUILD_INFO};
244	delete $tinfo->{LSTAT};
245	$build_info->{SIGNATURE} = Mpp::File::signature $tinfo;
246
247	if( $tinfo->{LINK_DEREF} && $tinfo->{LSTAT}[Mpp::File::STAT_NLINK] == 1 ) {
248				# Assume nlink > 1 to mean action only created
249				# a link to an already existing symlink.
250	  $build_info->{SYMLINK} = readlink absolute_filename $tinfo;
251	} else {
252	  delete $build_info->{SYMLINK}; # Unlikely to have changed, but just in case.
253	}
254	$build_info->{DEP_SIGS} = $dep_sigs;
255	$build_info->{BUILD_SIGNATURE} = $sig ? $sig->signature( $tinfo ) : $build_info->{SIGNATURE};
256	$build_info->{RESCAN} = 2; # Let makepp double check what we built.
257	Mpp::File::mark_build_info_for_update $tinfo;
258      }
259      &Mpp::File::update_build_infos;
260    }
261  }
262};
263
264__DATA__
265Usage: makeppreplay [-options] [VAR=value] targets
266
267Valid options are:
268
269-A filename, --args-file=filename, --arguments-file=filename
270    Read the file and parse it as possibly quoted whitespace- and/or
271    newline-separated options.
272-C directory, --directory=directory
273    Cd to the given directory before loading the makefile and trying to
274    build the targets.
275-c, --root-dir, --root-directory
276    Cd up to the directory containing a RootMakeppfile.
277-I directory, --include=directory, --include-dir=directory
278    Add directory to Perl load path @INC.
279-?, -h, --help
280    Print out a brief summary of the options.
281-k, --keep-going
282    Build as many files as possible, even if some of them have errors.
283-M module[=arg,...], --module=module[=arg,...]
284    Load module and import any functions it exports.
285-n, --dry-run, --just-print, --recon
286    Print out commands without actually executing them.
287--no-print-directory
288    Turn off the entering or leaving directory messages.
289-t, --temporary
290    Makeppreplay modifies the build info of all files it touched and of all
291    dependencies it found modified.
292-V, --version
293    Print out the version number.
294--no-warn
295    Don't print any warning messages.
296
297Look at @htmldir@/makeppreplay.html for wrapper details,
298or at http://makepp.sourceforge.net/@BASEVERSION@/makeppreplay.html
299or type "man makeppreplay".
300