123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 |
- # ------------------------------------------------------------------------------
- # NAME
- # Fcm::ConfigSystem
- #
- # DESCRIPTION
- # This is the base class for FCM systems that are based on inherited
- # configuration files, e.g. the extract and the build systems.
- #
- # COPYRIGHT
- # (C) Crown copyright Met Office. All rights reserved.
- # For further details please refer to the file COPYRIGHT.txt
- # which you should have received as part of this distribution.
- # ------------------------------------------------------------------------------
- package Fcm::ConfigSystem;
- use base qw{Fcm::Base};
- use strict;
- use warnings;
- use Fcm::CfgFile;
- use Fcm::CfgLine;
- use Fcm::Dest;
- use Fcm::Util qw{expand_tilde e_report w_report};
- use Sys::Hostname qw{hostname};
- # List of property methods for this class
- my @scalar_properties = (
- 'cfg', # configuration file
- 'cfg_methods', # list of sub-methods for parse_cfg
- 'cfg_prefix', # optional prefix in configuration declaration
- 'dest', # destination for output
- 'inherit', # list of inherited configurations
- 'inherited', # list of inheritance hierarchy
- 'type', # system type
- );
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $obj = Fcm::ConfigSystem->new;
- #
- # DESCRIPTION
- # This method constructs a new instance of the Fcm::ConfigSystem class.
- # ------------------------------------------------------------------------------
- sub new {
- my $this = shift;
- my %args = @_;
- my $class = ref $this || $this;
- my $self = Fcm::Base->new (%args);
- $self->{$_} = undef for (@scalar_properties);
- bless $self, $class;
- # List of sub-methods for parse_cfg
- $self->cfg_methods ([qw/header inherit dest/]);
- return $self;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $value = $obj->X;
- # $obj->X ($value);
- #
- # DESCRIPTION
- # Details of these properties are explained in @scalar_properties.
- # ------------------------------------------------------------------------------
- for my $name (@scalar_properties) {
- no strict 'refs';
- *$name = sub {
- my $self = shift;
- # Argument specified, set property to specified argument
- if (@_) {
- $self->{$name} = $_[0];
- }
- # Default value for property
- if (not defined $self->{$name}) {
- if ($name eq 'cfg') {
- # New configuration file
- $self->{$name} = Fcm::CfgFile->new (TYPE => $self->type);
- } elsif ($name =~ /^(?:cfg_methods|inherit|inherited)$/) {
- # Reference to an array
- $self->{$name} = [];
- } elsif ($name eq 'cfg_prefix' or $name eq 'type') {
- # Reference to an array
- $self->{$name} = '';
- } elsif ($name eq 'dest') {
- # New destination
- $self->{$name} = Fcm::Dest->new (TYPE => $self->type);
- }
- }
- return $self->{$name};
- }
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # ($rc, $out_of_date) = $obj->check_cache ();
- #
- # DESCRIPTION
- # This method returns $rc = 1 on success or undef on failure. It returns
- # $out_of_date = 1 if current cache file is out of date relative to those in
- # inherited runs or 0 otherwise.
- # ------------------------------------------------------------------------------
- sub check_cache {
- my $self = shift;
- my $rc = 1;
- my $out_of_date = 0;
- if (@{ $self->inherit } and -f $self->dest->cache) {
- # Get modification time of current cache file
- my $cur_mtime = (stat ($self->dest->cache))[9];
- # Compare with modification times of inherited cache files
- for my $use (@{ $self->inherit }) {
- next unless -f $use->dest->cache;
- my $use_mtime = (stat ($use->dest->cache))[9];
- $out_of_date = 1 if $use_mtime > $cur_mtime;
- }
- }
- return ($rc, $out_of_date);
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $obj->check_lock ();
- #
- # DESCRIPTION
- # This method returns true if no lock is found in the destination or if the
- # locks found are allowed.
- # ------------------------------------------------------------------------------
- sub check_lock {
- my $self = shift;
- # Check all types of locks
- for my $method (@Fcm::Dest::lockfiles) {
- my $lock = $self->dest->$method;
- # Check whether lock exists
- next unless -e $lock;
- # Check whether this lock is allowed
- next if $self->check_lock_is_allowed ($lock);
- # Throw error if a lock exists
- w_report 'ERROR: ', $lock, ': lock file exists,';
- w_report ' ', $self->dest->rootdir, ': destination is busy.';
- return;
- }
- return 1;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->check_lock_is_allowed ($lock);
- #
- # DESCRIPTION
- # This method returns true if it is OK for $lock to exist in the destination.
- # ------------------------------------------------------------------------------
- sub check_lock_is_allowed {
- my ($self, $lock) = @_;
- # Disallow all types of locks by default
- return 0;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->compare_setting (
- # METHOD_LIST => \@method_list,
- # [METHOD_ARGS => \@method_args,]
- # [CACHEBASE => $cachebase,]
- # );
- #
- # DESCRIPTION
- # This method gets settings from the previous cache and updates the current.
- #
- # METHOD
- # The method returns true on success. @method_list must be a list of method
- # names for processing the cached lines in the previous run. If an existing
- # cache exists, its content is read into $old_lines, which is a list of
- # Fcm::CfgLine objects. Otherwise, $old_lines is set to undef. If $cachebase
- # is set, it is used for as the cache basename. Otherwise, the default for
- # the current system is used. It calls each method in the @method_list using
- # $self->$method ($old_lines, @method_args), which should return a
- # two-element list. The first element should be a return code (1 for out of
- # date, 0 for up to date and undef for failure). The second element should be
- # a reference to a list of Fcm::CfgLine objects for the output.
- # ------------------------------------------------------------------------------
- sub compare_setting {
- my ($self, %args) = @_;
- my @method_list = exists ($args{METHOD_LIST}) ? @{ $args{METHOD_LIST} } : ();
- my @method_args = exists ($args{METHOD_ARGS}) ? @{ $args{METHOD_ARGS} } : ();
- my $cachebase = exists ($args{CACHEBASE}) ? $args{CACHEBASE} : undef;
- my $rc = 1;
- # Read cache if the file exists
- # ----------------------------------------------------------------------------
- my $cache = $cachebase
- ? File::Spec->catfile ($self->dest->cachedir, $cachebase)
- : $self->dest->cache;
- my @in_caches = ();
- if (-r $cache) {
- push @in_caches, $cache;
- } else {
- for my $use (@{ $self->inherit }) {
- my $use_cache = $cachebase
- ? File::Spec->catfile ($use->dest->cachedir, $cachebase)
- : $use->dest->cache;
- push @in_caches, $use_cache if -r $use_cache;
- }
- }
- my $old_lines = undef;
- for my $in_cache (@in_caches) {
- next unless -r $in_cache;
- my $cfg = Fcm::CfgFile->new (SRC => $in_cache);
- if ($cfg->read_cfg) {
- $old_lines = [] if not defined $old_lines;
- push @$old_lines, @{ $cfg->lines };
- }
- }
- # Call methods in @method_list to see if cache is out of date
- # ----------------------------------------------------------------------------
- my @new_lines = ();
- my $out_of_date = 0;
- for my $method (@method_list) {
- my ($return, $lines);
- ($return, $lines) = $self->$method ($old_lines, @method_args) if $rc;
- if (defined $return) {
- # Method succeeded
- push @new_lines, @$lines;
- $out_of_date = 1 if $return;
- } else {
- # Method failed
- $rc = $return;
- last;
- }
- }
- # Update the cache in the current run
- # ----------------------------------------------------------------------------
- if ($rc) {
- if (@{ $self->inherited } and $out_of_date) {
- # If this is an inherited configuration, the cache must not be changed
- w_report 'ERROR: ', $self->cfg->src,
- ': inherited configuration does not match with its cache.';
- $rc = undef;
- } elsif ((not -f $cache) or $out_of_date) {
- my $cfg = Fcm::CfgFile->new;
- $cfg->lines ([sort {$a->label cmp $b->label} @new_lines]);
- $rc = $cfg->print_cfg ($cache, 1);
- }
- }
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # ($changed_hash_ref, $new_lines_array_ref) =
- # $self->compare_setting_in_config($prefix, \@old_lines);
- #
- # DESCRIPTION
- # This method compares old and current settings for a specified item.
- #
- # METHOD
- # This method does two things.
- #
- # It uses the current configuration for the $prefix item to generate a list of
- # new Fcm::CfgLine objects (which is returned as a reference in the second
- # element of the returned list).
- #
- # The values of the old lines are then compared with those of the new lines.
- # Any settings that are changed are stored in a hash, which is returned as a
- # reference in the first element of the returned list. The key of the hash is
- # the name of the changed setting, and the value is the value of the new
- # setting or undef if the setting no longer exists.
- #
- # ARGUMENTS
- # $prefix - the name of an item in Fcm::Config to be compared
- # @old_lines - a list of Fcm::CfgLine objects containing the old settings
- # ------------------------------------------------------------------------------
- sub compare_setting_in_config {
- my ($self, $prefix, $old_lines_ref) = @_;
-
- my %changed = %{$self->setting($prefix)};
- my (@new_lines, %new_val_of);
- while (my ($key, $val) = each(%changed)) {
- $new_val_of{$key} = (ref($val) eq 'ARRAY' ? join(q{ }, sort(@{$val})) : $val);
- push(@new_lines, Fcm::CfgLine->new(
- LABEL => $prefix . $Fcm::Config::DELIMITER . $key,
- VALUE => $new_val_of{$key},
- ));
- }
- if (defined($old_lines_ref)) {
- my %old_val_of
- = map {($_->label_from_field(1), $_->value())} # converts into a hash
- grep {$_->label_starts_with($prefix)} # gets relevant lines
- @{$old_lines_ref};
- while (my ($key, $val) = each(%old_val_of)) {
- if (exists($changed{$key})) {
- if ($val eq $new_val_of{$key}) { # no change from old to new
- delete($changed{$key});
- }
- }
- else { # exists in old but not in new
- $changed{$key} = undef;
- }
- }
- }
- return (\%changed, \@new_lines);
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $obj->invoke ([CLEAN => 1, ]%args);
- #
- # DESCRIPTION
- # This method invokes the system. If CLEAN is set to true, it will only parse
- # the configuration and set up the destination, but will not invoke the
- # system. See the invoke_setup_dest and the invoke_system methods for list of
- # other arguments in %args.
- # ------------------------------------------------------------------------------
- sub invoke {
- my $self = shift;
- my %args = @_;
- # Print diagnostic at beginning of run
- # ----------------------------------------------------------------------------
- # Name of the system
- (my $name = ref ($self)) =~ s/^Fcm:://;
- # Print start time on system run, if verbose is true
- my $date = localtime;
- print $name, ' command started on ', $date, '.', "\n"
- if $self->verbose;
- # Start time (seconds since epoch)
- my $otime = time;
- # Parse the configuration file
- my $rc = $self->invoke_stage ('Parse configuration', 'parse_cfg');
- # Set up the destination
- $rc = $self->invoke_stage ('Setup destination', 'invoke_setup_dest', %args)
- if $rc;
- # Invoke the system
- # ----------------------------------------------------------------------------
- $rc = $self->invoke_system (%args) if $rc and not $args{CLEAN};
- # Remove empty directories
- $rc = $self->dest->clean (MODE => 'EMPTY') if $rc;
- # Print diagnostic at end of run
- # ----------------------------------------------------------------------------
- # Print lapse time at the end, if verbose is true
- if ($self->verbose) {
- my $total = time - $otime;
- my $s_str = $total > 1 ? 'seconds' : 'second';
- print '->TOTAL: ', $total, ' ', $s_str, "\n";
- }
- # Report end of system run
- $date = localtime;
- if ($rc) {
- # Success
- print $name, ' command finished on ', $date, '.', "\n"
- if $self->verbose;
- } else {
- # Failure
- e_report $name, ' failed on ', $date, '.';
- }
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $obj->invoke_setup_dest ([CLEAN|FULL => 1], [IGNORE_LOCK => 1]);
- #
- # DESCRIPTION
- # This method sets up the destination and returns true on success.
- #
- # ARGUMENTS
- # CLEAN|FULL - If set to "true", set up the system in "clean|full" mode.
- # Sub-directories and files in the root directory created by
- # the previous invocation of the system will be removed. If
- # not set, the default is to run in "incremental" mode.
- # IGNORE_LOCK - If set to "true", it ignores any lock files that may exist in
- # the destination root directory.
- # ------------------------------------------------------------------------------
- sub invoke_setup_dest {
- my $self = shift;
- my %args = @_;
- # Set up destination
- # ----------------------------------------------------------------------------
- # Print destination in verbose mode
- if ($self->verbose()) {
- printf(
- "Destination: %s@%s:%s\n",
- scalar(getpwuid($<)),
- hostname(),
- $self->dest()->rootdir(),
- );
- }
- my $rc = 1;
- my $out_of_date = 0;
- # Check whether lock exists in the destination root
- $rc = $self->check_lock if $rc and not $args{IGNORE_LOCK};
- # Check whether current cache is out of date relative to the inherited ones
- ($rc, $out_of_date) = $self->check_cache if $rc;
- # Remove sub-directories and files in destination in "full" mode
- $rc = $self->dest->clean (MODE => 'ALL')
- if $rc and ($args{FULL} or $args{CLEAN} or $out_of_date);
- # Create build root directory if necessary
- $rc = $self->dest->create if $rc;
- # Set a lock in the destination root
- $rc = $self->dest->set_lock if $rc;
- # Generate an as-parsed configuration file
- $self->cfg->print_cfg ($self->dest->parsedcfg);
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->invoke_stage ($name, $method, @args);
- #
- # DESCRIPTION
- # This method invokes a named stage of the system, where $name is the name of
- # the stage, $method is the name of the method for invoking the stage and
- # @args are the arguments to the &method.
- # ------------------------------------------------------------------------------
- sub invoke_stage {
- my ($self, $name, $method, @args) = @_;
- # Print diagnostic at beginning of a stage
- print '->', $name, ': start', "\n" if $self->verbose;
- my $stime = time;
- # Invoke the stage
- my $rc = $self->$method (@args);
- # Print diagnostic at end of a stage
- my $total = time - $stime;
- my $s_str = $total > 1 ? 'seconds' : 'second';
- print '->', $name, ': ', $total, ' ', $s_str, "\n";
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->invoke_system (%args);
- #
- # DESCRIPTION
- # This is a prototype method for invoking the system.
- # ------------------------------------------------------------------------------
- sub invoke_system {
- my $self = shift;
- my %args = @_;
- print "Dummy code.\n";
- return 0;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $obj->parse_cfg ();
- #
- # DESCRIPTION
- # This method calls other methods to parse the configuration file.
- # ------------------------------------------------------------------------------
- sub parse_cfg {
- my $self = shift;
- return unless $self->cfg->src;
- # Read config file
- # ----------------------------------------------------------------------------
- return unless $self->cfg->read_cfg;
- if ($self->cfg->type ne $self->type) {
- w_report 'ERROR: ', $self->cfg->src, ': not a ', $self->type,
- ' config file.';
- return;
- }
- # Strip out optional prefix from all labels
- # ----------------------------------------------------------------------------
- if ($self->cfg_prefix) {
- for my $line (@{ $self->cfg->lines }) {
- $line->prefix ($self->cfg_prefix);
- }
- }
- # Filter lines from the configuration file
- # ----------------------------------------------------------------------------
- my @cfg_lines = grep {
- $_->slabel and # ignore empty/comment lines
- index ($_->slabel, '%') != 0 and # ignore user variable
- not $_->slabel_starts_with_cfg ('INC') # ignore INC line
- } @{ $self->cfg->lines };
- # Parse the lines to read in the various settings, by calling the methods:
- # $self->parse_cfg_XXX, where XXX is: header, inherit, dest, and the values
- # in the list @{ $self->cfg_methods }.
- # ----------------------------------------------------------------------------
- my $rc = 1;
- for my $name (@{ $self->cfg_methods }) {
- my $method = 'parse_cfg_' . $name;
- $self->$method (\@cfg_lines) or $rc = 0;
- }
- # Report warnings/errors
- # ----------------------------------------------------------------------------
- for my $line (@cfg_lines) {
- $rc = 0 if not $line->parsed;
- my $mesg = $line->format_error;
- w_report $mesg if $mesg;
- }
- return ($rc);
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->parse_cfg_dest (\@cfg_lines);
- #
- # DESCRIPTION
- # This method parses the destination settings in the @cfg_lines.
- # ------------------------------------------------------------------------------
- sub parse_cfg_dest {
- my ($self, $cfg_lines) = @_;
- my $rc = 1;
- # DEST/DIR declarations
- # ----------------------------------------------------------------------------
- my @lines = grep {
- $_->slabel_starts_with_cfg ('DEST') or $_->slabel_starts_with_cfg ('DIR')
- } @$cfg_lines;
- # Only ROOTDIR declarations are accepted
- for my $line (@lines) {
- my ($d, $method) = $line->slabel_fields;
- $d = lc $d;
- $method = lc $method;
- # Backward compatibility
- $d = 'dest' if $d eq 'dir';
- # Default to "rootdir"
- $method = 'rootdir' if (not $method) or $method eq 'root';
- # Only "rootdir" can be set
- next unless $method eq 'rootdir';
- $self->$d->$method (&expand_tilde ($line->value));
- $line->parsed (1);
- }
- # Make sure root directory is set
- # ----------------------------------------------------------------------------
- if (not $self->dest->rootdir) {
- w_report 'ERROR: ', $self->cfg->actual_src,
- ': destination root directory not set.';
- $rc = 0;
- }
- # Inherit destinations
- # ----------------------------------------------------------------------------
- for my $use (@{ $self->inherit }) {
- push @{ $self->dest->inherit }, (@{ $use->dest->inherit }, $use->dest);
- }
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->parse_cfg_header (\@cfg_lines);
- #
- # DESCRIPTION
- # This method parses the header setting in the @cfg_lines.
- # ------------------------------------------------------------------------------
- sub parse_cfg_header {
- my ($self, $cfg_lines) = @_;
- # Set header lines as "parsed"
- map {$_->parsed (1)} grep {$_->slabel_starts_with_cfg ('CFGFILE')} @$cfg_lines;
- return 1;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = $self->parse_cfg_inherit (\@cfg_lines);
- #
- # DESCRIPTION
- # This method parses the inherit setting in the @cfg_lines.
- # ------------------------------------------------------------------------------
- sub parse_cfg_inherit {
- my ($self, $cfg_lines) = @_;
- # USE declaration
- # ----------------------------------------------------------------------------
- my @lines = grep {$_->slabel_starts_with_cfg ('USE')} @$cfg_lines;
- # Check for cyclic dependency
- if (@lines and grep {$_ eq $self->cfg->actual_src} @{ $self->inherited }) {
- # Error if current configuration file is in its own inheritance hierarchy
- w_report 'ERROR: ', $self->cfg->actual_src, ': attempt to inherit itself.';
- $_->error ($_->label . ': ignored due to cyclic dependency.') for (@lines);
- return 0;
- }
- my $rc = 1;
- for my $line (@lines) {
- # Invoke new instance of the current class
- my $use = ref ($self)->new;
- # Set configuration file, inheritance hierarchy
- # and attempt to parse the configuration
- $use->cfg->src (&expand_tilde ($line->value));
- $use->inherited ([$self->cfg->actual_src, @{ $self->inherited }]);
- $use->parse_cfg;
- # Add to list of inherit configurations
- push @{ $self->inherit }, $use;
- $line->parsed (1);
- }
- # Check locks in inherited destination
- # ----------------------------------------------------------------------------
- for my $use (@{ $self->inherit }) {
- $rc = 0 unless $use->check_lock;
- }
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # @cfglines = $obj->to_cfglines ();
- #
- # DESCRIPTION
- # This method returns the configuration lines of this object.
- # ------------------------------------------------------------------------------
- sub to_cfglines {
- my ($self) = @_;
- my @inherited_dests = map {
- Fcm::CfgLine->new (
- label => $self->cfglabel ('USE'), value => $_->dest->rootdir
- );
- } @{ $self->inherit };
- return (
- Fcm::CfgLine::comment_block ('File header'),
- Fcm::CfgLine->new (
- label => $self->cfglabel ('CFGFILE') . $Fcm::Config::DELIMITER . 'TYPE',
- value => $self->type,
- ),
- Fcm::CfgLine->new (
- label => $self->cfglabel ('CFGFILE') . $Fcm::Config::DELIMITER . 'VERSION',
- value => '1.0',
- ),
- Fcm::CfgLine->new (),
- @inherited_dests,
- Fcm::CfgLine::comment_block ('Destination'),
- ($self->dest->to_cfglines()),
- );
- }
- # ------------------------------------------------------------------------------
- 1;
- __END__
|