#!/usr/bin/env perl # # Copyright (c) 2011-2014 Todd C. Miller # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # use File::Temp qw/ :mktemp /; use Fcntl; use warnings; die "usage: $0 Makefile ...\n" unless $#ARGV >= 0; my @incpaths; my %dir_vars; my %implicit; my %generated; # Read in MANIFEST fail if present my %manifest; if (open(MANIFEST, ") { chomp; next unless /([^\/]+\.[cly])$/; $manifest{$1} = $_; } } foreach (@ARGV) { mkdep($_); } sub mkdep { my $file = $_[0]; $file =~ s:^\./+::; # strip off leading ./ my $makefile; if (open(MF, "<$file")) { local $/; # enable "slurp" mode $makefile = ; } else { warn "$0: $file: $!\n"; return undef; } close(MF); # New makefile, minus the autogenerated dependencies my $separator = "# Autogenerated dependencies, do not modify"; my $new_makefile = $makefile; $new_makefile =~ s/${separator}.*$//s; $new_makefile .= "$separator\n"; # Old makefile, join lines with continuation characters $makefile =~ s/\\\n//mg; # Expand some configure bits $makefile =~ s:\@DEV\@::g; $makefile =~ s:\@COMMON_OBJS\@:aix.lo event_poll.lo event_select.lo:; $makefile =~ s:\@SUDO_OBJS\@:openbsd.o preload.o selinux.o sesh.o solaris.o sudo_noexec.lo:; $makefile =~ s:\@SUDOERS_OBJS\@:bsm_audit.lo linux_audit.lo ldap.lo solaris_audit.lo sssd.lo:; # XXX - fill in AUTH_OBJS from contents of the auth dir instead $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:; $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo fnmatch.lo getaddrinfo.lo getcwd.lo getgrouplist.lo getline.lo getopt_long.lo glob.lo inet_ntop_lo inet_pton.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo strtonum.lo utimens.lo:; # Parse OBJS lines my %objs; while ($makefile =~ /^[A-Z0-9_]*OBJS\s*=\s*(.*)/mg) { foreach (split/\s+/, $1) { next if /^\$[\(\{].*[\)\}]$/; # skip included vars for now $objs{$_} = 1; } } # Find include paths @incpaths = (); while ($makefile =~ /-I(\S+)/mg) { push(@incpaths, $1) unless $1 eq "."; } # Check for generated files if ($makefile =~ /GENERATED\s*=\s*(.+)$/m) { foreach (split(/\s+/, $1)) { $generated{$_} = 1; } } # Values of srcdir, top_srcdir, top_builddir, incdir %dir_vars = (); $file =~ m:^(.*)/+[^/]+:; $dir_vars{'srcdir'} = $1 || '.'; $dir_vars{'devdir'} = $dir_vars{'srcdir'}; $dir_vars{'authdir'} = $dir_vars{'srcdir'} . "/auth"; $dir_vars{'top_srcdir'} = '.'; #$dir_vars{'top_builddir'} = '.'; $dir_vars{'incdir'} = 'include'; # Find implicit rules for generated .o and .lo files %implicit = (); while ($makefile =~ /^\.c\.(l?o):\s*\n\t+(.*)$/mg) { $implicit{$1} = $2; } # Find existing .o and .lo dependencies my %old_deps; while ($makefile =~ /^(\w+\.l?o):\s*(\S+\.c)/mg) { $old_deps{$1} = $2; } # Sort files so we do .lo files first foreach my $obj (sort keys %objs) { next unless $obj =~ /(\S+)\.(l?o)$/; if ($2 eq "o" && exists($objs{"$1.lo"})) { # If we have both .lo and .o files, make the .o depend on the .lo $new_makefile .= sprintf("%s: %s.lo\n", $obj, $1); } else { # Use old depenencies when mapping objects to their source. # If no old depenency, use the MANIFEST file to find the source. my $src = $1 . '.c'; my $ext = $2; if (exists $old_deps{$obj}) { $src = $old_deps{$obj}; } elsif (exists $manifest{$src}) { $src = $manifest{$src}; foreach (sort { length($b) <=> length($a) } keys %dir_vars) { next if $_ eq "devdir"; last if $src =~ s:^\Q$dir_vars{$_}/\E:\$\($_\)/:; } } else { warn "$file: unable to find source for $obj\n"; } my $imp = $implicit{$ext}; $imp =~ s/\$ 80) { my $off = 0; my $indent = length($obj) + 2; while (length($deps) - $off > 80 - $indent) { my $pos; if ($off != 0) { $new_makefile .= ' ' x $indent; $pos = rindex($deps, ' ', $off + 80 - $indent - 2); } else { $pos = rindex($deps, ' ', $off + 78); } $new_makefile .= substr($deps, $off, $pos - $off) . " \\\n"; $off = $pos + 1; } $new_makefile .= ' ' x $indent; $new_makefile .= substr($deps, $off) . "\n"; } else { $new_makefile .= "$deps\n"; } $new_makefile .= "\t$imp\n"; } } my $newfile = $file . ".new"; if (!open(MF, ">$newfile")) { warn("cannot open $newfile: $!\n"); } else { print MF $new_makefile || warn("cannot write $newfile: $!\n"); close(MF) || warn("cannot close $newfile: $!\n");; rename($newfile, $file); } } exit(0); sub find_depends { my $src = $_[0]; my ($deps, $code, %headers); if ($src !~ /\//) { # XXX - want build dir not src dir $src = "$dir_vars{'srcdir'}/$src"; } # resolve $(srcdir) etc. foreach (keys %dir_vars) { $src =~ s/\$[\(\{]$_[\)\}]/$dir_vars{$_}/g; } # find open source file and find headers used by it if (!open(FILE, "<$src")) { warn "unable to open $src\n"; return ""; } local $/; # enable "slurp" mode $code = ; close(FILE); # find all headers while ($code =~ /^#\s*include\s+["<](\S+)[">]/mg) { my ($hdr, $hdr_path) = find_header($1); if (defined($hdr)) { $headers{$hdr} = 1; # Look for other includes in the .h file foreach (find_depends($hdr_path)) { $headers{$_} = 1; } } } sort keys %headers; } # find the path to a header file # returns path or undef if not found sub find_header { my $hdr = $_[0]; # Look for .h.in files in top_builddir and build dir return ("\$(top_builddir\)/$hdr", "./${hdr}.in") if -r "./${hdr}.in"; return ("./$hdr", "$dir_vars{'srcdir'}/${hdr}.in") if -r "$dir_vars{'srcdir'}/${hdr}.in"; if (exists $generated{$hdr}) { my $hdr_path = $dir_vars{'devdir'} . '/' . $hdr; return ('$(devdir)/' . $hdr, $hdr_path) if -r $hdr_path; } foreach my $inc (@incpaths) { my $hdr_path = "$inc/$hdr"; # resolve variables in include path foreach (keys %dir_vars) { next if $_ eq "devdir"; $hdr_path =~ s/\$[\(\{]$_[\)\}]/$dir_vars{$_}/g; } return ("$inc/$hdr", $hdr_path) if -r $hdr_path; } undef; }