forked from OSchip/llvm-project
630 lines
20 KiB
Perl
Executable File
630 lines
20 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
#
|
|
#//===----------------------------------------------------------------------===//
|
|
#//
|
|
#// The LLVM Compiler Infrastructure
|
|
#//
|
|
#// This file is dual licensed under the MIT and the University of Illinois Open
|
|
#// Source Licenses. See LICENSE.txt for details.
|
|
#//
|
|
#//===----------------------------------------------------------------------===//
|
|
#
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use File::Glob ":glob";
|
|
use Data::Dumper;
|
|
|
|
use FindBin;
|
|
use lib "$FindBin::Bin/lib";
|
|
|
|
use tools;
|
|
use Platform ":vars";
|
|
|
|
our $VERSION = "0.004";
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Set of objects: # Ref to hash, keys are names of objects.
|
|
# object0: # Ref to hash of two elements with keys "defined" and "undefined".
|
|
# defined: # Ref to array of symbols defined in object0.
|
|
# - symbol0 # Symbol name.
|
|
# - ...
|
|
# undefined: # Ref to array of symbols referenced in object0.
|
|
# - symbol0
|
|
# - ...
|
|
# object1:
|
|
# ...
|
|
# ...
|
|
# --------------------------------------------------------------------------------------------------
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Set of symbols: # Ref to hash, keys are names of symbols.
|
|
# symbol0: # Ref to array of object names where the symbol0 is defined.
|
|
# - object0 # Object file name.
|
|
# - ...
|
|
# symbol1:
|
|
# ...
|
|
# ...
|
|
# --------------------------------------------------------------------------------------------------
|
|
|
|
sub dump_objects($$$) {
|
|
|
|
my ( $title, $objects, $dump ) = @_;
|
|
|
|
if ( $dump > 0 ) {
|
|
STDERR->print( $title, "\n" );
|
|
foreach my $object ( sort( keys( %$objects ) ) ) {
|
|
STDERR->print( " $object\n" );
|
|
if ( $dump > 1 ) {
|
|
STDERR->print( " Defined symbols:\n" );
|
|
foreach my $symbol ( sort( @{ $objects->{ $object }->{ defined } } ) ) {
|
|
STDERR->print( " $symbol\n" );
|
|
}; # foreach $symbol
|
|
STDERR->print( " Undefined symbols:\n" );
|
|
foreach my $symbol ( sort( @{ $objects->{ $object }->{ undefined } } ) ) {
|
|
STDERR->print( " $symbol\n" );
|
|
}; # foreach $symbol
|
|
}; # if
|
|
}; # foreach $object
|
|
}; # if
|
|
|
|
}; # sub dump_objects
|
|
|
|
sub dump_symbols($$$) {
|
|
|
|
my ( $title, $symbols, $dump ) = @_;
|
|
|
|
if ( $dump > 0 ) {
|
|
STDERR->print( $title, "\n" );
|
|
foreach my $symbol ( sort( keys( %$symbols ) ) ) {
|
|
STDERR->print( " $symbol\n" );
|
|
if ( $dump > 1 ) {
|
|
foreach my $object ( sort( @{ $symbols->{ $symbol } } ) ) {
|
|
STDERR->print( " $object\n" );
|
|
}; # foreach
|
|
}; # if
|
|
}; # foreach $object
|
|
}; # if
|
|
|
|
}; # sub dump_symbols
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Name:
|
|
# load_symbols -- Fulfill objects data structure with symbol names.
|
|
# Synopsis:
|
|
# load_symbols( $objects );
|
|
# Arguments:
|
|
# $objects (in/out) -- Set of objects. On enter, it is expected that top-level hash has filled
|
|
# with object names only. On exit, it is completely fulfilled with lists of symbols
|
|
# defined or referenced in each object file.
|
|
# Returns:
|
|
# Nothing.
|
|
# Example:
|
|
# my $objects = { foo.o => {} };
|
|
# load_symbols( $objects );
|
|
# # Now $objects is { goo.o => { defined => [ ... ], undefined => [ ... ] } }.
|
|
#
|
|
# --------------------------------------------------------------------------------------------------
|
|
# This version of load_symbols parses output of nm command and works on Linux* OS and OS X*.
|
|
#
|
|
sub _load_symbols_nm($) {
|
|
|
|
my $objects = shift( @_ );
|
|
# It is a ref to hash. Keys are object names, values are empty hashes (for now).
|
|
my @bulk;
|
|
|
|
if ( %$objects ) {
|
|
# Do not run nm if a set of objects is empty -- nm will try to open a.out in this case.
|
|
execute(
|
|
[
|
|
"nm",
|
|
"-g", # Display only external (global) symbols.
|
|
"-o", # Precede each symbol by the name of the input file.
|
|
keys( %$objects )
|
|
# Running nm once (rather than once per object) improves performance
|
|
# drastically.
|
|
],
|
|
-stdout => \@bulk
|
|
);
|
|
}; # if
|
|
|
|
foreach my $line ( @bulk ) {
|
|
if ( $line !~ m{^(.*):(?: ?[0-9a-f]*| *) ([A-Za-z]) (.*)$} ) {
|
|
die "Cannot parse nm output, line:\n $line\n";
|
|
}; # if
|
|
my ( $file, $tag, $symbol ) = ( $1, $2, $3 );
|
|
if ( not exists( $objects->{ $file } ) ) {
|
|
die "nm reported unknown object file:\n $line\n";
|
|
}; # if
|
|
# AC: exclude some libc symbols from renaming, otherwise we have problems
|
|
# in tests for gfortran + static libiomp on Lin_32.
|
|
# These symbols came from libtbbmalloc.a
|
|
if ( $target_os eq "lin" ) {
|
|
if ( $symbol =~ m{__i686} ) {
|
|
next;
|
|
}
|
|
}
|
|
# AC: added "w" to tags of undefined symbols, e.g. malloc is weak in libirc v12.1.
|
|
if ( $tag eq "U" or $tag eq "w" ) { # Symbol not defined.
|
|
push( @{ $objects->{ $file }->{ undefined } }, $symbol );
|
|
} else { # Symbol defined.
|
|
push( @{ $objects->{ $file }->{ defined } }, $symbol );
|
|
}; # if
|
|
}; # foreach
|
|
|
|
return undef;
|
|
|
|
}; # sub _load_symbols_nm
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# This version of load_symbols parses output of link command and works on Windows* OS.
|
|
#
|
|
sub _load_symbols_link($) {
|
|
|
|
my $objects = shift( @_ );
|
|
# It is a ref to hash. Keys are object names, values are empty hashes (for now).
|
|
my @bulk;
|
|
|
|
if ( %$objects ) {
|
|
# Do not run nm if a set of objects is empty -- nm will try to open a.out in this case.
|
|
execute(
|
|
[
|
|
"link",
|
|
"/dump",
|
|
"/symbols",
|
|
keys( %$objects )
|
|
# Running nm once (rather than once per object) improves performance
|
|
# drastically.
|
|
],
|
|
-stdout => \@bulk
|
|
);
|
|
}; # if
|
|
|
|
my $num_re = qr{[0-9A-F]{3,4}};
|
|
my $addr_re = qr{[0-9A-F]{8}};
|
|
my $tag_re = qr{DEBUG|ABS|UNDEF|SECT[0-9A-F]+};
|
|
my $class_re = qr{Static|External|Filename|Label|BeginFunction|EndFunction|WeakExternal|\.bf or\.ef};
|
|
|
|
my $file;
|
|
foreach my $line ( @bulk ) {
|
|
if ( $line =~ m{\ADump of file (.*?)\n\z} ) {
|
|
$file = $1;
|
|
if ( not exists( $objects->{ $file } ) ) {
|
|
die "link reported unknown object file:\n $line\n";
|
|
}; # if
|
|
} elsif ( $line =~ m{\A$num_re } ) {
|
|
if ( not defined( $file ) ) {
|
|
die "link reported symbol of unknown object file:\n $line\n";
|
|
}; # if
|
|
if ( $line !~ m{\A$num_re $addr_re ($tag_re)\s+notype(?: \(\))?\s+($class_re)\s+\| (.*?)\n\z} ) {
|
|
die "Cannot parse link output, line:\n $line\n";
|
|
}; # if
|
|
my ( $tag, $class, $symbol ) = ( $1, $2, $3 );
|
|
# link.exe /dump sometimes prints comments for symbols, e. g.:
|
|
# ".?0_memcopyA ([Entry] ?0_memcopyA)", or "??_C@_01A@r?$AA@ (`string')".
|
|
# Strip these comments.
|
|
$symbol =~ s{ \(.*\)\z}{};
|
|
if ( $class eq "External" ) {
|
|
if ( $tag eq "UNDEF" ) { # Symbol not defined.
|
|
push( @{ $objects->{ $file }->{ undefined } }, $symbol );
|
|
} else { # Symbol defined.
|
|
push( @{ $objects->{ $file }->{ defined } }, $symbol );
|
|
}; # if
|
|
}; # if
|
|
} else {
|
|
# Ignore all other lines.
|
|
}; # if
|
|
}; # foreach
|
|
|
|
return undef;
|
|
|
|
}; # sub _load_symbols_link
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Name:
|
|
# symbols -- Construct set of symbols with specified tag in the specified set of objects.
|
|
# Synopsis:
|
|
# my $symbols = defined_symbols( $objects, $tag );
|
|
# Arguments:
|
|
# $objects (in) -- Set of objects.
|
|
# $tag (in) -- A tag, "defined" or "undefined".
|
|
# Returns:
|
|
# Set of symbols with the specified tag.
|
|
#
|
|
sub symbols($$) {
|
|
|
|
my $objects = shift( @_ );
|
|
my $tag = shift( @_ );
|
|
|
|
my $symbols = {};
|
|
|
|
foreach my $object ( keys( %$objects ) ) {
|
|
foreach my $symbol ( @{ $objects->{ $object }->{ $tag } } ) {
|
|
push( @{ $symbols->{ $symbol } }, $object );
|
|
}; # foreach $symbol
|
|
}; # foreach $object
|
|
|
|
return $symbols;
|
|
|
|
}; # sub symbols
|
|
|
|
sub defined_symbols($) {
|
|
|
|
my $objects = shift( @_ );
|
|
my $defined = symbols( $objects, "defined" );
|
|
return $defined;
|
|
|
|
}; # sub defined_symbols
|
|
|
|
sub undefined_symbols($) {
|
|
|
|
my $objects = shift( @_ );
|
|
my $defined = symbols( $objects, "defined" );
|
|
my $undefined = symbols( $objects, "undefined" );
|
|
foreach my $symbol ( keys( %$defined ) ) {
|
|
delete( $undefined->{ $symbol } );
|
|
}; # foreach symbol
|
|
return $undefined;
|
|
|
|
}; # sub undefined_symbols
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Name:
|
|
# _required_extra_objects -- Select a subset of extra objects required to resolve undefined
|
|
# symbols in a set of objects. It is a helper sub for required_extra_objects().
|
|
# Synopsis:
|
|
# my $required = _required_extra_objects( $objects, $extra, $symbols );
|
|
# Arguments:
|
|
# $objects (in) -- A set of objects to be searched for undefined symbols.
|
|
# $extra (in) -- A set of extra objects to be searched for defined symbols to resolve undefined
|
|
# symbols in objects.
|
|
# $symbols (in/out) -- Set of symbols defined in the set of external objects. At the first call
|
|
# it should consist of all the symbols defined in all the extra objects. Symbols defined in
|
|
# the selected subset of extra objects are removed from set of defined symbols, because
|
|
# they are out of interest for subsequent calls.
|
|
# Returns:
|
|
# A subset of extra objects required by the specified set of objects.
|
|
#
|
|
sub _required_extra_objects($$$$) {
|
|
|
|
my $objects = shift( @_ );
|
|
my $extra = shift( @_ );
|
|
my $symbols = shift( @_ );
|
|
my $dump = shift( @_ );
|
|
|
|
my $required = {};
|
|
|
|
if ( $dump > 0 ) {
|
|
STDERR->print( "Required extra objects:\n" );
|
|
}; # if
|
|
foreach my $object ( keys( %$objects ) ) {
|
|
foreach my $symbol ( @{ $objects->{ $object }->{ undefined } } ) {
|
|
if ( exists( $symbols->{ $symbol } ) ) {
|
|
# Add all objects where the symbol is defined to the required objects.
|
|
foreach my $req_obj ( @{ $symbols->{ $symbol } } ) {
|
|
if ( $dump > 0 ) {
|
|
STDERR->print( " $req_obj\n" );
|
|
if ( $dump > 1 ) {
|
|
STDERR->print( " by $object\n" );
|
|
STDERR->print( " due to $symbol\n" );
|
|
}; # if
|
|
}; # if
|
|
$required->{ $req_obj } = $extra->{ $req_obj };
|
|
}; # foreach $req_obj
|
|
# Delete the symbol from list of defined symbols.
|
|
delete( $symbols->{ $symbol } );
|
|
}; # if
|
|
}; # foreach $symbol
|
|
}; # foreach $object
|
|
|
|
return $required;
|
|
|
|
}; # sub _required_extra_objects
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Name:
|
|
# required_extra_objects -- Select a subset of extra objects required to resolve undefined
|
|
# symbols in a set of base objects and selected extra objects.
|
|
# Synopsis:
|
|
# my $required = required_extra_objects( $base, $extra );
|
|
# Arguments:
|
|
# $base (in/out) -- A set of base objects to be searched for undefined symbols. On enter, it is
|
|
# expected that top-level hash has filled with object names only. On exit, it is completely
|
|
# fulfilled with lists of symbols defined and/or referenced in each object file.
|
|
# $extra (in/out) -- A set of extra objects to be searched for defined symbols required to
|
|
# resolve undefined symbols in a set of base objects. Usage is similar to base objects.
|
|
# Returns:
|
|
# A subset of extra object files.
|
|
#
|
|
sub required_extra_objects($$$) {
|
|
|
|
my $base = shift( @_ );
|
|
my $extra = shift( @_ );
|
|
my $dump = shift( @_ );
|
|
|
|
# Load symbols for each object.
|
|
load_symbols( $base );
|
|
load_symbols( $extra );
|
|
if ( $dump ) {
|
|
dump_objects( "Base objects:", $base, $dump );
|
|
dump_objects( "Extra objects:", $extra, $dump );
|
|
}; # if
|
|
|
|
# Collect symbols defined in extra objects.
|
|
my $symbols = defined_symbols( $extra );
|
|
|
|
my $required = {};
|
|
# Select extra objects required by base objects.
|
|
my $delta = _required_extra_objects( $base, $extra, $symbols, $dump );
|
|
while ( %$delta ) {
|
|
%$required = ( %$required, %$delta );
|
|
# Probably, just selected objects require some more objects.
|
|
$delta = _required_extra_objects( $delta, $extra, $symbols, $dump );
|
|
}; # while
|
|
|
|
if ( $dump ) {
|
|
my $base_undefined = undefined_symbols( $base );
|
|
my $req_undefined = undefined_symbols( $required );
|
|
dump_symbols( "Symbols undefined in base objects:", $base_undefined, $dump );
|
|
dump_symbols( "Symbols undefined in required objects:", $req_undefined, $dump );
|
|
}; # if
|
|
|
|
return $required;
|
|
|
|
}; # sub required_extra_objects
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Name:
|
|
# copy_objects -- Copy (and optionally edit) object files to specified directory.
|
|
# Synopsis:
|
|
# copy_objects( $objects, $target, $prefix, @symbols );
|
|
# Arguments:
|
|
# $objects (in) -- A set of object files.
|
|
# $target (in) -- A name of target directory. Directory must exist.
|
|
# $prefix (in) -- A prefix to add to all the symbols listed in @symbols. If prefix is undefined,
|
|
# object files are just copied.
|
|
# @symbols (in) -- List of symbol names to be renamed.
|
|
# Returns:
|
|
# None.
|
|
#
|
|
sub copy_objects($$;$\@) {
|
|
|
|
my $objects = shift( @_ );
|
|
my $target = shift( @_ );
|
|
my $prefix = shift( @_ );
|
|
my $symbols = shift( @_ );
|
|
my @redefine;
|
|
my @redefine_;
|
|
my $syms_file = "__kmp_sym_pairs.log";
|
|
|
|
if ( not -e $target ) {
|
|
die "\"$target\" directory does not exist\n";
|
|
}; # if
|
|
if ( not -d $target ) {
|
|
die "\"$target\" is not a directory\n";
|
|
}; # if
|
|
|
|
if ( defined( $prefix ) and @$symbols ) {
|
|
my %a = map ( ( "$_ $prefix$_" => 1 ), @$symbols );
|
|
@redefine_ = keys( %a );
|
|
}; # if
|
|
foreach my $line ( @redefine_ ) {
|
|
$line =~ s{$prefix(\W+)}{$1$prefix};
|
|
push( @redefine, $line );
|
|
}
|
|
write_file( $syms_file, \@redefine );
|
|
foreach my $src ( sort( keys( %$objects ) ) ) {
|
|
my $dst = cat_file( $target, get_file( $src ) );
|
|
if ( @redefine ) {
|
|
execute( [ "objcopy", "--redefine-syms", $syms_file, $src, $dst ] );
|
|
} else {
|
|
copy_file( $src, $dst, -overwrite => 1 );
|
|
}; # if
|
|
}; # foreach $object
|
|
|
|
}; # sub copy_objects
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Main.
|
|
# --------------------------------------------------------------------------------------------------
|
|
|
|
my $base = {};
|
|
my $extra = {};
|
|
my $switcher = $base;
|
|
my $dump = 0;
|
|
my $print_base;
|
|
my $print_extra;
|
|
my $copy_base;
|
|
my $copy_extra;
|
|
my $prefix;
|
|
|
|
# Parse command line.
|
|
|
|
Getopt::Long::Configure( "permute" );
|
|
get_options(
|
|
Platform::target_options(),
|
|
"base" => sub { $switcher = $base; },
|
|
"extra" => sub { $switcher = $extra; },
|
|
"print-base" => \$print_base,
|
|
"print-extra" => \$print_extra,
|
|
"print-all" => sub { $print_base = 1; $print_extra = 1; },
|
|
"copy-base=s" => \$copy_base,
|
|
"copy-extra=s" => \$copy_extra,
|
|
"copy-all=s" => sub { $copy_base = $_[ 1 ]; $copy_extra = $_[ 1 ]; },
|
|
"dump" => sub { ++ $dump; },
|
|
"prefix=s" => \$prefix,
|
|
"<>" =>
|
|
sub {
|
|
my $arg = $_[ 0 ];
|
|
my @args;
|
|
if ( $^O eq "MSWin32" ) {
|
|
# Windows* OS does not expand wildcards. Do it...
|
|
@args = bsd_glob( $arg );
|
|
} else {
|
|
@args = ( $arg );
|
|
}; # if
|
|
foreach my $object ( @args ) {
|
|
if ( exists( $base->{ $object } ) or exists( $extra->{ $object } ) ) {
|
|
die "Object \"$object\" has already been specified.\n";
|
|
}; # if
|
|
$switcher->{ $object } = { defined => [], undefined => [] };
|
|
}; # foreach
|
|
},
|
|
);
|
|
if ( not %$base ) {
|
|
cmdline_error( "No base objects specified" );
|
|
}; # if
|
|
|
|
if ( $target_os eq "win" ) {
|
|
*load_symbols = \&_load_symbols_link;
|
|
} elsif ( $target_os eq "lin" or $target_os eq "lrb" ) {
|
|
*load_symbols = \&_load_symbols_nm;
|
|
} elsif ( $target_os eq "mac" ) {
|
|
*load_symbols = \&_load_symbols_nm;
|
|
} else {
|
|
runtime_error( "OS \"$target_os\" not supported" );
|
|
}; # if
|
|
|
|
# Do the work.
|
|
|
|
my $required = required_extra_objects( $base, $extra, $dump );
|
|
if ( $print_base ) {
|
|
print( map( "$_\n", sort( keys( %$base ) ) ) );
|
|
}; # if
|
|
if ( $print_extra ) {
|
|
print( map( "$_\n", sort( keys( %$required ) ) ) );
|
|
}; # if
|
|
my @symbols;
|
|
if ( defined( $prefix ) ) {
|
|
foreach my $object ( sort( keys( %$required ) ) ) {
|
|
push( @symbols, @{ $required->{ $object }->{ defined } } );
|
|
}; # foreach $objects
|
|
}; # if
|
|
if ( $copy_base ) {
|
|
copy_objects( $base, $copy_base, $prefix, @symbols );
|
|
}; # if
|
|
if ( $copy_extra ) {
|
|
copy_objects( $required, $copy_extra, $prefix, @symbols );
|
|
}; # if
|
|
|
|
exit( 0 );
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
B<required-objects.pl> -- Select a required extra object files.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
B<required-objects.pl> I<option>... [--base] I<file>... --extra I<file>...
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<required-objects.pl> works with two sets of object files -- a set of I<base> objects
|
|
and a set of I<extra> objects, and selects those extra objects which are required for resolving
|
|
undefined symbols in base objects I<and> selected extra objects.
|
|
|
|
Selected object files may be copied to specified location or their names may be printed to stdout,
|
|
a name per line. Additionally, symbols defined in selected extra objects may be renamed.
|
|
|
|
Depending on OS, different external tools may be used. For example, B<required-objects.pl> uses
|
|
F<link.exe> on "win" and F<nm> on "lin" and "mac" OSes. Normally OS is autodetected, but
|
|
detection can be overrided with B<--os> option. It may be helpful in cross-build environments.
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over
|
|
|
|
=item B<--base>
|
|
|
|
The list of base objects follows this option.
|
|
|
|
=item B<--extra>
|
|
|
|
List of extra objects follows this option.
|
|
|
|
=item B<--print-all>
|
|
|
|
Print list of base objects and list of required extra objects.
|
|
|
|
=item B<--print-base>
|
|
|
|
Print list of base objects.
|
|
|
|
=item B<--print-extra>
|
|
|
|
Print list of selected extra objects.
|
|
|
|
=item B<--copy-all=>I<dir>
|
|
|
|
Copy all base and selected extra objects to specified directory. The directory must exist. Existing
|
|
files are overwritten.
|
|
|
|
=item B<--copy-base=>I<dir>
|
|
|
|
Copy all base objects to specified directory.
|
|
|
|
=item B<--copy-extra=>I<dir>
|
|
|
|
Copy selected extra objects to specified directory.
|
|
|
|
=item B<--prefix=>I<str>
|
|
|
|
If prefix is specified, copied object files are edited -- symbols defined in selected extra
|
|
object files are renamed (in all the copied object files) by adding this prefix.
|
|
|
|
F<objcopy> program should be available for performing this operation.
|
|
|
|
=item B<--os=>I<str>
|
|
|
|
Specify OS name. By default OS is autodetected.
|
|
|
|
Depending on OS, B<required-objects.pl> uses different external tools.
|
|
|
|
=item B<--help>
|
|
|
|
Print short help message and exit.
|
|
|
|
=item B<--doc>
|
|
|
|
=item B<--manual>
|
|
|
|
Print full documentation and exit.
|
|
|
|
=item B<--version>
|
|
|
|
Print version and exit.
|
|
|
|
=back
|
|
|
|
=head1 ARGUMENTS
|
|
|
|
=over
|
|
|
|
=item I<file>
|
|
|
|
A name of object file.
|
|
|
|
=back
|
|
|
|
=head1 EXAMPLES
|
|
|
|
$ required-objects.pl --base obj/*.o --extra ../lib/obj/*.o --print-extra > required.lst
|
|
$ ar cr libx.a obj/*.o $(cat required.lst)
|
|
|
|
$ required-objects.pl --base internal/*.o --extra external/*.o --prefix=__xyz_ --copy-all=obj
|
|
$ ar cr xyz.a obj/*.o
|
|
|
|
=cut
|
|
|
|
# end of file #
|
|
|