File Coverage

blib/lib/Makefile/Update/Makefile.pm
Criterion Covered Total %
statement 119 120 99.1
branch 66 74 89.1
condition 26 36 72.2
subroutine 7 7 100.0
pod 1 1 100.0
total 219 238 92.0


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