#! /bin/sh --
eval '(exit $?0)' && eval 'PERL_BADLANG=x;export PERL_BADLANG;: \
;exec perl -T -S -- "$0" ${1+"$@"};#'if 0;
eval 'setenv PERL_BADLANG x;exec perl -x -S -- "$0" $argv:q;#'.q
#!perl -w
+($0=~/(.*)/s);do$1;die$@if$@;__END__+if 0;
# Don't touch/remove lines 1--7: http://www.inf.bme.hu/~pts/Magic.Perl.Header
#
# ccdep.pl v0.32 -- semiautomatic dependency discovery for C/C++ programs
# by pts@math.bme.hu at Fri May 31 13:36:29 CEST 2002
# 0.31 by pts@math.bme.hu at Sat Jun  1 15:19:55 CEST 2002
# 0.32 by pts@math.bme.hu at Tue Sep  3 19:12:20 CEST 2002
# 0.33 by pts@math.bme.hu at Thu Oct 31 09:47:25 CET 2002
#
# Dat: no -T (tainting checks) anymore, does bad to our readpipe()
# OK : emit `TARGETS_yes = ...'
# Imp: make #warning etc. in *.h files work as expected
# OK : generated.h
# Imp: add external libraries (-L...)
# Imp: abort if gcc command not found...
# Imp: abort if not all .o are mentioned in gcc output
# Imp: avoid $ etc. in Makefile
# OK : all 8 combinations of PROVIDES|REQUIRES|CONFLICTS
#
BEGIN { eval { require integer; import integer } }
BEGIN { eval { require strict ; import strict  } }

# Typical invocation: ccdep.pl --FAL=assert,no,yes,checker $(CXX)
my @FAL=();
if (@ARGV and $ARGV[0]=~/\A--FAL=(.*)/s) { @FAL=split/\W+/,$1; shift@ARGV }
my $GCCP; # 'g++' or 'gcc': C/C++ preproc with `-E -MG' switches
$GCCP="@ARGV";
$GCCP="gcc" if $GCCP!~y/ \t//c;

# Make sure we get English error messages from gcc.
delete $ENV{LANG};
delete $ENV{LANGUAGE};
$ENV{LC_ALL} = "C";

# ---

sub find_ds() {
  #** @return a list of .ds file in the current directory
  my @L;  my $E;
  die unless opendir DIR, '.';
  while (defined($E=readdir DIR)) {
    push @L, $E if $E=~/\.(c(|c|xx|pp|[+][+])|C)\Z(?!\n)/ and -f $E;
  }
  @L
}

sub expand_glob($$ $) {
  #** @param $_[0] string containing globs (? and *)
  #** @param $_[1] listref of names
  #** @param $_[2] dest listref
  my $S=quotemeta($_[0]);
  if (0==($S=~s@\\([?*])@.$1@g)) { push @{$_[2]}, $_[0]; return }
  my $RE=eval"+sub{\$_[0]=~/$S/}";
  die$@if$@;
  for my $E (@{$_[1]}) { push @{$_[2]}, $E if $RE->($E) }
}

sub mustbe_subset_of($$ $$) {
  #** A must be a subset (sublist) of B
  #** @param $_[0] caption A
  #** @param $_[1] ref(array(string)) A
  #** @param $_[2] caption B
  #** @param $_[3] ref(array(string)) B
  my @A=sort @{$_[1]};
  my @B=sort @{$_[3]};
  my($AV,$BV);
  while (defined($AV=pop@A)) {
    1 while defined($BV=pop@B) and $BV gt $AV;
    if (!defined($BV) or $BV ne $AV) {
      print STDERR "$0: $_[0] are: @{$_[1]}\n";
      print STDERR "$0: $_[2] are: @{$_[3]}\n";
      die "$0: $_[0] must be a subset of $_[2]\n";
    }
  }
}

# ---

print "$0: running.\n";

sub unix_shq($) {
  my $S=$_[0];
  $S=~s@'@'\\''@g;
  "'$S'"
}

