New URL for NEMO forge!   http://forge.nemo-ocean.eu

Since March 2022 along with NEMO 4.2 release, the code development moved to a self-hosted GitLab.
This present forge is now archived and remained online for history.
Config.pm in vendors/lib/FCM/System/Make/Share – NEMO

source: vendors/lib/FCM/System/Make/Share/Config.pm @ 10669

Last change on this file since 10669 was 10669, checked in by nicolasmartin, 5 years ago

Import latest FCM release from Github into the repository for testing

File size: 14.5 KB
Line 
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#-------------------------------------------------------------------------------
19use strict;
20use warnings;
21#-------------------------------------------------------------------------------
22
23package FCM::System::Make::Share::Config;
24use base qw{FCM::Class::CODE};
25
26use Cwd qw{cwd};
27use FCM::Context::ConfigEntry;
28use FCM::Context::Locator;
29use FCM::System::Exception;
30use File::Spec::Functions qw{file_name_is_absolute};
31use File::Temp;
32use Text::ParseWords qw{shellwords};
33use Scalar::Util qw{blessed};
34
35# Alias to class name
36my $E = 'FCM::System::Exception';
37
38# Configuration parser label to action map
39my %CONFIG_PARSER_OF = (
40    'description'     => \&_parse_description,
41    'dest'            => \&_parse_dest,
42    'name'            => \&_parse_name,
43    'require-version' => \&_parse_require_version,
44    'step.class'      => \&_parse_step_class,
45    'steps'           => \&_parse_steps,
46    'use'             => \&_parse_use,
47);
48
49__PACKAGE__->class(
50    {shared_util_of => '%', subsystem_of => '%', util => '&'},
51    {action_of => {parse => \&_parse, unparse => \&_unparse}},
52);
53
54# Get configuration file entries from an iterator, and use the entries to
55# populate the context of the current make.
56sub _parse {
57    my ($attrib_ref, $entry_callback_ref, $m_ctx, @args) = @_;
58    my $DEST_UTIL = $attrib_ref->{shared_util_of}{dest};
59    my $dir = $m_ctx->get_option_of('directory')
60        ? $m_ctx->get_option_of('directory') : cwd();
61    my $dir_locator = FCM::Context::Locator->new($dir);
62    my @config_file_paths = $m_ctx->get_option_of('config-file-path')
63        ? @{$m_ctx->get_option_of('config-file-path')} : ();
64    my @config_file_path_locators
65        = map {FCM::Context::Locator->new($_)} @config_file_paths;
66    my @config_file_names = $m_ctx->get_option_of('config-file')
67        ? @{$m_ctx->get_option_of('config-file')} : (undef);
68    my @config_reader_refs;
69    for my $config_file_name (@config_file_names) {
70        my $is_specified_name = 1;
71        if (!defined($config_file_name)) {
72            $config_file_name = $DEST_UTIL->path(
73                {'name' => $m_ctx->get_name()}, 'config');
74            $is_specified_name = 0;
75        }
76        if (    $attrib_ref->{util}->uri_match($config_file_name)
77            ||  file_name_is_absolute($config_file_name)
78        ) {
79            push(@config_reader_refs, _get_config_reader(
80                $attrib_ref, $config_file_name, [@config_file_path_locators],
81            ));
82        }
83        else {  # $config_file_name is relative
84            my $config_reader_ref;
85            HEAD:
86            for my $head_locator ($dir_locator, @config_file_path_locators) {
87                my $locator = $attrib_ref->{util}->loc_cat(
88                    $head_locator, $config_file_name,
89                );
90                if ($attrib_ref->{util}->loc_exists($locator)) {
91                    $config_reader_ref = _get_config_reader(
92                        $attrib_ref, $locator, [@config_file_path_locators],
93                    );
94                    last HEAD;
95                }
96            }
97            if (defined($config_reader_ref)) {
98                push(@config_reader_refs, $config_reader_ref);
99            }
100            elsif ($is_specified_name) {
101                return $E->throw($E->MAKE_CFG_FILE, $config_file_name);
102            }
103        }
104    }
105    if (!@config_reader_refs) {
106        my $config_file_name = $DEST_UTIL->path(
107            {'dest' => $dir_locator->get_value(), 'name' => $m_ctx->get_name()},
108            'config',
109        );
110        if (-f $config_file_name) {
111            push(@config_reader_refs, _get_config_reader(
112                $attrib_ref, $config_file_name, [@config_file_path_locators],
113            ));
114        }
115    }
116    my $args_config_handle;
117    if (@args) {
118        $args_config_handle = File::Temp->new(
119            SUFFIX   => '-fcm-make-args.cfg',
120            TEMPLATE => 'XXXXXX',
121            TMPDIR   => 1,
122        );
123        for my $arg (@args) {
124            print($args_config_handle "$arg\n");
125        }
126        $args_config_handle->seek(0, 0);
127        push(@config_reader_refs, _get_config_reader(
128            $attrib_ref,
129            $args_config_handle->filename(),
130            [@config_file_path_locators],
131        ));
132    }
133    if (!@config_reader_refs) {
134        return $E->throw($E->MAKE_CFG);
135    }
136    my $entry_iter_ref = sub {
137        while (@config_reader_refs) {
138            my $entry = $config_reader_refs[0]->();
139            if (defined($entry)) {
140                return $entry;
141            }
142            shift(@config_reader_refs);
143        }
144        return undef;
145    };
146    my @unknown_entries;
147    while (defined(my $entry = $entry_iter_ref->())) {
148        if (defined($entry_callback_ref)) {
149            $entry_callback_ref->($entry);
150        }
151        if (exists($CONFIG_PARSER_OF{$entry->get_label()})) {
152            $CONFIG_PARSER_OF{$entry->get_label()}->(
153                $attrib_ref, $m_ctx, $entry,
154            );
155        }
156        else {
157            my ($id, $label) = split(qr{\.}msx, $entry->get_label(), 2);
158            if (    $label
159                &&  $label eq 'prop'
160                &&  exists($entry->get_modifier_of()->{'class'})
161                &&  exists($attrib_ref->{subsystem_of}{$id})
162            ) {
163                my $subsystem = $attrib_ref->{subsystem_of}{$id};
164                if (!$subsystem->config_parse_class_prop($entry, $label)) {
165                    push(@unknown_entries, $entry);
166                }
167            }
168            else {
169                my $ctx = $m_ctx->get_ctx_of($id);
170                if (    !defined($ctx)
171                    &&  exists($attrib_ref->{subsystem_of}{$id})
172                ) {
173                    $ctx = $attrib_ref->{subsystem_of}{$id}->ctx($id, $id);
174                    $m_ctx->get_ctx_of()->{$id} = $ctx;
175                }
176                my $rc;
177                if (defined($ctx)) {
178                    my $id_of_class = $ctx->get_id_of_class();
179                    my $subsystem = $attrib_ref->{subsystem_of}{$id_of_class};
180                    $rc = $subsystem->config_parse($ctx, $entry, $label);
181                }
182                if (!$rc) {
183                    push(@unknown_entries, $entry);
184                }
185            }
186        }
187    }
188    if (defined($args_config_handle)) {
189        $args_config_handle->close();
190    }
191    if (@unknown_entries) {
192        return $E->throw($E->CONFIG_UNKNOWN, \@unknown_entries);
193    }
194    $m_ctx;
195}
196
197# Returns a config reader.
198sub _get_config_reader {
199    my ($attrib_ref, $locator, $config_file_path_locators_ref) = @_;
200    if (!blessed($locator)) {
201        $locator = FCM::Context::Locator->new($locator);
202    }
203    $attrib_ref->{util}->config_reader(
204        $locator,
205        {   event_level   => $attrib_ref->{util}->util_of_report()->LOW,
206            include_paths => $config_file_path_locators_ref,
207        },
208    );
209}
210
211# Reads the "description" declaration from a config entry.
212sub _parse_description {
213    my ($attrib_ref, $m_ctx, $entry) = @_;
214    $m_ctx->set_description($entry->get_value());
215}
216
217# Reads the "dest" declaration from a config entry.
218sub _parse_dest {
219    my ($attrib_ref, $m_ctx, $entry) = @_;
220    $m_ctx->set_dest($entry->get_value());
221}
222
223# Reads the "name" declaration from a config entry.
224sub _parse_name {
225    my ($attrib_ref, $m_ctx, $entry) = @_;
226    $m_ctx->set_name($entry->get_value());
227}
228
229# Parse "require-version" declaration from a config entry.
230sub _parse_require_version {
231    my ($attrib_ref, $m_ctx, $entry) = @_;
232    my ($version) = shellwords($attrib_ref->{util}->version());
233    my ($min_version, $max_version) = shellwords($entry->get_value());
234    if (    $min_version gt $version
235        ||  defined($max_version) && $version gt $max_version
236    ) {
237        return $E->throw($E->CONFIG_VERSION, $entry, $version);
238    }
239}
240
241# Reads the step.class declaration from a config entry.
242sub _parse_step_class {
243    my ($attrib_ref, $m_ctx, $entry) = @_;
244    my $id_of_class = $entry->get_value();
245    if (!exists($attrib_ref->{subsystem_of}{$id_of_class})) {
246        return $E->throw($E->CONFIG_VALUE, $entry);
247    }
248    my $subsystem = $attrib_ref->{subsystem_of}{$id_of_class};
249    for my $id (@{$entry->get_ns_list()}) {
250        if (!defined($m_ctx->get_ctx_of($id))) {
251            $m_ctx->get_ctx_of()->{$id} = $subsystem->ctx($id_of_class, $id);
252        }
253    }
254}
255
256# Reads the steps declaration from a config entry.
257sub _parse_steps {
258    my ($attrib_ref, $m_ctx, $entry) = @_;
259    my @steps = $entry->get_values();
260    $m_ctx->set_steps(\@steps);
261    for my $id (@steps) {
262        if (!defined($m_ctx->get_ctx_of($id))) {
263            if (!exists($attrib_ref->{subsystem_of}{$id})) {
264                return $E->throw($E->CONFIG_VALUE, $entry);
265            }
266            my $subsystem = $attrib_ref->{subsystem_of}{$id};
267            $m_ctx->get_ctx_of()->{$id} = $subsystem->ctx($id, $id);
268        }
269    }
270}
271
272# Reads the use declaration.
273sub _parse_use {
274    my ($attrib_ref, $m_ctx, $entry) = @_;
275    my $DEST = $attrib_ref->{shared_util_of}{dest};
276    my $inherit_ctx_list_ref = $m_ctx->get_inherit_ctx_list();
277    for my $value ($entry->get_values()) {
278        $value = $attrib_ref->{util}->file_tilde_expand($value);
279        my $i_m_ctx = eval {$DEST->ctx_load($m_ctx, $value)};
280        if (my $e = $@) {
281            return $E->throw($E->CONFIG_VALUE, $entry, $e);
282        }
283        if (!defined($i_m_ctx) || $i_m_ctx->get_status() != $i_m_ctx->ST_OK) {
284            return $E->throw($E->CONFIG_INHERIT, $entry);
285        }
286        push(@{$m_ctx->get_inherit_ctx_list()}, $i_m_ctx);
287        while (my ($id, $i_ctx) = each(%{$i_m_ctx->get_ctx_of()})) {
288            my $id_of_class = $i_ctx->get_id_of_class();
289            if (exists($attrib_ref->{subsystem_of}{$id_of_class})) {
290                my $subsystem = $attrib_ref->{subsystem_of}{$id_of_class};
291                if (!defined($m_ctx->get_ctx_of($id))) {
292                    $m_ctx->get_ctx_of()->{$id}
293                        = $subsystem->ctx($id_of_class, $id);
294                }
295                if ($subsystem->can('config_parse_inherit_hook')) {
296                    $subsystem->config_parse_inherit_hook(
297                        $m_ctx->get_ctx_of($id), $i_ctx,
298                    );
299                }
300            }
301        }
302        if (!@{$m_ctx->get_steps()}) {
303            $m_ctx->set_steps([@{$i_m_ctx->get_steps()}]);
304        }
305    }
306}
307
308# Turns the context back into a config.
309sub _unparse {
310    my ($attrib_ref, $m_ctx) = @_;
311    my %subsystem_of = map {
312        my $id = $m_ctx->get_ctx_of()->{$_}->get_id_of_class();
313        ($id, $attrib_ref->{subsystem_of}->{$id});
314    } @{$m_ctx->get_steps()};
315    map {$_->as_string()} (
316        (   map {   FCM::Context::ConfigEntry->new({
317                        label   => 'step.class',
318                        ns_list => [$_->get_id()],
319                        value   => $_->get_id_of_class(),
320                    });
321                }
322            grep {$_->get_id() ne $_->get_id_of_class()}
323            values(%{$m_ctx->get_ctx_of()})
324        ),
325        (   map {   my ($action_ref, $label) = @{$_};
326                    my $value = $action_ref->($attrib_ref, $m_ctx);
327                    defined($value)
328                        ? FCM::Context::ConfigEntry->new(
329                            {label => $label, value => $value},
330                        )
331                        : ()
332                    ;
333                }
334            (   [sub {$m_ctx->get_name()}       , 'name'        ],
335                [\&_unparse_use                 , 'use'         ],
336                [\&_unparse_steps               , 'steps'       ],
337                [sub {$m_ctx->get_dest()}       , 'dest'        ],
338                [sub {$m_ctx->get_description()}, 'description' ],
339            ),
340        ),
341        (   map {   my $id = $_;
342                    $subsystem_of{$id}->config_unparse_class_prop($id);
343            }
344            sort keys(%subsystem_of)
345        ),
346        (   map {   my $ctx = $m_ctx->get_ctx_of()->{$_};
347                    my $id_of_class = $ctx->get_id_of_class();
348                    $subsystem_of{$id_of_class}->config_unparse($ctx);
349                }
350            @{$m_ctx->get_steps()}
351        ),
352    );
353}
354
355# Serializes a list of words.
356sub _unparse_join {
357    join(q{ }, map {s{(["'\s])}{\\$1}xms; $_} grep {defined()} @_);
358}
359
360# The value of "steps" declaration from the context.
361sub _unparse_steps {
362    my ($attrib_ref, $m_ctx) = @_;
363    if (!@{$m_ctx->get_steps()}) {
364        return;
365    }
366    _unparse_join(@{$m_ctx->get_steps()});
367}
368
369# The value of "use" declaration from the context.
370sub _unparse_use {
371    my ($attrib_ref, $m_ctx) = @_;
372    if (!@{$m_ctx->get_inherit_ctx_list()}) {
373        return;
374    }
375    my @i_ctx_list = @{$m_ctx->get_inherit_ctx_list()};
376    _unparse_join(map {$_->get_dest()} @i_ctx_list);
377}
378
379#-------------------------------------------------------------------------------
3801;
381__END__
382
383=head1 NAME
384
385FCM::System::Make::Share::Config
386
387=head1 SYNOPSIS
388
389    use FCM::System::Make::Share::Config;
390    my $instance = FCM::System::Make::Share::Config->new(\%attrib);
391    my $ok = $instance->parse($m_ctx, $entry_iter_ref);
392    my @entries = $instance->unparse($m_ctx);
393
394=head1 DESCRIPTION
395
396A helper class for (un)parsing make config entries into the make context.
397
398=head1 METHODS
399
400=over 4
401
402=item $class->new(\%attrib)
403
404Returns a new instance. The allowed elements for %attrib are:
405
406=over 4
407
408=item {shared_util_of}{dest}
409
410A helper object for manipulating the destination in a make context. Expects an
411instance of L<FCM::System::Make::Share::Dest|FCM::System::Make::Share::Dest>.
412
413=back
414
415=item $instance->parse($m_ctx, $entry_iter_ref)
416
417Parses entries returned by the $entry_iter_ref iterator into the $m_ctx.
418Throws a variety of L<FCM::System::Exception|FCM::System::Exception> if some
419data in the configuration file is incorrectly set.
420
421=item $instance->unparse($m_ctx)
422
423Turns $m_ctx back into a list of configuration entries.
424
425=back
426
427=head1 COPYRIGHT
428
429(C) Crown copyright Met Office. All rights reserved.
430
431=cut
Note: See TracBrowser for help on using the repository browser.