Utilities.pm
Go to the documentation of this file.
1 # ============================================================================
2 # Copyright (c) 2011-2012 University of Pennsylvania
3 # Copyright (c) 2013-2016 Andreas Schuh
4 # All rights reserved.
5 #
6 # See COPYING file for license information or visit
7 # https://cmake-basis.github.io/download.html#license
8 # ============================================================================
9 
10 ##############################################################################
11 # @file Utilities.pm
12 # @brief Main module of project-independent BASIS utilities.
13 #
14 # This module defines the BASIS utility functions. It uses the BASIS::Sub::Exporter
15 # module to enable the generation of these functions customized to the request
16 # of the particular project they are used in. This is, for example, used by
17 # the BASIS::Basis module. The default utility functions defined by
18 # this module, i.e., without any customizaton are intended for use in Perl
19 # scripts that are not build as part of a particular package. In case of a
20 # BASIS package, the already customized project-specific implementations should
21 # be used instead, i.e., those defined by the BASIS::Basis module of
22 # the project.
23 #
24 # @note This module exports also all other BASIS utility functions that are
25 # defined in other Perl modules. Therefore, only this or the
26 # BASIS::Basis module should be used.
27 #
28 # @ingroup BasisPerlUtilities
29 ##############################################################################
30 
31 use strict;
32 use warnings;
33 
34 package BASIS::Utilities;
35 {
36  $BASIS::Utilities::VERSION = 3.03_00;
37 }
38 
39 
40 use Cwd qw(realpath);
41 use File::Basename qw(dirname basename);
42 use File::Spec::Functions qw(catfile);
43 use BASIS::File::Which qw(which);
44 
45 
46 ## @addtogroup BasisPerlUtilities
47 # @{
48 
49 
50 # ============================================================================
51 # constants
52 # ============================================================================
53 
54 ## @brief Default copyright of executables.
55 use constant COPYRIGHT => '2011-12 University of Pennsylvania, 2013-14 Carnegie Mellon University, 2013-16 Andreas Schuh';
56 ## @brief Default license of executables.
57 use constant LICENSE => 'See https://cmake-basis.github.io/download.html#license or COPYING file.';
58 ## @brief Default contact to use for help output of executables.
59 use constant CONTACT => 'andreas.schuh.84@gmail.com';
60 
61 # ============================================================================
62 # exports
63 # ============================================================================
64 
65 # Note: The generators are defined at the end of this file.
66 
67 use BASIS::Sub::Exporter -setup => {
68  exports => [
69  qw(tostring qsplit),
70  print_contact => \&_build_print_contact,
71  print_version => \&_build_print_version,
72  targetuid => \&_build_executabletargetinfo_function,
73  istarget => \&_build_executabletargetinfo_function,
74  exepath => \&_build_executabletargetinfo_function,
75  exename => \&_build_executabletargetinfo_function,
76  exedir => \&_build_executabletargetinfo_function,
77  execute => \&_build_execute],
78  groups => {
79  default => [qw(print_contact print_version exepath exename exedir execute)],
80  everything => [qw(print_contact print_version targetuid istarget exepath
81  exename exedir tostring qsplit execute)]
82  }
83 };
84 
85 # ============================================================================
86 # executable information
87 # ============================================================================
88 
89 # ----------------------------------------------------------------------------
90 ## @brief Print contact information.
91 #
92 # @param [in] $contact Name of contact. If @c undef, the default contact is used.
93 sub print_contact
94 {
95  my $contact = shift;
96  $contact = CONTACT unless $contact;
97  print "Contact:\n $contact\n";
98 }
99 
100 # ----------------------------------------------------------------------------
101 ## @brief Print version information including copyright and license notices.
102 #
103 # @note This function can be customized when importing it in order to set
104 # default values for its parameters, which is in particular done by
105 # the Basis module.
106 #
107 # Example:
108 # @code
109 # use BASIS::Utilities qw(print_version);
110 # print_version('foo', '1.0');
111 # print_version('foo', version => '1.0');
112 # print_version(name => 'foo', version => '1.0');
113 # @endcode
114 #
115 # Example:
116 # @code
117 # use BASIS::Utilities
118 # print_version => {project => 'FooBar',
119 # version => '1.0',
120 # copyright => '2012 Andreas Schuh',
121 # license => 'Licensed under the Apache License, Version 2.0'};
122 # print_version('foo');
123 # @endcode
124 # which results in the output
125 # @verbatim
126 # foo (FooBar) 1.0
127 # Copyright (c) 2012 Andreas Schuh. All rights reserved.
128 # Licensed under the Apache License, Version 2.0
129 # @endverbatim
130 #
131 # @param [in] $name Name of executable. Should not be set programmatically
132 # to the first argument of the main script, but a string
133 # literal instead. This argument is required if no default
134 # has been set during customization. The argument can be
135 # either given as first argument or as keyword argument
136 # as in "name => 'foo'".
137 # @param [in] $version Version of executable, e.g., release of project this
138 # executable belongs to. This argument is required if no
139 # default has been set during customization. The argument
140 # can be either given as second argument or as keyword
141 # argument as in "version => '1.0'".
142 # @param [in] $project Name of project this executable belongs to.
143 # If @c undef or an empty string is given, no project
144 # information is included in output.
145 # @param [in] $copyright The copyright notice. If @c undef, the default copyright
146 # is used. If an empty string is given, no copyright notice
147 # is printed.
148 # @param [in] $license Information regarding licensing. If @c undef, the default
149 # software license is used. If an empty string is given,
150 # no license information is printed.
151 sub print_version
152 {
153  my $name = undef;
154  my $version = undef;
155  if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
156  $name = $_[0];
157  shift;
158  }
159  if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
160  $version = $_[0];
161  shift;
162  }
163  die "print_version(): Invalid number of arguments" if scalar(@_) % 2 == 1;
164  my %defaults = (name => undef, version => undef, project => undef, copyright => COPYRIGHT, license => LICENSE);
165  my %options = (%defaults, @_);
166  die "print_version(): Name argument given twice" if defined $options{'name'} and defined $name;
167  die "print_version(): Version argument given twice" if defined $options{'version'} and defined $version;
168  $name = $options{'name'} unless $name;
169  $version = $options{'version'} unless $version;
170  die "print_version(): Missing name argument" unless $name;
171  die "print_version(): Missing version argument" unless $version;
172  # program identification
173  print $name;
174  print " ($options{'project'})" if $options{'project'};
175  print " ", $version, "\n";
176  # copyright notice
177  print "Copyright (c) ", $options{'copyright'}, ". All rights reserved.\n" if $options{'copyright'};
178  # license information
179  print $options{'license'}, "\n" if $options{'license'};
180 }
181 
182 # ----------------------------------------------------------------------------
183 ## @brief Get UID of build target.
184 #
185 # @note This function can be customized when importing it in order to set
186 # default values for @p prefix and @p targets, which is in particular
187 # done by the Basis module.
188 #
189 # This function prepends the default namespace used for targets build as
190 # part of the project this module belongs to if the given target name is yet
191 # neither known nor fully-qualified, i.e., in the form "<namespace>::<target>".
192 #
193 # @param [in] $target Name of build target.
194 # @param [in] $prefix Common target name prefix. If @c undef, the given
195 # target name must match excactly. Otherwise, targets
196 # within the specified namespace are considered.
197 # @param [in] %$targets Reference to hash which maps known build targets to
198 # executable file paths. If not specified, this function
199 # always returns the input target name unchanged.
200 #
201 # @returns Fully-qualified build target name or @c undef if @p target is
202 # @c undef or an empty string.
203 sub targetuid
204 {
205  my $target = shift;
206  my $prefix = shift;
207  my $targets = shift;
208  # handle invalid arguments
209  return undef unless defined $target and length($target) > 0;
210  # in case of a leading namespace separator or if no lookup table
211  # of executable build target is provided, do not modify target name
212  return $target if $target =~ /^\./ or not defined $targets;
213  # project namespace
214  $prefix = '' unless defined $prefix;
215  $prefix = $prefix . '.DUMMY'; # simplifies while loop
216  # try prepending namespace or parts of it until target is known
217  while ($prefix =~ s/(.*)\.[^.]*/$1/) {
218  if (exists $targets->{$prefix . '.' . $target}) {
219  return $prefix . '.' . $target;
220  }
221  }
222  # otherwise, return target name unchanged
223  return $target;
224 }
225 
226 # ----------------------------------------------------------------------------
227 ## @brief Determine whether a given target is known.
228 #
229 # @note This function can be customized when importing it in order to set
230 # default values for @p prefix and @p targets, which is in particular
231 # done by the Basis module.
232 #
233 # @param [in] $target Name of build target.
234 # @param [in] $prefix Common target name prefix. If @c undef, the given
235 # target name must match excactly. Otherwise, targets
236 # within the specified namespace are considered.
237 # @param [in] %$targets Reference to hash which maps known build targets to
238 # executable file paths. If not specified, this function
239 # always returns false.
240 #
241 # @returns Whether the given build target is known by this module.
242 sub istarget
243 {
244  my $target = shift;
245  my $prefix = shift;
246  my $targets = shift;
247  if (defined $targets) {
248  my $uid = targetuid($target, $prefix, $targets);
249  defined $uid or return 0;
250  $uid =~ s/^[.]?(.*)/$1/;
251  exists $targets->{$uid};
252  } else {
253  return 0;
254  }
255 }
256 
257 # ----------------------------------------------------------------------------
258 ## @brief Get absolute path of executable file.
259 #
260 # @note This function can be customized when importing it in order to set
261 # default values for @p prefix and @p targets, which is in particular
262 # done by the Basis module.
263 #
264 # This function determines the absolute file path of an executable. If no
265 # arguments are given, the absolute path of this executable is returned.
266 # Otherwise, the named command is searched in the system PATH and its
267 # absolute path returned if found. If the executable is not found, @c undef
268 # is returned.
269 #
270 # @param [in] $name Name of command or @c undef.
271 # @param [in] $prefix Common target name prefix. If @c undef, the given
272 # target name must match excactly. Otherwise, targets
273 # within the specified namespace are considered.
274 # @param [in] %$targets Reference to hash which maps known build targets to
275 # executable file paths. If not specified, this function
276 # always returns false.
277 # @param [in] $base Base directory used for relative paths in @p %$targets.
278 #
279 # @returns Absolute path of executable or @c undef if not found.
280 # If @p name is @c undef, the path of this executable is returned.
281 sub exepath
282 {
283  my $name = shift;
284  my $prefix = shift;
285  my $targets = shift;
286  my $base = shift || '.';
287  my $path = undef;
288  if (not defined $name) {
289  $path = realpath($0);
290  } elsif (defined $targets) {
291  my $uid = targetuid($name, $prefix, $targets);
292  defined $uid and $uid =~ s/^[.]?(.*)/$1/;
293  if (defined $uid and exists $targets->{$uid}) {
294  $path = $targets->{$uid};
295  if ($path =~ m/\$<CONFIG>/) {
296  my $tmppath = '';
297  my $config = '';
298  foreach $config ('Release', 'Debug', 'RelWithDebInfo', 'MinSizeRel') {
299  $tmppath = $path;
300  $tmppath =~ s/\$<CONFIG>/$config/g;
301  if (-e $tmppath) {
302  $path = $tmppath;
303  last;
304  }
305  }
306  $path =~ s/\$<CONFIG>//g;
307  }
308  $path = File::Spec->rel2abs($path, File::Spec->rel2abs($base, dirname(__FILE__)));
309  # the realpath() function only works for existing paths
310  $path = realpath($path) if -e $path;
311  }
312  }
313  $path = which($name) unless defined $path;
314  return $path;
315 }
316 
317 # ----------------------------------------------------------------------------
318 ## @brief Get name of executable file.
319 #
320 # @note This function can be customized when importing it in order to set
321 # default values for @p prefix and @p targets, which is in particular
322 # done by the Basis module.
323 #
324 # @param [in] $name Name of command or @c undef.
325 # @param [in] $prefix Common target name prefix. If @c undef, the given
326 # target name must match excactly. Otherwise, targets
327 # within the specified namespace are considered.
328 # @param [in] %$targets Reference to hash which maps known build targets to
329 # executable file paths. If not specified, this function
330 # always returns false.
331 # @param [in] $base Base directory used for relative paths in @p %$targets.
332 #
333 # @returns Name of executable file or @c undef if not found.
334 # If @p name is @c undef, the path of this executable is returned.
335 sub exename
336 {
337  my $path = exepath(@_);
338  defined $path or return undef;
339  return basename($path);
340 }
341 
342 # ----------------------------------------------------------------------------
343 ## @brief Get directory of executable file.
344 #
345 # @note This function can be customized when importing it in order to set
346 # default values for @p prefix and @p targets, which is in particular
347 # done by the Basis module.
348 #
349 # @param [in] $name Name of command or @c undef.
350 # @param [in] $prefix Common target name prefix. If @c undef, the given
351 # target name must match excactly. Otherwise, targets
352 # within the specified namespace are considered.
353 # @param [in] %$targets Reference to hash which maps known build targets to
354 # executable file paths. If not specified, this function
355 # always returns false.
356 # @param [in] $base Base directory used for relative paths in @p %$targets.
357 #
358 # @returns Absolute path to directory containing executable or @c undef if not found.
359 # If @p name is @c undef, the directory of this executable is returned.
360 sub exedir
361 {
362  my $path = exepath(@_);
363  defined $path or return undef;
364  return dirname($path);
365 }
366 
367 # ============================================================================
368 # command execution
369 # ============================================================================
370 
371 # ----------------------------------------------------------------------------
372 ## @brief Convert list to double quoted string.
373 #
374 # @param [in] @$args Array of arguments.
375 #
376 # @returns Double quoted string, i.e., string where array elements are separated
377 # by a space character and surrounded by double quotes if necessary.
378 # Double quotes within an array element are escaped with a backslash.
379 sub tostring
380 {
381  my $str = '';
382  if (ref($_[0]) eq 'ARRAY') {
383  foreach my $arg (@{$_[0]}) {
384  $arg =~ s/"/\\"/g; # escape double quotes
385  $arg = '"' . $arg . '"' if $arg =~ m/'|\s|^$/; # quote if necessary
386  $str .= ' ' if $str ne '';
387  $str .= $arg;
388  }
389  } else {
390  $str = $_[0];
391  $str =~ s/"/\\"/g; # escape double quotes
392  $str = '"' . $str . '"' if $str =~ m/'|\s|^$/; # quote if necessary
393  }
394  return $str;
395 }
396 
397 # ----------------------------------------------------------------------------
398 ## @brief Split quoted string.
399 #
400 # @param [in] $str Quoted string.
401 sub qsplit
402 {
403  my $str = shift;
404  my $max = shift;
405  my $arg = '';
406  my @args = ();
407  LOOP: {
408  while ($str =~ /[ ]*('([^']|\\\')*[^\\]'|\"([^\"]|\\\")*[^\\]\"|[^ ]+)(.*)/) {
409  $arg = $1; # matched element including quotes
410  $str = $4; # continue with residual command-line
411  $arg =~ s/^['\"]|(^|[^\\])['\"]$//g; # remove quotes
412  $arg =~ s/[\\]('|\")/$1/g; # unescape quotes
413  push @args, $arg; # add to resulting array
414  last LOOP if defined $max and scalar(@args) >= $max;
415  }
416  }
417  if (defined $max) {
418  if ($max eq 1) { return ($args[0], $str); }
419  else { return (@args, $str); }
420  } else { return @args; }
421 }
422 
423 # ----------------------------------------------------------------------------
424 # @brief Split/Convert quoted string or array of arguments into command name
425 # and quoted string of command arguments.
426 #
427 # @param [in] @$args Array of command name and arguments or quoted string.
428 #
429 # @returns Tuple of command name and quoted string of command arguments.
430 sub _split_command_and_arguments
431 {
432  my $args = $_[0];
433  my $command = '';
434  my $arguments = '';
435  if (ref($args) eq 'ARRAY') {
436  my @argv = @$args; # otherwise input is modified
437  $command = shift @argv or die "execute(): No command specified for execution";
438  $arguments = tostring(\@argv);
439  } elsif (ref($args) eq '') {
440  ($command, $arguments) = qsplit($args, 1);
441  } else {
442  die "Argument must be either array reference or string";
443  }
444  return ($command, $arguments);
445 }
446 
447 # ----------------------------------------------------------------------------
448 ## @brief Execute command as subprocess.
449 #
450 # @note This function can be customized when importing it in order to set
451 # default values for @p prefix and @p targets, which is in particular
452 # done by the Basis module.
453 #
454 # This command takes either an array reference or a string as first argument.
455 # All other arguments are keyword arguments using hash notation.
456 #
457 # Example:
458 # @code
459 # # only returns exit code of command but does not output anything
460 # my $status = execute(['ls', '/'], quiet => 1);
461 # # returns exit code of command and returns command output w/o printing to stdout
462 # my ($status, $stdout) = execute('ls /', quiet => 1, stdout => 1);
463 # @endcode
464 #
465 # @param [in] $args Command with arguments given either as single quoted
466 # string or array of command name and arguments.
467 # @param [in] $quiet Turns off output of @c stdout of child process to
468 # @c stdout of parent process.
469 # @param [in] $stdout Whether to return the command output.
470 # @param [in] $allow_fail If true, does not raise an exception if return
471 # value is non-zero. Otherwise, an exception is
472 # raised by this function using die.
473 # @param [in] $verbose Verbosity of output messages.
474 # Does not affect verbosity of executed command.
475 # @param [in] $simulate Whether to simulate command execution only.
476 # @param [in] $prefix Common target name prefix. If @c undef, the given
477 # target name must match excactly. Otherwise, targets
478 # within the specified namespace are considered.
479 # @param [in] %$targets Reference to hash which maps known build targets to
480 # executable file paths. If not specified, this function
481 # always returns false.
482 # @param [in] $base Base directory used for relative paths in @p %$targets.
483 #
484 # @returns The exit code of the subprocess if @p stdout is false (the default).
485 # Otherwise, if @p stdout is true, a tuple consisting of exit code
486 # command output is returned. Note that if @p allow_fail is false,
487 # the returned exit code will always be 0.
488 #
489 # @throws die If command execution failed. This exception is not raised
490 # if the command executed with non-zero exit code but
491 # @p allow_fail is true.
492 sub execute
493 {
494  # arguments
495  my $args = shift or die "execute(): No command specified for execution";
496  if ($args =~ m/^(quiet|stdout|allow_fail|verbose|simulate|prefix|targets|base)$/) {
497  warn "First argument matches option name. Missing args argument?";
498  }
499  my %defaults = (quiet => 0, stdout => 0, allow_fail => 0,
500  verbose => 0, simulate => 0,
501  prefix => undef, targets => undef, base => '.');
502  my %options = (%defaults, @_);
503  # get absolute path of executable
504  my ($command, $arguments) = _split_command_and_arguments($args);
505  my $exec_path = exepath($command, $options{'prefix'}, $options{'targets'}, $options{'base'});
506  defined $exec_path or die "$command: Command not found";
507  $exec_path = '"' . $exec_path . '"' if $exec_path =~ m/'|\s/; # quote if necessary
508  $args = "$exec_path $arguments";
509  # some verbose output
510  if ($options{'verbose'} gt 0 or $options{'simulate'}) {
511  print "\$ ", $args;
512  $options{'simulate'} and print " (simulated)";
513  print "\n";
514  }
515  # execute command
516  my $status = 0;
517  my $output = '';
518  if (not $options{'simulate'}) {
519  open CMD, "$args |" or die "$command: Failed to open subprocess";
520  my $ofh = select STDOUT;
521  $|++;
522  while (<CMD>) {
523  print $_ unless $options{'quiet'};
524  $output .= $_ if $options{'stdout'};
525  }
526  $|--;
527  select $ofh;
528  close CMD;
529  $status = $?;
530  }
531  # if command failed, throw an exception
532  if ($status != 0 and not $options{'allow_fail'}) {
533  die "Command $args failed";
534  }
535  # return
536  if ($options{'stdout'}) { return ($status, $output); }
537  else { return $status; }
538 }
539 
540 
541 ## @}
542 # end of Doxygen group
543 
544 
545 # ============================================================================
546 # exports
547 # ============================================================================
548 
549 # ----------------------------------------------------------------------------
550 # builder of customized print_contact()
551 sub _build_print_contact
552 {
553  my ($class, $fn, $args) = @_;
554  return sub {
555  my $contact = shift || $args->{contact};
556  print_contact($contact);
557  }
558 }
559 
560 # ----------------------------------------------------------------------------
561 # builder of customized print_version()
562 sub _build_print_version
563 {
564  my ($class, $fn, $args) = @_;
565  return sub {
566  my $name = undef || $args->{name};
567  my $version = undef || $args->{version};
568  if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
569  $name = $_[0];
570  shift;
571  }
572  if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
573  $version = $_[0];
574  shift;
575  }
576  die "print_version(): Invalid number of arguments" if scalar(@_) % 2 == 1;
577  my %defaults = (name => $args->{name},
578  version => $args->{version},
579  project => $args->{project},
580  copyright => $args->{copyright},
581  license => $args->{license});
582  my %options = (%defaults, @_);
583  die "print_version(): Name argument given twice" if defined $options{'name'} and defined $name;
584  die "print_version(): Version argument given twice" if defined $options{'version'} and defined $version;
585  $options{'name'} = $name if $name;
586  $options{'version'} = $version if $version;
587  print_version(%options);
588  }
589 }
590 
591 # ----------------------------------------------------------------------------
592 # builder of customized functions related to executable target information
593 sub _build_executabletargetinfo_function
594 {
595  my ($class, $fn, $args) = @_;
596  return sub {
597  my $target = shift;
598  my $prefix = shift || $args->{prefix};
599  my $targets = shift || $args->{targets};
600  my $base = shift || $args->{base};
601  eval "$fn(\$target, \$prefix, \$targets, \$base)";
602  }
603 }
604 
605 # ----------------------------------------------------------------------------
606 # builder of customized execute()
607 sub _build_execute
608 {
609  my ($class, $fn, $args) = @_;
610  return sub {
611  my $argv = shift;
612  my %defaults = (quiet => $args->{quiet} || 0,
613  stdout => $args->{stdout} || 0,
614  allow_fail => $args->{allow_fail} || 0,
615  verbose => $args->{verbose} || 0,
616  simulate => $args->{simulate} || 0,
617  prefix => $args->{prefix},
618  targets => $args->{targets},
619  base => $args->{base});
620  my %options = (%defaults, @_);
621  execute($argv, %options);
622  }
623 }
624 
625 
626 1; # indicate success of module loading