1 # ============================================================================
2 # Copyright (c) 2011-2012 University of Pennsylvania
3 # Copyright (c) 2013-2016 Andreas Schuh
6 # See COPYING file for license information or visit
7 # https://cmake-basis.github.io/download.html#license
8 # ============================================================================
10 ##############################################################################
12 # @brief Main module of project-independent BASIS utilities.
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
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.
28 # @ingroup BasisPerlUtilities
29 ##############################################################################
34 package BASIS::Utilities;
36 $BASIS::Utilities::VERSION = 3.03_00;
41 use File::Basename qw(dirname basename);
42 use File::Spec::Functions qw(catfile);
43 use BASIS::File::Which qw(which);
46 ## @addtogroup BasisPerlUtilities
50 # ============================================================================
52 # ============================================================================
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';
61 # ============================================================================
63 # ============================================================================
65 # Note: The generators are defined at the end of this file.
67 use BASIS::Sub::Exporter -setup => {
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],
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)]
85 # ============================================================================
86 # executable information
87 # ============================================================================
89 # ----------------------------------------------------------------------------
90 ## @brief Print contact information.
92 # @param [in] $contact Name of contact. If @c undef, the default contact is used.
96 $contact = CONTACT unless $contact;
97 print "Contact:\n $contact\n";
100 # ----------------------------------------------------------------------------
101 ## @brief Print version information including copyright and license notices.
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
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');
117 # use BASIS::Utilities
118 # print_version => {project => 'FooBar',
120 # copyright => '2012 Andreas Schuh',
121 # license => 'Licensed under the Apache License, Version 2.0'};
122 # print_version('foo');
124 # which results in the output
127 # Copyright (c) 2012 Andreas Schuh. All rights reserved.
128 # Licensed under the Apache License, Version 2.0
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
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.
155 if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
159 if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
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
174 print " ($options{'project'})" if $options{'project'};
175 print " ", $version, "\n";
177 print "Copyright (c) ", $options{'copyright'}, ". All rights reserved.\n" if $options{'copyright'};
178 # license information
179 print $options{'license'}, "\n" if $options{'license'};
182 # ----------------------------------------------------------------------------
183 ## @brief Get UID of build target.
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.
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>".
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.
201 # @returns Fully-qualified build target name or @c undef if @p target is
202 # @c undef or an empty string.
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;
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;
222 # otherwise, return target name unchanged
226 # ----------------------------------------------------------------------------
227 ## @brief Determine whether a given target is known.
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.
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.
241 # @returns Whether the given build target is known by this module.
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};
257 # ----------------------------------------------------------------------------
258 ## @brief Get absolute path of executable file.
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.
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
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.
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.
286 my $base = shift || '.';
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>/) {
298 foreach $config ('Release', 'Debug', 'RelWithDebInfo', 'MinSizeRel') {
300 $tmppath =~ s/\$<CONFIG>/$config/g;
306 $path =~ s/\$<CONFIG>//g;
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;
313 $path = which($name) unless defined $path;
317 # ----------------------------------------------------------------------------
318 ## @brief Get name of executable file.
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.
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.
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.
337 my $path = exepath(@_);
338 defined $path or return undef;
339 return basename($path);
342 # ----------------------------------------------------------------------------
343 ## @brief Get directory of executable file.
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.
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.
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.
362 my $path = exepath(@_);
363 defined $path or return undef;
364 return dirname($path);
367 # ============================================================================
369 # ============================================================================
371 # ----------------------------------------------------------------------------
372 ## @brief Convert list to double quoted string.
374 # @param [in] @$args Array of arguments.
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.
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 '';
391 $str =~ s/"/\\"/g; # escape double quotes
392 $str = '"' . $str . '"' if $str =~ m/'|\s|^$/; # quote if necessary
397 # ----------------------------------------------------------------------------
398 ## @brief Split quoted string.
400 # @param [in] $str Quoted string.
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;
418 if ($max eq 1) { return ($args[0], $str); }
419 else { return (@args, $str); }
420 } else { return @args; }
423 # ----------------------------------------------------------------------------
424 # @brief Split/Convert quoted string or array of arguments into command name
425 # and quoted string of command arguments.
427 # @param [in] @$args Array of command name and arguments or quoted string.
429 # @returns Tuple of command name and quoted string of command arguments.
430 sub _split_command_and_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);
442 die "Argument must be either array reference or string";
444 return ($command, $arguments);
447 # ----------------------------------------------------------------------------
448 ## @brief Execute command as subprocess.
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.
454 # This command takes either an array reference or a string as first argument.
455 # All other arguments are keyword arguments using hash notation.
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);
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.
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.
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.
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?";
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'}) {
512 $options{'simulate'} and print " (simulated)";
518 if (not $options{'simulate'}) {
519 open CMD, "$args |" or die "$command: Failed to open subprocess";
520 my $ofh = select STDOUT;
523 print $_ unless $options{'quiet'};
524 $output .= $_ if $options{'stdout'};
531 # if command failed, throw an exception
532 if ($status != 0 and not $options{'allow_fail'}) {
533 die "Command $args failed";
536 if ($options{'stdout'}) { return ($status, $output); }
537 else { return $status; }
542 # end of Doxygen group
545 # ============================================================================
547 # ============================================================================
549 # ----------------------------------------------------------------------------
550 # builder of customized print_contact()
551 sub _build_print_contact
553 my ($class, $fn, $args) = @_;
555 my $contact = shift || $args->{contact};
556 print_contact($contact);
560 # ----------------------------------------------------------------------------
561 # builder of customized print_version()
562 sub _build_print_version
564 my ($class, $fn, $args) = @_;
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)$/)) {
572 if (@_ != 0 and (not defined $_[0] or $_[0] !~ /^(name|version|project|copyright|license)$/)) {
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);
591 # ----------------------------------------------------------------------------
592 # builder of customized functions related to executable target information
593 sub _build_executabletargetinfo_function
595 my ($class, $fn, $args) = @_;
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)";
605 # ----------------------------------------------------------------------------
606 # builder of customized execute()
609 my ($class, $fn, $args) = @_;
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);
626 1; # indicate success of module loading