File Coverage

blib/lib/Makefile/Update/Makefile.pm
Criterion Covered Total %
statement 87 96 90.6
branch 45 58 77.5
condition 7 12 58.3
subroutine 7 7 100.0
pod 1 1 100.0
total 147 174 84.4


line stmt bran cond sub pod time code
1             package Makefile::Update::Makefile;
2             # ABSTRACT: Update lists of files in makefile variables.
3              
4 1     1   31721 use Exporter qw(import);
  1         2  
  1         43  
5             our @EXPORT = qw(update_makefile);
6              
7 1     1   4 use strict;
  1         1  
  1         24  
8 1     1   4 use warnings;
  1         1  
  1         196  
9              
10             our $VERSION = '0.2'; # VERSION
11              
12              
13              
14             sub update_makefile
15             {
16 1     1 1 3090 my ($in, $out, $vars) = @_;
17              
18             # Variable whose contents is being currently replaced.
19 1         2 my $var;
20              
21             # Hash with files defined for the specified variable as keys and 0 or 1
22             # depending on whether we have seen them in the input file as values.
23             my %files;
24              
25             # Array of lines in the existing makefile.
26 0         0 my @values;
27              
28             # True if the values are in alphabetical order: we use this to add new
29             # entries in alphabetical order too if the existing ones use it, otherwise
30             # we just append them at the end.
31 1         2 my $sorted = 1;
32              
33             # Extension of the files in the files list and in the makefile, can be
34             # different (e.g. ".cpp" and ".o") and we translate between them then.
35 1         1 my ($src_ext, $make_ext);
36              
37             # Helper to get the extension. Note that the "extension" may be a make
38             # variable, e.g. the file could be something like "foo.$(obj)", so don't
39             # restrict it to just word characters.
40 12 100   12   40 sub _get_ext { $_[0] =~ /(\.\S+)$/ ? $1 : undef }
41              
42             # Indent and the part after the value (typically some amount of spaces and
43             # a backslash) for normal lines and, separately, for the last one, as it
44             # may or not have backslash after it.
45 0         0 my ($indent, $tail, $last_tail);
46              
47             # Set to 1 if we made any changes.
48 1         2 my $changed = 0;
49 1         5 while (defined(my $line = <$in>)) {
50 18         21 chomp $line;
51              
52             # If we're inside the variable definition, parse the current line as
53             # another file name,
54 18 100       24 if (defined $var) {
55 12 100       29 if ($line =~ /^(?\s*)(?[^ ]+)(?\s*\\?)$/) {
56 9 100       16 if (defined $indent) {
57 6 50       22 warn qq{Inconsistent indent at line $. in the } .
58             qq{definition of the variable "$var".\n"}
59 1     1   527 if $+{indent} ne $indent;
  1         366  
  1         867  
60             } else {
61 3         12 $indent = $+{indent};
62             }
63              
64 9         25 $last_tail = $+{tail};
65 9         20 my $file_orig = $+{file};
66              
67 9 100       14 $tail = $last_tail if !defined $tail;
68              
69             # Check if we have something with the correct extension and
70             # preserve unchanged all the rest -- we don't want to remove
71             # expansions of other makefile variables from this one, for
72             # example, but such expansions would never be in the files
73             # list as they don't make sense for the other formats.
74 9         7 my $file = $file_orig;
75 9 100       10 if (defined (my $file_ext = _get_ext($file))) {
76 7 100       10 if (defined $make_ext) {
77 5 50       9 if ($file_ext ne $make_ext) {
78 0         0 warn qq{Values of variable "$var" use both } .
79             qq{"$file_ext" and "$make_ext" extensions.\n};
80             }
81             } else {
82 2         2 $make_ext = $file_ext;
83             }
84              
85 7 50       10 if ($file_ext ne $src_ext) {
86 7         48 $file =~ s/\Q$file_ext\E$/$src_ext/
87             }
88              
89 7 100       10 if (exists $files{$file}) {
90 5 50       7 if ($files{$file}) {
91 0         0 warn qq{Duplicate file "$file" in the definition of the } .
92             qq{variable "$var" at line $.\n}
93             } else {
94 5         34 $files{$file} = 1;
95             }
96             } else {
97             # This file was removed.
98 2         3 $changed = 1;
99              
100             # Don't store this line in @values below.
101 2         5 next;
102             }
103             }
104              
105             # Are we still sorted?
106 7 50 66     25 if (@values && lc $line lt $values[-1]) {
107 0         0 $sorted = 0;
108             }
109              
110 7         7 push @values, $line;
111 7         18 next;
112             }
113              
114             # The variable definition is expected to end with a blank line.
115 3 50       6 warn qq{Expected blank line at line $..\n} if $line =~ /\S/;
116              
117             # End of variable definition, add new lines.
118 3         3 my $new_files = 0;
119 3         9 while (my ($file, $seen) = each(%files)) {
120 10 100       20 next if $seen;
121              
122             # This file was wasn't present in the input, add it.
123              
124             # If this is the first file we add, ensure that the last line
125             # present in the makefile so far has the line continuation
126             # character at the end as this might not have been the case.
127 5 100       7 if (!$new_files) {
128 2         2 $new_files = 1;
129              
130 2 100 66     23 if (@values && $values[-1] !~ /\\$/) {
131 1         2 $values[-1] .= $tail;
132             }
133             }
134              
135             # Next give it the right extension.
136 5 100 66     19 if (defined $make_ext && $make_ext ne $src_ext) {
137 2         15 $file =~ s/\Q$src_ext\E$/$make_ext/
138             }
139              
140             # Finally store it.
141 5         14 push @values, "$indent$file$tail";
142             }
143              
144 3 100       12 if ($new_files) {
145 2         3 $changed = 1;
146              
147             # Sort them if necessary using the usual Schwartzian transform.
148 2 50       3 if ($sorted) {
149 10         13 @values = map { $_->[0] }
  14         19  
150 10         18 sort { $a->[1] cmp $b->[1] }
151 2         3 map { [$_, lc $_] } @values;
152             }
153              
154             # Fix up the tail of the last line to be the same as that of
155             # the previous last line.
156 2         12 $values[-1] =~ s/\s*\\$/$last_tail/;
157             }
158              
159 3         3 undef $var;
160              
161 3         12 print $out join("\n", @values), "\n";
162             }
163              
164             # We're only interested in variable or target declarations.
165 9 100       32 if ($line =~ /^\s*(?\S+)\s*(?::?=|:)(?.*)/) {
166 3         21 $var = $+{var};
167 3         10 my $tail = $+{tail};
168              
169             # And only those of them for which we have values, but this is
170             # where it gets tricky as we try to be smart to accommodate common
171             # use patterns with minimal effort.
172 3 100       8 if (!exists $vars->{$var}) {
173             # Helper: return name if a variable with such name exists or
174             # undef otherwise.
175 2 50   2   10 my $var_if_exists = sub { exists $vars->{$_[0]} ? $_[0] : undef };
  2         13  
176              
177 2 50 33     24 if ($var =~ /^objects$/i || $var =~ /^obj$/i) {
    100          
    50          
178             # Special case: map it to "sources" as we work with the
179             # source, not object, files.
180 0         0 $var = $var_if_exists->('sources');
181             } elsif ($var =~ /^(\w+)_(objects|obj|sources|src|headers|hdr)$/i) {
182 1 50       3 $var = $var_if_exists->($1) or $var_if_exists->("$1_sources");
183             } elsif ($var =~ /^(\w+)\$\(\w+\)/) {
184             # This one is meant to catch relatively common makefile
185             # constructions like "target$(exe_ext)".
186 1         3 $var = $var_if_exists->($1);
187             } else {
188 0         0 undef $var;
189             }
190             }
191              
192 3 50       7 if (defined $var) {
193 3 50       10 if ($tail !~ /\s*\\$/) {
194 0         0 warn qq{Unsupported format for variable "$var" at line $..\n};
195 0         0 undef $var;
196             } else {
197 3         3 %files = map { $_ => 0 } @{$vars->{$var}};
  10         20  
  3         6  
198              
199 3         5 @values = ();
200              
201             # We assume all files have the same extension (it's not
202             # clear what could we do if this were not the case anyhow).
203 3         4 $src_ext = _get_ext(${$vars->{$var}}[0]);
  3         5  
204              
205             # Not known yet.
206 3         3 undef $make_ext;
207              
208 3         4 undef $indent;
209 3         3 $tail = $tail;
210 3         3 undef $last_tail;
211             }
212             }
213             }
214              
215 9         33 print $out "$line\n";
216             }
217              
218             $changed
219 1         5 }
220              
221             __END__