The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

# This program is like "makedepend", except that it uses gcc/g++ to
# generate the dependencies. The benefit is C++ support, which
# makedepend doesn't have, and the ability to handle bugs in different
# versions of gcc/g++. Call it like:
# makedependgcc g++ -M -Idir1 -Idir2 input.cc -o input.o

my $VERSION = 0.20;

use strict;
use Getopt::Std;

die usage() if !@ARGV || $ARGV[0] =~ /--help/;

my %opts = Get_Options();

die usage() if $opts{'h'};

my $dependencies = Get_Dependencies(@ARGV);

unless ($opts{'f'})
{
  print $dependencies;
  exit;
}

my $makefile_name = Get_Makefile_Name();

my $makefile_text = Get_Makefile_Text($makefile_name);

$makefile_text = Remove_Old_Dependencies($makefile_text)
  unless $opts{'a'};

if ($opts{'r'})
{
  Write_Makefile_Text($makefile_name, $makefile_text);
  exit;
}

Back_Up_Makefile($makefile_name) unless $opts{'B'};

$makefile_text = Add_Dependencies($makefile_text, $dependencies);

Write_Makefile_Text($makefile_name, $makefile_text);

exit;

# ----------------------------------------------------------------------------

sub usage
{
  <<"  EOF";
usage: $0 [FLAGS] -- <g++ compile line with -M flag, but no -o flag>
-a Append dependencies to makefile
-r Just remove dependencies from makefile and exit
-f Specify makefile name
-B Suppress backup file

If -f is not specified, output is printed to standard output.
  EOF
}

# ----------------------------------------------------------------------------

sub Get_Dependencies
{
  my @commands = @_;

  my $dependencies = `@commands`;
  exit 1 if $?;

  my ($output_filename) = "@commands" =~ /-o (\S+)/;

  # New versions of g++ are buggy. They write the output to the file
  # specified by the -o flag instead of dumping to the screen. Plus the -o
  # target does not have directories--i.e. -o build/foo.o ends up as "foo.o:"
  # instead of "build/foo.o:"
  if ($dependencies !~ /\b\Q$output_filename\E\s*:/)
  {
    open OUTPUT, $output_filename
      or die "Can't read dependencies from $output_filename: $!";
    local $/ = undef;
    $dependencies = <OUTPUT>;
    close OUTPUT;

    unlink $output_filename;

    # The first dependency should have the incorrect target
    $dependencies =~ s/(.*?)(\s*:)/$output_filename$2/;
  }

  return $dependencies;
}

# ----------------------------------------------------------------------------

sub Get_Options
{
  my %opts;

  $opts{'a'} = $opts{'r'} = $opts{'B'} = 0;

  getopt('f',\%opts);

  return %opts;
}

# ----------------------------------------------------------------------------

sub Get_Makefile_Name
{
  my $makefile_name;

  if (defined $opts{'f'})
  {
    $makefile_name = $opts{'f'};
  }
  else
  {
    $makefile_name =
      -e 'GNUMakefile' ? 'GNUMakefile' : -e 'makefile' ? 'makefile' : -e 'Makefile' ? 'Makefile' : undef;
  }

  die "Couldn't find makefile named \"$makefile_name\"!\n"
    unless -e $makefile_name;

  return $makefile_name
}

# ----------------------------------------------------------------------------

sub Back_Up_Makefile
{
  my $makefile_name = shift;

  if (-e "$makefile_name.bak")
  {
    unlink "$makefile_name.bak"
      or die "Couldn't remove $makefile_name.bak: $!\n";
  }

  system("cp $makefile_name $makefile_name.bak") == 0
    or die "Couldn't copy $makefile_name to $makefile_name.bak: $!\n";
}

# ----------------------------------------------------------------------------

sub Get_Makefile_Text
{
  my $makefile_name = shift;

  open MAKEFILE, "$makefile_name"
    or die "Couldn't open $makefile_name: $!\n";
  local $/ = undef;
  my $makefile_text = <MAKEFILE>;
  close MAKEFILE;

  return $makefile_text;
}

# ----------------------------------------------------------------------------

sub Remove_Old_Dependencies
{
  my $makefile_text = shift;

  $makefile_text =~ s/\s*\n# DO NOT DELETE\s*\n.*$/\n/s;

  return $makefile_text;
}

# ----------------------------------------------------------------------------

sub Add_Dependencies
{
  my $makefile_text = shift;
  my $dependencies = shift;

  $makefile_text .= "\n# DO NOT DELETE\n"
    unless $makefile_text =~ /\n# DO NOT DELETE\n/;
  $makefile_text .= "\n$dependencies";

  return $makefile_text;
}

# ----------------------------------------------------------------------------

sub Write_Makefile_Text
{
  my $makefile_name = shift;
  my $makefile_text = shift;

  open MAKEFILE, ">$makefile_name"
    or die "Couldn't open $makefile_name for writing: $!\n";
  print MAKEFILE $makefile_text;
  close MAKEFILE;
}