sub shq($) {
  my $S=$_[0];
  return $S if $S!~/[^\w.-]/;
  if ($^O eq 'MSWin32') {
    # assume the called program is CygWin/Ming32; see later
    # Arguments are delimited by white space, which is either a space or a tab.
    # .       A string surrounded by double quotation marks is interpreted as a
    # single argument, regardless of white space contained within. A quoted
    # string can be embedded in an argument. Note that the caret (^) is not
    # recognized as an escape character or delimiter. 
    # .       A double quotation mark preceded by a backslash, \", is interpreted as
    # a literal double quotation mark (").
    # .       Backslashes are interpreted literally, unless they immediately precede
    # a double quotation mark.
    # .       If an even number of backslashes is followed by a double quotation
    # mark, then one backslash (\) is placed in the argv array for every pair
    # of backslashes (\\), and the double quotation mark (") is interpreted as
    # a string delimiter.
    # .       If an odd number of backslashes is followed by a double quotation
    # mark, then one backslash (\) is placed in the argv array for every pair
    # of backslashes (\\) and the double quotation mark is interpreted as an
    # escape sequence by the remaining backslash, causing a literal double
    # quotation mark (") to be placed in argv.
    $S=~s@"@\\"@g; return qq{"$S"}
  } else {
    $S=~s@'@'\\''@g; return qq{'$S'}
  }
}

sub backtick(@) {
  my $S=$_[0];
  if ($^O eq 'MSWin32') {
    # assume the called program is CygWin/Ming32; and we have proper /bin/sh
    $S="sh -c ".unix_shq($S); # /bin/sh can handle IO-redirections such as `2>&1' right
  } else {
    # assume UNIX
  }
  print "+ $S\n";
  #die unless $ENV{PATH}=~/(.*)/s; # Dat: untaint()
  #$ENV{PATH}=$1;
  #die "$ENV{PATH}";
  # Dat: if `.' is part of $ENV{PATH}, `Insecure directory...' is reported
  #die unless $S=~/(.*)/s; # Dat: untaint()
  readpipe $S # `` qx``
}

my @DS=find_ds();
my @DSQ=map{shq$_}@DS;
my $DIAG=" -fno-diagnostics-show-caret";
my $Q="$GCCP -DOBJDEP$DIAG -M -MG -E 2>&1 @DSQ";
my $R=backtick($Q);
if ($R=~/\berror: .*-fno-diagnostics-show-caret\b/) {
  # gcc-4.6 and earlier don't have this flag, and they fail.
  $Q=~s@ -fno-diagnostics-show-caret(?=\s)@@;
  $DIAG="";
  $R=backtick($Q);
}

if ($R!~/: warning: #warning\b/) {
  # config2.h:314:4: warning: #warning REQUIRES: c_lgcc3.o
  # Dat: g++-3.3 ignores #warning with -M -MG -E
  $R.="\n".backtick("$GCCP -DOBJDEP$DIAG -E 2>&1 >/dev/null @DSQ");
}

## die $R;

#** $pro{"x.ds"} is the list of features provided by "x.ds"; multiplicity
my %pro;
#** $req{"x.ds"} is the list of features required by "x.ds"; multiplicity
my %req;
#** $con{"x.ds"} is the list of features conflicted by "x.ds"; multiplicity
my %con;
#** $repro{"feature"} is a hash=>1 of .ds that provide "feature"
my %repro;
#** $mapro{"executable"} eq "x.ds" if "x.ds" provides main() for "executable"
my %mapro;
#** $idep{"x"} contains the the dependency line for x.ds
my %idep;
#** hash=>1 of "feature"s of NULL-PROVIDES
my %nullpro;
my $included_from;

