#!/usr/bin/perl # $FabBSD$ # $OpenBSD: libtool,v 1.19 2008/05/01 22:02:32 steven Exp $ # Copyright (c) 2007 Steven Mestdagh # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use strict; use warnings; use Cwd qw(getcwd abs_path); use File::Basename; use File::Glob ':glob'; use File::Path; use Getopt::Long; use Getopt::Std; use subs qw( help handle_special_chars notyet perform parse_version_info guess_implicit_mode create_symlinks write_lo_file write_la_file write_prog_wrapper is_prog_wrapper parse_file resolve_la parse_linkargs find_la find_lib process_deplibs reverse_zap_duplicates linkcmd generate_objlist get_search_dirs ); use constant { OBJECT => 0, LIBRARY => 1, PROGRAM => 2, }; my $version = '1.5.22'; # pretend to be this version of libtool my @no_shared_archs = qw(m88k vax sh); # XXX my $machine_arch = `machine -a`; my $machine_arch = 'amd64'; (my $gnu_arch = $machine_arch) =~ s/amd64/x86_64/; my @valid_modes = qw(clean compile execute finish install link uninstall); my @valid_src = qw(asm c cc cpp cxx f s); my $cwd = getcwd(); my $ltdir = '.libs'; my $picflags = '-fPIC -DPIC'; my $sharedflag = '-shared'; my $instlibdir = '/usr/local/lib'; my @libsearchdirs; $instlibdir = $ENV{'LIBDIR'} if defined $ENV{'LIBDIR'}; my $mode; my $D = 0; # debug flag my $verbose = 1; my $dry = 0; # dry-run my %opts; # options passed to libtool my @Ropts; # -R options my @tags; # list of --tag options passed to libtool my @deplibs; # list of dependent libraries (both -L and -l flags) my %libs; # libraries my %libstofind; my @orderedlibs; # ordered library keys (may contain duplicates) my %dirs; # paths to find libraries my %file_parsed; # which files have been parsed my $res_level = 0; # resolve level my $parse_level = 0; # parse recursion level my $performed = 0; # number of commands executed via system() # build static/shared objects? my $static = 1; my $shared = 0; my $noshared = 0; if (grep { $_ eq $machine_arch } @no_shared_archs) { $noshared = 1; } # put a priority in the dir hash # always look here $dirs{'/usr/lib'} = 3; my $gp = new Getopt::Long::Parser; # require_order so we stop parsing at the first non-option or argument, # instead of parsing the whole ARGV. $gp->configure( 'no_ignore_case', 'pass_through', 'no_auto_abbrev', 'require_order' ); $gp->getoptions('config' => \&config, 'debug' => \$D, 'dry-run' => sub { $dry = 1; }, 'features' => \&features, 'finish' => sub { $mode = 'finish'; }, 'help' => \&help, # does not return 'mode=s{1}' => \$mode, 'quiet' => sub { $verbose = 0; }, 'silent' => sub { $verbose = 0; }, 'tag=s{1}' => \@tags, 'version' => sub { print "$version\n" ; exit(0); }, ); # what are we going to run (cc, c++, ...) my $ltprog = shift @ARGV or die "no libtool command\n"; # check mode and guess it if needed if (!($mode && grep { $_ eq $mode } @valid_modes)) { $mode = guess_implicit_mode(); if ($mode) { print "implicit mode: $mode\n" if $D; } else { die "MODE must be one of:\n@valid_modes\n"; } } # from here, options may be intermixed with arguments $gp->configure('permute'); print "\n###\n### WARNING: ", "This version of libtool is EXPERIMENTAL. ###\n###\n\n"; if ($mode eq 'compile') { # deal with multi-arg ltprog while (@ARGV && $ltprog !~ m/(cc|c\+\+|f77|g77|asm)$/) { my $arg = shift @ARGV; $ltprog .= " $arg"; } $gp->getoptions('o=s' => \$opts{'o'}, 'prefer-pic' => \$opts{'prefer-pic'}, 'prefer-non-pic'=> \$opts{'prefer-non-pic'}, 'static' => \$opts{'static'}, ); # XXX options ignored: -prefer-pic and -prefer-non-pic my $cmd; my $pic = 0; my $nonpic = 1; # assume we need to build pic objects $pic = 1 if (!$noshared); $nonpic = 0 if ($pic && grep { $_ eq 'disable-static' } @tags); $pic = 0 if ($nonpic && grep { $_ eq 'disable-shared' } @tags); $nonpic = 1 if ($opts{'static'}); my $outfile = $opts{'o'}; my $srcfile; # XXX check whether -c flag is present and if not, die? if (!$outfile) { # XXX sometimes no -o flag is present and we need another way my $srcre = join '|', @valid_src; my $found = 0; foreach my $a (@ARGV) { if ($a =~ m/\.($srcre)$/i) { $srcfile = $a; $found = 1; last; } } $found or die "cannot find source file in command\n"; # the output file ends up in the current directory $outfile = basename $srcfile; $outfile =~ s/\.($srcre)$//i; $outfile .= '.lo'; } print "srcfile = $srcfile\n" if ($srcfile && $D); # put output file in . if no -o flag is given my $odir = '.'; # fix extension if needed $outfile =~ s/\.o$/.lo/; $odir = dirname $outfile if ($opts{'o'}); my $ofile = basename $outfile; print "outfile = $odir/$ofile\n" if $D; (my $nonpicobj = $ofile) =~ s/\.lo$/.o/; my $picobj = "$ltdir/$nonpicobj"; handle_special_chars(\@ARGV); mkdir "$odir/$ltdir" if (! -d "$odir/$ltdir"); if ($pic) { $cmd = $ltprog; $cmd .= " @ARGV" if (@ARGV); $cmd .= " $picflags"; $cmd .= " -o "; $cmd .= ($odir eq '.') ? '' : $odir.'/'; $cmd .= $picobj; perform($cmd); } if ($nonpic) { $cmd = $ltprog; $cmd .= " @ARGV" if (@ARGV); $cmd .= " -o "; $cmd .= ($odir eq '.') ? '' : $odir.'/'; $cmd .= $nonpicobj; perform($cmd); } write_lo_file($outfile, ($pic) ? $picobj : '', ($nonpic) ? $nonpicobj : ''); } elsif ($mode eq 'install') { # deal with multi-arg ltprog (e.g. /bin/sh install-sh ...) while (@ARGV && $ltprog !~ m/(install([.-]sh)?|cp)$/) { my $arg = shift @ARGV; $ltprog .= " $arg"; } # we just parse the options in order to find the actual arguments my @argvcopy = @ARGV; my %install_opts; if ($ltprog =~ m/install([.-]sh)?$/) { getopts('BbCcdf:g:m:o:pSs', \%install_opts); if (@ARGV < 2 && (!defined $install_opts{'d'} && @ARGV == 1)) { die "wrong number of arguments for install\n"; } } elsif ($ltprog =~ m/cp$/) { getopts('HLPRfipr', \%install_opts); if (@ARGV < 2) { die "wrong number of arguments for install\n"; } } else { die "unsupported install program $ltprog\n"; } my @instopts = @argvcopy[0 .. (@argvcopy - @ARGV - 1)]; my $dst = pop @ARGV; # assume dst is a directory my $dstdir = $dst; my @src = @ARGV; # dst is not a directory, i.e. a file if (! -d $dst) { if (@src > 1) { # XXX not really libtool's task to check this die "multiple source files combined with file destination"; } else { $dstdir = dirname $dst; } } my %toinstall; my %tosymlink; # for libraries with a -release in their name foreach my $s (@src) { my $dstfile = basename $s; # resolve symbolic links, so we don't try to test later # whether the symlink is a program wrapper etc. if (-l $s) { $s = readlink($s) or die "Cannot readlink $s"; } my $srcdir = dirname $s; my $srcfile = basename $s; print "srcdir = $srcdir\nsrcfile = $srcfile\n" if $D; print "dstdir = $dstdir\ndstfile = $dstfile\n" if $D; if ($srcfile =~ m/^\S+\.la$/) { # we are installing a .la library if ($ltprog =~ m/install([.-]sh)?$/) { push @instopts, '-m 644'; } my %lainfo; parse_file($s, \%lainfo); # replace info where needed when installing the .la file my $sharedlib = $lainfo{'dlname'}; my $staticlib = $lainfo{'old_library'}; my @libnames = split /\s+/, $lainfo{'library_names'}; my $laipath = "$srcdir/$ltdir/$srcfile".'i'; $toinstall{"$srcdir/$ltdir/$staticlib"} = "$dstdir/$staticlib" if ($staticlib); $toinstall{"$srcdir/$ltdir/$sharedlib"} = "$dstdir/$sharedlib" if ($sharedlib); $toinstall{"$laipath"} = "$dstdir/$dstfile" if ($sharedlib); foreach my $n (@libnames) { $tosymlink{$n} = $sharedlib if ($n ne $sharedlib); } } elsif (-f "$srcdir/$ltdir/$srcfile" && is_prog_wrapper($s)) { $toinstall{"$srcdir/$ltdir/$srcfile"} = $dst; } else { $toinstall{$s} = $dst; } } while (my ($s, $d) = each %toinstall) { my @realinstopts = @instopts; # do not try to strip .la files if ($s =~ m/\.la$/) { map { $_ = '' if $_ eq '-s' } @realinstopts; } perform("$ltprog @realinstopts $s $d"); } while (my ($d, $s) = each %tosymlink) { perform("cd $dstdir && rm -f $d && ln -s $s $d"); } if (defined $install_opts{'d'}) { perform("$ltprog @instopts @ARGV"); } } elsif ($mode eq 'link') { my $cmd; $gp->getoptions('all-static' => \$opts{'all-static'}, 'avoid-version' => \$opts{'avoid-version'}, 'dlopen=s{1}' => \$opts{'dlopen'}, 'dlpreopen=s{1}' => \$opts{'dlpreopen'}, 'export-dynamic' => \$opts{'export-dynamic'}, 'export-symbols=s' => \$opts{'export-symbols'}, 'export-symbols-regex=s'=> \$opts{'export-symbols-regex'}, 'module' => \$opts{'module'}, 'no-fast-install' => \$opts{'no-fast-install'}, 'no-install' => \$opts{'no-install'}, 'no-undefined' => \$opts{'no-undefined'}, 'o=s' => \$opts{'o'}, 'objectlist=s' => \$opts{'objectlist'}, 'precious-files-regex=s'=> \$opts{'precious-files-regex'}, 'prefer-pic' => \$opts{'prefer-pic'}, 'prefer-non-pic' => \$opts{'prefer-non-pic'}, 'release=s' => \$opts{'release'}, 'rpath=s' => \$opts{'rpath'}, 'R=s' => \@Ropts, 'shrext=s' => \$opts{'shrext'}, 'static' => \$opts{'static'}, 'thread-safe' => \$opts{'thread-safe'}, 'version-info=s{1}' => \$opts{'version-info'}, 'version_info=s{1}' => \$opts{'version-info'}, 'version-number=s{1}' => \$opts{'version-info'}, ); # XXX options ignored: dlopen, dlpreopen, export-dynamic, # export-symbols, export-symbols-regex, no-fast-install, # no-install, no-undefined, objectlist, precious-files-regex, # shrext, thread-safe, prefer-pic, prefer-non-pic @libsearchdirs = get_search_dirs(); my $outfile = $opts{'o'}; if (!$outfile) { die "no output file given.\n"; } print "outfile = $outfile\n" if $D; my $odir = dirname $outfile; my $absodir = abs_path($odir); my $ofile = basename $outfile; # what are we linking? my $linkmode = PROGRAM; if ($ofile =~ m/\.la$/) { $linkmode = LIBRARY; } elsif ($ofile =~ s/\.a$/.la/) { $outfile =~ s/\.a$/.la/; $linkmode = LIBRARY; $noshared = 1; $static = 1; # XXX improve! } print "linkmode: $linkmode\n" if $D; handle_special_chars(\@ARGV); my $argvstring = join ' ', @ARGV; $argvstring = parse_linkargs($argvstring, 1); @ARGV = split /\s+/, $argvstring; print "deplibs = @deplibs\n" if $D; # eat multiple version-info arguments, we only accept the first. map { $_ = '' if ($_ =~ m/\d+:\d+:\d+/); } @ARGV; my @objs; my @sobjs; my $allpicobj = generate_objlist(\@objs, \@sobjs); print "objs = @objs\n" if $D; print "sobjs = @sobjs\n" if $D; if ($linkmode == PROGRAM) { # XXX give higher priority to dirs of not installed libs # XXX no static linking yet here my @tmpcmd = linkcmds($ofile, $ofile, $odir, PROGRAM, 1, \@objs); $cmd = $tmpcmd[0]; perform($cmd); write_prog_wrapper($outfile); chmod 0755, $outfile; } elsif ($linkmode == LIBRARY) { (my $libname = $ofile) =~ s/\.la$//; # remove extension my $staticlib = $libname.'.a'; my $sharedlib = $libname.'.so'; my $sharedlib_symlink; # XXX how is the creation of a shared library switched on? # XXX to do: deal with -rpath correctly $shared = 1 if ($opts{'version-info'} || $opts{'avoid-version'} || $opts{'module'} || $opts{'rpath'} ); if ($opts{'static'}) { $shared = 0; $static = 1; } $shared = 0 if $noshared; my $sover = '0.0'; # environment overrides -version-info (my $envlibname = $libname) =~ s/[.+-]/_/g; my ($current, $revision, $age) = (0, 0, 0); if ($ENV{"${envlibname}_ltversion"}) { $sover = $ENV{"${envlibname}_ltversion"}; ($current, $revision) = split /\./, $sover; $age = 0; } elsif ($opts{'version-info'}) { ($current, $revision, $age) = parse_version_info($opts{'version-info'}); $sover = "$current.$revision"; } if ($opts{'release'}) { $sharedlib_symlink = $sharedlib; $sharedlib = $libname.'-'.$opts{'release'}.'.so'; } if (!$opts{'avoid-version'}) { $sharedlib .= ".$sover"; if ($opts{'release'}) { $sharedlib_symlink .= ".$sover"; } } # XXX add error condition somewhere... $static = 0 if ($shared && grep { $_ eq 'disable-static' } @tags); $shared = 0 if ($static && grep { $_ eq 'disable-shared' } @tags); print "SHARED: $shared\nSTATIC: $static\n" if $D; my %lainfo; if ($shared) { $lainfo{'dlname'} = $sharedlib; $lainfo{'library_names'} = $sharedlib; $lainfo{'library_names'} .= " $sharedlib_symlink" if ($opts{'release'}); perform(linkcmds($ofile, $sharedlib, $odir, LIBRARY, 1, \@sobjs)); print "sharedlib: $sharedlib\n" if $D; $lainfo{'current'} = $current; $lainfo{'revision'} = $revision; $lainfo{'age'} = $age; } if ($static) { $lainfo{'old_library'} = $staticlib; perform(linkcmds($ofile, $staticlib, $odir, LIBRARY, 0, ($allpicobj) ? \@sobjs : \@objs)); print "staticlib: $staticlib\n" if $D; } $lainfo{'installed'} = 'no'; $lainfo{'shouldnotlink'} = $opts{'module'} ? 'yes' : 'no'; my @Rflags = @Ropts; map { $_ = "-R$_" } @Rflags; my $deplibstring = join ' ', @deplibs; $deplibstring = "@Rflags $deplibstring" if (@Rflags); @deplibs = split /\s+/, $deplibstring; my @finaldeplibs = reverse_zap_duplicates(@deplibs); $deplibstring = join ' ', @finaldeplibs; $lainfo{'dependency_libs'} = $deplibstring; if ($opts{'rpath'}) { $lainfo{'libdir'} = $opts{'rpath'}; } else { # XXX sensible default? $lainfo{'libdir'} = $instlibdir; } write_la_file($outfile, $ofile, \%lainfo); perform("cd $odir/$ltdir && rm -f $ofile && ln -s ../$ofile $ofile"); if ($shared) { my $lai = "$odir/$ltdir/$ofile".'i'; $lainfo{'dependency_libs'} = process_deplibs($deplibstring); $lainfo{'installed'} = 'yes'; # write .lai file (.la file that will be installed) write_la_file($lai, $ofile, \%lainfo); } } } elsif ($mode eq 'finish' || $mode eq 'clean' || $mode eq 'uninstall') { # don't do anything exit 0; } elsif ($mode eq 'execute') { # XXX check whether this is right perform("$ltprog @ARGV"); } else { die "MODE=$mode not implemented yet.\n"; } if ($performed == 0) { die "no commands to execute.\n" } ########################################################################### sub help { print <', $filename) or die "cannot write $filename: $!\n"; print "creating $filename\n" if ($verbose || $D); print $lo <{'dlname'} || ''; my $staticlibname = $lainfo->{'old_library'} || ''; my $librarynames = $lainfo->{'library_names'} || ''; my $deplibs = $lainfo->{'dependency_libs'}; my ($current, $revision, $age) = ('', '', ''); $current = $lainfo->{'current'} if (defined $lainfo->{'current'}); $revision = $lainfo->{'revision'} if (defined $lainfo->{'revision'}); $age = $lainfo->{'age'} if (defined $lainfo->{'age'}); my $installed = $lainfo->{'installed'}; my $shouldnotlink = $lainfo->{'shouldnotlink'}; my $libdir = $lainfo->{'libdir'}; open(my $la, '>', $filename) or die "cannot write $filename: $!\n"; print "creating $filename\n" if ($verbose || $D); print $la <', $program) or die "cannot write $program: $!\n"; print $pw <&2 exit 1 fi EOF ; } sub is_prog_wrapper { my $program = shift; open(my $pw, '<', $program) or die "cannot open $program: $!\n"; return eval(grep { m/wrapper\sfor/ } <$pw>); } sub parse_file { my $filename = shift; my $info = shift; print "parsing $filename\n" if $D; open(my $fh, '<', $filename) or die "cannot read $filename: $!\n"; while (<$fh>) { next if /^#/; next if /^\s*$/; if (m/^(\S+)='(.*)'$/) { $info->{$1} = $2; } elsif (m/^(\S+)=(\S+)$/) { $info->{$1} = $2; } } } # resolve .la files until a level with empty dependency_libs is reached. sub resolve_la { my $argstring = shift; my @args = split /\s+/, $argstring; print "resolve level: $res_level\n" if $D; foreach my $a (@args) { next if ($a !~ m/(.*)\.la$/); my %lainfo; if (!exists $file_parsed{$a} || !$file_parsed{$a}) { parse_file($a, \%lainfo); $file_parsed{$a} = 1; if (exists $lainfo{'dependency_libs'}) { $res_level++; $a = $a . ' ' . resolve_la($lainfo{'dependency_libs'}); $res_level--; push @deplibs, $lainfo{'dependency_libs'}; } } else { $a = ''; next; } } return join ' ', @args; } # parse link flags and arguments # eliminate all -L and -l flags in the argument string and add the # corresponding directories and library names to the dirs/libs hashes. # fill deplibs, to be taken up as dependencies in the resulting .la file... # set up a hash for library files which haven't been found yet. # deplibs are formed by collecting the original -L/-l flags, plus # any .la files passed on the command line, EXCEPT when the .la file # does not point to a shared library. # pass 1 (la == 1) # -Lfoo, -lfoo, foo.a, foo.la # recursively find .la files corresponding to -l flags; if there is no .la # file, just inspect the library file itself for any dependencies. # pass 2 (la == 0) # -Lfoo, -lfoo, foo.a # no recursion in pass 2 # fill orderedlibs array, which is the sequence after resolving all .la sub parse_linkargs { my $argstring = shift; my $la = shift; my @args = split /\s+/, $argstring; print "parse_linkargs level: $parse_level\n" if $D; my $seen_pthread = 0; foreach my $a (@args) { if ($a eq '-lc') { # don't link explicitly with libc (just remove -lc) $a = ''; } elsif ($a eq '-pthread' && !$seen_pthread) { # XXX special treatment since it's not a -l flag push @deplibs, $a; $seen_pthread = 1; } elsif ($a && $a =~ m/^-L(.*)/) { if (!exists $dirs{$1}) { $dirs{$1} = 1; push @deplibs, $a; } $a = ''; } elsif ($a && $a =~ m/^-R(.*)/) { # -R options coming from .la resolution # those from @ARGV are in @Ropts $a = "-Wl,-rpath,$1"; } elsif ($a && $a =~ m/^-l(.*)/) { my $lstring = ''; my $key = $1; if (!exists $libstofind{$key}) { $libstofind{$key} = 1; if ($la) { my $lafile = find_la($key); if ($lafile) { push @deplibs, $lafile; $a = $lafile; next; } else { my $libpath = find_lib($key, @libsearchdirs); # avoid searching again later $libs{$key} = $libpath if ($libpath); my @deps = inspect_lib($libpath); # push @deplibs, @deps; foreach my $d (@deps) { my $k = basename $d; $k =~ s/^(\S+)\.so.*$/$1/; $k =~ s/^lib//; $lstring .= "-l$k "; } push @deplibs, $a; } } } if ($la) { $parse_level++; $a .= ' '; $a .= parse_linkargs($lstring, $la) if ($lstring); $parse_level--; } else { push @orderedlibs, $key; $a = ''; } } elsif ($a && $a =~ m/(\S+\/)*(\S+)\.a$/) { my $key = $2; $key =~ s/^lib//; $libs{$key} = $a; if (!$la) { push @orderedlibs, $key; $a = ''; } } elsif ($a && $a =~ m/(\S+\/)*(\S+)\.la$/) { my $key = $2; $key =~ s/^lib//; my %lainfo; my $d = abs_path(dirname($a)); $dirs{$d} = 1; my $fulla = abs_path($a); parse_file($fulla, \%lainfo); my $dlname = $lainfo{'dlname'}; my $oldlib = $lainfo{'old_library'}; if ($d !~ m/\Q$ltdir\E$/ && $lainfo{'installed'} eq 'no') { $d .= "/$ltdir"; } if ($dlname && $la) { push @deplibs, $fulla; } # the following should happen only in pass 2 next if ($la); push @orderedlibs, $key; $a = ''; # get the name we need (this may include a -release) if (!$dlname && !$oldlib) { die "neither static nor shared library found in $a\n"; } # XXX in some cases there are multiple libs with the same name # so probably need to use a different key if ($dlname eq '') { # static $libs{$key} = "$d/$oldlib"; } else { # shared $libs{$key} = "$d/$dlname"; } print "\$libs{$key} = ", $libs{$key}, "\n" if $D; } } return join ' ', @args; } # find .la file associated with a -llib flag # XXX pick the right one if multiple are found! sub find_la { my $l = shift; # sort dir search order by priority # XXX not fully correct yet my @dirs = sort { $dirs{$b} <=> $dirs{$a} } keys %dirs; print "searching .la for $l ...\n" if $D; foreach my $d (@dirs) { print " ... in $d\n" if $D; foreach my $la_candidate ("$d/lib$l.la", "$d/$l.a") { if (-f $la_candidate) { return $la_candidate; } } } print ".la for $l not found!\n" if $D; return 0; } # find actual library filename # XXX pick the right one if multiple are found! sub find_lib { my $libtofind = shift; my @ldconfigdirs = @_; # search there last my $libfile = 0; my @globbedlib; # sort dir search order by priority # XXX not fully correct yet my @dirs = sort { $dirs{$b} <=> $dirs{$a} } keys %dirs; push @dirs, @ldconfigdirs; print "searching for $libtofind ...\n" if $D; foreach my $d (@dirs) { my $sd = $d; # search in .libs when priority is high $sd = "$d/$ltdir" if (exists $dirs{$d} && $dirs{$d} > 3); print " ... in $d\n" if $D; @globbedlib = glob <$sd/lib$libtofind.so.*>; if ($globbedlib[0]) { print "found $libtofind in $sd\n" if $D; $libfile = $globbedlib[0]; last; } else { # XXX find static library instead? if (-f "$sd/lib$libtofind.a") { print "found static $libtofind in $sd\n" if $D; $libfile = "$sd/lib$libtofind.a"; last; } } } print "$libtofind not found!\n" if (!$libfile); return $libfile; } # give a list of library dependencies found in the actual shared library sub inspect_lib { my $filename = shift; my @deps; print "inspecting $filename for library dependencies...\n" if $D; open(my $fh, '-|', "objdump -p $filename"); while (<$fh>) { if (m/\s+NEEDED\s+(\S+)\s*$/) { push @deps, $1; } } print "found ", (@deps == 0) ? 'no ' : '', "deps for $filename\n@deps\n" if $D; return @deps; } # prepare dependency_libs information for the .la file which is installed # i.e. remove any .libs directories and use the final libdir for all the # .la files sub process_deplibs { my $deplibline = shift; my @linkflags = split /\s+/, $deplibline; my %la_in_ldpath; foreach my $lf (@linkflags) { if ($lf =~ m/-L\S+\Q$ltdir\E$/) { $lf = ''; } elsif ($lf =~ m/\/\S+\/(\S+\.la)/) { my $lafile = $1; my %lainfo; parse_file($lf, \%lainfo); $lf = $lainfo{'libdir'}.'/'.$lafile; } } return join ' ', @linkflags; } # construct linker command (list) for either libraries or programs sub linkcmds { my $la = shift; my $fname = shift; my $odir = shift; my $lmode = shift; # LIBRARY or PROGRAM my $shared = shift; my $objs = shift; print "creating link command for ", ($lmode == PROGRAM) ? "program" : "library", " (linked ", ($shared) ? "dynam" : "stat", "ically)\n" if $D; my @libflags; my @cmdlist; my $cmd = ''; my $dst = ($odir eq '.') ? "$ltdir/$fname" : "$odir/$ltdir/$fname"; mkdir "$odir/$ltdir" if (! -d "$odir/$ltdir"); my @argvcopy = @ARGV; my $argvstring = join ' ', @argvcopy; $argvstring = resolve_la($argvstring); @orderedlibs = (); $argvstring = parse_linkargs($argvstring, 0); @argvcopy = split /\s+/, $argvstring; print "orderedlibs = @orderedlibs\n" if $D; my @finalorderedlibs = reverse_zap_duplicates(@orderedlibs); print "final orderedlibs = @finalorderedlibs\n" if $D; # static linking if (!$shared) { my @libfiles = values %libs; if ($D) { my @dirval = keys %dirs; my @libval = keys %libs; print "dirs: @dirval\n"; print "libs: @libval\n"; print "libfiles: @libfiles\n"; } if ($lmode == LIBRARY) { $cmd = "ar cru $dst"; $cmd .= " @$objs" if (@$objs); foreach my $k (@finalorderedlibs) { my $a = $libs{$k}; if ($a =~ m/\.a$/ && $a !~ m/_pic\.a/) { # extract objects from archive my $libfile = basename $a; my $xdir = "$odir/$ltdir/${la}x/$libfile"; extract_archive($xdir, $a); my @kobjs = get_objlist_from_archive($a); map { $_ = "$xdir/$_"; } @kobjs; push @libflags, @kobjs; } } $cmd .= " @libflags" if (@libflags); push @cmdlist, $cmd; push @cmdlist, "ranlib $dst"; return @cmdlist; } elsif ($lmode == PROGRAM) { die "static linking of programs not supported yet\n"; } } # dynamic linking my @Rflags = @Ropts; map { $_ = "-Wl,-rpath,$_" } @Rflags; foreach my $l (keys %libstofind) { my $libpath = find_lib($l); $libs{$l} = $libpath if ($libpath); } my @libfiles = values %libs; if ($D) { my @dirval = keys %dirs; my @libval = keys %libs; print "dirs: @dirval\n"; print "libs: @libval\n"; print "libfiles: @libfiles\n"; } create_symlinks($ltdir, \@libfiles); map { $_ = "$ltdir/". basename $_ } @libfiles; print "symlinks to libfiles used for linking: @libfiles\n" if $D; foreach my $k (@finalorderedlibs) { my $a = $libs{$k}; if ($a =~ m/\.a$/) { # don't make a -lfoo out of a static library if ($lmode == LIBRARY) { # extract objects from archive my $libfile = basename $a; my $xdir = "$odir/$ltdir/${la}x/$libfile"; extract_archive($xdir, $a); my @kobjs = get_objlist_from_archive($a); map { $_ = "$xdir/$_"; } @kobjs; push @libflags, @kobjs; } elsif ($lmode == PROGRAM) { push @libflags, $a; } } else { my $lib = basename $a; if ($lib =~ m/^lib(.*)\.so(\.\d+){2}/) { $lib = $1; } else { print "warning: cannot derive -l flag from library filename, assuming hash key\n"; $lib = $k; } push @libflags, "-l$lib"; } } $cmd = "$ltprog"; $cmd .= " $sharedflag $picflags" if ($lmode == LIBRARY); $cmd .= " -o $dst"; $cmd .= " @argvcopy"; $cmd .= " @$objs" if (@$objs); $cmd .= " -L$ltdir @libflags" if (@libflags); $cmd .= " @Rflags" if (@Rflags); push @cmdlist, $cmd; return @cmdlist; } # populate arrays of non-pic and pic objects and remove these from @ARGV sub generate_objlist { my $objs = shift; my $sobjs = shift; my $allpic = 1; foreach my $a (@ARGV) { if ($a =~ m/\S+\.lo$/) { my %loinfo; my $ofile = basename $a; my $odir = dirname $a; parse_file($a, \%loinfo); if ($loinfo{'non_pic_object'}) { my $o; $o .= "$odir/" if ($odir ne '.'); $o .= $loinfo{'non_pic_object'}; push @$objs, $o; } if ($loinfo{'pic_object'}) { my $o; $o .= "$odir/" if ($odir ne '.'); $o .= $loinfo{'pic_object'}; push @$sobjs, $o; } else { $allpic = 0; } $a = ''; } } return $allpic; } # XXX reuse code from SharedLibs.pm instead sub get_search_dirs { my @libsearchdirs; open(my $fh, '-|', 'ldconfig -r'); if (defined $fh) { while (<$fh>) { if (m/^\s*search directories:\s*(.*?)\s*$/o) { foreach my $d (split(/\:/o, $1)) { push @libsearchdirs, $d; } last; } } close($fh); } else { die "Can't find ldconfig\n"; } return @libsearchdirs; } sub extract_archive { my $dir = shift; my $archive = shift; if (! -d $dir) { print "mkdir -p $dir\n" if $D; File::Path::mkpath($dir); } perform("cd $dir && ar x $archive"); } sub get_objlist_from_archive { my $a = shift; open(my $arh, '-|', "ar t $a"); my @o = <$arh>; close $arh; map { chomp; } @o; return @o; } # walk a list from back to front, removing any duplicates # this should make sure a library's dependencies are behind the library itself sub reverse_zap_duplicates { my @arglist = @_; my $h = {}; my @r; for (my $i = $#arglist; $i >= 0; $i--) { my $el = $arglist[$i]; next if (defined $h->{$el}); unshift @r, $el; $h->{$el} = 1; } return @r; } # try to guess libtool mode when it is not specified sub guess_implicit_mode { my $m = 0; if ($ltprog =~ m/(install([.-]sh)?|cp)$/) { $m = 'install'; } elsif ($ltprog =~ m/cc|c\+\+/) { # XXX improve test if (grep { $_ eq '-c' } @ARGV) { $m = 'compile'; } else { $m = 'link'; } } return $m; } # escape quotes and meta-characters # protect elements containing whitespace or meta-characters by quotes sub handle_special_chars { my $a = shift; map { $_ =~ s,(['"]),\\$1,g; $_ = "\"$_\"" if $_ =~ m/[\s&()<>]/ } @$a; }