Chris Rankin <net.bellsouth@{no.spam}rankinc> writes:
> I find that the single biggest pain when programming in C/C++ is
> configuring the Makefile. I can manage the job by hand for a small
> program, but for a large program or set of programs this quickly becomes
> impossible. I CANNOT be the only person to have this problem, so I guess
> that there must be a number of solutions out there already. I have
> looked at sunsite's GNU Make tools, but saw nothing obvious. Can
> somebody recommend a package and give me a website to download it from,
> please?
I've been really frustrated with this too, and found several solutions.
The simplest is a perl script I wrote to determine makefile
dependencies. It's much faster than makedepend or using gcc -M, because
it only rescans files that have changed. However, it doesn't run the C
preprocessor, so if you do something like
#if 0
#include "junkfile.h"
#endif
it will still think you included junkfile.h. It ignores any includes
with angle brackets, e.g., "#include <stdio.h>", since those files don't
change.
Since it's fast, you can run it on every invocation of make; you never
again can forget to run "make depend" manually. I've enclosed the
script (called "update_dependencies.pl") after my signature file. To
use it, you simply append to your makefile lines that look like this:
#
# This line causes the dependencies list to be updated automatically, so make
# knows without being told what .h files each .cxx file depends on.
#
dependencies.mk: $(wildcard *.cxx *.h)
@update_dependencies.pl dependencies.mk -obj_prefix . \
$(ADDL_CXX_FLAGS) $^
# ADDL_CXX_FLAGS contains any -I flags you need, so the dependency
# scanner can find your include files.
-include dependencies.mk # This forces make to include the
# generated dependency list, and to
# update it automatically if anything changed.
--
Gary Holt (213)-740-3397
h...@lnc.usc.edu http://lnc.usc.edu/~holt/
#!/usr/local/bin/perl
#
# This perl script updates the list of dependencies for a given set of C
# sources. It is intended to be sufficiently fast that it can be run every
# time you run make.
#
# It simply scans files for '#include "file.h"', and scans those files, etc.
# It maintains a database of the last time that it scanned each file, so
# it only scans files which have changed.
#
# Usage:
# update_dependencies.pl [-Iinclude-directory] Makefile *.cxx *.c
#
# This updates the dependency information in the Makefile. Alternatively,
# you can store the dependency information in a separate file and have the
# Makefile include it.
#
@include_path = ('./'); # The default list of directories to search.
# Each directory must be followed by a
# trailing slash.
$obj_prefix = ''; # No prefix to add to object files.
#
# Parse the argument list:
#
@files = (); # No files to parse yet.
while ($_ = shift(@ARGV)) { # Get the next argument.
if (/^-I(.*)$/) { # Specify include path?
unshift(@include_path, "$1/"); # Remember the path.
} elsif (/^-obj_prefix$/) { # Object file directory specified?
$obj_prefix = shift(@ARGV) . "/"; # Remember it.
} elsif (/^-/) { # Ignore other compiler options.
next;
} else { # Unrecognized name, must be a file name.
push(@files, $_);
}
}
$dependency_file = shift(@files); # Take the first file specified as the
# file we're going to update.
$update_time = time; # Remember when we started to update this file.
%includes = (); # Array containing a list of the files that
# each file directly includes.
%file_scanned = (); # The time when the file was last scanned.
%not_found = (); # Array used to make sure we only print error
# messages about files not found once.
if (open(INFILE, "$dependency_file")) { # Does the input file exist?
rename($dependency_file, "$dependency_file~") ||
die("Rename of $dependency_file to $dependency_file~ failed--$!\n");
# Make a backup copy of the file.
open(OUTFILE, ">$dependency_file") ||
die("Can't create new $dependency_file--$!\n");
#
# Copy everything above our special header to the output file verbatim:
#
while (defined($_ = <INFILE>)) {
last if (/^\#\# Output of update_dependencies.pl below this line \#\#$/);
# Did we find our special header line?
print OUTFILE $_; # Just copy the line verbatim.
}
#
# Now we've gotten to the point in our file where we stored the dependency
# list. Read it in:
#
while (defined($_ = <INFILE>)) {
next unless s/^\#\#//; # If it doesn't begin with two number signs,
# we're not interested in it.
#
# Lines with this format contain the file name in the first column, the
# time it was last scanned in the second, and the list of files that it
# explicitly includes (not the ones included by those include files).
#
my ($file, $scan_time, @includes) = split(' ', $_);
# Extract all the information.
$includes{$file} = \@includes; # Remember this list of files.
$file_scanned{$file} = $scan_time; # Remember when it was scanned.
}
close(INFILE); # Done with the input.
}
else # No input file:
{
warn("No input file $dependency_file--creating it\n");
open(OUTFILE, ">$dependency_file") || # Make the output file.
die("Can't create new $dependency_file--$!\n");
}
#
# Now we've read the input file. Scan each of the files we're supposed to.
# Note that we don't scan any files that we aren't told to.
#
print OUTFILE "## Output of update_dependencies.pl below this line ##
# Do not not modify these two comment lines or anything below them.
";
# Make a header to separate our output from
# the rest of the file.
foreach $file (@files) {
my $obj_fname;
($obj_fname .= $file) =~ s/\.[^.]+$/.o/; # Form the name of the .o file.
$obj_fname =~ s@.*/@@ # Strip out any path
if $obj_prefix; # if an object directory was specified.
$obj_fname = canonicalize_filename($obj_prefix . $obj_fname);
# Mark the location of the output file.
print OUTFILE "$obj_fname : ", join(' ', &all_includes($file)), "\n\n";
# Output the result.
}
#
# Now output our internal information back into the file so we know what
# we have scanned in the past:
#
foreach $file (keys %includes) {
print OUTFILE "##$file $file_scanned{$file} ",
join(' ', sort @{$includes{$file}}), "\n";
}
close(OUTFILE);
#
# The following subroutine returns all the files that a given file includes.
# Arguments:
# 1) The file name.
# 2) (Optional) whether this is being called recursively or not.
#
# Returns:
# An array of all the files that will be referenced when the C
# preprocessor is run on this file.
#
# Side effects:
# Updates the %includes and %file_scanned arrays as necessary.
#
sub all_includes {
my ($file, $recurse_flag) = @_; # Name the arguments.
%already_returned_includes = () if (!$recurse_flag);
$file = canonicalize_filename($file); # Make sure the file name is
# of the canonical form.
return () if $already_returned_includes{$file}; # Don't get stuck in a loop
# if the file is included recursively.
# (Presumably preprocessor directives that
# we don't see eliminate the recursion.)
$already_returned_includes{$file} = 1; # Remember not to do anything for this
# file again.
my %this_include_list; # Keys of this array are the files that it
# includes. It's an associative array so we
# can remove duplicate names.
my $ftime = (stat($file))[9]; # Get the file's time.
unless (defined($ftime)) {
warn "Can't stat $file--$!\n";
return ();
}
if ($ftime > ($file_scanned{$file} || 0)) { # Do we need to rescan this file?
$includes{$file} = &scan_file($file); # Remember which files it includes.
$file_scanned{$file} = $update_time; # Remember when we scanned it.
}
foreach (@{$includes{$file}}) { # Look at each one of these files.
my $canon_name = find_file_in_path($_, \@include_path);
# Get the full name for the file.
if (!$canon_name) { # File not found?
warn("File $_ not found in include path\n") unless $not_found{$_}++;
# Warn once about the file.
next; # Just skip it.
}
$this_include_list{$canon_name} = 1; # Remember that this file is included.
my @addl_includes = &all_includes($canon_name, 1); # Get all the files that
# this includes as well.
@this_include_list{@addl_includes} = (1) x @addl_includes;
# Remember those files as well.
}
keys %this_include_list; # Return the list of files.
}
#
# The following subroutine performs a quick scan on a file, looking only for
# "#include", and returns a list of files that it includes. It does not
# search the path for these files.
#
# Arguments:
# 1) The file name.
#
# Returns a reference to an array of the file names specified by
# '#include "file"'. It does not return the file names specified by
# '#include <file>' since those are system files.
# This subroutine does not attempt to locate the files in the path.
#
# Side effects:
# None.
#
sub scan_file {
my ($file) = @_; # Name the arguments.
local (*INFILE); # Make a local file handle.
local ($_);
my @directly_included_files;
if (open(INFILE, $file)) { # Open the file.
print STDERR "Scanning file $file\n";
while (defined($_ = <INFILE>)) {
next unless /^\s*\#\s*include\s+\"([^\" ]+)\"/; # Ignore if this line
# isn't a #include.
push(@directly_included_files, $1); # Remember the name.
}
close(INFILE); # Done with this file.
} else { # File not found:
warn("Can't scan file $file--$!\n");
}
\@directly_included_files;
}
#
# The following subroutine scans a path for a given file.
# Arguments:
# 1) The file to scan for.
# 2) The path. Each element of the path should have the trailing slashes
# (on unix), or trailing colons/directory specification on VMS.
#
# Returns the canonicalized file name if the file was found, or undef if not.
#
sub find_file_in_path {
my ($file, $path) = @_; # Name the arguments.
foreach
...
read more »