#!/usr/bin/perl -w
# $Id: needed-syms,v 1.6 2014/11/19 21:58:13 tom Exp $
# -----------------------------------------------------------------------------
# Copyright 2014 by Thomas E. Dickey
#
#                         All Rights Reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the
# sale, use or other dealings in this Software without prior written
# authorization.
# -----------------------------------------------------------------------------
# Inspect the given (build-)tree for libraries and programs, determining which
# symbols exported by the libraries are needed by other libraries or the found
# programs.  Print a list showing the symbols, where they are declared, and
# their needed status (global or local).

use strict;

our %all_externs;
our %all_exports;

sub read_pipe($) {
    my $command = shift;
    my @result;
    if ( open( FP, "$command |" ) ) {
        (@result) = <FP>;
        close FP;
        for my $n ( 0 .. $#result ) {
            chomp( $result[$n] );
        }
    }
    return @result;
}

sub is_program($) {
    my $pathname = shift;
    my $result   = 0;
    my $whatis   = `file $pathname`;
    $result = 1 if ( $whatis =~ /^.*executable.*not stripped.*$/ );
    return $result;
}

# Find all of the symbols exported by the library(s).  The script will list
# those, one way or another.  Also find the symbols imported by one library or
# another.  Those will be marked "(internal)", but may be overridden if used in
# one of the programs.
sub find_libs() {
    my @libs = &read_pipe("find . -type f -name '*.so*'");
    my %externs;

    for my $n ( 0 .. $#libs ) {
        my $library = $libs[$n];
        chomp $library;
        printf "# %s", $library;
        my $name = $library;
        $name =~ s/^.*\/lib//;
        $name =~ s/\..*$//;
        my @externs = &read_pipe("externs $library");
        my @exports = &read_pipe("exports $library");
        printf "\t%d externs, %d exports\n", $#externs + 1, $#exports + 1;

        for my $e ( 0 .. $#externs ) {
            $externs{ $externs[$e] } = $name;
        }
        for my $e ( 0 .. $#exports ) {
            $all_exports{ $exports[$e] } = $name;
        }
    }

    # Examine the list of exported symbols, looking for those which begin with
    # "_" (denoting internal use), and if found as an extern, mark it as such.
    foreach my $e ( sort keys %all_exports ) {
        if ( $e !~ /^_/ ) {
            $all_externs{$e} = sprintf "global: %s", $all_exports{$e};
        }
        elsif ( defined( $externs{$e} ) ) {
            if ( not defined( $all_externs{$e} ) ) {
                $all_externs{$e} = sprintf "glue: %s", $all_exports{$e};
            }
            $all_externs{$e} .= ", library:"
              unless ( $all_externs{$e} =~ /\blibrary:/ );
            $all_externs{$e} .= " " . $externs{$e};
        }
    }
}

sub find_progs() {
    my @progs = &read_pipe("find . -type f -perm -u+x");
    for my $n ( 0 .. $#progs ) {
        my $program = $progs[$n];
        chomp $program;
        next unless &is_program($program);
        my $name = $program;
        $name =~ s/^.*\///;
        printf "# %s", $program;
        my @externs = &read_pipe("externs $program");
        printf "\t%d externs\n", $#externs + 1;

        # Examine the program, adding any "_" symbols which are used by it.
        for my $N ( 0 .. $#externs ) {
            my $e = $externs[$N];
            if ( $e =~ /^_/ and defined( $all_exports{$e} ) ) {
                if ( not defined( $all_externs{$e} ) ) {
                    $all_externs{$e} = sprintf "glue: %s", $all_exports{$e};
                }
                $all_externs{$e} .= ", program:"
                  if ( $all_externs{$e} !~ /program:/ );
                $all_externs{$e} .= " " . $name;
            }
        }
    }
}

sub needed_syms() {
    &find_libs;
    &find_progs;

    # Mark any other "_" symbols as local
    foreach my $e ( sort keys %all_exports ) {
        $all_externs{$e} = sprintf "local: %s", $all_exports{$e}
          unless ( defined( $all_externs{$e} ) );
        printf "%s\t%s\n", $e, $all_externs{$e};
    }
}

&needed_syms;
