File: | lib/Makefile/Update/Makefile.pm |
Coverage: | 91.1% |
line | stmt | bran | cond | sub | code |
---|---|---|---|---|---|
1 | package Makefile::Update::Makefile; | ||||
2 | # ABSTRACT: Update lists of files in makefile variables. | ||||
3 | |||||
4 | 12 12 12 | use Exporter qw(import); | |||
5 | our @EXPORT = qw(update_makefile); | ||||
6 | |||||
7 | 12 12 12 | use strict; | |||
8 | 12 12 12 | use warnings; | |||
9 | |||||
10 | # VERSION | ||||
11 | |||||
12 - 24 | =head1 SYNOPSIS This can be used to update the contents of a variable containing a list of files in a makefile. use Makefile::Update::Makefile; Makefile::Update::upmake('GNUmakefile', \&update_makefile, $vars); =head1 SEE ALSO Makefile::Update =cut | ||||
25 | |||||
26 | =func update_makefile | ||||
27 | |||||
28 | Update variable definitions in a makefile format with the data from the hash | ||||
29 | ref containing all the file lists. | ||||
30 | |||||
31 | Only most straightforward cases of variable or target definitions are | ||||
32 | recognized here, i.e. just "var := value", "var = value" or "target: value". | ||||
33 | In particular we don't support any GNU make extensions such as "export" or | ||||
34 | "override" without speaking of anything more complex. | ||||
35 | |||||
36 | On top of it, currently the value should contain a single file per line with | ||||
37 | none at all on the first line (but this restriction could be relaxed later if | ||||
38 | needed), i.e. the only supported case is | ||||
39 | |||||
40 | var = \ | ||||
41 | foo \ | ||||
42 | bar \ | ||||
43 | baz | ||||
44 | |||||
45 | and it must be followed by an empty line, too. | ||||
46 | |||||
47 | Notice that if any of the "files" in the variable value looks like a makefile | ||||
48 | variable, i.e. has "$(foo)" form, it is ignored by this function, i.e. not | ||||
49 | removed even if it doesn't appear in the list of files (which will never be | ||||
50 | the case normally). | ||||
51 | |||||
52 | Takes the (open) file handles of the files to read and to write and the file | ||||
53 | lists hash ref as arguments. | ||||
54 | |||||
55 | Returns 1 if any changes were made. | ||||
56 | =cut | ||||
57 | |||||
58 | sub update_makefile | ||||
59 | { | ||||
60 | 60 | my ($in, $out, $vars) = @_; | |||
61 | |||||
62 | # Variable whose contents is being currently replaced and its original | ||||
63 | # name in the makefile. | ||||
64 | 60 | my ($var, $makevar); | |||
65 | |||||
66 | # Hash with files defined for the specified variable as keys and 0 or 1 | ||||
67 | # depending on whether we have seen them in the input file as values. | ||||
68 | 0 | my %files; | |||
69 | |||||
70 | # Array of lines in the existing makefile. | ||||
71 | 0 | my @values; | |||
72 | |||||
73 | # True if the values are in alphabetical order: we use this to add new | ||||
74 | # entries in alphabetical order too if the existing ones use it, otherwise | ||||
75 | # we just append them at the end. | ||||
76 | 60 | my $sorted = 1; | |||
77 | |||||
78 | # Extensions of the files in the files list (they're keys of this hash, | ||||
79 | # the values are not used), there can be more than one (e.g. ".c" and | ||||
80 | # ".cpp"). | ||||
81 | 60 | my %src_exts; | |||
82 | |||||
83 | # Extension of the files in the makefiles: here there can also be more | ||||
84 | # than one, but in this case we just give up and don't perform any | ||||
85 | # extensions translation because we don't have enough information to do it | ||||
86 | # (e.g. which extension should be used for the new files in the makefile?). | ||||
87 | # Such case is indicated by make_ext being empty (as opposed to its | ||||
88 | # initial undefined value). | ||||
89 | my $make_ext; | ||||
90 | |||||
91 | # Helper to get the extension. Note that the "extension" may be a make | ||||
92 | # variable, e.g. the file could be something like "foo.$(obj)", so don't | ||||
93 | # restrict it to just word characters. | ||||
94 | 264 | sub _get_ext { $_[0] =~ /(\.\S+)$/ ? $1 : undef } | |||
95 | |||||
96 | # Indent and the part after the value (typically some amount of spaces and | ||||
97 | # a backslash) for normal lines and, separately, for the last one, as it | ||||
98 | # may or not have backslash after it. | ||||
99 | 60 | my ($indent, $tail, $last_tail); | |||
100 | |||||
101 | # We can't use the usual check for EOF inside while itself because this | ||||
102 | # wouldn't work for files with no new line after the last line, so check | ||||
103 | # for the EOF manually. | ||||
104 | 60 | my $eof = 0; | |||
105 | |||||
106 | # Set to 1 if we made any changes. | ||||
107 | 60 | my $changed = 0; | |||
108 | 60 | while (1) { | |||
109 | 339 | my $line = <$in>; | |||
110 | 339 | if (defined $line) { | |||
111 | 279 | chomp $line; | |||
112 | } else { | ||||
113 | 60 | $line = ''; | |||
114 | 60 | $eof = 1; | |||
115 | } | ||||
116 | |||||
117 | # If we're inside the variable definition, parse the current line as | ||||
118 | # another file name, | ||||
119 | 339 | if (defined $var) { | |||
120 | 204 | if ($line =~ /^(?<indent>\s*)(?<file>[^ ]+)(?<tail>\s*\\?)$/) { | |||
121 | 135 | if (defined $indent) { | |||
122 | warn qq{Inconsistent indent at line $. in the } . | ||||
123 | qq{definition of the variable "$makevar".\n} | ||||
124 | 12 12 12 66 | if $+{indent} ne $indent; | |||
125 | } else { | ||||
126 | 69 | $indent = $+{indent}; | |||
127 | } | ||||
128 | |||||
129 | 135 | $last_tail = $+{tail}; | |||
130 | 135 | my $file_orig = $+{file}; | |||
131 | |||||
132 | 135 | $tail = $last_tail if !defined $tail; | |||
133 | |||||
134 | # Check if we have something with the correct extension and | ||||
135 | # preserve unchanged all the rest -- we don't want to remove | ||||
136 | # expansions of other makefile variables from this one, for | ||||
137 | # example, but such expansions would never be in the files | ||||
138 | # list as they don't make sense for the other formats. | ||||
139 | 135 | my $file = $file_orig; | |||
140 | 135 | if (defined (my $file_ext = _get_ext($file))) { | |||
141 | 132 | if (defined $make_ext) { | |||
142 | 63 | if ($file_ext ne $make_ext) { | |||
143 | # As explained in the comment before make_ext | ||||
144 | # definition, just don't do anything in this case. | ||||
145 | 0 | $make_ext = ''; | |||
146 | } | ||||
147 | } else { | ||||
148 | 69 | $make_ext = $file_ext; | |||
149 | } | ||||
150 | |||||
151 | # We need to try this file with all of the source | ||||
152 | # extensions we have as it can correspond to any of them. | ||||
153 | 132 | for my $src_ext (keys %src_exts) { | |||
154 | 147 | if ($file_ext ne $src_ext) { | |||
155 | 51 | (my $file_try = $file) =~ s/\Q$file_ext\E$/$src_ext/; | |||
156 | 51 | if (exists $files{$file_try}) { | |||
157 | 21 | $file = $file_try; | |||
158 | last | ||||
159 | 21 | } | |||
160 | } | ||||
161 | } | ||||
162 | |||||
163 | 132 | if (!exists $files{$file}) { | |||
164 | # This file was removed. | ||||
165 | 42 | $changed = 1; | |||
166 | |||||
167 | # Don't store this line in @values below. | ||||
168 | 42 | next; | |||
169 | } | ||||
170 | } | ||||
171 | |||||
172 | 93 | if (exists $files{$file}) { | |||
173 | 90 | if ($files{$file}) { | |||
174 | 3 | warn qq{Duplicate file "$file" in the definition of the } . | |||
175 | qq{variable "$makevar" at line $.\n} | ||||
176 | } else { | ||||
177 | 87 | $files{$file} = 1; | |||
178 | } | ||||
179 | } | ||||
180 | |||||
181 | # Are we still sorted? | ||||
182 | 93 | if (@values && lc $line lt $values[-1]) { | |||
183 | 9 | $sorted = 0; | |||
184 | } | ||||
185 | |||||
186 | 93 | push @values, $line; | |||
187 | 93 | next; | |||
188 | } | ||||
189 | |||||
190 | # If the last line had a continuation character, the file list | ||||
191 | # should only end if there is nothing else on the following line. | ||||
192 | 69 | if ($last_tail =~ /\\$/ && $line =~ /\S/) { | |||
193 | 3 | warn qq{Expected blank line at line $..\n}; | |||
194 | } | ||||
195 | |||||
196 | # End of variable definition, add new lines. | ||||
197 | |||||
198 | # We can only map the extensions if we have a single extension to | ||||
199 | # map them to (i.e. make_ext is not empty) and we only need to do | ||||
200 | # it if are using more than one extension in the source files list | ||||
201 | # or the single extension that we use is different from make_ext. | ||||
202 | 69 | if (defined $make_ext) { | |||
203 | 69 | if ($make_ext eq '' || | |||
204 | (keys %src_exts == 1 && exists $src_exts{$make_ext})) { | ||||
205 | 57 | undef $make_ext | |||
206 | } | ||||
207 | } | ||||
208 | |||||
209 | 69 | my $new_files = 0; | |||
210 | 69 | while (my ($file, $seen) = each(%files)) { | |||
211 | 129 | next if $seen; | |||
212 | |||||
213 | # This file was wasn't present in the input, add it. | ||||
214 | |||||
215 | # If this is the first file we add, ensure that the last line | ||||
216 | # present in the makefile so far has the line continuation | ||||
217 | # character at the end as this might not have been the case. | ||||
218 | 42 | if (!$new_files) { | |||
219 | 36 | $new_files = 1; | |||
220 | |||||
221 | 36 | if (@values && $values[-1] !~ /\\$/) { | |||
222 | 12 | $values[-1] .= $tail; | |||
223 | } | ||||
224 | } | ||||
225 | |||||
226 | # Next give it the right extension. | ||||
227 | 42 | if (defined $make_ext) { | |||
228 | 9 | $file =~ s/\.\S+$/$make_ext/ | |||
229 | } | ||||
230 | |||||
231 | # Finally store it. | ||||
232 | 42 | push @values, "$indent$file$tail"; | |||
233 | } | ||||
234 | |||||
235 | 69 | if ($new_files) { | |||
236 | 36 | $changed = 1; | |||
237 | |||||
238 | # Sort them if necessary using the usual Schwartzian transform. | ||||
239 | 36 | if ($sorted) { | |||
240 | 78 | @values = map { $_->[0] } | |||
241 | 72 | sort { $a->[1] cmp $b->[1] } | |||
242 | 36 78 | map { [$_, lc $_] } @values; | |||
243 | } | ||||
244 | |||||
245 | # Fix up the tail of the last line to be the same as that of | ||||
246 | # the previous last line. | ||||
247 | 36 | $values[-1] =~ s/\s*\\$/$last_tail/; | |||
248 | } | ||||
249 | |||||
250 | 69 | undef $var; | |||
251 | |||||
252 | 69 | print $out join("\n", @values), "\n"; | |||
253 | } | ||||
254 | |||||
255 | # We're only interested in variable or target declarations, and does | ||||
256 | # not look like target-specific variable (this would contain an equal | ||||
257 | # sign after the target). | ||||
258 | 204 | if ($line =~ /^\s*(?<var>\S+)\s*(?::?=|:)(?<tail>[^=]*)$/) { | |||
259 | 72 | $makevar = $+{var}; | |||
260 | 72 | my $tail = $+{tail}; | |||
261 | |||||
262 | # And only those of them for which we have values, but this is | ||||
263 | # where it gets tricky as we try to be smart to accommodate common | ||||
264 | # use patterns with minimal effort. | ||||
265 | 72 | if (exists $vars->{$makevar}) { | |||
266 | 18 | $var = $makevar; | |||
267 | } else { | ||||
268 | # Helper: return name if a variable with such name exists or | ||||
269 | # undef otherwise. | ||||
270 | 54 81 | my $var_if_exists = sub { exists $vars->{$_[0]} ? $_[0] : undef }; | |||
271 | |||||
272 | 54 | if ($makevar =~ /^objects$/i || $makevar =~ /^obj$/i) { | |||
273 | # Special case: map it to "sources" as we work with the | ||||
274 | # source, not object, files. | ||||
275 | 3 | $var = $var_if_exists->('sources'); | |||
276 | } elsif ($makevar =~ /^(\w+)_(objects|obj|sources|src)$/i) { | ||||
277 | # Here we deal with "foo_sources" typically found in | ||||
278 | # hand-written makefiles but also "foo_SOURCES" used in | ||||
279 | # automake ones, but the latter also uses libfoo_a_SOURCES | ||||
280 | # for static libraries and libfoo_la_SOURCES for the | ||||
281 | # libtool libraries, be smart about it to allow defining | ||||
282 | # just "foo" or "foo_sources" variables usable with all | ||||
283 | # kinds of make/project files. | ||||
284 | 48 | $var = $var_if_exists->($1) || $var_if_exists->("$1_sources"); | |||
285 | 48 | if (!defined $var && $2 eq 'SOURCES' && $1 =~ /^(\w+)_l?a$/) { | |||
286 | 9 | $var = $var_if_exists->($1) || $var_if_exists->("$1_sources"); | |||
287 | 9 | if (!defined $var && $1 =~ /^lib(\w+)$/) { | |||
288 | 3 | $var = $var_if_exists->($1) || $var_if_exists->("$1_sources"); | |||
289 | } | ||||
290 | } | ||||
291 | } elsif ($makevar =~ /^(\w+)\$\(\w+\)/) { | ||||
292 | # This one is meant to catch relatively common makefile | ||||
293 | # constructions like "target$(exe_ext)". | ||||
294 | 3 | $var = $var_if_exists->($1); | |||
295 | } | ||||
296 | } | ||||
297 | |||||
298 | 72 | if (defined $var) { | |||
299 | 72 | if ($tail !~ /\s*\\$/) { | |||
300 | 3 | warn qq{Unsupported format for variable "$makevar" at line $..\n}; | |||
301 | 3 | undef $var; | |||
302 | } else { | ||||
303 | 69 129 69 | %files = map { $_ => 0 } @{$vars->{$var}}; | |||
304 | |||||
305 | 69 | @values = (); | |||
306 | |||||
307 | # Find all the extensions used by files in this variable. | ||||
308 | 69 69 | for my $file (@{$vars->{$var}}) { | |||
309 | 129 | if (defined (my $src_ext = _get_ext($file))) { | |||
310 | 129 | $src_exts{$src_ext} = 1; | |||
311 | } | ||||
312 | } | ||||
313 | |||||
314 | # Not known yet. | ||||
315 | 69 | undef $make_ext; | |||
316 | |||||
317 | 69 | undef $indent; | |||
318 | 69 | $tail = $tail; | |||
319 | 69 | undef $last_tail; | |||
320 | |||||
321 | # Not unsorted so far. | ||||
322 | 69 | $sorted = 1; | |||
323 | } | ||||
324 | } | ||||
325 | } | ||||
326 | |||||
327 | 204 | print $out "$line"; | |||
328 | |||||
329 | # Don't add an extra new line at the EOF if it hadn't been there. | ||||
330 | 204 | last if $eof; | |||
331 | |||||
332 | 144 | print $out "\n"; | |||
333 | } | ||||
334 | |||||
335 | $changed | ||||
336 | 60 | } |