Blame wip/scan_dlls.pl

Packit 82cce8
#!/usr/bin/perl
Packit 82cce8
Packit 82cce8
# recursively find NEEDED (in the ELF sense) shared libraries 
Packit 82cce8
# for a given share library or for all installed Perl "glue" libraries
Packit 82cce8
Packit 82cce8
use strict;
Packit 82cce8
use warnings;
Packit 82cce8
Packit 82cce8
use File::Spec;
Packit 82cce8
use File::Find;
Packit 82cce8
use File::Basename;
Packit 82cce8
Packit 82cce8
package DLL
Packit 82cce8
{
Packit 82cce8
    use strict;
Packit 82cce8
    use warnings;
Packit 82cce8
    use Capture::Tiny qw(:all);
Packit 82cce8
Packit 82cce8
    our ($show_system_libs, $show_perl_libs);   # default: don't show
Packit 82cce8
Packit 82cce8
    my @dll_path = File::Spec->path;            # Windows
Packit 82cce8
    # my @dll_path = qw(/lib /lib/x86_64-linux-gnu /usr/lib /usr/lib/x86_64-linux-gnu);
Packit 82cce8
    # + $ENV{LD_LIBRARY_PATH} if set
Packit 82cce8
    #                                           Linux (Debian multi-arch)
Packit 82cce8
    # maybe use "gcc -print-search-dirs" (pathnames may need canonicalization)
Packit 82cce8
    #   install: /usr/lib/gcc/x86_64-linux-gnu/4.9/
Packit 82cce8
    #   programs: =/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/bin/
Packit 82cce8
    #   libraries: =/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../lib/:/lib/x86_64-linux-gnu/4.9/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/4.9/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/lib/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../:/lib/:/usr/lib/
Packit 82cce8
Packit 82cce8
    require Tie::CPHash;
Packit 82cce8
    tie my %cache, "Tie::CPHash";
Packit 82cce8
Packit 82cce8
    sub name    { shift->{name} }
Packit 82cce8
    sub path    { shift->{path} }
Packit 82cce8
Packit 82cce8
Packit 82cce8
    sub find                            # class method
Packit 82cce8
    {
Packit 82cce8
        my ($class, $name) = @_;
Packit 82cce8
        unless ($cache{$name})
Packit 82cce8
        {
Packit 82cce8
            my $found;
Packit 82cce8
            foreach (@dll_path)
Packit 82cce8
            {
Packit 82cce8
                my $path = File::Spec->catfile($_, $name);
Packit 82cce8
                $found = $path, last if -e $path;
Packit 82cce8
            }
Packit 82cce8
Packit 82cce8
            $cache{$name} = bless {
Packit 82cce8
                name    => $name,
Packit 82cce8
                path    => $found,
Packit 82cce8
            }, $class;
Packit 82cce8
        }
Packit 82cce8
        return $cache{$name};
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    sub needed
Packit 82cce8
    {
Packit 82cce8
        my ($self, $path) = @_;
Packit 82cce8
        if (ref $self)
Packit 82cce8
        {
Packit 82cce8
            return @{ $self->{needed} } if $self->{needed};
Packit 82cce8
            $path = $self->{path};
Packit 82cce8
            die "can't find DLL $self->{name}" unless defined $path;
Packit 82cce8
        }
Packit 82cce8
        else
Packit 82cce8
        {
Packit 82cce8
            die __PACKAGE__."->needed: argument PATH missing" unless defined $path;
Packit 82cce8
        }
Packit 82cce8
Packit 82cce8
        my ($out, $err, $exit) = capture { system(qw( objdump -ax ), $path) };
Packit 82cce8
        die qq["objdump -ax $path" failed: $err] unless $exit == 0;
Packit 82cce8
Packit 82cce8
        my @needed = map { __PACKAGE__->find($_) } 
Packit 82cce8
                         $out =~ /^\s*DLL Name:\s*(\S+)/gm;     # Windows
Packit 82cce8
        #                $out =~ /^\s*NEEDED\s+(\S+)/gm;        # Linux
Packit 82cce8
        $self->{needed} = \@needed if ref $self;
Packit 82cce8
        return @needed;
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
Packit 82cce8
    sub depends
Packit 82cce8
    {
Packit 82cce8
        my ($self, $path) = @_;
Packit 82cce8
        if (ref $self)
Packit 82cce8
        {
Packit 82cce8
            $path = $self->{path};
Packit 82cce8
            die "can't find DLL $self->{name}" unless defined $path;
Packit 82cce8
        }
Packit 82cce8
        else
Packit 82cce8
        {
Packit 82cce8
            die __PACKAGE__."->depends argument PATH missing" unless defined $path;
Packit 82cce8
        }
Packit 82cce8
Packit 82cce8
        tie my %seen, "Tie::CPHash";
Packit 82cce8
        $seen{$self->name} = $self if ref $self;
Packit 82cce8
        _depends(\%seen, $self->needed($path));
Packit 82cce8
        return values %seen;
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    sub _depends
Packit 82cce8
    {
Packit 82cce8
        my ($seen, @needed) = @_;
Packit 82cce8
Packit 82cce8
        foreach (@needed)
Packit 82cce8
        {
Packit 82cce8
            next if $seen->{$_->name};
Packit 82cce8
            if (defined $_->path)
Packit 82cce8
            {
Packit 82cce8
                next if $_->is_system_lib && !$show_system_libs;
Packit 82cce8
                next if $_->is_perl_lib   && !$show_perl_libs;
Packit 82cce8
            }
Packit 82cce8
Packit 82cce8
            $seen->{$_->name} = $_;
Packit 82cce8
            _depends($seen, $_->needed) if defined $_->path;
Packit 82cce8
        }
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    sub canon_path
Packit 82cce8
    {
Packit 82cce8
        my ($self) = @_;
Packit 82cce8
        return unless defined $_->path;
Packit 82cce8
Packit 82cce8
        return $_->{canon_path} ||= _canon_path($_->path);
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    sub _canon_path
Packit 82cce8
    {
Packit 82cce8
        my ($path, $no_file) = @_;
Packit 82cce8
Packit 82cce8
        my ($vol, $dirs, $file) = File::Spec->splitpath($path, $no_file);
Packit 82cce8
        $dirs =~ s{[/\\]$}{};
Packit 82cce8
        my $foo = join("/", $vol, File::Spec->splitdir($dirs), $file);
Packit 82cce8
        return lc $foo;
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    my $system_root = _canon_path($ENV{SystemRoot}, 1);
Packit 82cce8
Packit 82cce8
    sub is_system_lib
Packit 82cce8
    {
Packit 82cce8
        my ($self) = @_;
Packit 82cce8
        my $canon_path = $_->canon_path or return;
Packit 82cce8
        return length $canon_path > length $system_root
Packit 82cce8
               && substr($canon_path, 0, length $system_root) eq $system_root;
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    tie my %perl_libs, "Tie::CPHash";
Packit 82cce8
    {
Packit 82cce8
        local $show_system_libs = 0;
Packit 82cce8
        local $show_perl_libs = 1;
Packit 82cce8
        $perl_libs{$_->name} = $_ foreach __PACKAGE__->depends($^X);
Packit 82cce8
    };        
Packit 82cce8
Packit 82cce8
    sub is_perl_lib     { $perl_libs{shift->name} ? 1 : 0 }
Packit 82cce8
}
Packit 82cce8
Packit 82cce8
Packit 82cce8
# return a list of installed (ie. found below some directory in @INC) glue DLLs
Packit 82cce8
sub find_all_installed_glue_dlls
Packit 82cce8
{
Packit 82cce8
    my @dlls;
Packit 82cce8
Packit 82cce8
    find(sub { push @dlls, $File::Find::name if /\.dll/i; }, 
Packit 82cce8
         grep { my $auto;
Packit 82cce8
                !ref $_ && -d ($auto = File::Spec->catdir($_, "auto")) ? 
Packit 82cce8
                    $auto : () 
Packit 82cce8
              } @INC);
Packit 82cce8
Packit 82cce8
    return @dlls;
Packit 82cce8
}
Packit 82cce8
Packit 82cce8
Packit 82cce8
# guess the Perl module from the pathname of a glue DLL
Packit 82cce8
sub guess_module_from_glue_dll
Packit 82cce8
{
Packit 82cce8
    my ($path) = @_;
Packit 82cce8
Packit 82cce8
    # module Foo::Bar::Quux typically installs its glue DLL as
Packit 82cce8
    # .../auto/Foo/Bar/Quux/Quux.dll or
Packit 82cce8
    # .../auto/Foo/Bar/Quux/Quux.xs.dll
Packit 82cce8
    my ($vol, $dirs, $file) = File::Spec->splitpath($path);
Packit 82cce8
    $dirs =~ s{[/\\]$}{};
Packit 82cce8
    $dirs =~ s{^(?:.*?[/\\])?auto[/\\]}{}
Packit 82cce8
        or warn(qq[DLL "$path": path doesn't contain "auto"\n]), return;
Packit 82cce8
    return join("::", File::Spec->splitdir($dirs));
Packit 82cce8
}
Packit 82cce8
Packit 82cce8
Packit 82cce8
my $show_lib_path = 0;
Packit 82cce8
sub show_lib
Packit 82cce8
{
Packit 82cce8
    my ($dll) = @_;
Packit 82cce8
    if ($show_lib_path)
Packit 82cce8
    {
Packit 82cce8
        printf "\t%s => %s\n", $dll->name, $dll->path || "(not found)";
Packit 82cce8
    }
Packit 82cce8
    else
Packit 82cce8
    {
Packit 82cce8
        printf "\t%s\n", $dll->name;
Packit 82cce8
    }
Packit 82cce8
}
Packit 82cce8
Packit 82cce8
if (@ARGV)
Packit 82cce8
{
Packit 82cce8
    foreach (@ARGV)
Packit 82cce8
    {
Packit 82cce8
        print $_, "\n";
Packit 82cce8
        show_lib($_) foreach DLL->depends($_);
Packit 82cce8
    }
Packit 82cce8
}
Packit 82cce8
else
Packit 82cce8
{
Packit 82cce8
    my %mod2dll;
Packit 82cce8
    my @non_mod_dlls;
Packit 82cce8
    foreach (find_all_installed_glue_dlls())
Packit 82cce8
    {
Packit 82cce8
        my $mod = guess_module_from_glue_dll($_);
Packit 82cce8
        push(@non_mod_dlls, $_), next unless $mod;
Packit 82cce8
        $mod2dll{$mod} = $_;
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    foreach my $mod (sort keys %mod2dll)
Packit 82cce8
    {
Packit 82cce8
	my $dll = $mod2dll{$mod};
Packit 82cce8
        my @deps = DLL->depends($dll) or next; # suppress glue DLLs w/o dependencies
Packit 82cce8
        print "$mod ($dll)\n";
Packit 82cce8
        show_lib($_) foreach @deps;
Packit 82cce8
    }
Packit 82cce8
Packit 82cce8
    print "\n";
Packit 82cce8
    foreach my $dll (sort @non_mod_dlls)
Packit 82cce8
    {
Packit 82cce8
        print "$dll\n";
Packit 82cce8
        show_lib($_) foreach DLL->depends($dll);
Packit 82cce8
    }
Packit 82cce8
}