File Coverage

blib/lib/Makefile/Update/MSBuild.pm
Criterion Covered Total %
statement 130 131 99.2
branch 64 66 96.9
condition 3 3 100.0
subroutine 9 9 100.0
pod 3 3 100.0
total 209 212 98.5


line stmt bran cond sub pod time code
1             package Makefile::Update::MSBuild;
2             # ABSTRACT: Update list of sources and headers in MSBuild projects.
3              
4 5     5   183688 use Exporter qw(import);
  5         26  
  5         206  
5             our @EXPORT = qw(update_msbuild_project update_msbuild update_msbuild_filters);
6              
7 5     5   25 use strict;
  5         6  
  5         88  
8 5     5   20 use warnings;
  5         9  
  5         281  
9              
10             our $VERSION = '0.4'; # VERSION
11              
12              
13              
14             sub update_msbuild_project
15             {
16 3     3 1 3604 my ($file_or_options, $sources, $headers) = @_;
17              
18 5     5   717 use Makefile::Update;
  5         8  
  5         2017  
19              
20 3 100       10 if (!Makefile::Update::upmake($file_or_options,
21             \&update_msbuild, $sources, $headers
22             )) {
23 1         5 return 0;
24             }
25              
26 2         4 my $args;
27 2 50       6 if (ref $file_or_options eq 'HASH') {
28             # Need to make a copy to avoid modifying the callers hash.
29 2         9 $args = { %$file_or_options };
30 2         5 $args->{file} .= ".filters"
31             } else {
32 0         0 $args = "$file_or_options.filters"
33             }
34              
35 2         8 return Makefile::Update::upmake($args,
36             \&update_msbuild_filters, $sources, $headers
37             );
38             }
39              
40              
41              
42             sub update_msbuild
43             {
44 7     7 1 9068 my ($in, $out, $sources, $headers) = @_;
45              
46             # Hashes mapping the sources/headers names to 1 if they have been seen in
47             # the project or 0 otherwise.
48 7         20 my %sources = map { $_ => 0 } @$sources;
  9         32  
49 7         19 my %headers = map { $_ => 0 } @$headers;
  6         13  
50              
51             # Reference to the hash corresponding to the files currently being
52             # processed.
53 7         49 my $files;
54              
55             # Set to 1 when we are inside any tag.
56 7         27 my $in_group = 0;
57              
58             # Set to 1 when we are inside an item group containing sources or headers
59             # respectively.
60 7         18 my ($in_sources, $in_headers) = 0;
61              
62             # Set to 1 if we made any changes.
63 7         11 my $changed = 0;
64 7         73 while (my $line_with_eol = <$in>) {
65 128         486 (my $line = $line_with_eol) =~ s/\r?\n?$//;
66              
67 128 100       416 if ($line =~ /^\s*$/) {
    100          
    100          
68 11         42 $in_group = 1;
69             } elsif ($line =~ m{^\s*$}) {
70 18 100       63 if (defined $files) {
71 11 100       22 my $kind = $in_sources ? 'Compile' : 'Include';
72              
73             # Check if we have any new files.
74             #
75             # TODO Insert them in alphabetical order.
76 11         47 while (my ($file, $seen) = each(%$files)) {
77 14 100       41 if (!$seen) {
78             # Convert path separator to the one used by MSBuild.
79 5         9 $file =~ s@/@\\@g;
80              
81 5         16 print $out qq{ \r\n};
82              
83 5         13 $changed = 1;
84             }
85             }
86              
87 11         16 $in_sources = $in_headers = 0;
88 11         16 $files = undef;
89             }
90              
91 18         27 $in_group = 0;
92             } elsif ($in_group) {
93 19 50       113 if ($line =~ m{^\s*Compile|Include) Include="(?[^"]+)"\s*(?/)?>$}) {
94 5     5   2064 my $kind = $+{kind};
  5         1712  
  5         4469  
  19         132  
95 19 100       90 if ($kind eq 'Compile') {
96 11 100       42 warn "Mix of sources and headers at line $.\n" if $in_headers;
97 11         82 $in_sources = 1;
98 11         19 $files = \%sources;
99             } else {
100 8 100       30 warn "Mix of headers and sources at line $.\n" if $in_sources;
101 8         69 $in_headers = 1;
102 8         13 $files = \%headers;
103             }
104              
105 19         84 my $closed_tag = defined $+{slash};
106              
107             # Normalize the path separator, we always use Unix ones but the
108             # project files use Windows one.
109 19         64 my $file = $+{file};
110 19         45 $file =~ s@\\@/@g;
111              
112 19 100       38 if (not exists $files->{$file}) {
113             # This file was removed.
114 8         12 $changed = 1;
115              
116 8 100       20 if (!$closed_tag) {
117             # We have just the opening or
118             # tag, ignore everything until the matching closing one.
119 1         3 my $tag = "Cl$kind";
120 1         6 while (<$in>) {
121 2 100       52 last if m{^\s*\r?\n$};
122             }
123             }
124              
125             # In any case skip either this line containing the full
126             # tag or the line with the closing tag.
127 8         25 next;
128             } else {
129 11 100       23 if ($files->{$file}) {
130 1         12 warn qq{Duplicate file "$file" in the project at line $.\n};
131             } else {
132 10         17 $files->{$file} = 1;
133             }
134             }
135             }
136             }
137              
138 120         498 print $out $line_with_eol;
139             }
140              
141             $changed
142 7         51 }
143              
144              
145             sub update_msbuild_filters
146             {
147 7     7 1 7326 my ($in, $out, $sources, $headers, $filter_cb) = @_;
148              
149             # Use standard/default classifier for the files if none is explicitly
150             # specified.
151 7 100       19 if (!defined $filter_cb) {
152             $filter_cb = sub {
153 4     4   9 my ($file) = @_;
154              
155 4 100       18 return 'Source Files' if $file =~ q{\.c(c|pp|xx|\+\+)?$};
156 2 100       20 return 'Header Files' if $file =~ q{\.h(h|pp|xx|\+\+)?$};
157              
158 1         7 warn qq{No filter defined for the file "$file".\n};
159              
160             undef
161 1         49 }
162 6         37 }
163              
164             # Hashes mapping the sources/headers names to the text representing them in
165             # the input file if they have been seen in it or nothing otherwise.
166 7         14 my %sources = map { $_ => undef } @$sources;
  11         31  
167 7         28 my %headers = map { $_ => undef } @$headers;
  5         22  
168              
169             # Reference to the hash corresponding to the files currently being
170             # processed.
171 7         10 my $files;
172              
173             # Set to 1 when we are inside any tag.
174 7         10 my $in_group = 0;
175              
176             # Set to 1 when we are inside an item group containing sources or headers
177             # respectively.
178 7         13 my ($in_sources, $in_headers) = 0;
179              
180             # Set to 1 if we made any changes.
181 7         7 my $changed = 0;
182 7         48 while (my $line_with_eol = <$in>) {
183 119         385 (my $line = $line_with_eol) =~ s/\r?\n?$//;
184              
185 119 100 100     471 if ($line =~ /^\s*?$/) {
    100          
    100          
186 17         23 $in_group = 1;
187             } elsif ($line =~ m{^\s*?$}) {
188 17 100       31 if (defined $files) {
189             # Output the group contents now, all at once, inserting any new
190             # files: we must do it like this to ensure that they are
191             # inserted in alphabetical order.
192 10 100       19 my $kind = $in_sources ? 'Compile' : 'Include';
193              
194 10         30 foreach my $file (sort keys %$files) {
195 15 100       41 if (defined $files->{$file}) {
196 8         20 print $out $files->{$file};
197             } else {
198 7         16 my $filter = $filter_cb->($file);
199              
200             # Convert path separator to the one used by MSBuild.
201 7         25 $file =~ s@/@\\@g;
202              
203 7         10 my $indent = ' ' x 2;
204              
205 7         18 print $out qq{$indent$indent
206 7 100       15 if (defined $filter) {
207 5         15 print $out ">\r\n$indent$indent$indent$filter\r\n$indent$indent\r\n";
208             } else {
209 2         13 print $out " />\r\n";
210             }
211              
212 7         11 $changed = 1;
213             }
214             }
215              
216 10         17 $in_sources = $in_headers = 0;
217 10         12 $files = undef;
218             }
219              
220 17         24 $in_group = 0;
221             } elsif ($in_group &&
222             $line =~ m{^\s*Compile|Include) Include="(?[^"]+)"\s*(?/)?>?$}) {
223 18         95 my $kind = $+{kind};
224 18 100       47 if ($kind eq 'Compile') {
225 11 100       31 warn "Mix of sources and headers at line $.\n" if $in_headers;
226 11         65 $in_sources = 1;
227 11         17 $files = \%sources;
228             } else {
229 7 100       26 warn "Mix of headers and sources at line $.\n" if $in_sources;
230 7         49 $in_headers = 1;
231 7         10 $files = \%headers;
232             }
233              
234 18         55 my $closed_tag = defined $+{slash};
235              
236             # Normalize the path separator, we always use Unix ones but the
237             # project files use Windows one.
238 18         53 my $file = $+{file};
239 18         31 $file =~ s@\\@/@g;
240              
241 18         24 my $text = $line_with_eol;
242 18 100       31 if (!$closed_tag) {
243             # We have just the opening tag, get everything
244             # until the next .
245 14         44 while (<$in>) {
246 28         48 $text .= $_;
247 28 100       265 last if m{^\s*\r?\n?$};
248             }
249             }
250              
251 18 100       39 if (not exists $files->{$file}) {
252             # This file was removed.
253 8         11 $changed = 1;
254             } else {
255 10 100       20 if ($files->{$file}) {
256 1         10 warn qq{Duplicate file "$file" in the project at line $.\n};
257             } else {
258 9         13 $files->{$file} = $text;
259             }
260             }
261              
262             # Don't output this line yet, wait until the end of the group.
263             next
264 18         96 }
265              
266 101         327 print $out $line_with_eol;
267             }
268              
269             $changed
270 7         62 }
271              
272             __END__