my $BS; # undef
while ($R=~/\G(.*)\n?/g) {
  my $S=$1;

  if ($S=~/\\\Z/) { if(defined$BS){$BS.="\n$S"}else{$BS=$S} next }
  if (defined $BS) { $S="$BS\n$S"; undef $BS }
  
  if (!length$S) {
  } elsif ($S=~/\AIn file included from ([^:]+)/) {
    $included_from=$1;
  } elsif ($S=~/\A\s{3,}from ([^:]+)/) {
    # ^^^ gcc-3.2
    $included_from=$1;
  } elsif ($S=~/\A([^:]+):\d+:(\d+:)? warning: #warning (NULL-PROVIDES|PROVIDES|CONFLICTS|REQUIRES):(.*)\Z/) {
    # ^^^ (\d+:)? added for gcc-3.1
    # print STDERR "[$S]\n";
    my($DS,$B)=($1,$3);
    if (defined $included_from) { $DS=$included_from; undef $included_from }
    # print STDERR "[$DS] [$B] [$4]\n";
    if ($B eq 'NULL-PROVIDES') {
      for my $FEAT (split' ',$4) { $nullpro{$FEAT}=1 }
    } elsif ($B eq 'PROVIDES') {
       my @L=split' ',$4;
       push @{$pro{$DS}}, @L;
       push @{$con{$DS}}, @L;
       for my $FEAT (@L) {
         $repro{$FEAT}{$DS}=1;
         if ($FEAT=~/\A(.*)_main\Z/) { $mapro{$1}=$DS }
       }
    } elsif ($B eq 'REQUIRES') {
      push @{$req{$DS}}, split' ',$4;
    } elsif ($B eq 'CONFLICTS') {
      push @{$con{$DS}}, split' ',$4;
    } else { die }
    # print "$1;$2;@L;\n";
    undef $included_from;
  } elsif ($S=~/\A([^: ]+)\.o: (([^: ]+?)\.([^ \n]+).*)\Z/s and $1 eq $3) {
    # print STDERR "($S)\n";
    my($A,$B,$C)=("$1.o",$2,"$3.$4");
    die "$0: multi gcc output: $A" if exists $idep{$B};
    # ^^^ Dat: maybe both t.c and t.cxx
    $B=~s@ /[^ ]+@@g; # remove absolute pathnames
    $B=~s@( \\\n)+@ \\\n@g;
    $B=~s@ \\\n\Z@@;
    $B=~s@^ +\\\n@@gsm; # what was left from absolute pathnames
    $B=~s@ *\\\n *\Z(?!\n)@@gsm; # what was left from absolute pathnames
    $idep{$C}=$B;
    # vvv automatic `t.ds: PROVIDES: t.o'
    # print "::: $C $A\n";
    push @{$pro{$C}}, $A;
    push @{$con{$C}}, $A;
    $repro{$A}{$C}=1;
    undef $included_from;
  } elsif ($S=~/\A([^:]+):\d+: .*\Z/) {
    print "# $S\n";
    undef $included_from;
  } elsif ($S=~/\A([^:]+):\d+:(\d+:)? warning: ".*?" redefined\Z/) {
    # ^^^ gcc-3.1
    undef $included_from;
  } elsif ($S=~/\A([^:]+):\d+:(\d+:)? warning: this is the location of /) {
    # ^^^ gcc-3.1
    undef $included_from;
  } elsif ($S=~/: No such file or directory$/) {  # Depends on $ENV{LC_ALL}.
    # ^^^ gcc-3.3
    undef $included_from;
  } elsif ($S eq 'compilation terminated.') {  # Useless message, ignore.
  } elsif ($S=~/^distcc\[/) {  # Useless message, ignore.
  } elsif ($S=~/^ *#/) {  # Useless message from gcc 4.8, ignore.
  } elsif ($S=~/^ *\^/) {  # Useless message from gcc 4.8, ignore.
  } else {
    die "$0: invalid depret: [$S]\n";
  }
}

mustbe_subset_of "providers"=>[keys(%pro)], "dep_sources"=>[keys(%idep)];
mustbe_subset_of "dep_sources"=>[keys(%idep)], "sources"=>\@DS;

{ my @K=keys %repro;
  while (my($DS,$L)=each%con) {
    my @R=();
    for my $FEAT (@$L) { expand_glob $FEAT, \@K, \@R }
    # ^^^ multiplicity remains
    $con{$DS}=\@R;
  }
}

die unless open MD, "> Makedep";
die unless print MD '
ifndef CCALL
CCALL=$(CC) $(CFLAGS) $(CFLAGSB) $(CPPFLAGS) $(INCLUDES)
endif
ifndef CXXALL
CXXALL=$(CXX) $(CXXFLAGS) $(CXXFLAGSB) $(CPPFLAGS) $(INCLUDES)
endif
ifndef LDALL
LDALL=$(LDY) $(LDFLAGS) $(LIBS)
endif
ifndef CC
CC=gcc
endif
ifndef CXX
CXX=g++
endif
ifndef LD
LD=$(CC) -s
endif
ifndef LDXX
LDXX=$(CXX) -s
endif
ifndef LDY
LDY=$(LD)
endif
ifndef CFLAGS
CFLAGS=-O2 -W -Wall -fsigned-char
endif
ifndef CXXFLAGS
CXXFLAGS=-O2 -W -Wall -fsigned-char
endif
ifndef GLOBFILES
GLOBFILES=Makefile Makedep
endif
';

die unless print MD "ALL +=", join(' ',keys%mapro), "\n"; 
die unless print MD "TARGETS =", join(' ',keys%mapro), "\n"; 

# vvv Thu Oct 31 09:49:02 CET 2002
# (not required)
#my %targets_fal;
#for my $FA (@FAL) { $targets_fal{$FA}="TARGETS_$FA =" }

while (my($EXE,$MDS)=each%mapro) {
  print "exe $EXE\n";
  my @FEA2BA=("${EXE}_main");
  my @DSO=(); # list of .o files required
  my @DSL=(); # list of .ds files required
  my %CON=(); # hash=>1 of features already conflicted
  my %PRO=%nullpro; # hash=>1 of features already provided
  my $FEAT;
  
  while (defined($FEAT=pop@FEA2BA)) {
    next if exists $PRO{$FEAT};
    # print " feat $FEAT (@FEA2BA)\n"; ##
    # vvv Dat: r.b.e == required by executable
    die "$0: feature $FEAT r.b.e $EXE conflicts\n" if exists $CON{$FEAT};
    my @L=keys%{$repro{$FEAT}};
    die "$0: feature $FEAT r.b.e $EXE unprovided\n" if!@L;
    die "$0: feature $FEAT r.b.e $EXE overprovided: @L\n" if$#L>=1;
    # Now: $L[0] is a .ds providing the feature
    push @DSL, $L[0];
    my $O=$L[0]; $O=~s@\.[^.]+\Z@.o@;
    push @DSO, $O;

    $PRO{$FEAT}=1;
    for my $FEAT2 (@{$pro{$L[0]}}) {
      die "$0: extra feature $FEAT2 r.b.e $EXE conflicts\n" if exists $CON{$FEAT2} and not exists $PRO{$FEAT2};
      $PRO{$FEAT2}=1;
    }
    for my $FEAT2 (@{$req{$L[0]}}) { push @FEA2BA, $FEAT2 if!exists $PRO{$FEAT2} }
    for my $FEAT2 (@{$con{$L[0]}}) { $CON{$FEAT2}=1 }
    # die if! exists $PRO{$FEAT}; # assert
  }

  die unless print MD "${EXE}_DS=@DSL\n".
    "$EXE: \$(GLOBFILES) @DSO\n\t\$(LDALL) @DSO -o $EXE\n\t".
    q!@echo "Created executable file: !.$EXE.
    q! (size: `perl -e 'print -s "!.$EXE.q!"'`)."!. "\n";
  # vvv Sat Jun  1 15:40:19 CEST 2002
  for my $FA (@FAL) {
    die unless print MD "$EXE.$FA: \$(GLOBFILES) \$(${EXE}_DS)\n\t".
      "\$(CXD_$FA) \$(CXDFAL) \$(${EXE}_DS) -o $EXE.$FA\n";
    # $targets_fal{$FA}.=" $EXE.$FA";
  }
}
print MD "\n";

# vvv Thu Oct 31 09:49:02 CET 2002
# (not required)
# for my $FA (@FAL) { print MD "$targets_fal{$FA}\n" }
# print MD "\n";

while (my($K,$V)=each%idep) {
  my $O=$K; $O=~s@\.([^.]+)\Z@.o@;
  print MD "$O: \$(GLOBFILES) $V\n\t\$(", ($1 eq'c'?"CC":"CXX"), "ALL) -c $K\n"
}
print MD "\n";

die unless close MD;

print "$0: done.\n";

__END__
