1 | # ------------------------------------------------------------------------------ |
---|
2 | # (C) British Crown Copyright 2006-17 Met Office. |
---|
3 | # |
---|
4 | # This file is part of FCM, tools for managing and building source code. |
---|
5 | # |
---|
6 | # FCM is free software: you can redistribute it and/or modify |
---|
7 | # it under the terms of the GNU General Public License as published by |
---|
8 | # the Free Software Foundation, either version 3 of the License, or |
---|
9 | # (at your option) any later version. |
---|
10 | # |
---|
11 | # FCM is distributed in the hope that it will be useful, |
---|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
14 | # GNU General Public License for more details. |
---|
15 | # |
---|
16 | # You should have received a copy of the GNU General Public License |
---|
17 | # along with FCM. If not, see <http://www.gnu.org/licenses/>. |
---|
18 | # ------------------------------------------------------------------------------ |
---|
19 | use strict; |
---|
20 | use warnings; |
---|
21 | # ------------------------------------------------------------------------------ |
---|
22 | package FCM::System::Make::Build; |
---|
23 | use base qw{FCM::Class::CODE}; |
---|
24 | |
---|
25 | use Cwd qw{cwd realpath}; |
---|
26 | use FCM::Context::ConfigEntry; |
---|
27 | use FCM::Context::Event; |
---|
28 | use FCM::Context::Make::Build; |
---|
29 | use FCM::Context::Task; |
---|
30 | use FCM::System::Exception; |
---|
31 | use FCM::System::Make::Build::FileType::C; |
---|
32 | use FCM::System::Make::Build::FileType::CXX; |
---|
33 | use FCM::System::Make::Build::FileType::Data; |
---|
34 | use FCM::System::Make::Build::FileType::Fortran; |
---|
35 | use FCM::System::Make::Build::FileType::H; |
---|
36 | use FCM::System::Make::Build::FileType::NS; |
---|
37 | use FCM::System::Make::Build::FileType::Script; |
---|
38 | use FCM::System::Make::Share::Subsystem; |
---|
39 | use File::Basename qw{basename dirname fileparse}; |
---|
40 | use File::Find qw{find}; |
---|
41 | use File::Path qw{mkpath rmtree}; |
---|
42 | use File::Spec::Functions qw{abs2rel catfile rel2abs splitdir splitpath}; |
---|
43 | use Storable qw{dclone}; |
---|
44 | use Text::ParseWords qw{shellwords}; |
---|
45 | |
---|
46 | # Aliases |
---|
47 | our ($EVENT, $UTIL); |
---|
48 | my $E = 'FCM::System::Exception'; |
---|
49 | my $STATE = 'FCM::System::Make::Build::State'; |
---|
50 | |
---|
51 | # Classes for working with typed source files |
---|
52 | our @FILE_TYPE_UTILS = ( |
---|
53 | 'FCM::System::Make::Build::FileType::C', |
---|
54 | 'FCM::System::Make::Build::FileType::CXX', |
---|
55 | 'FCM::System::Make::Build::FileType::Data', |
---|
56 | 'FCM::System::Make::Build::FileType::Fortran', |
---|
57 | 'FCM::System::Make::Build::FileType::H', |
---|
58 | 'FCM::System::Make::Build::FileType::NS', |
---|
59 | 'FCM::System::Make::Build::FileType::Script', |
---|
60 | ); |
---|
61 | |
---|
62 | # Default target selection |
---|
63 | our %TARGET_SELECT_BY = ( |
---|
64 | 'category' => {}, |
---|
65 | 'key' => {}, |
---|
66 | 'task' => {}, |
---|
67 | ); |
---|
68 | |
---|
69 | # Configuration parser label to action map |
---|
70 | our %CONFIG_PARSER_OF = ( |
---|
71 | 'ns-excl' => _config_parse_ns_filter_func(sub {$_[0]->get_input_ns_excl()}), |
---|
72 | 'ns-incl' => _config_parse_ns_filter_func(sub {$_[0]->get_input_ns_incl()}), |
---|
73 | 'source' => \&_config_parse_source, |
---|
74 | 'target' => \&_config_parse_target, |
---|
75 | 'target-rename' => \&_config_parse_target_rename, |
---|
76 | ); |
---|
77 | |
---|
78 | # Default properties |
---|
79 | our %PROP_OF = ( |
---|
80 | # [default , ns-ok] |
---|
81 | 'archive-ok-target-category' => [q{include o} , undef], |
---|
82 | 'checksum-method' => [q{} , undef], |
---|
83 | 'ignore-missing-dep-ns' => [q{} , undef], |
---|
84 | 'no-step-source' => [q{} , undef], |
---|
85 | 'no-inherit-source' => [q{} , undef], |
---|
86 | 'no-inherit-target-category' => [q{bin etc lib}, undef], |
---|
87 | ); |
---|
88 | |
---|
89 | # Creates the class. |
---|
90 | __PACKAGE__->class( |
---|
91 | { config_parser_of => {isa => '%', default => {%CONFIG_PARSER_OF}}, |
---|
92 | file_type_utils => {isa => '@', default => [@FILE_TYPE_UTILS]}, |
---|
93 | file_type_util_of => '%', |
---|
94 | prop_of => {isa => '%', default => {%PROP_OF}}, |
---|
95 | target_select_by => {isa => '%', default => {%TARGET_SELECT_BY}}, |
---|
96 | util => '&', |
---|
97 | }, |
---|
98 | { init => \&_init, |
---|
99 | action_of => { |
---|
100 | config_parse => \&_config_parse, |
---|
101 | config_parse_class_prop => \&_config_parse_class_prop, |
---|
102 | config_parse_inherit_hook => \&_config_parse_inherit_hook, |
---|
103 | config_unparse => \&_config_unparse, |
---|
104 | config_unparse_class_prop => \&_config_unparse_class_prop, |
---|
105 | ctx => \&_ctx, |
---|
106 | ctx_load_hook => \&_ctx_load_hook, |
---|
107 | main => \&_main, |
---|
108 | }, |
---|
109 | }, |
---|
110 | ); |
---|
111 | |
---|
112 | # Initialises the helpers of the class. |
---|
113 | sub _init { |
---|
114 | my ($attrib_ref) = @_; |
---|
115 | # Initialises file type utilities, if necessary |
---|
116 | for my $class (@{$attrib_ref->{file_type_utils}}) { |
---|
117 | $attrib_ref->{util}->class_load($class); |
---|
118 | my $file_type_util = $class->new({util => $attrib_ref->{util}}); |
---|
119 | my $id = $file_type_util->id(); |
---|
120 | if (!defined($attrib_ref->{file_type_util_of}{$id})) { |
---|
121 | $attrib_ref->{file_type_util_of}{$id} = $file_type_util; |
---|
122 | } |
---|
123 | } |
---|
124 | # Initialises properties derived from the file type utilities |
---|
125 | # TBD: warn if a property is already set and is different from previous? |
---|
126 | while ( |
---|
127 | my ($id, $file_type_util) = each(%{$attrib_ref->{file_type_util_of}}) |
---|
128 | ) { |
---|
129 | # File name extension, name pattern and she-bang pattern |
---|
130 | for my $key (qw{ext pat she}) { |
---|
131 | my $method = 'file_' . $key; |
---|
132 | if ($file_type_util->can($method)) { |
---|
133 | my $value = $file_type_util->$method(); |
---|
134 | if (defined($value)) { |
---|
135 | $attrib_ref->{prop_of}{"file-$key.$id"} = [$value]; |
---|
136 | } |
---|
137 | } |
---|
138 | } |
---|
139 | # Dependency types |
---|
140 | if ($file_type_util->can('source_analyse_deps')) { |
---|
141 | for my $name ($file_type_util->source_analyse_deps()) { |
---|
142 | $attrib_ref->{prop_of}{"dep.$name"} = [q{}, 1]; |
---|
143 | $attrib_ref->{prop_of}{"no-dep.$name"} = [q{}, 1]; |
---|
144 | } |
---|
145 | } |
---|
146 | # Name-space dependency types |
---|
147 | if ($file_type_util->can('ns_targets_deps')) { |
---|
148 | for my $name ($file_type_util->ns_targets_deps()) { |
---|
149 | $attrib_ref->{prop_of}{"ns-dep.$name"} = [q{}, 1]; |
---|
150 | } |
---|
151 | } |
---|
152 | # Target extensions |
---|
153 | if ($file_type_util->can('target_file_ext_of')) { |
---|
154 | while (my ($key, $value) |
---|
155 | = each(%{$file_type_util->target_file_ext_of()}) |
---|
156 | ) { |
---|
157 | $attrib_ref->{prop_of}{"file-ext.$key"} = [$value, 1]; |
---|
158 | } |
---|
159 | } |
---|
160 | # Target file naming options |
---|
161 | if ($file_type_util->can('target_file_name_option_of')) { |
---|
162 | while (my ($key, $value) |
---|
163 | = each(%{$file_type_util->target_file_name_option_of()}) |
---|
164 | ) { |
---|
165 | $attrib_ref->{prop_of}{"file-name-option.$key"} = [$value, 1]; |
---|
166 | } |
---|
167 | } |
---|
168 | # Task properties |
---|
169 | my %task_of = %{$file_type_util->task_of()}; |
---|
170 | while (my ($name, $task) = each(%task_of)) { |
---|
171 | if ($task->can('prop_of')) { |
---|
172 | my %prop_of = %{$task->prop_of()}; |
---|
173 | while (my ($key, $value) = each(%prop_of)) { |
---|
174 | $attrib_ref->{prop_of}{$key} = [$value, 1]; |
---|
175 | } |
---|
176 | } |
---|
177 | } |
---|
178 | } |
---|
179 | } |
---|
180 | |
---|
181 | # A hook command for the "inherit/use" declaration. |
---|
182 | sub _config_parse_inherit_hook { |
---|
183 | my ($attrib_ref, $ctx, $i_ctx) = @_; |
---|
184 | push(@{$ctx->get_input_ns_excl()}, @{$i_ctx->get_input_ns_excl()}); |
---|
185 | push(@{$ctx->get_input_ns_incl()}, @{$i_ctx->get_input_ns_incl()}); |
---|
186 | while (my ($key, $value) = each(%{$i_ctx->get_target_key_of()})) { |
---|
187 | $ctx->get_target_key_of()->{$key} = $value; |
---|
188 | } |
---|
189 | while (my ($key, $item_ref) = each(%{$i_ctx->get_target_select_by()})) { |
---|
190 | while (my ($key2, $attr_set) = each(%{$item_ref})) { |
---|
191 | if (ref($attr_set)) { |
---|
192 | $ctx->get_target_select_by()->{$key}{$key2} = {%{$attr_set}}; |
---|
193 | } |
---|
194 | else { |
---|
195 | # Backward compat, $key2 is an $attr |
---|
196 | $ctx->get_target_select_by()->{$key}{q{}}{$key2} = 1; |
---|
197 | } |
---|
198 | } |
---|
199 | } |
---|
200 | _config_parse_inherit_hook_prop($attrib_ref, $ctx, $i_ctx); |
---|
201 | } |
---|
202 | |
---|
203 | # Returns a function to parse a build/preprocess.ns-??cl declaration. |
---|
204 | sub _config_parse_ns_filter_func { |
---|
205 | my ($getter) = @_; |
---|
206 | sub { |
---|
207 | my ($attrib_ref, $ctx, $entry) = @_; |
---|
208 | if (@{$entry->get_ns_list()}) { |
---|
209 | return $E->throw($E->CONFIG_NS, $entry); |
---|
210 | } |
---|
211 | @{$getter->($ctx)} = map {$_ eq q{/} ? q{} : $_} $entry->get_values(); |
---|
212 | }; |
---|
213 | } |
---|
214 | |
---|
215 | # Parses a build/preprocess.source declaration. |
---|
216 | sub _config_parse_source { |
---|
217 | my ($attrib_ref, $ctx, $entry) = @_; |
---|
218 | my ($ns) = @{$entry->get_ns_list()}; |
---|
219 | $ns ||= q{}; |
---|
220 | $ctx->get_input_source_of()->{$ns} = [$entry->get_values()]; |
---|
221 | } |
---|
222 | |
---|
223 | # Parses a build/preprocess.target declaration. |
---|
224 | sub _config_parse_target { |
---|
225 | my ($attrib_ref, $ctx, $entry) = @_; |
---|
226 | my %modifier_of = %{$entry->get_modifier_of()}; |
---|
227 | if (!(%modifier_of)) { |
---|
228 | %modifier_of = (key => 1); |
---|
229 | } |
---|
230 | my @ns_list = map {$_ eq q{/} ? q{} : $_} @{$entry->get_ns_list()}; |
---|
231 | if (exists($modifier_of{'key'}) && grep {$_} @ns_list) { |
---|
232 | return $E->throw($E->CONFIG_NS, $entry); |
---|
233 | } |
---|
234 | if (!@ns_list) { |
---|
235 | @ns_list = (q{}); |
---|
236 | } |
---|
237 | while (my $name = each(%modifier_of)) { |
---|
238 | if (!grep {$_ eq $name} qw{category key task}) { |
---|
239 | return $E->throw($E->CONFIG_MODIFIER, $entry); |
---|
240 | } |
---|
241 | my %attr_set = map {($_ => 1)} $entry->get_values(); |
---|
242 | for my $ns (@ns_list) { |
---|
243 | $ctx->get_target_select_by()->{$name}{$ns} = \%attr_set; |
---|
244 | } |
---|
245 | } |
---|
246 | } |
---|
247 | |
---|
248 | # Parses a build/preprocess.target-rename declaration. |
---|
249 | sub _config_parse_target_rename { |
---|
250 | my ($attrib_ref, $ctx, $entry) = @_; |
---|
251 | $ctx->set_target_key_of({ |
---|
252 | map { |
---|
253 | my ($old, $new) = split(qr{:}msx, $_, 2); |
---|
254 | if (!$old || !$new) { |
---|
255 | return $E->throw($E->CONFIG_VALUE, $entry); |
---|
256 | } |
---|
257 | ($old => $new); |
---|
258 | } ($entry->get_values()), |
---|
259 | }); |
---|
260 | } |
---|
261 | |
---|
262 | # Turns a context into a list of configuration entries. |
---|
263 | sub _config_unparse { |
---|
264 | my ($attrib_ref, $ctx) = @_; |
---|
265 | my %LABEL_OF |
---|
266 | = map {($_ => $ctx->get_id() . q{.} . $_)} keys(%CONFIG_PARSER_OF); |
---|
267 | ( ( @{$ctx->get_input_ns_excl()} |
---|
268 | ? FCM::Context::ConfigEntry->new({ |
---|
269 | label => $LABEL_OF{'ns-excl'}, |
---|
270 | value => _config_unparse_join( |
---|
271 | map {$_ ? $_ : q{/}} @{$ctx->get_input_ns_excl()} |
---|
272 | ), |
---|
273 | }) |
---|
274 | : () |
---|
275 | ), |
---|
276 | ( @{$ctx->get_input_ns_incl()} |
---|
277 | ? FCM::Context::ConfigEntry->new({ |
---|
278 | label => $LABEL_OF{'ns-incl'}, |
---|
279 | value => _config_unparse_join( |
---|
280 | map {$_ ? $_ : q{/}} @{$ctx->get_input_ns_incl()} |
---|
281 | ), |
---|
282 | }) |
---|
283 | : () |
---|
284 | ), |
---|
285 | ( map { |
---|
286 | FCM::Context::ConfigEntry->new({ |
---|
287 | label => $LABEL_OF{source}, |
---|
288 | ns_list => [$_], |
---|
289 | value => _config_unparse_join( |
---|
290 | sort(@{$ctx->get_input_source_of()->{$_}}) |
---|
291 | ), |
---|
292 | }) |
---|
293 | } |
---|
294 | sort keys(%{$ctx->get_input_source_of()}) |
---|
295 | ), |
---|
296 | ( keys(%{$ctx->get_target_key_of()}) |
---|
297 | ? FCM::Context::ConfigEntry->new({ |
---|
298 | label => $LABEL_OF{'target-rename'}, |
---|
299 | value => _config_unparse_join( |
---|
300 | map {$_ . ':' . $ctx->get_target_key_of()->{$_}} |
---|
301 | sort keys(%{$ctx->get_target_key_of()}) |
---|
302 | ), |
---|
303 | }) |
---|
304 | : () |
---|
305 | ), |
---|
306 | ( map { |
---|
307 | my $modifier = $_; |
---|
308 | map { |
---|
309 | my $ns = $_; |
---|
310 | my @values = sort(keys( |
---|
311 | %{$ctx->get_target_select_by()->{$modifier}{$ns}} |
---|
312 | )); |
---|
313 | FCM::Context::ConfigEntry->new({ |
---|
314 | label => $LABEL_OF{'target'}, |
---|
315 | modifier_of => {$modifier => 1}, |
---|
316 | ns_list => [$ns], |
---|
317 | value => _config_unparse_join(@values), |
---|
318 | }); |
---|
319 | } |
---|
320 | sort keys(%{$ctx->get_target_select_by()->{$modifier}}); |
---|
321 | } |
---|
322 | sort keys(%{$ctx->get_target_select_by()}) |
---|
323 | ), |
---|
324 | _config_unparse_prop($attrib_ref, $ctx), |
---|
325 | ); |
---|
326 | } |
---|
327 | |
---|
328 | # Returns a new context. |
---|
329 | sub _ctx { |
---|
330 | my ($attrib_ref, $id_of_class, $id) = @_; |
---|
331 | FCM::Context::Make::Build->new({ |
---|
332 | id => $id, |
---|
333 | id_of_class => $id_of_class, |
---|
334 | target_select_by => dclone($attrib_ref->{target_select_by}), |
---|
335 | }); |
---|
336 | } |
---|
337 | |
---|
338 | # Hook when loading a previous ctx. |
---|
339 | sub _ctx_load_hook { |
---|
340 | my ($attrib_ref, $old_m_ctx, $old_ctx, $old_m_dest, $old_dest) = @_; |
---|
341 | my $path_mod_func = sub { |
---|
342 | my ($get_func, $set_func) = @_; |
---|
343 | my $path = $get_func->(); |
---|
344 | if (!defined($path)) { |
---|
345 | return; |
---|
346 | } |
---|
347 | my $rel_path = abs2rel($path, $old_m_dest); |
---|
348 | if (index($rel_path, '..') != 0) { |
---|
349 | $set_func->(catfile($old_m_ctx->get_dest(), $rel_path)); |
---|
350 | } |
---|
351 | }; |
---|
352 | if (@{$old_ctx->get_dests()}) { |
---|
353 | $old_ctx->get_dests()->[0] = $old_ctx->get_dest(); |
---|
354 | } |
---|
355 | while (my ($ns, $source) = each(%{$old_ctx->get_source_of()})) { |
---|
356 | $path_mod_func->( |
---|
357 | sub {$source->get_path()}, |
---|
358 | sub {$source->set_path(@_)}, |
---|
359 | ); |
---|
360 | } |
---|
361 | while (my ($key, $target) = each(%{$old_ctx->get_target_of()})) { |
---|
362 | $path_mod_func->( |
---|
363 | sub {$target->get_path()}, |
---|
364 | sub {$target->set_path(@_)}, |
---|
365 | ); |
---|
366 | $path_mod_func->( |
---|
367 | sub {$target->get_path_of_prev()}, |
---|
368 | sub {$target->set_path_of_prev(@_)}, |
---|
369 | ); |
---|
370 | $path_mod_func->( |
---|
371 | sub {$target->get_path_of_source()}, |
---|
372 | sub {$target->set_path_of_source(@_)}, |
---|
373 | ); |
---|
374 | } |
---|
375 | } |
---|
376 | |
---|
377 | # The main function of the class. |
---|
378 | sub _main { |
---|
379 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
380 | local($UTIL) = $attrib_ref->{util}; |
---|
381 | local($EVENT) = sub {$UTIL->event(@_)}; |
---|
382 | for my $function ( |
---|
383 | \&_sources_locate, |
---|
384 | \&_sources_type, |
---|
385 | \&_sources_analyse, |
---|
386 | \&_targets_update, |
---|
387 | ) { |
---|
388 | $function->($attrib_ref, $m_ctx, $ctx); |
---|
389 | } |
---|
390 | } |
---|
391 | |
---|
392 | # Locates the actual source files, and determines their types. |
---|
393 | sub _sources_locate { |
---|
394 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
395 | # From inherited |
---|
396 | my %NO_INHERIT_FROM |
---|
397 | = map {($_ => 1)} _props($attrib_ref, 'no-inherit-source', $ctx); |
---|
398 | if (!$NO_INHERIT_FROM{'*'}) { |
---|
399 | for my $i_ctx (_i_ctx_list($m_ctx, $ctx)) { |
---|
400 | while (my ($ns, $source) = each(%{$i_ctx->get_source_of()})) { |
---|
401 | if (!exists($NO_INHERIT_FROM{$ns})) { # exact name-spaces only |
---|
402 | $ctx->get_source_of()->{$ns} = dclone($source); |
---|
403 | } |
---|
404 | } |
---|
405 | } |
---|
406 | } |
---|
407 | # From specified input |
---|
408 | while (my ($ns, $input_sources_ref) = each(%{$ctx->get_input_source_of()})) { |
---|
409 | for my $input_source (@{$input_sources_ref}) { |
---|
410 | my $path = realpath(rel2abs($input_source, $m_ctx->get_dest())); |
---|
411 | _sources_locate_by_find($attrib_ref, $m_ctx, $ctx, $ns, $path); |
---|
412 | } |
---|
413 | } |
---|
414 | # From completed make destinations |
---|
415 | my %NO_SOURCE_FROM |
---|
416 | = map {($_, 1)} _props($attrib_ref, 'no-step-source', $ctx); |
---|
417 | for my $step (@{$m_ctx->get_steps()}) { |
---|
418 | my $a_ctx = $m_ctx->get_ctx_of($step); |
---|
419 | if ( !exists($NO_SOURCE_FROM{$step}) |
---|
420 | && defined($a_ctx) |
---|
421 | && $a_ctx->get_status() eq $m_ctx->ST_OK |
---|
422 | && $a_ctx->can('get_target_of') |
---|
423 | ) { |
---|
424 | my @target_list |
---|
425 | = grep {$_->can_be_source()} values(%{$a_ctx->get_target_of()}); |
---|
426 | for my $target (@target_list) { |
---|
427 | if ($target->is_ok() && -e $target->get_path()) { |
---|
428 | my $checksum; |
---|
429 | if ($target->can('get_checksum')) { |
---|
430 | $checksum = $target->get_checksum(); |
---|
431 | } |
---|
432 | my $source = $ctx->CTX_SOURCE->new({ |
---|
433 | checksum => $checksum, |
---|
434 | ns => $target->get_ns(), |
---|
435 | path => $target->get_path(), |
---|
436 | }); |
---|
437 | $ctx->get_source_of()->{$target->get_ns()} = $source; |
---|
438 | } |
---|
439 | elsif (exists($ctx->get_source_of()->{$target->get_ns()})) { |
---|
440 | delete($ctx->get_source_of()->{$target->get_ns()}); |
---|
441 | } |
---|
442 | } |
---|
443 | } |
---|
444 | } |
---|
445 | # Applies filter |
---|
446 | my %INPUT_NS_EXCL = map {($_, 1)} @{$ctx->get_input_ns_excl()}; |
---|
447 | my %INPUT_NS_INCL = map {($_, 1)} @{$ctx->get_input_ns_incl()}; |
---|
448 | if (keys(%INPUT_NS_EXCL) || keys(%INPUT_NS_INCL)) { |
---|
449 | while (my ($ns, $source) = each(%{$ctx->get_source_of()})) { |
---|
450 | my $ns_iter_ref = $UTIL->ns_iter($ns, $UTIL->NS_ITER_UP); |
---|
451 | NS: |
---|
452 | while (defined(my $head = $ns_iter_ref->())) { |
---|
453 | if (exists($INPUT_NS_INCL{$head})) { |
---|
454 | last NS; |
---|
455 | } |
---|
456 | if (exists($INPUT_NS_EXCL{$head})) { |
---|
457 | delete($ctx->get_source_of()->{$ns}); |
---|
458 | last NS; |
---|
459 | } |
---|
460 | } |
---|
461 | } |
---|
462 | } |
---|
463 | } |
---|
464 | |
---|
465 | # Locates the actual source files in $path. |
---|
466 | sub _sources_locate_by_find { |
---|
467 | my ($attrib_ref, $m_ctx, $ctx, $key, $path) = @_; |
---|
468 | if (!-e $path) { |
---|
469 | return $E->throw($E->BUILD_SOURCE, $path, $!); |
---|
470 | } |
---|
471 | find( |
---|
472 | sub { |
---|
473 | my $path_found = $File::Find::name; |
---|
474 | if (-d $path_found) { |
---|
475 | return; |
---|
476 | } |
---|
477 | my $ns = abs2rel($path_found, $path); |
---|
478 | if ($ns ne q{.}) { |
---|
479 | for my $name (split(q{/}, $ns)) { |
---|
480 | if (index($name, q{.}) == 0) { |
---|
481 | return; # ignore Unix hidden/system files |
---|
482 | } |
---|
483 | } |
---|
484 | } |
---|
485 | if ($key) { |
---|
486 | $ns = $UTIL->ns_cat($key, $ns); |
---|
487 | } |
---|
488 | $ctx->get_source_of()->{$ns} |
---|
489 | = $ctx->CTX_SOURCE->new({ns => $ns, path => $path_found}); |
---|
490 | }, |
---|
491 | $path, |
---|
492 | ); |
---|
493 | } |
---|
494 | |
---|
495 | # Determines source types. |
---|
496 | sub _sources_type { |
---|
497 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
498 | my %INPUT_FILE_EXT_TO_TYPE_MAP; |
---|
499 | my %INPUT_FILE_PAT_TO_TYPE_MAP; |
---|
500 | my %INPUT_FILE_SHE_TO_TYPE_MAP; |
---|
501 | for ( |
---|
502 | ['file-ext.', \%INPUT_FILE_EXT_TO_TYPE_MAP, 1], |
---|
503 | ['file-pat.', \%INPUT_FILE_PAT_TO_TYPE_MAP, 0], |
---|
504 | ['file-she.', \%INPUT_FILE_SHE_TO_TYPE_MAP, 0], |
---|
505 | ) { |
---|
506 | my ($prefix, $map_ref, $value_is_words) = @{$_}; |
---|
507 | for my $id (keys(%{$attrib_ref->{file_type_util_of}})) { |
---|
508 | my $name = $prefix . $id; |
---|
509 | my $value = _prop($attrib_ref, $name, $ctx); |
---|
510 | if (defined($value)) { |
---|
511 | for my $key (($value_is_words ? shellwords($value) : ($value))) { |
---|
512 | $map_ref->{$key} = $id; |
---|
513 | } |
---|
514 | } |
---|
515 | } |
---|
516 | } |
---|
517 | my $type_func = sub { |
---|
518 | my ($path) = @_; |
---|
519 | # Try file name extension |
---|
520 | my $extension = $UTIL->file_ext($path); |
---|
521 | $extension = $extension ? q{.} . $extension : undef; |
---|
522 | if ($extension && exists($INPUT_FILE_EXT_TO_TYPE_MAP{$extension})) { |
---|
523 | return $INPUT_FILE_EXT_TO_TYPE_MAP{$extension}; |
---|
524 | } |
---|
525 | # Try she-bang line |
---|
526 | if (-T $path) { |
---|
527 | my $line = $UTIL->file_head($path); |
---|
528 | if ($line) { |
---|
529 | while (my ($pattern, $type) = each(%INPUT_FILE_SHE_TO_TYPE_MAP)) { |
---|
530 | if (index($line, '#!') == 0) { # OK to hard code this |
---|
531 | keys(%INPUT_FILE_SHE_TO_TYPE_MAP); # reset iterator |
---|
532 | return $type; |
---|
533 | } |
---|
534 | } |
---|
535 | } |
---|
536 | } |
---|
537 | # Try file name pattern |
---|
538 | my $base_name = basename($path); |
---|
539 | while (my ($pattern, $type) = each(%INPUT_FILE_PAT_TO_TYPE_MAP)) { |
---|
540 | if ($base_name =~ $pattern) { |
---|
541 | keys(%INPUT_FILE_PAT_TO_TYPE_MAP); # reset iterator |
---|
542 | return $type; |
---|
543 | } |
---|
544 | } |
---|
545 | return q{}; |
---|
546 | }; |
---|
547 | while (my ($ns, $source) = each(%{$ctx->get_source_of()})) { |
---|
548 | if (!defined($source->get_type())) { |
---|
549 | $source->set_type($type_func->($source->get_path())); |
---|
550 | } |
---|
551 | } |
---|
552 | } |
---|
553 | |
---|
554 | # Reads source files to gather dependency and other information. |
---|
555 | sub _sources_analyse { |
---|
556 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
557 | my $timer = $UTIL->timer(); |
---|
558 | my $checksum_method = _prop($attrib_ref, 'checksum-method', $ctx); |
---|
559 | my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}}; |
---|
560 | # Checksum |
---|
561 | while (my ($ns, $source) = each(%{$ctx->get_source_of()})) { |
---|
562 | if ( exists($FILE_TYPE_UTIL_OF{$source->get_type()}) |
---|
563 | && !defined($source->get_checksum()) |
---|
564 | ) { |
---|
565 | $source->set_checksum( |
---|
566 | $UTIL->file_checksum($source->get_path(), $checksum_method), |
---|
567 | ); |
---|
568 | } |
---|
569 | } |
---|
570 | # Source information |
---|
571 | my $n_jobs = $m_ctx->get_option_of('jobs'); |
---|
572 | my $runner = $UTIL->task_runner( |
---|
573 | sub {_source_analyse($attrib_ref, @_)}, |
---|
574 | $n_jobs, |
---|
575 | ); |
---|
576 | my $elapse_tasks = 0; |
---|
577 | my $n = eval { |
---|
578 | $runner->main( |
---|
579 | _source_analyse_get_func($attrib_ref, $m_ctx, $ctx), |
---|
580 | _source_analyse_put_func($attrib_ref, $m_ctx, $ctx, \$elapse_tasks), |
---|
581 | ); |
---|
582 | }; |
---|
583 | my $e = $@; |
---|
584 | $runner->destroy(); |
---|
585 | if ($e) { |
---|
586 | die($e); |
---|
587 | } |
---|
588 | my $n_total = scalar(keys(%{$ctx->get_source_of()})); |
---|
589 | $EVENT->( |
---|
590 | FCM::Context::Event->MAKE_BUILD_SOURCE_SUMMARY, |
---|
591 | $n_total, $n, $timer->(), $elapse_tasks, |
---|
592 | ); |
---|
593 | } |
---|
594 | |
---|
595 | # Reads a source to gather information. |
---|
596 | sub _source_analyse { |
---|
597 | my ($attrib_ref, $source) = @_; |
---|
598 | my $FILE_TYPE_UTIL |
---|
599 | = $attrib_ref->{file_type_util_of}->{$source->get_type()}; |
---|
600 | if (!$FILE_TYPE_UTIL->can('source_analyse')) { |
---|
601 | return; |
---|
602 | } |
---|
603 | $FILE_TYPE_UTIL->source_analyse($source); |
---|
604 | } |
---|
605 | |
---|
606 | # Generates an iterator for each source file requiring information gathering. |
---|
607 | sub _source_analyse_get_func { |
---|
608 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
609 | my $P_SOURCE_GETTER |
---|
610 | = _prev_hash_item_getter($m_ctx, $ctx, sub {$_[0]->get_source_of()}); |
---|
611 | my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}}; |
---|
612 | my $exhausted; |
---|
613 | sub { |
---|
614 | if ($exhausted) { |
---|
615 | return; |
---|
616 | } |
---|
617 | SOURCE: |
---|
618 | while (my ($ns, $source) = each(%{$ctx->get_source_of()})) { |
---|
619 | my $type = $source->get_type(); |
---|
620 | if (!exists($FILE_TYPE_UTIL_OF{$type})) { |
---|
621 | next SOURCE; |
---|
622 | } |
---|
623 | # Stores the current properties relevant to the source |
---|
624 | for my $dep_type ($FILE_TYPE_UTIL_OF{$type}->source_analyse_deps()) { |
---|
625 | for my $n (map {$_ . q{.} . $dep_type} qw{dep no-dep}) { |
---|
626 | $source->get_prop_of()->{$n} |
---|
627 | = _prop($attrib_ref, $n, $ctx, $ns); |
---|
628 | } |
---|
629 | } |
---|
630 | # Compare with previous source, if possible |
---|
631 | my $p_source = $P_SOURCE_GETTER->($ns); |
---|
632 | if (defined($p_source)) { |
---|
633 | $source->set_up_to_date( |
---|
634 | $p_source->get_checksum() eq $source->get_checksum()); |
---|
635 | if ( $source->get_up_to_date() |
---|
636 | && !$UTIL->hash_cmp( |
---|
637 | map {$_->get_prop_of()} ($source, $p_source) |
---|
638 | ) |
---|
639 | ) { |
---|
640 | $source->set_info_of(dclone($p_source->get_info_of())); |
---|
641 | $source->set_deps( dclone($p_source->get_deps() )); |
---|
642 | next SOURCE; |
---|
643 | } |
---|
644 | } |
---|
645 | return FCM::Context::Task->new({ctx => $source, id => $ns}); |
---|
646 | } |
---|
647 | $exhausted = 1; |
---|
648 | return; |
---|
649 | }; |
---|
650 | } |
---|
651 | |
---|
652 | # Generates a callback when a source read completes. |
---|
653 | sub _source_analyse_put_func { |
---|
654 | my ($attrib_ref, $m_ctx, $ctx, $elapse_tasks_ref) = @_; |
---|
655 | my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}}; |
---|
656 | sub { |
---|
657 | my ($task) = @_; |
---|
658 | if ($task->get_state() eq $task->ST_FAILED) { |
---|
659 | die($task->get_error()); |
---|
660 | } |
---|
661 | my $ns = $task->get_id(); |
---|
662 | my $source = $ctx->get_source_of()->{$ns} = $task->get_ctx(); |
---|
663 | for my $type ( |
---|
664 | $FILE_TYPE_UTIL_OF{$source->get_type()}->source_analyse_deps() |
---|
665 | ) { |
---|
666 | # Note: "dep" property: use name-space value only |
---|
667 | my $key = 'dep.' . $type; |
---|
668 | push( |
---|
669 | @{$source->get_deps()}, |
---|
670 | (map {[$_, $type]} _props($attrib_ref, $key, $ctx, $ns)), |
---|
671 | ); |
---|
672 | } |
---|
673 | ${$elapse_tasks_ref} += $task->get_elapse(); |
---|
674 | $EVENT->( |
---|
675 | FCM::Context::Event->MAKE_BUILD_SOURCE_ANALYSE, |
---|
676 | $source, $task->get_elapse(), |
---|
677 | ); |
---|
678 | } |
---|
679 | } |
---|
680 | |
---|
681 | # Updates the targets. |
---|
682 | sub _targets_update { |
---|
683 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
684 | my $timer = $UTIL->timer(); |
---|
685 | # Creates and changes directory to the destination |
---|
686 | eval {mkpath($ctx->get_dest())}; |
---|
687 | if ($@) { |
---|
688 | return $E->throw($E->DEST_CREATE, $ctx->get_dest()); |
---|
689 | } |
---|
690 | my $old_cwd = cwd(); |
---|
691 | chdir($ctx->get_dest()) || die(sprintf("%s: %s\n", $ctx->get_dest(), $!)); |
---|
692 | # Extract any target category directories that are in .tar.gz |
---|
693 | opendir(my $handle, '.'); |
---|
694 | while (my $name = readdir($handle)) { |
---|
695 | if ((fileparse($name, '.tar.gz'))[2] eq '.tar.gz') { |
---|
696 | my %value_of = %{$UTIL->shell_simple([qw{tar -x -z}, '-f', $name])}; |
---|
697 | if ($value_of{'rc'} == 0) { |
---|
698 | unlink($name); |
---|
699 | } |
---|
700 | } |
---|
701 | } |
---|
702 | closedir($handle); |
---|
703 | # Determines the destination search path |
---|
704 | my $id = $ctx->get_id(); |
---|
705 | @{$ctx->get_dests()} = ( |
---|
706 | $ctx->get_dest(), |
---|
707 | map {$_->get_ctx_of($id) ? @{$_->get_ctx_of($id)->get_dests()} : ()} |
---|
708 | @{$m_ctx->get_inherit_ctx_list()} |
---|
709 | , |
---|
710 | ); |
---|
711 | # Performs targets update |
---|
712 | my $checksum_method = _prop($attrib_ref, 'checksum-method', $ctx); |
---|
713 | my %stat_of = (); |
---|
714 | eval { |
---|
715 | my $n_jobs = $m_ctx->get_option_of('jobs'); |
---|
716 | my $runner = $UTIL->task_runner( |
---|
717 | sub {_target_update($attrib_ref, $checksum_method, @_)}, |
---|
718 | $n_jobs, |
---|
719 | ); |
---|
720 | eval { |
---|
721 | my ($get_ref, $put_ref) = _targets_manager_funcs( |
---|
722 | $attrib_ref, $m_ctx, $ctx, \%stat_of, |
---|
723 | ); |
---|
724 | $runner->main($get_ref, $put_ref); |
---|
725 | }; |
---|
726 | my $e = $@; |
---|
727 | $runner->destroy(); |
---|
728 | if ($e) { |
---|
729 | die($e); |
---|
730 | } |
---|
731 | }; |
---|
732 | my $e = $@; |
---|
733 | # Back to original working directory |
---|
734 | chdir($old_cwd) || die(sprintf("%s: %s\n", $old_cwd, $!)); |
---|
735 | if ($e) { |
---|
736 | _finally($attrib_ref, $m_ctx, $ctx); |
---|
737 | die($e); |
---|
738 | } |
---|
739 | # Finally |
---|
740 | my @targets = values(%{$ctx->get_target_of()}); |
---|
741 | for my $key (sort(keys(%stat_of))) { |
---|
742 | $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_MODIFIED} ||= 0; |
---|
743 | $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_UNCHANGED} ||= 0; |
---|
744 | $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_FAILED} ||= 0; |
---|
745 | $stat_of{$key}{t} ||= 0.0; |
---|
746 | $EVENT->( |
---|
747 | FCM::Context::Event->MAKE_BUILD_TARGET_TASK_SUMMARY, |
---|
748 | $key, |
---|
749 | $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_MODIFIED}, |
---|
750 | $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_UNCHANGED}, |
---|
751 | $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_FAILED}, |
---|
752 | $stat_of{$key}{t}, |
---|
753 | ); |
---|
754 | } |
---|
755 | $EVENT->( |
---|
756 | FCM::Context::Event->MAKE_BUILD_TARGET_SUMMARY, |
---|
757 | scalar(grep {$_->is_modified() } @targets), |
---|
758 | scalar(grep {$_->is_unchanged()} @targets), |
---|
759 | scalar(grep {$_->is_failed() } @targets), |
---|
760 | $timer->(), |
---|
761 | ); |
---|
762 | my @failed_targets = grep {$_->is_failed()} @targets; |
---|
763 | if (@failed_targets) { |
---|
764 | $EVENT->( |
---|
765 | FCM::Context::Event->MAKE_BUILD_TARGETS_FAIL, |
---|
766 | \@failed_targets |
---|
767 | ); |
---|
768 | _finally($attrib_ref, $m_ctx, $ctx); |
---|
769 | die("\n"); |
---|
770 | } |
---|
771 | _finally($attrib_ref, $m_ctx, $ctx); |
---|
772 | } |
---|
773 | |
---|
774 | # Updates a target. |
---|
775 | sub _target_update { |
---|
776 | my ($attrib_ref, $checksum_method, $target) = @_; |
---|
777 | my $file_type_util = $attrib_ref->{file_type_util_of}{$target->get_type()}; |
---|
778 | eval {$file_type_util->task_of()->{$target->get_task()}->main($target)}; |
---|
779 | if ($@) { |
---|
780 | if ($target->get_path() && -e $target->get_path()) { |
---|
781 | unlink($target->get_path()); |
---|
782 | } |
---|
783 | die($@); |
---|
784 | } |
---|
785 | if (! -e $target->get_path()) { |
---|
786 | return $E->throw($E->BUILD_TARGET, $target); |
---|
787 | } |
---|
788 | $target->set_status($target->ST_MODIFIED); |
---|
789 | my $checksum = $UTIL->file_checksum($target->get_path(), $checksum_method); |
---|
790 | if ($target->get_checksum() && $checksum eq $target->get_checksum()) { |
---|
791 | $target->set_status($target->ST_UNCHANGED); |
---|
792 | if ($target->get_path_of_prev()) { |
---|
793 | $target->set_path($target->get_path_of_prev()); |
---|
794 | } |
---|
795 | } |
---|
796 | $target->set_checksum($checksum); |
---|
797 | $target->set_prop_of_prev_of({}); # unset |
---|
798 | $target->set_path_of_prev(undef); # unset |
---|
799 | } |
---|
800 | |
---|
801 | # Returns the get/put functions to send/receive targets to update. |
---|
802 | sub _targets_manager_funcs { |
---|
803 | my ($attrib_ref, $m_ctx, $ctx, $stat_hash_ref) = @_; |
---|
804 | |
---|
805 | my @targets; |
---|
806 | _targets_from_sources($attrib_ref, $m_ctx, $ctx, \@targets); |
---|
807 | _targets_props_assign($attrib_ref, $m_ctx, $ctx, \@targets); |
---|
808 | |
---|
809 | my ($stack_ref, $state_hash_ref) |
---|
810 | = _targets_select($attrib_ref, $m_ctx, $ctx, \@targets); |
---|
811 | |
---|
812 | my $checksum_method = _prop($attrib_ref, 'checksum-method', $ctx); |
---|
813 | my $get_action_ref = sub { |
---|
814 | STATE: |
---|
815 | while (my $state = pop(@{$stack_ref})) { |
---|
816 | if ( !$state->is_ready() |
---|
817 | || !_target_deps_are_done($state, $state_hash_ref, $stack_ref) |
---|
818 | ) { |
---|
819 | next STATE; |
---|
820 | } |
---|
821 | my $target = $state->get_target(); |
---|
822 | if (_target_check_failed_dep($state, $state_hash_ref)) { |
---|
823 | _target_update_failed( |
---|
824 | $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref, |
---|
825 | ); |
---|
826 | } |
---|
827 | elsif (_target_check_ood($state, $state_hash_ref, $checksum_method)) { |
---|
828 | _target_prep($state, $ctx); |
---|
829 | $state->set_value($STATE->PENDING); |
---|
830 | # Adds tasks that can be triggered by this task |
---|
831 | for my $key (sort @{$target->get_triggers()}) { |
---|
832 | if ( exists($state_hash_ref->{$key}) |
---|
833 | && !$state_hash_ref->{$key}->is_done() |
---|
834 | && !grep {$_->get_id() eq $key} @{$stack_ref} |
---|
835 | ) { |
---|
836 | my $trigger_target |
---|
837 | = $state_hash_ref->{$key}->get_target(); |
---|
838 | $trigger_target->set_status($trigger_target->ST_OOD); |
---|
839 | push(@{$stack_ref}, $state_hash_ref->{$key}); |
---|
840 | } |
---|
841 | } |
---|
842 | return FCM::Context::Task->new( |
---|
843 | {ctx => $target, id => $state->get_id()}, |
---|
844 | ); |
---|
845 | } |
---|
846 | else { |
---|
847 | _target_update_ok( |
---|
848 | $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref, |
---|
849 | ); |
---|
850 | } |
---|
851 | } |
---|
852 | return; |
---|
853 | }; |
---|
854 | my $put_action_ref = sub { |
---|
855 | my $task = shift(); |
---|
856 | my $target = $task->get_ctx(); |
---|
857 | if ($task->get_state() eq $task->ST_FAILED) { |
---|
858 | $EVENT->(FCM::Context::Event->E, $task->get_error()); |
---|
859 | _target_update_failed( |
---|
860 | $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref, |
---|
861 | $task->get_elapse(), |
---|
862 | ); |
---|
863 | } |
---|
864 | else { |
---|
865 | my $target = $task->get_ctx(); |
---|
866 | _target_update_ok( |
---|
867 | $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref, |
---|
868 | $task->get_elapse(), |
---|
869 | ); |
---|
870 | } |
---|
871 | }; |
---|
872 | ($get_action_ref, $put_action_ref); |
---|
873 | } |
---|
874 | |
---|
875 | # Determines and returns the targets from the sources. |
---|
876 | sub _targets_from_sources { |
---|
877 | my ($attrib_ref, $m_ctx, $ctx, $targets_ref) = @_; |
---|
878 | my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}}; |
---|
879 | my %FILE_EXT_OF; |
---|
880 | my %FILE_NAME_OPTION_OF; |
---|
881 | for my $FILE_TYPE_UTIL (values(%FILE_TYPE_UTIL_OF)) { |
---|
882 | while (my $key = each(%{$FILE_TYPE_UTIL->target_file_ext_of()})) { |
---|
883 | $FILE_EXT_OF{$key} ||= _prop($attrib_ref, 'file-ext.' . $key, $ctx); |
---|
884 | } |
---|
885 | while (my $key = each(%{$FILE_TYPE_UTIL->target_file_name_option_of()})) { |
---|
886 | $FILE_NAME_OPTION_OF{$key} |
---|
887 | ||= _prop($attrib_ref, 'file-name-option.' . $key, $ctx); |
---|
888 | } |
---|
889 | } |
---|
890 | # Determine the targets for each source |
---|
891 | SOURCE: |
---|
892 | while (my ($ns, $source) = each(%{$ctx->get_source_of()})) { |
---|
893 | my $type = $source->get_type(); |
---|
894 | $type ||= q{}; |
---|
895 | if (!exists($FILE_TYPE_UTIL_OF{$type})) { |
---|
896 | next SOURCE; |
---|
897 | } |
---|
898 | my $FILE_TYPE_UTIL = $FILE_TYPE_UTIL_OF{$type}; |
---|
899 | if (!$FILE_TYPE_UTIL->can('source_to_targets')) { |
---|
900 | next SOURCE; |
---|
901 | } |
---|
902 | for my $target ( |
---|
903 | $FILE_TYPE_UTIL->source_to_targets( |
---|
904 | $source, \%FILE_EXT_OF, \%FILE_NAME_OPTION_OF) |
---|
905 | ) { |
---|
906 | my $key = $target->get_key(); |
---|
907 | if (exists($ctx->get_target_key_of()->{$key})) { |
---|
908 | $key = $ctx->get_target_key_of()->{$key}; |
---|
909 | $target->set_key($key); |
---|
910 | } |
---|
911 | push(@{$targets_ref}, $target); |
---|
912 | $target->set_ns($ns); |
---|
913 | $target->set_path( |
---|
914 | catfile($ctx->get_dest(), $target->get_category(), $key), |
---|
915 | ); |
---|
916 | $target->set_path_of_source($source->get_path()); |
---|
917 | $target->set_type($type); |
---|
918 | if (!$source->get_up_to_date()) { |
---|
919 | $target->set_status($target->ST_OOD); |
---|
920 | } |
---|
921 | } |
---|
922 | } |
---|
923 | # Determines name-space dependencies |
---|
924 | my %deps_in_ns_in_cat_of; # $cat => {$ns => [$targets ...]} |
---|
925 | FILE_TYPE_UTIL: |
---|
926 | while (my ($type, $FILE_TYPE_UTIL) = each(%FILE_TYPE_UTIL_OF)) { |
---|
927 | if (!$FILE_TYPE_UTIL->can('ns_targets')) { |
---|
928 | next FILE_TYPE_UTIL; |
---|
929 | } |
---|
930 | for my $cat ($FILE_TYPE_UTIL->ns_targets_deps()) { |
---|
931 | $deps_in_ns_in_cat_of{$cat} = {}; |
---|
932 | } |
---|
933 | for my $target ( |
---|
934 | $FILE_TYPE_UTIL->ns_targets( |
---|
935 | $targets_ref, \%FILE_EXT_OF, \%FILE_NAME_OPTION_OF) |
---|
936 | ) { |
---|
937 | my $key = $target->get_key(); |
---|
938 | if (exists($ctx->get_target_key_of()->{$key})) { |
---|
939 | $key = $ctx->get_target_key_of()->{$key}; |
---|
940 | $target->set_key($key); |
---|
941 | } |
---|
942 | push(@{$targets_ref}, $target); |
---|
943 | $target->set_type($type); |
---|
944 | $target->set_path( |
---|
945 | catfile($ctx->get_dest(), $target->get_category(), $key), |
---|
946 | ); |
---|
947 | } |
---|
948 | } |
---|
949 | for my $target ( |
---|
950 | sort { |
---|
951 | $a->get_ns() cmp $b->get_ns() || $a->get_key() cmp $b->get_key(); |
---|
952 | } @{$targets_ref} |
---|
953 | ) { |
---|
954 | $EVENT->( |
---|
955 | FCM::Context::Event->MAKE_BUILD_TARGET_FROM_NS, |
---|
956 | ($target->get_ns() ? $target->get_ns() : '/'), |
---|
957 | $target->get_task(), |
---|
958 | $target->get_category(), |
---|
959 | $target->get_key(), |
---|
960 | ); |
---|
961 | } |
---|
962 | # Target categories and name-spaces. |
---|
963 | for my $target (@{$targets_ref}) { |
---|
964 | my $cat = $target->get_category(); |
---|
965 | if ($cat && exists($deps_in_ns_in_cat_of{$cat})) { |
---|
966 | my $ns_iter = $UTIL->ns_iter($target->get_ns(), $UTIL->NS_ITER_UP); |
---|
967 | # $ns_iter->(); # discard |
---|
968 | while (defined(my $ns = $ns_iter->())) { |
---|
969 | $deps_in_ns_in_cat_of{$cat}{$ns} ||= []; |
---|
970 | push(@{$deps_in_ns_in_cat_of{$cat}{$ns}}, $target->get_key()); |
---|
971 | } |
---|
972 | } |
---|
973 | } |
---|
974 | |
---|
975 | my %CTX_PROP_OF = %{$ctx->get_prop_of()}; |
---|
976 | for my $target (@{$targets_ref}) { |
---|
977 | my $key = $target->get_key(); |
---|
978 | # Adds categorised name-space dependencies. |
---|
979 | if (exists($target->get_info_of()->{'deps'})) { |
---|
980 | CATEGORY: |
---|
981 | while (my ($cat, $deps_in_ns_ref) = each(%deps_in_ns_in_cat_of)) { |
---|
982 | if (!exists($target->get_info_of()->{'deps'}{$cat})) { |
---|
983 | next CATEGORY; |
---|
984 | } |
---|
985 | my $cfg_key = 'ns-dep.' . $cat; |
---|
986 | my @ns_list = map {$_ eq q{/} ? q{} : $_} |
---|
987 | _props($attrib_ref, $cfg_key, $ctx, $target->get_ns()); |
---|
988 | for my $ns (@ns_list) { |
---|
989 | if (exists($deps_in_ns_ref->{$ns})) { |
---|
990 | push( |
---|
991 | @{$target->get_deps()}, |
---|
992 | ( map {[$_, $cat]} |
---|
993 | grep {$_ ne $key} |
---|
994 | @{$deps_in_ns_ref->{$ns}} |
---|
995 | ), |
---|
996 | ); |
---|
997 | } |
---|
998 | else { |
---|
999 | # This will be reported later as missing dependency |
---|
1000 | push(@{$target->get_deps()}, [$ns, $cat, 'ns-dep']); |
---|
1001 | } |
---|
1002 | } |
---|
1003 | } |
---|
1004 | } |
---|
1005 | # Remove target dependencies, if necessary |
---|
1006 | my @deps; |
---|
1007 | DEP: |
---|
1008 | for my $dep (@{$target->get_deps()}) { |
---|
1009 | my ($dep_key, $dep_type) = @{$dep}; |
---|
1010 | my $cfg_key = 'no-dep.' . $dep_type; |
---|
1011 | if ( !exists($CTX_PROP_OF{$cfg_key}) |
---|
1012 | || !exists($CTX_PROP_OF{$cfg_key}->get_ctx_of()->{$key}) |
---|
1013 | ) { |
---|
1014 | push(@deps, $dep); |
---|
1015 | next DEP; |
---|
1016 | } |
---|
1017 | my @no_dep_keys = shellwords( |
---|
1018 | $CTX_PROP_OF{$cfg_key}->get_ctx_of()->{$key}->get_value()); |
---|
1019 | if (!grep {$_ eq $dep_key} @no_dep_keys) { |
---|
1020 | push(@deps, $dep); |
---|
1021 | next DEP; |
---|
1022 | } |
---|
1023 | } |
---|
1024 | $target->set_deps(\@deps); |
---|
1025 | # Add target dependencies, if necessary |
---|
1026 | for my $dep_type (keys(%{$target->get_dep_policy_of()})) { |
---|
1027 | my $cfg_key = 'dep.' . $dep_type; |
---|
1028 | if ( exists($CTX_PROP_OF{$cfg_key}) |
---|
1029 | && exists($CTX_PROP_OF{$cfg_key}->get_ctx_of()->{$key}) |
---|
1030 | ) { |
---|
1031 | my @dep_keys = shellwords( |
---|
1032 | $CTX_PROP_OF{$cfg_key}->get_ctx_of()->{$key}->get_value()); |
---|
1033 | for my $dep_key (@dep_keys) { |
---|
1034 | push(@{$target->get_deps()}, [$dep_key, $dep_type]); |
---|
1035 | } |
---|
1036 | } |
---|
1037 | } |
---|
1038 | } |
---|
1039 | } |
---|
1040 | |
---|
1041 | # Stores the properties relevant to the target. |
---|
1042 | # Assigns previous checksum and properties, where appropriate. |
---|
1043 | sub _targets_props_assign { |
---|
1044 | my ($attrib_ref, $m_ctx, $ctx, $targets_ref) = @_; |
---|
1045 | my $P_TARGET_GETTER |
---|
1046 | = _prev_hash_item_getter($m_ctx, $ctx, sub {$_[0]->get_target_of()}); |
---|
1047 | my %NO_INHERIT_CATEGORY_IN |
---|
1048 | = map {$_ => 1} _props($attrib_ref, 'no-inherit-target-category', $ctx); |
---|
1049 | my %CTX_PROP_OF = %{$ctx->get_prop_of()}; |
---|
1050 | for my $target (@{$targets_ref}) { |
---|
1051 | my $key = $target->get_key(); |
---|
1052 | # Properties |
---|
1053 | my $FILE_TYPE_UTIL |
---|
1054 | = $attrib_ref->{file_type_util_of}->{$target->get_type()}; |
---|
1055 | my $task = $FILE_TYPE_UTIL->task_of()->{$target->get_task()}; |
---|
1056 | if ($task->can('prop_of')) { |
---|
1057 | my %prop_of = %{$task->prop_of($target)}; |
---|
1058 | while (my $name = each(%prop_of)) { |
---|
1059 | if ( exists($CTX_PROP_OF{$name}) |
---|
1060 | && exists($CTX_PROP_OF{$name}->get_ctx_of()->{$key}) |
---|
1061 | ) { |
---|
1062 | $target->get_prop_of()->{$name} |
---|
1063 | = $CTX_PROP_OF{$name}->get_ctx_of()->{$key}->get_value(); |
---|
1064 | } |
---|
1065 | else { |
---|
1066 | $target->get_prop_of()->{$name} |
---|
1067 | = _prop($attrib_ref, $name, $ctx, $target->get_ns()); |
---|
1068 | } |
---|
1069 | } |
---|
1070 | } |
---|
1071 | if ($FILE_TYPE_UTIL->can('target_deps_filter')) { |
---|
1072 | $FILE_TYPE_UTIL->target_deps_filter($target); |
---|
1073 | } |
---|
1074 | # Path, checksum and previous properties |
---|
1075 | my $p_target = $P_TARGET_GETTER->($key); |
---|
1076 | if (defined($p_target)) { |
---|
1077 | $target->set_checksum($p_target->get_checksum()); |
---|
1078 | if ($p_target->is_ok()) { |
---|
1079 | $target->set_path_of_prev($p_target->get_path()); |
---|
1080 | $target->set_prop_of_prev_of($p_target->get_prop_of()); |
---|
1081 | } |
---|
1082 | else { |
---|
1083 | $target->set_path_of_prev($p_target->get_path_of_prev()); |
---|
1084 | $target->set_prop_of_prev_of($p_target->get_prop_of_prev_of()); |
---|
1085 | $target->set_status($target->ST_OOD); |
---|
1086 | } |
---|
1087 | if (exists($NO_INHERIT_CATEGORY_IN{$target->get_category()})) { |
---|
1088 | $target->set_path_of_prev($target->get_path()); |
---|
1089 | } |
---|
1090 | } |
---|
1091 | } |
---|
1092 | } |
---|
1093 | |
---|
1094 | # Selects targets to update. |
---|
1095 | sub _targets_select { |
---|
1096 | my ($attrib_ref, $m_ctx, $ctx, $targets_ref) = @_; |
---|
1097 | my $time = time(); |
---|
1098 | my $timer = $UTIL->timer(); |
---|
1099 | my %select_by = %{$ctx->get_target_select_by()}; |
---|
1100 | my %target_of; |
---|
1101 | my %targets_of; |
---|
1102 | my %target_set; |
---|
1103 | my %has_ns_in; # available sets of name-spaces |
---|
1104 | for my $target (@{$targets_ref}) { |
---|
1105 | ATTR_NAME: |
---|
1106 | for ( |
---|
1107 | #$attr_name, $attr_func |
---|
1108 | ['key' , sub {$_[0]->get_key()}], |
---|
1109 | ['category', sub {$_[0]->get_category()}], |
---|
1110 | ['task' , sub {$_[0]->get_task()}], |
---|
1111 | ) { |
---|
1112 | my ($attr_name, $attr_func) = @{$_}; |
---|
1113 | for my $ns (sort keys(%{$select_by{$attr_name}})) { |
---|
1114 | my %attr_set = %{$select_by{$attr_name}->{$ns}}; |
---|
1115 | if ( exists($attr_set{$attr_func->($target)}) |
---|
1116 | && (!$ns || $UTIL->ns_in_set($target->get_ns(), {$ns => 1})) |
---|
1117 | ) { |
---|
1118 | $target_set{$target->get_key()} = 1; |
---|
1119 | last ATTR_NAME; |
---|
1120 | } |
---|
1121 | } |
---|
1122 | } |
---|
1123 | if (exists($target_of{$target->get_key()})) { |
---|
1124 | if (!exists($targets_of{$target->get_key()})) { |
---|
1125 | $targets_of{$target->get_key()} |
---|
1126 | = [delete($target_of{$target->get_key()})]; |
---|
1127 | } |
---|
1128 | push(@{$targets_of{$target->get_key()}}, $target); |
---|
1129 | } |
---|
1130 | else { |
---|
1131 | $target_of{$target->get_key()} = $target; |
---|
1132 | } |
---|
1133 | # Name-spaces |
---|
1134 | my $ns_iter = $UTIL->ns_iter($target->get_ns(), $UTIL->NS_ITER_UP); |
---|
1135 | NS: |
---|
1136 | while (defined(my $ns = $ns_iter->())) { |
---|
1137 | if (exists($has_ns_in{$ns})) { |
---|
1138 | last NS; |
---|
1139 | } |
---|
1140 | $has_ns_in{$ns} = 1; |
---|
1141 | } |
---|
1142 | } |
---|
1143 | my @target_keys = sort keys(%target_set); |
---|
1144 | |
---|
1145 | # Wraps each relevant target with a state object. |
---|
1146 | # Walks the target dependency tree to build a state dependency tree. |
---|
1147 | # Checks for missing dependencies. |
---|
1148 | # Checks for duplicated target. |
---|
1149 | my @items = map {[[$_, undef]]} @target_keys; |
---|
1150 | my %state_of; |
---|
1151 | my %dup_in; |
---|
1152 | my %cyc_in; |
---|
1153 | my %missing_deps_in; |
---|
1154 | ITEM: |
---|
1155 | while (my $item = pop(@items)) { |
---|
1156 | my ($unit, @up_units) = @{$item}; |
---|
1157 | my ($key, $type) = @{$unit}; |
---|
1158 | my @up_keys = map {$_->[0]} @up_units; |
---|
1159 | if ( exists($cyc_in{$key}) |
---|
1160 | || exists($dup_in{$key}) |
---|
1161 | || exists($missing_deps_in{$key}) |
---|
1162 | ) { |
---|
1163 | next ITEM; |
---|
1164 | } |
---|
1165 | if (exists($state_of{$key})) { |
---|
1166 | # Already visited this ITEM |
---|
1167 | # Detect cyclic dependency |
---|
1168 | if ( !$state_of{$key}->get_cyclic_ok() |
---|
1169 | && grep {$_->[0] eq $key} @up_units |
---|
1170 | ) { |
---|
1171 | my @_up_units = (@up_units, $unit); |
---|
1172 | my $_up_unit_last = pop(@_up_units); |
---|
1173 | DEP_UP_KEY: |
---|
1174 | while (my $_up_unit = pop(@_up_units)) { |
---|
1175 | my ($_up_key, $_up_type) = @{$_up_unit}; |
---|
1176 | my @dep_up_deps = @{$state_of{$_up_key}->get_deps()}; |
---|
1177 | # If parent of $_up_unit_last does not depend on |
---|
1178 | # $_up_unit_last, chain is broken, and we are OK. |
---|
1179 | my ($_up_key_last, $_up_type_last) = @{$_up_unit_last}; |
---|
1180 | if (!grep { $_->[0]->get_key() eq $_up_key_last |
---|
1181 | || $_->[1] eq $_up_type_last |
---|
1182 | } @dep_up_deps |
---|
1183 | ) { |
---|
1184 | last DEP_UP_KEY; |
---|
1185 | } |
---|
1186 | if ($type && $key eq $_up_key && $type eq $_up_type) { |
---|
1187 | $cyc_in{$key} = {'keys' => [@up_keys, $key]}; |
---|
1188 | next ITEM; |
---|
1189 | } |
---|
1190 | $_up_unit_last = $_up_unit; |
---|
1191 | } |
---|
1192 | } |
---|
1193 | $state_of{$key}->set_cyclic_ok(1); |
---|
1194 | # Float current target up dependency chain |
---|
1195 | my $is_directly_related = 1; |
---|
1196 | UP_KEY: |
---|
1197 | for my $up_key (reverse(@up_keys)) { |
---|
1198 | if ($state_of{$up_key}->add_visitor( |
---|
1199 | $state_of{$key}->get_target(), |
---|
1200 | $type, |
---|
1201 | $is_directly_related, |
---|
1202 | )) { |
---|
1203 | last UP_KEY; |
---|
1204 | } |
---|
1205 | $is_directly_related = 0; |
---|
1206 | } |
---|
1207 | # Add floatable dependencies up the dependency chain |
---|
1208 | for my $visitor (values(%{$state_of{$key}->get_floatables()})) { |
---|
1209 | UP_KEY: |
---|
1210 | for my $up_key (reverse(@up_keys)) { |
---|
1211 | if ($state_of{$up_key}->add_visitor(@{$visitor})) { |
---|
1212 | last UP_KEY; |
---|
1213 | } |
---|
1214 | } |
---|
1215 | } |
---|
1216 | next ITEM; |
---|
1217 | } |
---|
1218 | |
---|
1219 | # First visit to this ITEM |
---|
1220 | # Checks for duplicated target |
---|
1221 | if (exists($targets_of{$key})) { |
---|
1222 | $dup_in{$key} = { |
---|
1223 | 'keys' => [@up_keys, $key], |
---|
1224 | 'values' => [map {$_->get_ns()} @{$targets_of{$key}}], |
---|
1225 | }; |
---|
1226 | next ITEM; |
---|
1227 | } |
---|
1228 | # Wraps all required targets with a STATE object |
---|
1229 | $state_of{$key} = $STATE->new( |
---|
1230 | {id => $key, target => $target_of{$key}}, |
---|
1231 | ); |
---|
1232 | my $target = $target_of{$key}; |
---|
1233 | DEP: |
---|
1234 | for ( |
---|
1235 | grep {$_->[0] ne $key} |
---|
1236 | sort {$a->[0] cmp $b->[0]} |
---|
1237 | @{$target->get_deps()} |
---|
1238 | ) { |
---|
1239 | my ($dep_key, $dep_type, $dep_remark) = @{$_}; |
---|
1240 | # Duplicated targets |
---|
1241 | if (exists($targets_of{$dep_key})) { |
---|
1242 | $dup_in{$dep_key} = { |
---|
1243 | 'keys' => [@up_keys, $key, $dep_key], |
---|
1244 | 'values' => [map {$_->get_ns()} @{$targets_of{$dep_key}}], |
---|
1245 | }; |
---|
1246 | next DEP; |
---|
1247 | } |
---|
1248 | # Missing dependency |
---|
1249 | if (!exists($target_of{$dep_key})) { |
---|
1250 | if (!exists($missing_deps_in{$key})) { |
---|
1251 | $missing_deps_in{$key} = { |
---|
1252 | 'keys' => [@up_keys, $key, $dep_key], |
---|
1253 | 'values' => [], |
---|
1254 | }; |
---|
1255 | } |
---|
1256 | push( |
---|
1257 | @{$missing_deps_in{$key}{'values'}}, |
---|
1258 | [$dep_key, $dep_type, $dep_remark], |
---|
1259 | ); |
---|
1260 | next DEP; |
---|
1261 | } |
---|
1262 | # OK |
---|
1263 | push(@items, [[$dep_key, $dep_type], @up_units, [$key, $type]]); |
---|
1264 | # add_visitor, is_directly_related=1 |
---|
1265 | $state_of{$key}->add_visitor($target_of{$dep_key}, $dep_type, 1) |
---|
1266 | } |
---|
1267 | # Float current target up dependency chain |
---|
1268 | my $is_directly_related = 1; |
---|
1269 | UP_KEY: |
---|
1270 | for my $up_key (reverse(@up_keys)) { |
---|
1271 | if ($state_of{$up_key}->add_visitor( |
---|
1272 | $target, $type, $is_directly_related, |
---|
1273 | )) { |
---|
1274 | last UP_KEY; |
---|
1275 | } |
---|
1276 | $is_directly_related = 0; |
---|
1277 | } |
---|
1278 | # Adds triggers |
---|
1279 | for my $trigger_key (@{$target->get_triggers()}) { |
---|
1280 | if (!exists($state_of{$trigger_key})) { |
---|
1281 | unshift(@items, [[$trigger_key, undef]]); |
---|
1282 | } |
---|
1283 | } |
---|
1284 | } |
---|
1285 | # Visitors no longer used |
---|
1286 | for my $state (values(%state_of)) { |
---|
1287 | $state->free_visitors(); |
---|
1288 | } |
---|
1289 | # Assigns targets to build context |
---|
1290 | %{$ctx->get_target_of()} |
---|
1291 | = map {($_->get_id() => $_->get_target())} values(%state_of); |
---|
1292 | |
---|
1293 | # Report cyclic dependencies |
---|
1294 | # Report duplicated targets |
---|
1295 | # Report missing dependencies |
---|
1296 | # Report bad keys in target select |
---|
1297 | if (keys(%cyc_in)) { |
---|
1298 | return $E->throw($E->BUILD_TARGET_CYC, \%cyc_in); |
---|
1299 | } |
---|
1300 | if (keys(%dup_in)) { |
---|
1301 | return $E->throw($E->BUILD_TARGET_DUP, \%dup_in); |
---|
1302 | } |
---|
1303 | my @ignore_missing_dep_ns_list = map {$_ eq q{/} ? q{} : $_} ( |
---|
1304 | _props($attrib_ref, 'ignore-missing-dep-ns', $ctx) |
---|
1305 | ); |
---|
1306 | KEY: |
---|
1307 | for my $key (sort(keys(%missing_deps_in))) { |
---|
1308 | my $target = $target_of{$key}; |
---|
1309 | for my $ns (@ignore_missing_dep_ns_list) { |
---|
1310 | if ($UTIL->ns_common($ns, $target->get_ns()) eq $ns) { # target in ns |
---|
1311 | my $hash_ref = delete($missing_deps_in{$key}); |
---|
1312 | my @deps = @{$hash_ref->{"values"}}; |
---|
1313 | for my $dep (@deps) { |
---|
1314 | $EVENT->( |
---|
1315 | FCM::Context::Event->MAKE_BUILD_TARGET_MISSING_DEP, |
---|
1316 | $key, @{$dep}, |
---|
1317 | ); |
---|
1318 | } |
---|
1319 | next KEY; |
---|
1320 | } |
---|
1321 | } |
---|
1322 | } |
---|
1323 | if (keys(%missing_deps_in)) { |
---|
1324 | return $E->throw($E->BUILD_TARGET_DEP, \%missing_deps_in); |
---|
1325 | } |
---|
1326 | if (exists($select_by{key}{q{}})) { |
---|
1327 | my @bad_keys = grep {!exists($state_of{$_})} keys(%{$select_by{key}{q{}}}); |
---|
1328 | if (@bad_keys) { |
---|
1329 | return $E->throw($E->BUILD_TARGET_BAD, \@bad_keys); |
---|
1330 | } |
---|
1331 | } |
---|
1332 | # Walk the tree and report it |
---|
1333 | my @report_items = map {[$_]} sort @target_keys; |
---|
1334 | my %reported; |
---|
1335 | ITEM: |
---|
1336 | while (my $item = pop(@report_items)) { |
---|
1337 | my ($key, @stack) = @{$item}; |
---|
1338 | my @deps = sort {$a->[0]->get_key() cmp $b->[0]->get_key()} |
---|
1339 | @{$state_of{$key}->get_deps()}; |
---|
1340 | my @more_items = reverse(map {[$_->[0]->get_key(), @stack, $key]} @deps); |
---|
1341 | my $n_more_items; |
---|
1342 | if (exists($reported{$key})) { |
---|
1343 | $n_more_items = scalar(@more_items); |
---|
1344 | } |
---|
1345 | else { |
---|
1346 | push(@report_items, @more_items); |
---|
1347 | } |
---|
1348 | $attrib_ref->{util}->event( |
---|
1349 | FCM::Context::Event->MAKE_BUILD_TARGET_STACK, |
---|
1350 | $key, scalar(@stack), $n_more_items, |
---|
1351 | ); |
---|
1352 | $reported{$key} = 1; |
---|
1353 | } |
---|
1354 | $EVENT->( |
---|
1355 | FCM::Context::Event->MAKE_BUILD_TARGET_SELECT, |
---|
1356 | {map {$_ => $target_of{$_}} @target_keys}, |
---|
1357 | ); |
---|
1358 | # TODO: error if nothing to build? |
---|
1359 | |
---|
1360 | # Checks whether properties with name-spaces are valid. |
---|
1361 | my @invalid_prop_ns_list; |
---|
1362 | while (my ($name, $prop) = each(%{$ctx->get_prop_of()})) { |
---|
1363 | while (my ($ns, $prop_ctx) = each(%{$prop->get_ctx_of()})) { |
---|
1364 | if ( !$prop_ctx->get_inherited() |
---|
1365 | && !exists($target_of{$ns}) |
---|
1366 | && !exists($has_ns_in{$ns}) |
---|
1367 | ) { |
---|
1368 | push( |
---|
1369 | @invalid_prop_ns_list, |
---|
1370 | [$ctx->get_id(), $name, $ns, $prop_ctx->get_value()], |
---|
1371 | ); |
---|
1372 | } |
---|
1373 | } |
---|
1374 | } |
---|
1375 | if (@invalid_prop_ns_list) { |
---|
1376 | return $E->throw($E->MAKE_PROP_NS, \@invalid_prop_ns_list); |
---|
1377 | } |
---|
1378 | |
---|
1379 | $EVENT->(FCM::Context::Event->MAKE_BUILD_TARGET_SELECT_TIMER, $timer->()); |
---|
1380 | |
---|
1381 | # Returns list of keys of top targets, and the states |
---|
1382 | ([map {$state_of{$_}} reverse(@target_keys)], \%state_of); |
---|
1383 | } |
---|
1384 | |
---|
1385 | # Returns true if $target dependencies are done. |
---|
1386 | sub _target_deps_are_done { |
---|
1387 | my ($state, $state_hash_ref, $stack_ref) = @_; |
---|
1388 | my @deps = map {[$_->[0]->get_key(), $_->[1]]} @{$state->get_deps()}; |
---|
1389 | for my $k (sort grep {$state_hash_ref->{$_}->is_ready()} map {$_->[0]} @deps) { |
---|
1390 | if (!grep {$_->get_id() eq $k} @{$stack_ref}) { |
---|
1391 | push(@{$stack_ref}, $state_hash_ref->{$k}); |
---|
1392 | } |
---|
1393 | } |
---|
1394 | my %not_done |
---|
1395 | = map {@{$_}} |
---|
1396 | grep {!$_->[1]->is_done()} |
---|
1397 | map {[$_->[0], $state_hash_ref->{$_->[0]}]} |
---|
1398 | @deps; |
---|
1399 | if (keys(%not_done)) { |
---|
1400 | $state->set_value($STATE->PENDING); |
---|
1401 | while (my ($k, $s) = each(%not_done)) { |
---|
1402 | $state->get_pending_for()->{$k} = $s; |
---|
1403 | $s->get_needed_by()->{$state->get_id()} = $state; |
---|
1404 | } |
---|
1405 | return 0; |
---|
1406 | } |
---|
1407 | return 1; |
---|
1408 | } |
---|
1409 | |
---|
1410 | # Returns true if $target has failed dependencies. |
---|
1411 | sub _target_check_failed_dep { |
---|
1412 | my ($state, $state_hash_ref) = @_; |
---|
1413 | my $target = $state->get_target(); |
---|
1414 | for my $dep (@{$state->get_deps()}) { |
---|
1415 | my ($target_of_dep, $type_of_dep) = @{$dep}; |
---|
1416 | if ($target_of_dep->is_failed()) { |
---|
1417 | return 1; |
---|
1418 | } |
---|
1419 | if ( exists($target_of_dep->get_status_of()->{$type_of_dep}) |
---|
1420 | && $target_of_dep->get_status_of()->{$type_of_dep} |
---|
1421 | eq $target->ST_FAILED |
---|
1422 | ) { |
---|
1423 | return 1; |
---|
1424 | } |
---|
1425 | } |
---|
1426 | return 0; |
---|
1427 | } |
---|
1428 | |
---|
1429 | # Returns true if $target is out of date. |
---|
1430 | sub _target_check_ood { |
---|
1431 | my ($state, $state_hash_ref, $checksum_method) = @_; |
---|
1432 | my $target = $state->get_target(); |
---|
1433 | # Dependencies |
---|
1434 | my $rc; |
---|
1435 | for my $dep (@{$state->get_deps()}) { |
---|
1436 | my ($target_of_dep, $type_of_dep) = @{$dep}; |
---|
1437 | if ( $target_of_dep->is_modified() |
---|
1438 | || exists($target_of_dep->get_status_of()->{$type_of_dep}) |
---|
1439 | && $target_of_dep->get_status_of()->{$type_of_dep} |
---|
1440 | eq $target->ST_MODIFIED |
---|
1441 | ) { |
---|
1442 | if (exists($target->get_status_of()->{$type_of_dep})) { |
---|
1443 | $target->get_status_of()->{$type_of_dep} = $target->ST_MODIFIED; |
---|
1444 | if ( $target->get_path_of_prev() |
---|
1445 | && $target->get_path() ne $target->get_path_of_prev() |
---|
1446 | ) { |
---|
1447 | # Inherited build, cannot just pass on a status |
---|
1448 | $rc = 1; |
---|
1449 | } |
---|
1450 | } |
---|
1451 | else { |
---|
1452 | $rc = 1; |
---|
1453 | } |
---|
1454 | } |
---|
1455 | } |
---|
1456 | if ($rc || $target->get_status() eq $target->ST_OOD) { |
---|
1457 | return 1; |
---|
1458 | } |
---|
1459 | # Dest and properties |
---|
1460 | my $path_of_prev = $target->get_path_of_prev(); |
---|
1461 | my $checksum = $target->get_checksum(); |
---|
1462 | my $prop_hash_ref = $target->get_prop_of(); |
---|
1463 | my $prop_of_prev_hash_ref = $target->get_prop_of_prev_of(); |
---|
1464 | ( !$path_of_prev |
---|
1465 | || !-e $path_of_prev |
---|
1466 | || $UTIL->file_checksum($path_of_prev, $checksum_method) ne $checksum |
---|
1467 | || $UTIL->hash_cmp($prop_hash_ref, $prop_of_prev_hash_ref) |
---|
1468 | ); |
---|
1469 | } |
---|
1470 | |
---|
1471 | # Callback to prepare the target for the task. |
---|
1472 | sub _target_prep { |
---|
1473 | my ($state, $ctx) = @_; |
---|
1474 | my $target = $state->get_target(); |
---|
1475 | # Creates the container directory, where necessary |
---|
1476 | my %paths_of_dirs_set; |
---|
1477 | for my $t ( |
---|
1478 | $target, |
---|
1479 | map {$ctx->get_target_of($_)} @{$target->get_triggers()}, |
---|
1480 | ) { |
---|
1481 | $paths_of_dirs_set{dirname($t->get_path())} = 1; |
---|
1482 | } |
---|
1483 | for my $path_of_dir (keys(%paths_of_dirs_set)) { |
---|
1484 | if (!-d $path_of_dir) { |
---|
1485 | eval {mkpath($path_of_dir)}; |
---|
1486 | if ($@) { |
---|
1487 | return $E->throw($E->DEST_CREATE, $path_of_dir); |
---|
1488 | } |
---|
1489 | } |
---|
1490 | } |
---|
1491 | # Put in required info |
---|
1492 | if ($target->get_info_of('paths')) { |
---|
1493 | @{$target->get_info_of('paths')} = @{$ctx->get_dests()}; |
---|
1494 | } |
---|
1495 | if ($target->get_info_of('deps')) { |
---|
1496 | my $info_deps_ref = $target->get_info_of('deps'); |
---|
1497 | my %set_of = map {$_ => {}} keys(%{$info_deps_ref}); |
---|
1498 | for my $dep (@{$state->get_deps()}) { |
---|
1499 | my ($target_of_dep, $type) = @{$dep}; |
---|
1500 | my $key = $target_of_dep->get_key(); |
---|
1501 | if (exists($set_of{$type}) && !$set_of{$type}{$key}) { |
---|
1502 | if ($target_of_dep->get_ns() eq $target->get_ns()) { |
---|
1503 | # E.g. main *.o of *.exe |
---|
1504 | unshift(@{$info_deps_ref->{$type}}, $key); |
---|
1505 | } |
---|
1506 | else { |
---|
1507 | push(@{$info_deps_ref->{$type}}, $key); |
---|
1508 | } |
---|
1509 | $set_of{$type}{$key} = 1; |
---|
1510 | } |
---|
1511 | } |
---|
1512 | } |
---|
1513 | } |
---|
1514 | |
---|
1515 | # Sets state and stack when a $target has failed to update or cannot be updated |
---|
1516 | # due to failed dependencies. |
---|
1517 | sub _target_update_failed { |
---|
1518 | my ($stat_hash_ref, |
---|
1519 | $ctx, |
---|
1520 | $target, |
---|
1521 | $state_hash_ref, |
---|
1522 | $stack_ref, |
---|
1523 | $elapsed_time, # only defined if target update action is done |
---|
1524 | ) = @_; |
---|
1525 | my $key = $target->get_key(); |
---|
1526 | my $state = $state_hash_ref->{$key}; |
---|
1527 | $state->set_value($STATE->DONE); |
---|
1528 | # If this target is needed by other targets... |
---|
1529 | while (my ($k, $s) = each(%{$state->get_needed_by()})) { |
---|
1530 | my $pending_for_ref = $s->get_pending_for(); |
---|
1531 | delete($pending_for_ref->{$key}); |
---|
1532 | if (!keys(%{$pending_for_ref})) { |
---|
1533 | $s->set_value($STATE->DONE); |
---|
1534 | # Remove from stack |
---|
1535 | @{$stack_ref} = grep {$_->get_id() ne $k} @{$stack_ref}; |
---|
1536 | $s->get_target()->set_status($target->ST_FAILED); |
---|
1537 | push(@{$s->get_target()->get_failed_by()}, $key); |
---|
1538 | } |
---|
1539 | } |
---|
1540 | if (defined($elapsed_time)) { # Done target update |
---|
1541 | my $target0 = $ctx->get_target_of()->{$target->get_key()}; |
---|
1542 | $target0->set_info_of({}); # unset |
---|
1543 | $target0->set_checksum(undef); |
---|
1544 | $target0->set_path(undef); |
---|
1545 | $target0->set_prop_of_prev_of({}); # unset |
---|
1546 | $target0->set_path_of_prev(undef); # unset |
---|
1547 | $target0->set_status($target->ST_FAILED); |
---|
1548 | push(@{$target0->get_failed_by()}, $target->get_key()); |
---|
1549 | ++$stat_hash_ref->{$target->get_task()}{n}{$target->ST_FAILED}; |
---|
1550 | $stat_hash_ref->{$target->get_task()}{t} += $elapsed_time; |
---|
1551 | } |
---|
1552 | else { # No target update required |
---|
1553 | $target->set_path(undef); |
---|
1554 | $target->set_prop_of_prev_of({}); # unset |
---|
1555 | $target->set_path_of_prev(undef); # unset |
---|
1556 | $target->set_status($target->ST_FAILED); |
---|
1557 | for my $dep (@{$state->get_deps()}) { |
---|
1558 | my ($dep_target, $dep_type) = @{$dep}; |
---|
1559 | my $dep_key = $dep_target->get_key(); |
---|
1560 | if ( $dep_target->is_failed() |
---|
1561 | && !grep {$_ eq $dep_key} @{$target->get_failed_by()} |
---|
1562 | ) { |
---|
1563 | push(@{$target->get_failed_by()}, $dep_key); |
---|
1564 | } |
---|
1565 | } |
---|
1566 | ++$stat_hash_ref->{$target->get_task()}{n}{$target->ST_FAILED}; |
---|
1567 | } |
---|
1568 | $EVENT->( |
---|
1569 | FCM::Context::Event->MAKE_BUILD_TARGET_FAIL, $target, $elapsed_time, |
---|
1570 | ); |
---|
1571 | } |
---|
1572 | |
---|
1573 | # Sets state and stack when a $target is up to date or updated successfully. |
---|
1574 | sub _target_update_ok { |
---|
1575 | my ($stat_hash_ref, |
---|
1576 | $ctx, |
---|
1577 | $target, |
---|
1578 | $state_hash_ref, |
---|
1579 | $stack_ref, |
---|
1580 | $elapsed_time, # only defined if target update action is done |
---|
1581 | ) = @_; |
---|
1582 | my $key = $target->get_key(); |
---|
1583 | my $state = $state_hash_ref->{$key}; |
---|
1584 | $state->set_value($STATE->DONE); |
---|
1585 | # If this target is needed by other targets... |
---|
1586 | my @released_pending_states; |
---|
1587 | while (my ($k, $s) = each(%{$state->get_needed_by()})) { |
---|
1588 | my $pending_for_ref = $s->get_pending_for(); |
---|
1589 | delete($pending_for_ref->{$key}); |
---|
1590 | if ($s->is_pending() && !keys(%{$pending_for_ref})) { |
---|
1591 | $s->set_value($STATE->READY); |
---|
1592 | if (!grep {$_->get_id() eq $k} @{$stack_ref}) { |
---|
1593 | push(@released_pending_states, $s); |
---|
1594 | } |
---|
1595 | } |
---|
1596 | } |
---|
1597 | push( |
---|
1598 | @{$stack_ref}, |
---|
1599 | sort {$a->get_id() cmp $b->get_id()} @released_pending_states, |
---|
1600 | ); |
---|
1601 | if (defined($elapsed_time)) { # Done target update |
---|
1602 | my $target0 = $ctx->get_target_of()->{$target->get_key()}; |
---|
1603 | $target0->set_info_of({}); # unset |
---|
1604 | $target0->set_checksum($target->get_checksum()); |
---|
1605 | $target0->set_path($target->get_path()); |
---|
1606 | $target0->set_prop_of_prev_of({}); # unset |
---|
1607 | $target0->set_path_of_prev(undef); # unset |
---|
1608 | $target0->set_status($target->get_status()); |
---|
1609 | ++$stat_hash_ref->{$target->get_task()}{n}{$target->get_status()}; |
---|
1610 | $stat_hash_ref->{$target->get_task()}{t} += $elapsed_time; |
---|
1611 | } |
---|
1612 | else { # No target update required |
---|
1613 | if ($target->get_path_of_prev()) { |
---|
1614 | $target->set_path($target->get_path_of_prev()); |
---|
1615 | } |
---|
1616 | $target->set_prop_of_prev_of({}); # unset |
---|
1617 | $target->set_path_of_prev(undef); # unset |
---|
1618 | $target->set_status($target->ST_UNCHANGED); |
---|
1619 | ++$stat_hash_ref->{$target->get_task()}{n}{$target->ST_UNCHANGED}; |
---|
1620 | } |
---|
1621 | $EVENT->( |
---|
1622 | FCM::Context::Event->MAKE_BUILD_TARGET_DONE, $target, $elapsed_time, |
---|
1623 | ); |
---|
1624 | } |
---|
1625 | |
---|
1626 | # Returns a list containing the inherited contexts with the same ID as $ctx. |
---|
1627 | sub _i_ctx_list { |
---|
1628 | my ($m_ctx, $ctx) = @_; |
---|
1629 | grep |
---|
1630 | {defined()} |
---|
1631 | map |
---|
1632 | {$_->get_ctx_of($ctx->get_id())} |
---|
1633 | @{$m_ctx->get_inherit_ctx_list()}; |
---|
1634 | } |
---|
1635 | |
---|
1636 | # Returns a function that returns the previous source/target of a specified key. |
---|
1637 | sub _prev_hash_item_getter { |
---|
1638 | my ($m_ctx, $ctx, $getter_ref) = @_; |
---|
1639 | my $p_m_ctx = $m_ctx->get_prev_ctx(); |
---|
1640 | my %p_item_of; |
---|
1641 | my $ctx_id = $ctx->get_id(); |
---|
1642 | if (defined($p_m_ctx) && defined($p_m_ctx->get_ctx_of($ctx_id))) { |
---|
1643 | %p_item_of = %{$getter_ref->($p_m_ctx->get_ctx_of($ctx_id))}; |
---|
1644 | } |
---|
1645 | else { |
---|
1646 | for my $i_ctx (_i_ctx_list($m_ctx, $ctx)) { |
---|
1647 | %p_item_of = (%p_item_of, %{$getter_ref->($i_ctx)}); |
---|
1648 | } |
---|
1649 | } |
---|
1650 | sub {exists($p_item_of{$_[0]}) ? $p_item_of{$_[0]} : undef}; |
---|
1651 | } |
---|
1652 | |
---|
1653 | # Perform final actions. |
---|
1654 | # Archive intermediate target directories if necessary. |
---|
1655 | sub _finally { |
---|
1656 | my ($attrib_ref, $m_ctx, $ctx) = @_; |
---|
1657 | if (!$m_ctx->get_option_of('archive')) { |
---|
1658 | return; |
---|
1659 | } |
---|
1660 | my %can_archive = map {($_ => 1)} _props( |
---|
1661 | $attrib_ref, 'archive-ok-target-category', $ctx); |
---|
1662 | opendir(my $handle, $ctx->get_dest()); |
---|
1663 | while (my $name = readdir($handle)) { |
---|
1664 | if ($can_archive{$name}) { |
---|
1665 | my @command = ( |
---|
1666 | qw{tar -c -z}, '-C', $ctx->get_dest(), |
---|
1667 | '-f', catfile($ctx->get_dest(), $name . '.tar.gz'), |
---|
1668 | $name, |
---|
1669 | ); |
---|
1670 | my %value_of = %{$UTIL->shell_simple(\@command)}; |
---|
1671 | if ($value_of{'rc'} == 0) { |
---|
1672 | rmtree(catfile($ctx->get_dest(), $name)); |
---|
1673 | } |
---|
1674 | } |
---|
1675 | } |
---|
1676 | closedir($handle); |
---|
1677 | } |
---|
1678 | |
---|
1679 | # ------------------------------------------------------------------------------ |
---|
1680 | package FCM::System::Make::Build::State; |
---|
1681 | use base qw{FCM::Class::HASH}; |
---|
1682 | |
---|
1683 | use constant { |
---|
1684 | DONE => 'DONE', # state value |
---|
1685 | READY => 'READY', # state value |
---|
1686 | PENDING => 'PENDING', # state value |
---|
1687 | }; |
---|
1688 | |
---|
1689 | __PACKAGE__->class({ |
---|
1690 | cyclic_ok => '$', |
---|
1691 | deps => '@', |
---|
1692 | floatables => '%', |
---|
1693 | id => '$', |
---|
1694 | needed_by => '%', |
---|
1695 | pending_for => '%', |
---|
1696 | target => 'FCM::Context::Make::Build::Target', |
---|
1697 | value => {isa => '$', default => READY}, |
---|
1698 | visited_by => '%', |
---|
1699 | }); |
---|
1700 | |
---|
1701 | sub add_visitor { |
---|
1702 | my ($self, $dep_target, $dep_type, $is_directly_related) = @_; |
---|
1703 | my $dep_key = $dep_target->get_key(); |
---|
1704 | my $dep_str = join(':', $dep_key, $dep_type); |
---|
1705 | # Dependency has already visited me, return cached return value |
---|
1706 | if (exists($self->get_visited_by()->{$dep_str})) { |
---|
1707 | return $self->get_visited_by()->{$dep_str}; |
---|
1708 | } |
---|
1709 | # Adopt dep_target as my dependency if there is a policy to do so |
---|
1710 | my $target = $self->get_target(); |
---|
1711 | my $policy = $target->get_dep_policy_of($dep_type); |
---|
1712 | if ( $policy |
---|
1713 | && ($policy ne $target->POLICY_FILTER_IMMEDIATE || $is_directly_related) |
---|
1714 | && (!grep {$_->[0]->get_key() eq $dep_key} @{$self->get_deps()}) |
---|
1715 | && (!grep {$_ eq $dep_key} @{$target->get_triggers()}) |
---|
1716 | ) { |
---|
1717 | push(@{$self->get_deps()}, [$dep_target, $dep_type]); |
---|
1718 | } |
---|
1719 | # If target is captured by me, return true. |
---|
1720 | # Otherwise, return false, and the target is a floatable. |
---|
1721 | $self->get_visited_by()->{$dep_str} |
---|
1722 | = ($policy && $policy eq $target->POLICY_CAPTURE); |
---|
1723 | if ( !$self->get_visited_by()->{$dep_str} |
---|
1724 | && !exists($self->get_floatables()->{$dep_str}) |
---|
1725 | ) { |
---|
1726 | $self->get_floatables()->{$dep_str} = [$dep_target, $dep_type]; |
---|
1727 | } |
---|
1728 | return $self->get_visited_by()->{$dep_str}; |
---|
1729 | } |
---|
1730 | |
---|
1731 | sub free_visitors { |
---|
1732 | my ($self) = @_; |
---|
1733 | %{$self->get_floatables()} = (); |
---|
1734 | %{$self->get_visited_by()} = (); |
---|
1735 | } |
---|
1736 | |
---|
1737 | sub is_done { |
---|
1738 | $_[0]->{value} eq DONE; |
---|
1739 | } |
---|
1740 | |
---|
1741 | sub is_pending { |
---|
1742 | $_[0]->{value} eq PENDING; |
---|
1743 | } |
---|
1744 | |
---|
1745 | sub is_ready { |
---|
1746 | $_[0]->{value} eq READY; |
---|
1747 | } |
---|
1748 | #------------------------------------------------------------------------------- |
---|
1749 | 1; |
---|
1750 | __END__ |
---|
1751 | |
---|
1752 | =head1 NAME |
---|
1753 | |
---|
1754 | FCM::System::Make::Build |
---|
1755 | |
---|
1756 | =head1 SYNOPSIS |
---|
1757 | |
---|
1758 | use FCM::System::Make::Build; |
---|
1759 | |
---|
1760 | =head1 DESCRIPTION |
---|
1761 | |
---|
1762 | Implements the build sub-system. An instance of this class is expected to be |
---|
1763 | initialised and called by L<FCM::System::Make|FCM::System::Make>. |
---|
1764 | |
---|
1765 | =head1 METHODS |
---|
1766 | |
---|
1767 | See L<FCM::System::Make|FCM::System::Make> for detail. |
---|
1768 | |
---|
1769 | =head1 ATTRIBUTES |
---|
1770 | |
---|
1771 | The $class->new(\%attrib) method of this class supports the following |
---|
1772 | attributes: |
---|
1773 | |
---|
1774 | =over 4 |
---|
1775 | |
---|
1776 | =item config_parser_of |
---|
1777 | |
---|
1778 | A HASH to map the labels in a configuration file to their parsers. (default = |
---|
1779 | %FCM::System::Make::Build::CONFIG_PARSER_OF) |
---|
1780 | |
---|
1781 | =item target_select_by |
---|
1782 | |
---|
1783 | A HASH to map the default target selector. The keys should be "category", "key", |
---|
1784 | "ns", or "task". (default = %FCM::System::Make::Build::TARGET_SELECT_by) |
---|
1785 | |
---|
1786 | =item file_type_utils |
---|
1787 | |
---|
1788 | An ARRAY of file type utility classes to be loaded into the file_type_util_of |
---|
1789 | HASH. (default = @FCM::System::Make::Build::FILE_TYPE_UTILS) |
---|
1790 | |
---|
1791 | =item file_type_util_of |
---|
1792 | |
---|
1793 | A HASH to map the file type names to the utilities to manipulate the given file |
---|
1794 | types. An values in this HASH overrides the classes in I<file_type_utils>. |
---|
1795 | (default = determined by I<file_type_utils>) |
---|
1796 | |
---|
1797 | =item prop_of |
---|
1798 | |
---|
1799 | A HASH to map the names of the properties to their settings. Each setting |
---|
1800 | is a 2-element ARRAY reference, where element [0] is the default setting |
---|
1801 | and element [1] is a flag to indicate whether the property accepts a name-space |
---|
1802 | or not. (default = %FCM::System::Make::Build::PROP_OF + values loaded from the |
---|
1803 | file type utilities) |
---|
1804 | |
---|
1805 | =item util |
---|
1806 | |
---|
1807 | See L<FCM::System::Make|FCM::System::Make> for detail. |
---|
1808 | |
---|
1809 | =back |
---|
1810 | |
---|
1811 | =head1 COPYRIGHT |
---|
1812 | |
---|
1813 | (C) Crown copyright Met Office. All rights reserved. |
---|
1814 | |
---|
1815 | =cut |
---|