File Coverage

blib/lib/Makefile/Update/MSBuild.pm
Criterion Covered Total %
statement 114 126 90.4
branch 50 64 78.1
condition 3 3 100.0
subroutine 7 9 77.7
pod 3 3 100.0
total 177 205 86.3


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