#!/usr/bin/perl -w
# multidelta; see License.txt for copyright and terms of use

# ****************
# driver for delta, which works with multiple input files
# Scott McPeak smcpeak@cs.berkeley.edu

# the goal here is to be able to minimize in-place: given
# a fragment of a build process which exhibits some failure,
# minimize the relevant files without disturbing the build
# process fragment itself

use strict 'subs';
use FindBin;

$delta          = "$FindBin::Bin/delta";
$topformflat    = "$FindBin::Bin/topformflat";
$log            = "multidelta.log";

$level          = 0;
$undo           = 0;

if (@ARGV == 0) {
  print(<<"EOF");
Multidelta version 2003.7.14

usage: $0 [options] test-script file [file [...]]

The collection of input files is minimized as much as possible, such
that the test-script continues to return true.

When the script is run with a source file as an argument, it should do
any single-file integrity checks on that file.  Then, it should
proceed to check the entire build, on the assumption that all other
files have already passed their integrity checks.

When the script is run with no arguments, it should check integrity of
all files and then check the build.

So that the script does not need to have the list of input files
hard-coded in, the environment variable "multidelta_all_files" is
always set to a space-separated list of the filenames no matter how
the script is run.

Options:
  -level=n       When topformflattening, flatten to level n [$level]
  -u             Undo the last invocation, by copying the *.bak files
                 onto the original copies.

EOF
  exit(0);
}

while ($ARGV[0] =~ m"^-") {
  if ($ARGV[0] =~ m"^-level=") {
    ($level) = ($ARGV[0] =~ m"=(\d+)");
  }
  elsif ($ARGV[0] eq "-u") {
    $undo = 1;
  }
  else {
    die ("unknown option: $ARGV[0]\n");
  }
  shift(@ARGV);
}


$script = $ARGV[0];
shift(@ARGV);
@files = @ARGV;
for my $f(@files) {
    die "Input file contains a space '$f'\n"
        if $f=~/ /;             # multidelta_all_files won't work if filenames contain spaces.
}
# So the script knows all the files even if it is called in a mode
# where not all of them are passed in.
$ENV{"multidelta_all_files"} = join(" ", @files);


if ($undo) {
  # copy the .bak files back
  for $fn (@files) {
    diagnostic("reverting to backup copy of $fn");
    run("cp -f ${fn}.bak $fn");
  }
  exit(0);
}


diagnostic("verify the script passes now");
if (system($script) != 0) {
  die "Initial test fails\n";
}

# for every input file, make a backup copy, and produce
# another version with all comments and preprocessor
# directives stripped, and toplevel forms flattened
for $fn (@files) {
  diagnostic("making a backup copy of $fn");
  run("cp -f $fn ${fn}.bak");

  # -P: don't emit #line directives
  diagnostic("preprocessing and flattening $fn");
  run("cpp -P ${fn}.bak | $topformflat $level >$fn");
}

diagnostic("verify still passes script");
if (system($script) != 0) {
  die "Test fails after applying cpp and topformflat\n";
}

# one by one, apply delta
for $fn (@files) {
  diagnostic("applying delta to $fn");
  run("$delta -in_place -test=$script $fn");
  
  if (-f "DELTA-STOP") {
    diagnostic("Stopping because DELTA-STOP exists");
    exit(0);
  }
}

print("multidelta is finished\n");
exit(0);


# -------------------- subroutines --------------------
sub diagnostic {
  print(@_, "\n");

  open (LOG, ">>$log");
  print LOG (@_, "\n");
  close (LOG);
}


sub run {
  diagnostic(@_);
  if (system(@_) != 0) {
    die "failed: " . join(' ', @_) . "\n";
  }
}
