#!/usr/bin/perl # $OpenBSD: libtool,v 1.23 2010/10/28 23:50:15 steven Exp $ # Copyright (c) 2007-2010 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 feature qw(say switch state); use Cwd qw(getcwd abs_path); use File::Basename; use File::Glob ':glob'; use File::Path; use Getopt::Long; use Getopt::Std; package main; use subs qw( create_symlinks generate_objlist get_search_dirs guess_implicit_mode help notyet parse_version_info process_deplibs reverse_zap_duplicates_ref ); package Trace; sub print(&) { my $val = shift; if (defined $ENV{'TRACE_LIBTOOL'}) { state $trace_file; if (!defined $trace_file) { open $trace_file, '>>', $ENV{'TRACE_LIBTOOL'}; } if (defined $trace_file) { print $trace_file (&$val); } } } sub debug(&;$) { my ($args, $level) = @_; $level = 1 if !defined $level; if (defined $main::D && $main::D >= $level) { print (&$args); } } { package Exec; my $dry = 0; my $verbose = 0; my $performed = 0; sub performed { return $performed; } sub dry_run { $dry = 1; } sub verbose_run { $verbose = 1; } sub silent_run { $verbose = 0; } sub new { my $class = shift; bless {}, $class; } sub chdir { my ($self, $dir) = @_; my $class = ref($self) || $self; bless {dir => $dir}, $class; } sub command_run { my ($self, @l) = @_; if ($self->{dir}) { Trace::print {"cd $self->{dir} && "}; } Trace::print { "@l\n" }; my $pid = fork(); if ($pid == -1) { die "Couldn't fork while running @l\n"; } if ($pid == 0) { if ($self->{dir}) { CORE::chdir($self->{dir}) or die "Can't chdir to $self->{dir}\n"; } exec(@l); die "Exec failed @l\n"; } else { my $kid = waitpid($pid, 0); if ($? != 0) { die "Error while executing @l\n"; } } } sub shell { my ($self, @cmds) = @_; # create an object "on the run" if (!ref($self)) { $self = $self->new; } for my $c (@cmds) { say $c if $verbose || $dry; if (!$dry) { $self->command_run($c); } } $performed++; } sub command { my ($self, @l) = @_; # create an object "on the run" if (!ref($self)) { $self = $self->new; } say "@l" if $verbose || $dry; if (!$dry) { $self->command_run(@l); } $performed++; } } { package Parser; my $calls = 0; sub internal_resolve_la { my ($self, $level, $result, $rdeplibs, $rlibdirs, $args) = @_; Trace::debug {"resolve level: $level\n"}; my $seen_pthread = 0; foreach my $a (@$args) { if ($a eq '-pthread') { $seen_pthread++; next; } push(@$result, $a); next if $a !~ m/\.la$/; my $lainfo = LaFile->parse($a); if (!exists $lainfo->{'cached_deplibs'}) { $lainfo->{'cached_deplibs'} = []; $lainfo->{'cached_result'} = []; $lainfo->{'cached_libdirs'} = []; $lainfo->{'cached_pthread'} = $self->internal_resolve_la($level+1, $lainfo->{'cached_result'}, $lainfo->{'cached_deplibs'}, $lainfo->{'cached_libdirs'}, $lainfo->deplib_list); push(@{$lainfo->{'cached_deplibs'}}, @{$lainfo->deplib_list}); if ($lainfo->{'libdir'} ne '') { push(@{$lainfo->{'cached_libdirs'}}, $lainfo->{'libdir'}); } if (@{$lainfo->{'cached_deplibs'}} > 50) { $lainfo->{'cached_deplibs'} = main::reverse_zap_duplicates_ref($lainfo->{'cached_deplibs'}); } if (@{$lainfo->{'cached_libdirs'}} > 50) { $lainfo->{'cached_libdirs'} = main::reverse_zap_duplicates_ref($lainfo->{'cached_libdirs'}); } if (@{$lainfo->{'cached_result'}} > 50) { $lainfo->{'cached_result'} = main::reverse_zap_duplicates_ref($lainfo->{'cached_result'}); } } $seen_pthread += $lainfo->{'cached_pthread'}; push(@$result, @{$lainfo->{'cached_result'}}); push(@$rdeplibs, @{$lainfo->{'cached_deplibs'}}); push(@$rlibdirs, @{$lainfo->{'cached_libdirs'}}); } $calls++; return $seen_pthread; } END { Trace::print { "Calls to resolve_la: $calls\n" } if $calls; } # resolve .la files until a level with empty dependency_libs is reached. sub resolve_la { my ($self, $deplibs, $libdirs) = @_; $self->{result} = []; if ($self->internal_resolve_la(0, $self->{result}, $deplibs, $libdirs, $self->{args})) { unshift(@{$self->{result}}, '-pthread'); unshift(@$deplibs, '-pthread'); } return $self->{result}; } # 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 # -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. sub parse_linkargs1 { state $seen_pthread = 0; my ($self, $deplibs, $Rresolved, $libsearchdirs, $dirs, $libs, $args, $level) = @_; Trace::debug {"parse_linkargs1, level: $level\n"}; Trace::debug {" args: @$args\n"}; my $result = $self->{result}; # first read all directories where we can search libraries foreach my $a (@$args) { if ($a =~ m/^-L(.*)/) { if (!exists $dirs->{$1}) { $dirs->{$1} = 1; Trace::debug {" adding $a to deplibs\n"} if ($level == 0); push @$deplibs, $a; } } } foreach my $a (@$args) { Trace::debug {" processing $a\n"}; if (!$a || $a eq '' || $a =~ m/^\s+$/) { # skip empty arguments } elsif ($a eq '-pthread' && !$seen_pthread) { # XXX special treatment since it's not a -l flag push @$deplibs, $a; $seen_pthread = 1; push(@$result, $a); } elsif ($a =~ m/^-L(.*)/) { # already read earlier, do nothing } elsif ($a =~ m/^-R(.*)/) { # -R options originating from .la resolution # those from @ARGV are in @Ropts push @$Rresolved, $1; } elsif ($a =~ m/^-l(\S+)/) { my @largs = (); my $key = $1; if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); my $lafile = LaFile->find($key, $dirs); if ($lafile) { $libs->{$key}->{lafile} = $lafile; my $absla = main::abs_path($lafile); Trace::debug {" adding $absla to deplibs\n"} if ($level == 0); push @$deplibs, $absla; push @$result, $lafile; next; } else { $libs->{$key}->find($dirs, 1, 0, 'notyet', $libsearchdirs); my @deps = $libs->{$key}->inspect; foreach my $d (@deps) { my $k = main::basename $d; $k =~ s/^(\S+)\.so.*$/$1/; $k =~ s/^lib//; push(@largs, "-l$k"); } } } Trace::debug {" adding $a to deplibs\n"} if ($level == 0); push @$deplibs, $a; push(@$result, $a); my $dummy = []; # no need to add deplibs recursively $self->parse_linkargs1($dummy, $Rresolved, $libsearchdirs, $dirs, $libs, \@largs, $level+1) if @largs; } elsif ($a =~ m/(\S+\/)*(\S+)\.a$/) { (my $key = $2) =~ s/^lib//; if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); } my $d = main::abs_path(main::dirname($a)); $dirs->{$d} = 1; $libs->{$key}->{fullpath} = $a; push(@$result, $a); } elsif ($a =~ m/(\S+\/)*(\S+)\.la$/) { (my $key = $2) =~ s/^lib//; my $d = main::abs_path(main::dirname($a)); $dirs->{$d} = 1; my $fulla = main::abs_path($a); my $lainfo = LaFile->parse($fulla); my $dlname = $lainfo->{'dlname'}; my $oldlib = $lainfo->{'old_library'}; my $libdir = $lainfo->{'libdir'}; if ($dlname ne '') { if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); $libs->{$key}->{lafile} = $fulla; } } push(@$result, $a); push(@$deplibs, $fulla) if ($libdir ne ''); } elsif ($a =~ m/(\S+\/)*(\S+)\.so(\.\d+){2}/) { (my $key = $2) =~ s/^lib//; my $d = main::abs_path(main::dirname($a)); $dirs->{$d} = 1; if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); } # not really normal argument # -lfoo should be used instead, so convert it push(@$result, "-l$key"); } else { push(@$result, $a); } } } # pass 2 # -Lfoo, -lfoo, foo.a # no recursion in pass 2 # fill orderedlibs array, which is the sequence of shared libraries # after resolving all .la # (this list may contain duplicates) # fill staticlibs array, which is the sequence of static and convenience # libraries # XXX the variable $parser->{seen_la_shared} will register whether or not # a .la file is found which refers to a shared library and which is not # yet installed # this is used to decide where to link executables and create wrappers sub parse_linkargs2 { state $seen_pthread = 0; my ($self, $Rresolved, $libsearchdirs, $orderedlibs, $staticlibs, $dirs, $libs) = @_; Trace::debug {"parse_linkargs2\n"}; Trace::debug {" args: @{$self->{args}}\n"}; $self->{result} = []; my $result = $self->{result}; my $ltdir = $main::ltdir; foreach my $a (@{$self->{args}}) { Trace::debug {" processing $a\n"}; if (!$a || $a eq '' || $a =~ m/^\s+$/) { # skip empty arguments } elsif ($a eq '-lc') { # don't link explicitly with libc (just remove -lc) } elsif ($a eq '-pthread' && !$seen_pthread) { # XXX special treatment since it's not a -l flag $seen_pthread = 1; push(@$result, $a); } elsif ($a =~ m/^-L(.*)/) { if (!exists $dirs->{$1}) { $dirs->{$1} = 1; } } elsif ($a =~ m/^-R(.*)/) { # -R options originating from .la resolution # those from @ARGV are in @Ropts push @$Rresolved, $1; } elsif ($a =~ m/^-l(.*)/) { my @largs = (); my $key = $1; if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); } push @$orderedlibs, $key; } elsif ($a =~ m/(\S+\/)*(\S+)\.a$/) { (my $key = $2) =~ s/^lib//; if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); } $libs->{$key}->{fullpath} = $a; push(@$staticlibs, $a); } elsif ($a =~ m/(\S+\/)*(\S+)\.la$/) { (my $key = $2) =~ s/^lib//; my $d = main::abs_path(main::dirname($a)); $dirs->{$d} = 1; my $fulla = main::abs_path($a); my $lainfo = LaFile->parse($fulla); my $dlname = $lainfo->stringize('dlname'); my $oldlib = $lainfo->stringize('old_library'); my $installed = $lainfo->stringize('installed'); if ($dlname ne '' && $installed eq 'no') { Trace::debug {"seen uninstalled la shared in $a\n"}; $self->{seen_la_shared} = 1; } if ($dlname eq '' && -f "$d/$ltdir/$oldlib") { push @$staticlibs, "$d/$ltdir/$oldlib"; } else { if (!exists $libs->{$key}) { $libs->{$key} = Library->new($key); $libs->{$key}->{lafile} = $fulla; } push @$orderedlibs, $key; } } elsif ($a =~ m/^-Wl,(\S+)/) { # libtool accepts a list of -Wl options separated # by commas, and possibly with a trailing comma # which is not accepted by the linker my @Wlflags = split(/,/, $1); foreach my $f (@Wlflags) { push(@$result, "-Wl,$f"); } } else { push(@$result, $a); } } Trace::debug {"end parse_linkargs2\n"}; return $self->{result}; } sub new { my ($class, $args) = @_; bless { args => $args }, $class; } } package LaLoFile; my %file_cache; # which files have been parsed my $cache_by_fullname = {}; my $cache_by_inode = {}; # allows special treatment for some keywords sub set { my ($self, $k, $v) = @_; $self->{$k} = $v; } sub stringize { my ($self, $k) = @_; if (defined $self->{$k}) { return $self->{$k}; } return ''; } sub read { my ($class, $filename) = @_; my $info = $class->new; open(my $fh, '<', $filename) or die "cannot read $filename: $!\n"; my $_; while (<$fh>) { chomp; next if /^\#/; next if /^\s*$/; if (m/^(\S+)\=\'(.*)\'$/) { $info->set($1, $2); } elsif (m/^(\S+)\=(\S+)$/) { $info->set($1, $2); } } return $info; } sub parse { my ($class, $filename) = @_; Trace::debug {"parsing $filename"}; if (defined $cache_by_fullname->{$filename}) { Trace::debug {" (cached)\n"}; return $cache_by_fullname->{$filename}; } my $key = join("/", (stat $filename)[0,1]); if (defined $cache_by_inode->{$key}) { Trace::debug {" (cached)\n"}; return $cache_by_inode->{$key}; } Trace::debug {"\n"}; return $cache_by_inode->{$key} = $cache_by_fullname->{$filename} = $class->read($filename); } sub new { my $class = shift; bless {}, $class; } package LaFile; our @ISA=(qw(LaLoFile)); use File::Basename; # allows special treatment for some keywords sub set { my ($self, $k, $v) = @_; $self->SUPER::set($k, $v); if ($k eq 'dependency_libs') { my @l = split /\s+/, $v; $self->{deplib_list} = \@l; } } sub deplib_list { my $self = shift; return $self->{deplib_list} } # XXX not sure how much of this cruft we need sub write { my ($lainfo, $filename, $name) = @_; my $libname = $lainfo->stringize('libname'); my $sharedlibname = $lainfo->stringize('dlname'); my $staticlibname = $lainfo->stringize('old_library'); my $librarynames = $lainfo->stringize('library_names'); my $deplibs = $lainfo->stringize('dependency_libs'); my $current = $lainfo->stringize('current'); my $revision = $lainfo->stringize('revision'); my $age = $lainfo->stringize('age'); my $installed = $lainfo->stringize('installed'); my $shouldnotlink = $lainfo->stringize('shouldnotlink'); my $libdir = $lainfo->stringize('libdir'); open(my $la, '>', $filename) or die "cannot write $filename: $!\n"; say "creating $filename" if $main::verbose || $main::D; print $la <stringize('libname'); my $v = $self->stringize('current') .'.'. $self->stringize('revision'); if (!defined $ENV{'SHARED_LIBS_LOG'}) { return; } my $logfile = $ENV{'SHARED_LIBS_LOG'}; my $fh; if (! -f $logfile) { open ($fh, '>', $logfile); print $fh "# SHARED_LIBS+= # \n"; close $fh; } open ($fh, '>>', $logfile); # Remove first leading 'lib', we don't want that in SHARED_LIBS_LOG. $libname =~ s/^lib//; printf $fh "SHARED_LIBS +=\t%-20s %-8s # %s\n", $libname, $v, $origv; } # find .la file associated with a -llib flag # XXX pick the right one if multiple are found! sub find { my ($self, $l, $dirs) = @_; # sort dir search order by priority # XXX not fully correct yet my @sdirs = sort { $dirs->{$b} <=> $dirs->{$a} } keys %$dirs; # search in cwd as well unshift @sdirs, '.'; Trace::debug {"searching .la for $l\n"}; Trace::debug {"search path= ", join(':', @sdirs), "\n"}; foreach my $d (@sdirs) { foreach my $la_candidate ("$d/lib$l.la", "$d/$l.la") { if (-f $la_candidate) { Trace::debug {"found $la_candidate\n"}; return $la_candidate; } } } Trace::debug {".la for $l not found!\n"}; return 0; } sub link { my $self = shift; my $ltprog = shift; my $la = shift; my $fname = shift; my $odir = shift; my $shared = shift; my $objs = shift; my $dirs = shift; my $libs = shift; my $deplibs = shift; my $libdirs = shift; my $parser = shift; my $opts = shift; Trace::debug {"creating link command for library (linked ", ($shared) ? "dynam" : "stat", "ically)\n"}; my $what = ref($self); my @libflags; my @cmd; my $ltdir = $main::ltdir; my $dst = ($odir eq '.') ? "$ltdir/$fname" : "$odir/$ltdir/$fname"; if ($la =~ m/\.a$/) { # probably just a convenience library $dst = ($odir eq '.') ? "$fname" : "$odir/$fname"; } my $symlinkdir = $ltdir; if ($odir ne '.') { $symlinkdir = "$odir/$ltdir"; } mkdir $symlinkdir if (! -d $symlinkdir); Trace::debug {"argvstring (pre resolve_la): @{$parser->{args}}\n"}; my $args = $parser->resolve_la($deplibs, $libdirs); Trace::debug {"argvstring (post resolve_la): @{$parser->{args}}\n"}; my $orderedlibs = []; my $staticlibs = []; $parser->{args} = $args; $args = $parser->parse_linkargs2(\@main::Rresolved, \@main::libsearchdirs, $orderedlibs, $staticlibs, $dirs, $libs); Trace::debug {"staticlibs = \n", join("\n", @$staticlibs), "\n"}; Trace::debug {"orderedlibs = @$orderedlibs\n"}; my $finalorderedlibs = main::reverse_zap_duplicates_ref($orderedlibs); Trace::debug {"final orderedlibs = @$finalorderedlibs\n"}; # static linking if (!$shared) { @cmd = ('ar', 'cru', $dst); foreach my $a (@$staticlibs) { if ($a =~ m/\.a$/ && $a !~ m/_pic\.a/) { # extract objects from archive my $libfile = main::basename $a; my $xdir = "$odir/$ltdir/${la}x/$libfile"; main::extract_archive($xdir, $a); my @kobjs = main::get_objlist_from_archive($a); map { $_ = "$xdir/$_"; } @kobjs; push @libflags, @kobjs; } } foreach my $k (@$finalorderedlibs) { my $l = $libs->{$k}; # XXX improve test # this has to be done probably only with # convenience libraries next if (!defined $l->{lafile}); my $lainfo = LaFile->parse($l->{lafile}); next if ($lainfo->stringize('dlname') ne ''); $l->find($dirs, 0, 0, $what); my $a = $l->{fullpath}; if ($a =~ m/\.a$/ && $a !~ m/_pic\.a/) { # extract objects from archive my $libfile = main::basename $a; my $xdir = "$odir/$ltdir/${la}x/$libfile"; main::extract_archive($xdir, $a); my @kobjs = main::get_objlist_from_archive($a); map { $_ = "$xdir/$_"; } @kobjs; push @libflags, @kobjs; } } push @cmd, @libflags if (@libflags); push @cmd, @$objs if (@$objs); Exec->command(@cmd); Exec->command('ranlib', $dst); return; } # dynamic linking my $symbolsfile; if ($opts->{'export-symbols'}) { $symbolsfile = $opts->{'export-symbols'}; } elsif ($opts->{'export-symbols-regex'}) { ($symbolsfile = "$odir/$ltdir/$la") =~ s/\.la$/.exp/; main::get_symbollist($symbolsfile, $opts->{'export-symbols-regex'}, $objs); } my $tmp = []; while (my $k = shift @$finalorderedlibs) { my $l = $libs->{$k}; $l->find($dirs, 1, $opts->{'static'}, $what); if ($l->{dropped}) { # remove library if dependency on it has been dropped delete $libs->{$k}; } else { push(@$tmp, $k); } } $finalorderedlibs = $tmp; my @libobjects = values %$libs; Trace::debug {"libs:\n", join("\n", (keys %$libs)), "\n"}; Trace::debug {"libfiles:\n", join("\n", map { $_->{fullpath}//'UNDEF' } @libobjects), "\n"}; main::create_symlinks($symlinkdir, $libs); my $prev_was_archive = 0; my $libcounter = 0; foreach my $k (@$finalorderedlibs) { my $a = $libs->{$k}->{fullpath} || die "ERROR: $k not found in \$libs"; if ($a =~ m/\.a$/) { # don't make a -lfoo out of a static library push @libflags, '-Wl,-whole-archive' unless $prev_was_archive; push @libflags, $a; if ($libcounter == @$finalorderedlibs - 1) { push @libflags, '-Wl,-no-whole-archive'; } $prev_was_archive = 1; } else { push @libflags, '-Wl,-no-whole-archive' if $prev_was_archive; $prev_was_archive = 0; my $lib = basename $a; if ($lib =~ m/^lib(.*)\.so(\.\d+){2}/) { $lib = $1; } else { say "warning: cannot derive -l flag from library filename, assuming hash key"; $lib = $k; } push @libflags, "-l$lib"; } $libcounter++; } @cmd = @$ltprog; push @cmd, $main::sharedflag, @main::picflags; push @cmd, '-o', $dst; push @cmd, @$args if ($args); push @cmd, @$objs if (@$objs); push @cmd, '-Wl,-whole-archive', @$staticlibs, '-Wl,-no-whole-archive' if (@$staticlibs); push @cmd, "-L$symlinkdir", @libflags if (@libflags); push @cmd, "-Wl,-retain-symbols-file,$symbolsfile" if ($symbolsfile); Exec->command(@cmd); } package LoFile; our @ISA=(qw(LaLoFile)); use File::Basename; # write a libtool object file sub write { my ($self, $filename) = @_; my $picobj = $self->stringize('picobj'); my $nonpicobj = $self->stringize('nonpicobj'); my $name = basename $filename; open(my $lo, '>', $filename) or die "cannot write $filename: $!\n"; say "creating $filename" if $main::verbose || $main::D; print $lo <{picobj}) { my @cmd = @$compiler; push @cmd, @$args if (@$args); push @cmd, @main::picflags, '-o'; my $o = ($odir eq '.') ? '' : "$odir/"; $o .= $self->{picobj}; push @cmd, $o; Exec->command(@cmd); } if (defined $self->{nonpicobj}) { my @cmd = @$compiler; push @cmd, @$args if (@$args); push @cmd, '-o'; my $o = ($odir eq '.') ? '' : "$odir/"; $o .= $self->{nonpicobj}; push @cmd, $o; Exec->command(@cmd); } } package Program; use File::Basename; sub new { my $class = shift; bless {}, $class; } # write a wrapper script for an executable so it can be executed within # the build directory sub write_wrapper { my $self = shift; my $program = $self->{outfilepath}; my $ltdir = $main::ltdir; my $version = $main::version; my $pfile = basename $program; my $realprogram = $ltdir . '/' . $pfile; open(my $pw, '>', $program) or die "cannot write $program: $!\n"; print $pw <&2 exit 1 fi EOF ; close($pw); chmod 0755, $program; } sub is_wrapper { # my $self = shift; my $program = shift; open(my $pw, '<', $program) or die "cannot open $program: $!\n"; return eval(grep { m/wrapper\sfor/ } <$pw>); } sub link { my $self = shift; my $ltprog = shift; my $dirs = shift; my $libs = shift; my $deplibs = shift; my $libdirs = shift; my $parser = shift; my $opts = shift; my $ltdir = $main::ltdir; Trace::debug {"linking program (", ($opts->{'static'}) ? "not " : "", "dynamically linking not-installed libtool libraries)\n"}; my $what = ref($self); my $fpath = $self->{outfilepath}; my $RPdirs = $self->{RPdirs}; my $odir = dirname $fpath; my $fname = basename $fpath; my @libflags; my @cmd; my $dst; Trace::debug {"argvstring (pre resolve_la): @{$parser->{args}}\n"}; my $args = $parser->resolve_la($deplibs, $libdirs); Trace::debug {"argvstring (post resolve_la): @{$parser->{args}}\n"}; my $orderedlibs = []; my $staticlibs = []; $parser->{args} = $args; $parser->{seen_la_shared} = 0; $args = $parser->parse_linkargs2(\@main::Rresolved, \@main::libsearchdirs, $orderedlibs, $staticlibs, $dirs, $libs); Trace::debug {"staticlibs = \n", join("\n", @$staticlibs), "\n"}; Trace::debug {"orderedlibs = @$orderedlibs\n"}; my $finalorderedlibs = main::reverse_zap_duplicates_ref($orderedlibs); Trace::debug {"final orderedlibs = @$finalorderedlibs\n"}; my $symlinkdir = $ltdir; if ($odir ne '.') { $symlinkdir = "$odir/$ltdir"; } mkdir $symlinkdir if (! -d $symlinkdir); if ($parser->{seen_la_shared}) { $dst = ($odir eq '.') ? "$ltdir/$fname" : "$odir/$ltdir/$fname"; $self->write_wrapper(); } else { $dst = ($odir eq '.') ? $fname : "$odir/$fname"; } my $symbolsfile; if ($opts->{'export-symbols'}) { $symbolsfile = $opts->{'export-symbols'}; } elsif ($opts->{'export-symbols-regex'}) { ($symbolsfile = "$odir/$ltdir/$fname") =~ s/\.la$/.exp/; main::get_symbollist($symbolsfile, $opts->{'export-symbols-regex'}, $self->{objlist}); } $libdirs = main::reverse_zap_duplicates_ref($libdirs); # add libdirs to rpath if they are not in standard lib path for my $l (@$libdirs) { my $found = 0; for my $d (@main::libsearchdirs) { if ($l eq $d) { $found = 1; last; } } if (!$found) { push @$RPdirs, $l; } } $RPdirs = main::reverse_zap_duplicates_ref($RPdirs); map { $_ = "-Wl,-rpath,$_" } @$RPdirs; foreach my $k (keys %$libs) { Trace::debug {"key = $k - "}; my $r = ref($libs->{$k}); Trace::debug {"ref = $r\n"}; if (!defined $libs->{$k}) { Trace::debug {"creating library object for $k\n"}; $libs->{$k} = Library->new($k); } my $l = $libs->{$k}; $l->find($dirs, 1, $opts->{'static'}, $what); } my @libobjects = values %$libs; Trace::debug {"libs:\n", join("\n", (keys %$libs)), "\n"}; Trace::debug {"libfiles:\n", join("\n", map { $_->{fullpath} } @libobjects), "\n"}; main::create_symlinks($symlinkdir, $libs); foreach my $k (@$finalorderedlibs) { my $a = $libs->{$k}->{fullpath} || die "ERROR: $k not found in \$libs"; if ($a =~ m/\.a$/) { # don't make a -lfoo out of a static library push @libflags, $a; } else { my $lib = basename $a; if ($lib =~ m/^lib(.*)\.so(\.\d+){2}/) { $lib = $1; } else { say "warning: cannot derive -l flag from library filename, assuming hash key"; $lib = $k; } push @libflags, "-l$lib"; } } @cmd = @$ltprog; push @cmd, '-o', $dst; push @cmd, @$args if ($args); push @cmd, @{$self->{objlist}} if (@{$self->{objlist}}); push @cmd, @$staticlibs if (@$staticlibs); push @cmd, "-L$symlinkdir", @libflags if (@libflags); push @cmd, @$RPdirs if (@$RPdirs); push @cmd, "-Wl,-retain-symbols-file,$symbolsfile" if ($symbolsfile); Exec->command(@cmd); } package Library; # find actual library filename # XXX pick the right one if multiple are found! sub find { my ($self, $dirs, $shared, $staticflag, $linkmode, $ldconfigdirs) = @_; my $libtofind = $self->{key}; my $libfile = 0; my @globbedlib; my $ltdir = $main::ltdir; my $pic = ''; # used when finding static libraries if ($linkmode eq 'LaFile') { $pic = '_pic'; } if (defined $self->{lafile}) { # if there is a .la file, use the info from there Trace::debug {"found .la file $self->{lafile} for library key: $self->{key}\n"}; my $lainfo = LaFile->parse($self->{lafile}); my $dlname = $lainfo->{'dlname'}; my $oldlib = $lainfo->{'old_library'}; my $libdir = $lainfo->{'libdir'}; my $installed = $lainfo->{'installed'}; my $d = main::abs_path(main::dirname($self->{lafile})); # get the name we need (this may include a -release) if (!$dlname && !$oldlib) { die "neither static nor shared library found in $self->{lafile}\n"; } if ($d !~ m/\Q$ltdir\E$/ && $installed eq 'no') { $d .= "/$ltdir"; } if ($shared) { if ($dlname) { $libfile = "$d/$dlname"; } else { # fall back to static $libfile = "$d/$oldlib"; } # if -static has been passed, don't link dynamically # against not-installed libraries if ($staticflag && $installed eq 'no') { $libfile = "$d/$oldlib"; } } else { $libfile = "$d/$oldlib"; } if (! -f $libfile) { Trace::debug {".la file $self->{lafile} points to nonexistent file $libfile !\n"}; } } else { # otherwise, search the filesystem # sort dir search order by priority # XXX not fully correct yet my @sdirs = sort { $dirs->{$b} <=> $dirs->{$a} } keys %$dirs; # search in .libs when priority is high map { $_ = "$_/$ltdir" if (exists $dirs->{$_} && $dirs->{$_} > 3) } @sdirs; push @sdirs, @$ldconfigdirs if ($ldconfigdirs); Trace::debug {"searching for $libtofind\n"}; Trace::debug {"search path= ", join(':', @sdirs), "\n"}; Trace::debug {"search type= ", ($shared) ? 'shared' : 'static', "\n"}; foreach my $sd (@sdirs) { if ($shared) { # select correct library by sorting by version number only @globbedlib = sort { my ($x,$y) = map { /\.so\.(\d+\.\d+)$/; $1 } ($a,$b); $y <=> $x } glob "$sd/lib$libtofind.so.*.*"; if ($globbedlib[0]) { Trace::debug {"found $libtofind in $sd\n"}; $libfile = $globbedlib[0]; last; } else { # XXX find static library instead? my $spath = "$sd/lib$libtofind$pic.a"; if (-f $spath) { Trace::debug {"found static $libtofind in $sd\n"}; $libfile = $spath; last; } } } else { # look for a static library my $spath = "$sd/lib$libtofind.a"; if (-f $spath) { Trace::debug {"found static $libtofind in $sd\n"}; $libfile = $spath; last; } } } } if (!$libfile) { if (defined $self->{fullpath}) { delete $self->{fullpath}; } if ($linkmode eq 'LaFile') { say "warning: dependency on $libtofind dropped"; $self->{dropped} = 1; } elsif ($linkmode eq 'Program') { die "$libtofind not found!\n"; } } else { $self->{fullpath} = $libfile; Trace::debug {"\$libs->{$self->{key}}->{fullpath} = ", $self->{fullpath}, "\n"}; } } # give a list of library dependencies found in the actual shared library sub inspect { my $self = shift; my $filename = $self->{fullpath}; my @deps; Trace::debug {"inspecting $filename for library dependencies...\n"}; open(my $fh, '-|', "objdump -p $filename"); while (<$fh>) { if (m/\s+NEEDED\s+(\S+)\s*$/) { push @deps, $1; } } Trace::debug {"found ", (@deps == 0) ? 'no ' : '', "deps for $filename\n@deps\n"}; return @deps; } sub new { my ($class, $key) = @_; bless { key => $key }, $class; } package main; use Config; use constant { OBJECT => 0, LIBRARY => 1, PROGRAM => 2, }; our $version = '1.5.26'; # pretend to be this version of libtool my @no_shared_archs = qw(m88k vax); # XXX my $machine_arch = `machine -a`; my $machine_arch = $Config{'ARCH'}; (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(); our $ltdir = '.libs'; our @picflags = ('-fPIC', '-DPIC'); our $sharedflag = '-shared'; my $instlibdir = '/usr/local/lib'; my @libsearchdirs; $instlibdir = $ENV{'LIBDIR'} if defined $ENV{'LIBDIR'}; my $mode; our $D = 0; # debug flag my $verbose = 1; my %opts; # options passed to libtool my @tags; # list of --tag options passed to libtool # just to be clear: # when building a library: # * -R libdir records libdir in dependency_libs # * -rpath is the path where the (shared) library will be installed # when building a program: # * both -R libdir and -rpath libdir add libdir to the run-time path # -Wl,-rpath,libdir will bypass libtool. # build static/shared objects? my $static = 1; my $shared = 0; my $convenience = 0; my $noshared = 0; if (grep { $_ eq $machine_arch } @no_shared_archs) { $noshared = 1; } 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|n' => sub { Exec->dry_run }, 'features' => \¬yet, '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 { say $version ; exit(0); }, ); if ($verbose || $D) { Exec->verbose_run; } # what are we going to run (cc, c++, ...) my $ltprog = (); # deal with multi-arg ltprog Trace::debug {"ARGV = @ARGV\n"}; while (@ARGV) { # just read arguments until the next option... if ($ARGV[0] =~ m/^\-/) { last; } # XXX improve checks if ($ARGV[0] =~ m/^\S+\.la/) { last; } my $arg = shift @ARGV; push @$ltprog, $arg; Trace::debug {"arg = \"$arg\"\n"}; # if the current argument is an install program, stop immediately if ($arg =~ /cp$/) { last; } if ($arg =~ /install([-.]sh)?$/) { last; } } Trace::debug {"ltprog = \"@$ltprog\"\n"}; if (@$ltprog == 0) { die "no libtool command\n" }; # make ltprog a list of elements without whitespace (prevent exec errors) my @tmp_ltprog = @$ltprog; @$ltprog = (); for my $el (@tmp_ltprog) { my @parts = split /\s+/, $el; push @$ltprog, @parts; } # check mode and guess it if needed if (!($mode && grep { $_ eq $mode } @valid_modes)) { $mode = guess_implicit_mode($ltprog); if ($mode) { Trace::debug {"implicit mode: $mode\n"}; } else { die "MODE must be one of:\n@valid_modes\n"; } } # from here, options may be intermixed with arguments $gp->configure('permute'); if ($mode eq 'compile') { my $lofile = LoFile->new; $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 $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, $odir, $ofile, $srcfile, $srcext); # XXX check whether -c flag is present and if not, die? if ($opts{'o'}) { # fix extension if needed ($outfile = $opts{'o'}) =~ s/\.o$/.lo/; $odir = dirname $outfile; $ofile = basename $outfile; } else { # 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; $srcext = $1; $found = 1; last; } } $found or die "cannot find source file in command\n"; # the output file ends up in the current directory $odir = '.'; ($ofile = basename $srcfile) =~ s/\.($srcext)$/.lo/i; $outfile = "$odir/$ofile"; } Trace::debug {"srcfile = $srcfile\n"} if $srcfile; Trace::debug {"outfile = $outfile\n"}; (my $nonpicobj = $ofile) =~ s/\.lo$/.o/; my $picobj = "$ltdir/$nonpicobj"; $lofile->{picobj} = $picobj if $pic; $lofile->{nonpicobj} = $nonpicobj if $nonpic; $lofile->compile($ltprog, $odir, \@ARGV); $lofile->write($outfile); } elsif ($mode eq 'install') { # we just parse the options in order to find the actual arguments my @argvcopy = @ARGV; my %install_opts; Trace::debug {"ltprog[-1] = $$ltprog[-1]\n"}; if ($$ltprog[-1] =~ 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[-1] =~ m/cp$/) { getopts('HLPRfipr', \%install_opts); if (@ARGV < 2) { die "wrong number of arguments for install\n"; } } else { die "unsupported install program $$ltprog[-1]\n"; } my @instopts = @argvcopy[0 .. (@argvcopy - @ARGV - 1)]; my $dst = pop @ARGV; my @src = @ARGV; my $dstdir; if (-d $dst) { $dstdir = $dst; } else { # dst is not a directory, i.e. a file 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 my $tostrip = []; my $addedmode = 0; 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; Trace::debug {"srcdir = $srcdir\nsrcfile = $srcfile\n"}; Trace::debug {"dstdir = $dstdir\ndstfile = $dstfile\n"}; if ($srcfile =~ m/^\S+\.la$/) { # we are installing a .la library if ($$ltprog[-1] =~ m/install([.-]sh)?$/) { push @instopts, '-m', '644' unless $addedmode; $addedmode = 1; } my $lainfo = LaFile->parse($s); # 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); push @$tostrip, "$dstdir/$staticlib" if ($install_opts{'s'} && $staticlib); $toinstall->{"$srcdir/$ltdir/$sharedlib"} = "$dstdir/$sharedlib" if ($sharedlib); $toinstall->{"$laipath"} = "$dstdir/$dstfile"; foreach my $n (@libnames) { $tosymlink->{$n} = $sharedlib if ($n ne $sharedlib); } } elsif (-f "$srcdir/$ltdir/$srcfile" && Program::is_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 # do not strip static libraries, this is done below if ($s =~ m/\.la$/ || $d =~ m /\.la$/ || $d =~ m/\.a$/) { @realinstopts = grep { $_ ne '-s' } @realinstopts; } Exec->command(@$ltprog, @realinstopts, $s, $d); } foreach my $f (@$tostrip) { my @stripopts = ('--strip-debug'); Exec->command('strip', @stripopts, $f); } while (my ($d, $s) = each %$tosymlink) { unlink("$dstdir/$d"); symlink($s, "$dstdir/$d"); } if (defined $install_opts{'d'}) { Exec->command(@$ltprog, @instopts, @ARGV); } } elsif ($mode eq 'link') { my $cmd; my @Ropts; # -R options on the command line my @Rresolved; # -R options originating from .la resolution my @RPopts; # -rpath options my $deplibs = []; # list of dependent libraries (both -L and -l flags) my $libdirs = []; # list of libdirs my $libs = {}; # libraries my $dirs = {}; # paths to find libraries # put a priority in the dir hash # always look here $dirs->{'/usr/lib'} = 3; $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' => \@RPopts, '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, no-fast-install, # no-install, no-undefined, precious-files-regex, # shrext, thread-safe, prefer-pic, prefer-non-pic @libsearchdirs = get_search_dirs(); # add the .libs dir as well in case people try to link directly # with the real library instead of the .la library push @libsearchdirs, './.libs'; my $outfile = $opts{'o'}; if (!$outfile) { die "no output file given.\n"; } Trace::debug {"outfile = $outfile\n"}; my $odir = dirname $outfile; my $ofile = basename $outfile; # what are we linking? my $linkmode = PROGRAM; if ($ofile =~ m/\.l?a$/) { $linkmode = LIBRARY; } Trace::debug {"linkmode: $linkmode\n"}; # eat multiple version-info arguments, we only accept the first. map { $_ = '' if ($_ =~ m/\d+:\d+:\d+/); } @ARGV; my @objs; my @sobjs; if ($opts{'objectlist'}) { my $objectlist = $opts{'objectlist'}; open(my $ol, '<', $objectlist) or die "cannot open $objectlist: $!\n"; my @objlist = <$ol>; for (@objlist) { chomp; } generate_objlist(\@objs, \@sobjs, \@objlist); } else { generate_objlist(\@objs, \@sobjs, \@ARGV); } Trace::debug {"objs = @objs\n"}; Trace::debug {"sobjs = @sobjs\n"}; my $parser = Parser->new(\@ARGV); $parser->{result} = []; if ($linkmode == PROGRAM) { my $program = Program->new; $program->{outfilepath} = $outfile; # XXX give higher priority to dirs of not installed libs if ($opts{'export-dynamic'}) { push(@{$parser->{args}}, "-Wl,-E"); } $parser->parse_linkargs1($deplibs, \@Rresolved, \@libsearchdirs, $dirs, $libs, $parser->{args}, 0); $parser->{args} = $parser->{result}; Trace::debug {"end parse_linkargs1\n"}; Trace::debug {"deplibs = @$deplibs\n"}; $program->{objlist} = \@objs; if (@objs == 0) { if (@sobjs > 0) { Trace::debug {"no non-pic libtool objects found, trying pic objects...\n"}; $program->{objlist} = \@sobjs; } elsif (@sobjs == 0) { Trace::debug {"no libtool objects of any kind found\n"}; Trace::debug {"hoping for real objects in ARGV...\n"}; } } my $RPdirs = []; @$RPdirs = (@Ropts, @RPopts, @Rresolved); $program->{RPdirs} = $RPdirs; $program->link($ltprog, $dirs, $libs, $deplibs, $libdirs, $parser, \%opts); } elsif ($linkmode == LIBRARY) { my $lainfo = LaFile->new; $shared = 1 if ($opts{'version-info'} || $opts{'avoid-version'} || $opts{'module'}); if (!@RPopts) { $convenience = 1; $noshared = 1; $static = 1; $shared = 0; } else { $shared = 1; } if ($ofile =~ m/\.a$/ && !$convenience) { $ofile =~ s/\.a$/.la/; $outfile =~ s/\.a$/.la/; } (my $libname = $ofile) =~ s/\.l?a$//; # remove extension my $staticlib = $libname.'.a'; my $sharedlib = $libname.'.so'; my $sharedlib_symlink; if ($opts{'static'}) { $shared = 0; $static = 1; } $shared = 0 if $noshared; $parser->parse_linkargs1($deplibs, \@Rresolved, \@libsearchdirs, $dirs, $libs, $parser->{args}, 0); $parser->{args} = $parser->{result}; Trace::debug {"end parse_linkargs1\n"}; Trace::debug {"deplibs = @$deplibs\n"}; my $sover = '0.0'; my $origver = 'unknown'; # environment overrides -version-info (my $envlibname = $libname) =~ s/[.+-]/_/g; my ($current, $revision, $age) = (0, 0, 0); if ($opts{'version-info'}) { ($current, $revision, $age) = parse_version_info($opts{'version-info'}); $origver = "$current.$revision"; $sover = $origver; } if ($ENV{"${envlibname}_ltversion"}) { # this takes priority over the previous $sover = $ENV{"${envlibname}_ltversion"}; ($current, $revision) = split /\./, $sover; $age = 0; } if (defined $opts{'release'}) { $sharedlib_symlink = $sharedlib; $sharedlib = $libname.'-'.$opts{'release'}.'.so'; } if ($opts{'avoid-version'} || (defined $opts{'release'} && !$opts{'version-info'})) { # don't add a version in these cases } else { $sharedlib .= ".$sover"; if (defined $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); Trace::debug {"SHARED: $shared\nSTATIC: $static\n"}; $lainfo->{'libname'} = $libname; if ($shared) { $lainfo->{'dlname'} = $sharedlib; $lainfo->{'library_names'} = $sharedlib; $lainfo->{'library_names'} .= " $sharedlib_symlink" if (defined $opts{'release'}); $lainfo->link($ltprog, $ofile, $sharedlib, $odir, 1, \@sobjs, $dirs, $libs, $deplibs, $libdirs, $parser, \%opts); Trace::debug {"sharedlib: $sharedlib\n"}; $lainfo->{'current'} = $current; $lainfo->{'revision'} = $revision; $lainfo->{'age'} = $age; } if ($static) { $lainfo->{'old_library'} = $staticlib; $lainfo->link($ltprog, $ofile, $staticlib, $odir, 0, ($convenience && @sobjs > 0) ? \@sobjs : \@objs, $dirs, $libs, $deplibs, $libdirs, $parser, \%opts); Trace::debug {($convenience ? "convenience" : "static")." lib: $staticlib\n"}; } $lainfo->{'installed'} = 'no'; $lainfo->{'shouldnotlink'} = $opts{'module'} ? 'yes' : 'no'; map { $_ = "-R$_" } @Ropts; unshift @$deplibs, @Ropts if (@Ropts); Trace::debug {"deplibs = @$deplibs\n"}; my $finaldeplibs = reverse_zap_duplicates_ref($deplibs); Trace::debug {"finaldeplibs = @$finaldeplibs\n"}; $lainfo->set('dependency_libs', "@$finaldeplibs"); if (@RPopts) { if (@RPopts > 1) { Trace::debug {"more than 1 -rpath option given, taking the first: ", $RPopts[0], "\n"}; } $lainfo->{'libdir'} = $RPopts[0]; } if (!($convenience && $ofile =~ m/\.a$/)) { $lainfo->write($outfile, $ofile); unlink("$odir/$ltdir/$ofile"); symlink("../$ofile", "$odir/$ltdir/$ofile"); } my $lai = "$odir/$ltdir/$ofile".'i'; if ($shared) { my $pdeplibs = process_deplibs($finaldeplibs); if (defined $pdeplibs) { $lainfo->set('dependency_libs', "@$pdeplibs"); } if (! $opts{'module'}) { $lainfo->write_shared_libs_log($origver); } } $lainfo->{'installed'} = 'yes'; # write .lai file (.la file that will be installed) $lainfo->write($lai, $ofile); } } 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 Exec->silent_run; Exec->command(@$ltprog, @ARGV); } else { die "MODE=$mode not implemented yet.\n"; } if (Exec->performed == 0) { die "no commands to execute.\n" } ########################################################################### sub help { print <{fullpath}; next if (!defined $f); next if ($f =~ m/\.a$/); my $libnames = []; if (defined $l->{lafile}) { my $lainfo = LaFile->parse($l->{lafile}); my $librarynames = $lainfo->stringize('library_names'); @$libnames = split /\s/, $librarynames; $libnames = reverse_zap_duplicates_ref($libnames); } else { push @$libnames, basename $f; } foreach my $libfile (@$libnames) { Trace::debug {"ln -s $f $dir/$libfile\n"}; if (! -f "$dir/$libfile") { symlink abs_path($f), "$dir/$libfile" or die "cannot create symlink: $!\n"; } } } } # 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 $linkflags = shift; my $result; foreach my $lf (@$linkflags) { if ($lf =~ m/-L\S+\Q$ltdir\E$/) { } elsif ($lf =~ m/-L\./) { } elsif ($lf =~ m/\/\S+\/(\S+\.la)/) { my $lafile = $1; my $libdir = LaFile->parse($lf)->{'libdir'}; if ($libdir eq '') { # this drops libraries which will not be # installed # XXX improve checks when adding to deplibs say "warning: $lf dropped from deplibs"; } else { $lf = $libdir.'/'.$lafile; push @$result, $lf; } } else { push @$result, $lf; } } return $result; } # populate arrays of non-pic and pic objects and remove these from @ARGV sub generate_objlist { my $objs = shift; my $sobjs = shift; my $objsource = shift; my $result = []; foreach my $a (@$objsource) { if ($a =~ m/\S+\.lo$/) { my $ofile = basename $a; my $odir = dirname $a; my $loinfo = LoFile->parse($a); 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; } } elsif ($a =~ m/\S+\.o$/) { push @$objs, $a; } else { push @$result, $a; } } @$objsource = @$result; } # 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) { Trace::debug {"mkdir -p $dir\n"}; File::Path::mkpath($dir); } Exec->chdir($dir)->command('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; } sub get_symbollist { my $filepath = shift; my $regex = shift; my $objlist = shift; if (@$objlist == 0) { die "get_symbollist: object list is empty\n"; } Trace::debug {"generating symbol list in file: $filepath\n"}; my $symbols = []; open(my $sh, '-|', 'nm', @$objlist) or die "error running nm on object list\n"; my $c = 0; while (my $line = <$sh>) { chomp $line; Trace::debug {"$c: $line\n"}; if ($line =~ m/\S+\s+[BCDEGRST]\s+(.*)/) { my $s = $1; if ($s =~ m/$regex/) { push @$symbols, $s; Trace::debug {"matched\n"}; } } $c++; } $symbols = reverse_zap_duplicates_ref($symbols); @$symbols = sort @$symbols; open(my $fh, '>', $filepath) or die "cannot open $filepath\n"; print $fh join("\n", @$symbols), "\n"; } # 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_ref { my $arglist = shift; my $h = {}; my $r = []; for my $el (reverse @$arglist) { 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 $ltprog = shift; my $m = 0; for my $a (@$ltprog) { if ($a =~ m/(install([.-]sh)?|cp)$/) { $m = 'install'; } elsif ($a =~ m/cc|c\+\+/) { # XXX improve test if (grep { $_ eq '-c' } @ARGV) { $m = 'compile'; } else { $m = 'link'; } } } return $m; }