changelog entry, too
[debian/sudo] / mkdep.pl
1 #!/usr/bin/env perl
2 #
3 # Copyright (c) 2011-2013 Todd C. Miller <Todd.Miller@courtesan.com>
4 #
5 # Permission to use, copy, modify, and distribute this software for any
6 # purpose with or without fee is hereby granted, provided that the above
7 # copyright notice and this permission notice appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #
17
18 use File::Temp qw/ :mktemp  /;
19 use Fcntl;
20 use warnings;
21
22 die "usage: $0 Makefile ...\n" unless $#ARGV >= 0;
23
24 my @incpaths;
25 my %dir_vars;
26 my %implicit;
27 my %generated;
28
29 # Read in MANIFEST fail if present
30 my %manifest;
31 if (open(MANIFEST, "<MANIFEST")) {
32     while (<MANIFEST>) {
33         chomp;
34         next unless /([^\/]+\.[cly])$/;
35         $manifest{$1} = $_;
36     }
37 }
38
39 foreach (@ARGV) {
40     mkdep($_);
41 }
42
43 sub mkdep {
44     my $file = $_[0];
45     $file =~ s:^\./+::;         # strip off leading ./
46
47     my $makefile;
48     if (open(MF, "<$file")) {
49         local $/;               # enable "slurp" mode
50         $makefile = <MF>;
51     } else {
52         warn "$0: $file: $!\n";
53         return undef;
54     }
55     close(MF);
56
57     # New makefile, minus the autogenerated dependencies
58     my $separator = "# Autogenerated dependencies, do not modify";
59     my $new_makefile = $makefile;
60     $new_makefile =~ s/${separator}.*$//s;
61     $new_makefile .= "$separator\n";
62
63     # Old makefile, join lines with continuation characters
64     $makefile =~ s/\\\n//mg;
65
66     # Expand some configure bits
67     $makefile =~ s:\@DEV\@::g;
68     $makefile =~ s:\@COMMON_OBJS\@:aix.lo:;
69     $makefile =~ s:\@SUDO_OBJS\@:openbsd.o preload.o selinux.o sesh.o solaris.o sudo_noexec.lo:;
70     $makefile =~ s:\@SUDOERS_OBJS\@:bsm_audit.lo linux_audit.lo ldap.lo sssd.lo:;
71     # XXX - fill in AUTH_OBJS from contents of the auth dir instead
72     $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:;
73     $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo dlopen.lo fnmatch.lo getcwd.lo getgrouplist.lo getline.lo getprogname.lo glob.lo isblank.lo memrchr.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo pw_dup.lo sig2str.lo siglist.lo signame.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo utimes.lo globtest.o fnm_test.o:;
74
75     # Parse OBJS lines
76     my %objs;
77     while ($makefile =~ /^[A-Z0-9_]*OBJS\s*=\s*(.*)/mg) {
78         foreach (split/\s+/, $1) {
79             next if /^\$[\(\{].*[\)\}]$/; # skip included vars for now
80             $objs{$_} = 1;
81         }
82     }
83
84     # Find include paths
85     @incpaths = ();
86     while ($makefile =~ /-I(\S+)/mg) {
87         push(@incpaths, $1) unless $1 eq ".";
88     }
89
90     # Check for generated files
91     if ($makefile =~ /GENERATED\s*=\s*(.+)$/m) {
92         foreach (split(/\s+/, $1)) {
93             $generated{$_} = 1;
94         }
95     }
96
97     # Values of srcdir, top_srcdir, top_builddir, incdir
98     %dir_vars = ();
99     $file =~ m:^(.*)/+[^/]+:;
100     $dir_vars{'srcdir'} = $1 || '.';
101     $dir_vars{'devdir'} = $dir_vars{'srcdir'};
102     $dir_vars{'authdir'} = $dir_vars{'srcdir'} . "/auth";
103     $dir_vars{'top_srcdir'} = '.';
104     #$dir_vars{'top_builddir'} = '.';
105     $dir_vars{'incdir'} = 'include';
106
107     # Find implicit rules for generate .o and .lo files
108     %implicit = ();
109     while ($makefile =~ /^\.c\.(l?o):\s*\n\t+(.*)$/mg) {
110         $implicit{$1} = $2;
111     }
112
113     # Find existing .o and .lo dependencies
114     my %old_deps;
115     while ($makefile =~ /^(\w+\.l?o):\s*(\S+\.c)/mg) {
116         $old_deps{$1} = $2;
117     }
118
119     # Sort files so we do .lo files first
120     foreach my $obj (sort keys %objs) {
121         next unless $obj =~ /(\S+)\.(l?o)$/;
122         if ($2 eq "o" && exists($objs{"$1.lo"})) {
123             # If we have both .lo and .o files, make the .o depend on the .lo
124             $new_makefile .= sprintf("%s: %s.lo\n", $obj, $1);
125         } else {
126             # Use old depenencies when mapping objects to their source.
127             # If no old depenency, use the MANIFEST file to find the source.
128             my $src = $1 . '.c';
129             my $ext = $2;
130             if (exists $old_deps{$obj}) {
131                 $src = $old_deps{$obj};
132             } elsif (exists $manifest{$src}) {
133                 $src = $manifest{$src};
134                 foreach (sort { length($b) <=> length($a) } keys %dir_vars) {
135                     next if $_ eq "devdir";
136                     last if $src =~ s:^\Q$dir_vars{$_}/\E:\$\($_\)/:;
137                 }
138             } else {
139                 warn "$file: unable to find source for $obj\n";
140             }
141             my $imp = $implicit{$ext};
142             $imp =~ s/\$</$src/g;
143
144             my $deps = sprintf("%s: %s %s", $obj, $src,
145                 join(' ', find_depends($src)));
146             if (length($deps) > 80) {
147                 my $off = 0;
148                 my $indent = length($obj) + 2;
149                 while (length($deps) - $off > 80 - $indent) {
150                     my $pos;
151                     if ($off != 0) {
152                         $new_makefile .= ' ' x $indent;
153                         $pos = rindex($deps, ' ', $off + 80 - $indent - 2);
154                     } else {
155                         $pos = rindex($deps, ' ', $off + 78);
156                     }
157                     $new_makefile .= substr($deps, $off, $pos - $off) . " \\\n";
158                     $off = $pos + 1;
159                 }
160                 $new_makefile .= ' ' x $indent;
161                 $new_makefile .= substr($deps, $off) . "\n";
162             } else {
163                 $new_makefile .= "$deps\n";
164             }
165             $new_makefile .= "\t$imp\n";
166         }
167     }
168
169     my $newfile = $file . ".new";
170     if (!open(MF, ">$newfile")) {
171         warn("cannot open $newfile: $!\n");
172     } else {
173         print MF $new_makefile || warn("cannot write $newfile: $!\n");
174         close(MF) || warn("cannot close $newfile: $!\n");;
175         rename($newfile, $file);
176     }
177 }
178
179 exit(0);
180
181 sub find_depends {
182     my $src = $_[0];
183     my ($deps, $code, @headers);
184
185     if ($src !~ /\//) {
186         # XXX - want build dir not src dir
187         $src = "$dir_vars{'srcdir'}/$src";
188     }
189
190     # resolve $(srcdir) etc.
191     foreach (keys %dir_vars) {
192         $src =~ s/\$[\(\{]$_[\)\}]/$dir_vars{$_}/g;
193     }
194
195     # find open source file and find headers used by it
196     if (!open(FILE, "<$src")) {
197         warn "unable to open $src\n";
198         return "";
199     }
200     local $/;           # enable "slurp" mode
201     $code = <FILE>;
202     close(FILE);
203
204     # find all headers
205     while ($code =~ /^#\s*include\s+["<](\S+)[">]/mg) {
206         my ($hdr, $hdr_path) = find_header($1);
207         if (defined($hdr)) {
208             push(@headers, $hdr);
209             # Look for other includes in the .h file
210             push(@headers, find_depends($hdr_path));
211         }
212     }
213
214     @headers;
215 }
216
217 # find the path to a header file
218 # returns path or undef if not found
219 sub find_header {
220     my $hdr = $_[0];
221
222     # Look for .h.in files in top_builddir and build dir
223     return ("\$(top_builddir\)/$hdr", "./${hdr}.in") if -r "./${hdr}.in";
224     return ("./$hdr", "$dir_vars{'srcdir'}/${hdr}.in") if -r "$dir_vars{'srcdir'}/${hdr}.in";
225
226     if (exists $generated{$hdr}) {
227         my $hdr_path = $dir_vars{'devdir'} . '/' . $hdr;
228         return ('$(devdir)/' . $hdr, $hdr_path) if -r $hdr_path;
229     }
230     foreach my $inc (@incpaths) {
231         my $hdr_path = "$inc/$hdr";
232         # resolve variables in include path
233         foreach (keys %dir_vars) {
234             next if $_ eq "devdir";
235             $hdr_path =~ s/\$[\(\{]$_[\)\}]/$dir_vars{$_}/g;
236         }
237         return ("$inc/$hdr", $hdr_path) if -r $hdr_path;
238     }
239
240     undef;
241 }