source: LMDZ6/branches/Amaury_dev/tools/fcm/lib/FCM1/Util.pm @ 5099

Last change on this file since 5099 was 5095, checked in by abarral, 4 months ago

Revert cosp*/ from the trunk, as it's external code
Add missing bits from FCM2 source

File size: 16.0 KB
Line 
1# ------------------------------------------------------------------------------
2# Copyright (C) 2006-2021 British Crown (Met Office) & Contributors.
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# NAME
20#   FCM1::Util
21#
22# DESCRIPTION
23#   This is a package of misc utilities used by the FCM command.
24#
25# ------------------------------------------------------------------------------
26
27use warnings;
28use strict;
29
30package FCM1::Util;
31require Exporter;
32our @ISA = qw{Exporter};
33
34sub expand_tilde;
35sub e_report;
36sub find_file_in_path;
37sub get_command_string;
38sub get_rev_of_wc;
39sub get_url_of_wc;
40sub get_url_peg_of_wc;
41sub get_wct;
42sub is_url;
43sub is_wc;
44sub print_command;
45sub run_command;
46sub svn_date;
47sub tidy_url;
48sub touch_file;
49sub w_report;
50
51our @EXPORT = qw{
52    expand_tilde
53    e_report
54    find_file_in_path
55    get_command_string
56    get_rev_of_wc
57    get_url_of_wc
58    get_url_peg_of_wc
59    get_wct
60    is_url
61    is_wc
62    print_command
63    run_command
64    svn_date
65    tidy_url
66    touch_file
67    w_report
68};
69
70# Standard modules
71use Carp;
72use Cwd;
73use File::Basename;
74use File::Find;
75use File::Path;
76use File::Spec;
77use POSIX qw{strftime SIGINT SIGKILL SIGTERM WEXITSTATUS WIFSIGNALED WTERMSIG};
78
79# FCM component modules
80use FCM1::Timer;
81
82# ------------------------------------------------------------------------------
83
84# Module level variables
85my %svn_info       = (); # "svn info" log, (key1 = path,
86                         # key2 = URL, Revision, Last Changed Rev)
87
88# ------------------------------------------------------------------------------
89# SYNOPSIS
90#   %srcdir = &FCM1::Util::find_file_in_path ($file, \@path);
91#
92# DESCRIPTION
93#   Search $file in @path. Returns the full path of the $file if it is found
94#   in @path. Returns "undef" if $file is not found in @path.
95# ------------------------------------------------------------------------------
96
97sub find_file_in_path {
98  my ($file, $path) = @_;
99
100  for my $dir (@$path) {
101    my $full_file = File::Spec->catfile ($dir, $file);
102    return $full_file if -e $full_file;
103  }
104
105  return undef;
106}
107
108# ------------------------------------------------------------------------------
109# SYNOPSIS
110#   $expanded_path = &FCM1::Util::expand_tilde ($path);
111#
112# DESCRIPTION
113#   Returns an expanded path if $path is a path that begins with a tilde (~).
114# ------------------------------------------------------------------------------
115
116sub expand_tilde {
117  my $file = $_[0];
118
119  $file =~ s#^~([^/]*)#$1 ? (getpwnam $1)[7] : ($ENV{HOME} || $ENV{LOGDIR})#ex;
120
121  # Expand . and ..
122  while ($file =~ s#/+\.(?:/+|$)#/#g) {next}
123  while ($file =~ s#/+[^/]+/+\.\.(?:/+|$)#/#g) {next}
124
125  # Remove trailing /
126  $file =~ s#/*$##;
127
128  return $file;
129}
130
131# ------------------------------------------------------------------------------
132# SYNOPSIS
133#   $rc = &FCM1::Util::touch_file ($file);
134#
135# DESCRIPTION
136#   Touch $file if it exists. Create $file if it does not exist. Return 1 for
137#   success or 0 otherwise.
138# ------------------------------------------------------------------------------
139
140sub touch_file {
141  my $file = $_[0];
142  my $rc   = 1;
143
144  if (-e $file) {
145    my $now = time;
146    $rc = utime $now, $now, $file;
147
148  } else {
149    mkpath dirname ($file) unless -d dirname ($file);
150
151    $rc = open FILE, '>', $file;
152    $rc = close FILE if $rc;
153  }
154
155  return $rc;
156}
157
158# ------------------------------------------------------------------------------
159# SYNOPSIS
160#   $flag = &is_wc ([$path]);
161#
162# DESCRIPTION
163#   Returns true if current working directory (or $path) is a Subversion
164#   working copy.
165# ------------------------------------------------------------------------------
166
167sub is_wc {
168  my $path = shift() || cwd();
169  my $path_of_dir = -f $path ? dirname($path) : $path;
170  if (-e File::Spec->catfile($path_of_dir, qw{.svn entries})) {
171      return 1;
172  }
173  my $inforc = &run_command (
174    [qw/svn info/, $path_of_dir],
175    METHOD => 'qx', DEVNULL => 1, ERROR => 'ignore'
176  );
177  return $inforc != 0;
178}
179
180# ------------------------------------------------------------------------------
181# SYNOPSIS
182#   $flag = &is_url ($url);
183#
184# DESCRIPTION
185#   Returns true if $url is a URL.
186# ------------------------------------------------------------------------------
187
188sub is_url {
189  # This should handle URL beginning with svn://, http:// and svn+ssh://
190  return ($_[0] =~ m#^[\+\w]+://#);
191}
192
193# ------------------------------------------------------------------------------
194# SYNOPSIS
195#   $url = tidy_url($url);
196#
197# DESCRIPTION
198#   Returns a tidied version of $url by removing . and .. in the path.
199# ------------------------------------------------------------------------------
200
201sub tidy_url {
202    my ($url) = @_;
203    if (!is_url($url)) {
204        return $url;
205    }
206    my $DOT_PATTERN     = qr{/+ \. (?:/+|(@|\z))}xms;
207    my $DOT_DOT_PATTERN = qr{/+ [^/]+ /+ \.\. (?:/+|(@|\z))}xms;
208    my $TRAILING_SLASH_PATTERN = qr{([^/]+) /* (@|\z)}xms;
209    my $RIGHT_EVAL = q{'/' . ($1 ? $1 : '')};
210    DOT:
211    while ($url =~ s{$DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
212        next DOT;
213    }
214    DOT_DOT:
215    while ($url =~ s{$DOT_DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
216        next DOT_DOT;
217    }
218    $url =~ s{$TRAILING_SLASH_PATTERN}{$1$2}xms;
219    return $url;
220}
221
222# ------------------------------------------------------------------------------
223# SYNOPSIS
224#   $string = &get_wct ([$dir]);
225#
226# DESCRIPTION
227#   If current working directory (or $dir) is a Subversion working copy,
228#   returns the top directory of this working copy; otherwise returns an empty
229#   string.
230# ------------------------------------------------------------------------------
231
232sub get_wct {
233  my $dir = @_ ? $_[0] : cwd ();
234
235  return '' if not &is_wc ($dir);
236
237  my $updir = dirname $dir;
238  while (&is_wc ($updir)) {
239    $dir   = $updir;
240    $updir = dirname $dir;
241    last if $updir eq $dir;
242  }
243
244  return $dir;
245}
246
247# ------------------------------------------------------------------------------
248# SYNOPSIS
249#   $string = &get_url_of_wc ([$path[, $refresh]]);
250#
251# DESCRIPTION
252#   If current working directory (or $path) is a Subversion working copy,
253#   returns the URL of the associated Subversion repository; otherwise returns
254#   an empty string. If $refresh is specified, do not use the cached
255#   information.
256# ------------------------------------------------------------------------------
257
258sub get_url_of_wc {
259  my $path    = @_ ? $_[0] : cwd ();
260  my $refresh = exists $_[1] ? $_[1] : 0;
261  my $url  = '';
262
263  if (&is_wc ($path)) {
264    delete $svn_info{$path} if $refresh;
265    &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
266    $url = $svn_info{$path}{URL};
267  }
268
269  return $url;
270}
271
272# ------------------------------------------------------------------------------
273# SYNOPSIS
274#   $string = &get_url_peg_of_wc ([$path[, $refresh]]);
275#
276# DESCRIPTION
277#   If current working directory (or $path) is a Subversion working copy,
278#   returns the URL@REV of the associated Subversion repository; otherwise
279#   returns an empty string. If $refresh is specified, do not use the cached
280#   information.
281# ------------------------------------------------------------------------------
282
283sub get_url_peg_of_wc {
284  my $path    = @_ ? $_[0] : cwd ();
285  my $refresh = exists $_[1] ? $_[1] : 0;
286  my $url  = '';
287
288  if (&is_wc ($path)) {
289    delete $svn_info{$path} if $refresh;
290    &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
291    $url = $svn_info{$path}{URL} . '@' . $svn_info{$path}{Revision};
292  }
293
294  return $url;
295}
296
297# ------------------------------------------------------------------------------
298# SYNOPSIS
299#   &_invoke_svn_info (PATH => $path);
300#
301# DESCRIPTION
302#   The function is internal to this module. It invokes "svn info" on $path to
303#   gather information on URL, Revision and Last Changed Rev. The information
304#   is stored in a hash table at the module level, so that the information can
305#   be re-used.
306# ------------------------------------------------------------------------------
307
308sub _invoke_svn_info {
309  my %args = @_;
310  my $path = $args{PATH};
311  my $cfg  = FCM1::Config->instance();
312
313  return if exists $svn_info{$path};
314
315  # Invoke "svn info" command
316  my @info = &run_command (
317    [qw/svn info/, $path],
318    PRINT => $cfg->verbose > 2, METHOD => 'qx', DEVNULL => 1, ERROR => 'ignore',
319  );
320  for (@info) {
321    chomp;
322
323    if (/^(URL|Revision|Last Changed Rev):\s*(.+)$/) {
324      $svn_info{$path}{$1} = $2;
325    }
326  }
327
328  return;
329}
330
331# ------------------------------------------------------------------------------
332# SYNOPSIS
333#   $string = &get_command_string ($cmd);
334#   $string = &get_command_string (\@cmd);
335#
336# DESCRIPTION
337#   The function returns a string by converting the list in @cmd or the scalar
338#   $cmd to a form, where it can be executed as a shell command.
339# ------------------------------------------------------------------------------
340
341sub get_command_string {
342  my $cmd    = $_[0];
343  my $return = '';
344
345  if (ref ($cmd) and ref ($cmd) eq 'ARRAY') {
346    # $cmd is a reference to an array
347
348    # Print each argument
349    for my $i (0 .. @{ $cmd } - 1) {
350      my $arg = $cmd->[$i];
351
352      $arg =~ s/./*/g if $i > 0 and $cmd->[$i - 1] eq '--password';
353
354      if ($arg =~ /[\s'"*?]/) {
355        # Argument contains a space, quote it
356        if (index ($arg, "'") >= 0) {
357          # Argument contains an apostrophe, quote it with double quotes
358          $return .= ($i > 0 ? ' ' : '') . '"' . $arg . '"';
359
360        } else {
361          # Otherwise, quote argument with apostrophes
362          $return .= ($i > 0 ? ' ' : '') . "'" . $arg . "'";
363        }
364
365      } else {
366        # Argument does not contain a space, just print it
367        $return .= ($i > 0 ? ' ' : '') . ($arg eq '' ? "''" : $arg);
368      }
369    }
370
371  } else {
372    # $cmd is a scalar, just print it "as is"
373    $return = $cmd;
374  }
375
376  return $return;
377}
378
379# ------------------------------------------------------------------------------
380# SYNOPSIS
381#   &print_command ($cmd);
382#   &print_command (\@cmd);
383#
384# DESCRIPTION
385#   The function prints the list in @cmd or the scalar $cmd, as it would be
386#   executed by the shell.
387# ------------------------------------------------------------------------------
388
389sub print_command {
390  my $cmd = $_[0];
391
392  print '=> ', &get_command_string ($cmd) , "\n";
393}
394
395# ------------------------------------------------------------------------------
396# SYNOPSIS
397#   @return = &run_command (\@cmd, <OPTIONS>);
398#   @return = &run_command ($cmd , <OPTIONS>);
399#
400# DESCRIPTION
401#   This function executes the command in the list @cmd or in the scalar $cmd.
402#   The remaining are optional arguments in a hash table. Valid options are
403#   listed below. If the command is run using "qx", the function returns the
404#   standard output from the command. If the command is run using "system", the
405#   function returns true on success. By default, the function dies on failure.
406#
407# OPTIONS
408#   METHOD  => $method - this can be "system", "exec" or "qx". This determines
409#                        how the command will be executed. If not set, the
410#                        default is to run the command with "system".
411#   PRINT   => 1       - if set, print the command before executing it.
412#   ERROR   => $flag   - this should only be set if METHOD is set to "system"
413#                        or "qx". The $flag can be "die" (default), "warn" or
414#                        "ignore". If set to "die", the function dies on error.
415#                        If set to "warn", the function issues a warning on
416#                        error, and the function returns false. If set to
417#                        "ignore", the function returns false on error.
418#   RC      => 1       - if set, must be a reference to a scalar, which will be
419#                        set to the return code of the command.
420#   DEVNULL => 1       - if set, re-direct STDERR to /dev/null before running
421#                        the command.
422#   TIME    => 1       - if set, print the command with a timestamp before
423#                        executing it, and print the time taken when it
424#                        completes. This option supersedes the PRINT option.
425# ------------------------------------------------------------------------------
426
427sub run_command {
428  my ($cmd, %input_opt_of) = @_;
429  my %opt_of = (
430    DEVNULL => undef,
431    ERROR   => 'die',
432    METHOD  => 'system',
433    PRINT   => undef,
434    RC      => undef,
435    TIME    => undef,
436    %input_opt_of,
437  );
438  local($|) = 1; # Make sure STDOUT is flushed before running command
439
440  # Print the command before execution, if necessary
441  if ($opt_of{TIME}) {
442    print(timestamp_command(get_command_string($cmd)));
443  }
444  elsif ($opt_of{PRINT}) {
445    print_command($cmd);
446  }
447
448  # Re-direct STDERR to /dev/null if necessary
449  if ($opt_of{DEVNULL}) {
450    no warnings;
451    open(OLDERR, ">&STDERR") || croak("Cannot dup STDERR ($!), abort");
452    use warnings;
453    open(STDERR, '>', File::Spec->devnull())
454      || croak("Cannot redirect STDERR ($!), abort");
455    # Make sure the channels are unbuffered
456    my $select = select();
457    select(STDERR); local($|) = 1;
458    select($select);
459  }
460
461  my @return = ();
462  if (ref($cmd) && ref($cmd) eq 'ARRAY') {
463    # $cmd is an array
464    my @command = @{$cmd};
465    if ($opt_of{METHOD} eq 'qx') {
466      @return = qx(@command);
467    }
468    elsif ($opt_of{METHOD} eq 'exec') {
469      exec(@command);
470    }
471    else {
472      system(@command);
473      @return = $? ? () : (1);
474    }
475  }
476  else {
477    # $cmd is an scalar
478    if ($opt_of{METHOD} eq 'qx') {
479      @return = qx($cmd);
480    }
481    elsif ($opt_of{METHOD} eq 'exec') {
482      exec($cmd);
483    }
484    else {
485      system($cmd);
486      @return = $? ? () : (1);
487    }
488  }
489  my $rc = $?;
490
491  # Put STDERR back to normal, if redirected previously
492  if ($opt_of{DEVNULL}) {
493    close(STDERR);
494    open(STDERR, ">&OLDERR") || croak("Cannot dup STDERR ($!), abort");
495  }
496
497  # Print the time taken for command after execution, if necessary
498  if ($opt_of{TIME}) {
499    print(timestamp_command(get_command_string($cmd), 'end'));
500  }
501
502  # Signal and return code
503  my ($signal, $status) = (WTERMSIG($rc), WEXITSTATUS($rc));
504  if (exists($opt_of{RC})) {
505    ${$opt_of{RC}} = $status;
506  }
507  if (WIFSIGNALED($rc) && grep {$signal == $_} (SIGINT, SIGKILL, SIGTERM)) {
508    croak(sprintf('%s terminated (%d)', get_command_string($cmd), $signal));
509  }
510  if ($status && $opt_of{ERROR} ne 'ignore') {
511    my $func_ref = $opt_of{ERROR} eq 'warn' ? \&carp : \&croak;
512    $func_ref->(sprintf('%s failed (%d)', get_command_string($cmd), $status));
513  }
514  return @return;
515}
516
517# ------------------------------------------------------------------------------
518# SYNOPSIS
519#   &e_report (@message);
520#
521# DESCRIPTION
522#   The function prints @message to STDERR and aborts with a error.
523# ------------------------------------------------------------------------------
524
525sub e_report {
526  print STDERR @_, "\n" if @_;
527
528  exit 1;
529}
530
531# ------------------------------------------------------------------------------
532# SYNOPSIS
533#   &w_report (@message);
534#
535# DESCRIPTION
536#   The function prints @message to STDERR and returns.
537# ------------------------------------------------------------------------------
538
539sub w_report {
540  print STDERR @_, "\n" if @_;
541
542  return;
543}
544
545# ------------------------------------------------------------------------------
546# SYNOPSIS
547#   $date = &svn_date ($time);
548#
549# DESCRIPTION
550#   The function returns a date, formatted as by Subversion. The argument $time
551#   is the number of seconds since epoch.
552# ------------------------------------------------------------------------------
553
554sub svn_date {
555  my $time = shift;
556
557  return strftime ('%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)', localtime ($time));
558}
559
560# ------------------------------------------------------------------------------
561
5621;
563
564__END__
Note: See TracBrowser for help on using the repository browser.