#------------------------------------------------------------------------
#       COPYRIGHT (c) NXP Semiconductors 2007
#     All rights are reserved. Reproduction in whole or in part is
#     prohibited without the written consent of the copyright owner.
#------------------------------------------------------------------------

use strict;
use File::Spec;

#------------------------------------------------------------------------
# File requires.pl
#------------------------------------------------------------------------
my $Version = "0.5";

# Author: Peter Kazakov, Shivaraj, Hans den Boer
#
# Revision history:
# -----------|---------|------------------------------------------
# date       | version | Description
# -----------|---------|------------------------------------------
# 2001-06-18 | 0.1     | Initial
# -----------|---------|------------------------------------------
# 2001-06-18 | 0.2     | Multiproject SDE2
# -----------|---------|------------------------------------------
# 2001-06-18 | 0.3     | Output to STDOUT
# -----------|---------|------------------------------------------
# 2001-06-18 | 0.4     | Gmake invokation to consider conditional
#            |         | REQUIRES setting in makefile
# -----------|---------|------------------------------------------
# 2006-06-15 | 0.5     | Use prjlist.txt to locate components
#            |         | Output in terms of prjlist.txt root
#            |         | definitions
# -----------|---------|------------------------------------------
# Globals
my @gPrjList = ();

sub FindUsage() {
    print STDERR <<"END_USAGE";

This is requires.pl version $Version.

The script is printing all recursively required components and interfaces
based on REQUIRES and REQUIRES_JAVA sections in the component makefiles.

Usage: [perl] requires.pl <ComponentName> <ComponentName> ...

Example: perl requires.pl tmSpOsal tmCom/tst/dcomtest
END_USAGE
}

sub CheckEnvironment() {
    my $errors = 0;

    if ( !exists $ENV{_TMROOT} ) {
        print STDERR "Environment variable \'_TMROOT\' not defined\n";
        ++$errors;
    }
    if ( !exists $ENV{MAKE} ) {
        print STDERR "Environment variable \'MAKE\' not defined\n";
        ++$errors;
    }
    #else {
    #    my $shortcmd= $ENV{MAKE};
    #    my $gmakeout= `$shortcmd -v 2>&1`;
    #    $shortcmd =~ s{^([^ ]*).*}{$1}xms; # cut off cmd line options to make
    #    if ( $gmakeout =~ m{.* $shortcmd .*}xms ) {
    #        print STDERR "Make utility \'$shortcmd\' not found in path\n";
    #        ++$errors;
    #    }
    #}
    if ( !exists $ENV{_TMSITE} ) {
        print STDERR "Environment variable \'_TMSITE\' not defined\n";
        ++$errors;
    }
    if ( !exists $ENV{UNAME} ) {
        print STDERR "Environment variable \'UNAME\' not defined\n";
        ++$errors;
    }
    if ( !exists $ENV{_TMTGTCPUCLASS} ) {
        print STDERR "Environment variable \'_TMTGTCPUCLASS\' not defined\n";
        ++$errors;
    }
    if ( !exists $ENV{_TMTGTOSCLASS} ) {
        print STDERR "Environment variable \'_TMTGTOSCLASS\' not defined\n";
        ++$errors;
    }
    if ( $errors > 0 ) {
        exit 1;
    }
}

# Expands all environment variables in given string
sub ExpandEnvVar {
    my ($line) = @_;

    # Matches environment variable, e.g., $_TMROOT
    my $envvar = qr{
            \$           # Start of environment variable
            ([^/\\:\s]*) # Capture until fw slash, bckslash, : or whitespace
          }xms;

    $line =~ s{$envvar}{$ENV{$1}}gxms;
    return $line;
}

# Load _TMROOT/project/prjlist.txt
# prjlist.txt contains all projects separated by whitespace
# Comments preceded by "#" are allowed
# Environment variables in project names are NOT expanded, i.e.,
# expansion of environment variables needs to be done when needed.
# Projects that actually exist are put in @gPrjList
sub LoadPrjList {
    return if (@gPrjList);

    # Matches comments
    my $comments = qr{
            \#           # Start with hash
            [^\n]*       # Followed by any charactor but \n
          }xms;

    my $filename
        = File::Spec->catfile( $ENV{_TMROOT}, "project", "prjlist.txt" );
	    my @prjlines = ();

    open my $fh, '<', $filename or die "Can't open '$filename'";
    while (<$fh>) {
        # Remove comments
        s{$comments}{}gxms;
        push @prjlines, split /\s+/;
    }
    close $fh or die "Can't close '$filename' after reading";

    # Expand environment variables and check existence
    foreach my $projshort (@prjlines) {
        my $projfull = ExpandEnvVar( $projshort );
        if ( -e $projfull ) {
            push @gPrjList, $projshort;
        }
    }

    # Reverse order so the same search order is used as
    # SDE2 itself uses.
    @gPrjList = reverse @gPrjList;
    return;
}

# Load project list and check if given component exists
# in one of the projects' comps or intfs directories.
# Returns directory where the component is found.
sub FindFileLocation {
    my ($Location) = @_;

    LoadPrjList();

    # first check intfs directories
    foreach (@gPrjList) {
        my $DirName =
            File::Spec->catfile( ExpandEnvVar($_), "intfs", "I".$Location );
        if ( -e $DirName ) {
            return File::Spec->catfile( $_, "intfs", "I".$Location );
        }
    }
    # then check comps directories
    foreach (@gPrjList) {
        my $DirName =
            File::Spec->catfile( ExpandEnvVar($_), "comps", $Location );
        #print "Checking existence \"$DirName\".\n";
        if ( -e $DirName ) {
            return File::Spec->catfile( $_, "comps", $Location );
        }
    }
	    foreach (@gPrjList) {
        my $DirName =
            File::Spec->catfile( ExpandEnvVar($_),, $Location );
        #print "Checking existence \"$DirName\".\n";
        if ( -e $DirName ) {
            return File::Spec->catfile( $_,, $Location );
        }
    }
    return "Not found!";
}

sub CompExists {
    my ($comp) = @_;

    return ( FindFileLocation($comp) ne "Not found!" );
}

# Provided with the full path to a component, this routine
# returns all components and interfaces that are listed in
# the REQUIRES section of that component's makefile.
# When supplied with an interface name, an empty string is
# returned: interfaces do not have makefiles.
sub RequiresFromMakefile {
    my ($makelocfull) = @_;
    my $requires;
    my $makeprocess;

    # Spawn gmake
    open $makeprocess,
        "$ENV{MAKE} -C $makelocfull -f makefile var=REQUIRES _sde_print_var |"
        or die "Can't start $ENV{MAKE}\n";

    # Read the output of the gmake process
    while ( defined( my $line = <$makeprocess> ) ) {
        chomp $line;
        if ( $line =~ /^\s*REQUIRES\s*=\s*/ ) {
            # filter out the comps and intfs from the REQUIRES line
            $line =~ s/^\s*REQUIRES\s*=\s*//;
            $line =~ s/\s*$//g;
            $line =~ s/\s+/ /g;
            $requires = $line;
        }
    }
    close $makeprocess;
    #print "$makelocfull:\n    $requires\n";
    return split /\s+/, $requires;
}

sub RequiresTransitive {
    my ($makelocfull) = @_;

    my @reqrs_list = RequiresFromMakefile($makelocfull);

    # Remove non-existing components from list
    for (0..$#reqrs_list) {
        if ( not CompExists($reqrs_list[$_]) ) {
            my $rem = splice @reqrs_list, $_, 1;
            #print "Removed $rem\n";
        }
    }

    # Recurse into requires to find more
    foreach my $compname (@reqrs_list) {
        
        print " Working on $compname ...";
        my $makelocshort = FindFileLocation($compname);
        print " $makelocshort\n";
    
        my $makelocfull = ExpandEnvVar($makelocshort);
        unless ( -e File::Spec->catfile($makelocfull, "makefile") 
                 && -r $makelocfull ) {
            #print "skip $makelocshort\n";
            next; #typically happens for interfaces
        }
        
        my @sublist = RequiresFromMakefile($makelocfull);
        foreach my $item (@sublist) {
            # Check if items from sub list are also present
            # in the requires list; if not add (if existent)
            if ( not grep { m{\b$item\b}xms } @reqrs_list ) {
                if ( CompExists($item) ) {
                    #print "Add $item\n";
                    push @reqrs_list, $item;
                }
            }
        }
    }
    return @reqrs_list;
}

# Expand component path to full, absolute path
# Input may be:
#   - full path (that is simple)
#   - relative path to current directory
#   - component name that must be looked up in prjlist.txt entries
sub ExpandCompPath {
    my ($arg) = @_;
    # clean up path
    $arg = File::Spec->canonpath($arg);
    
    # if path is already absolute, nothing to do ...
    if (not File::Spec->file_name_is_absolute($arg)) {
        # it could be a relative path, if so expand it 
        my $fullpath = File::Spec->rel2abs($arg);
        if (-e $fullpath) {
            $arg = $fullpath;
        }
        else {
            # finally, it could be a path relative to
            # one of the entries in the prjlist.txt
            if ( CompExists($arg) ) {
            $arg = ExpandEnvVar(FindFileLocation($arg));
            }
            else {
                die "The makefile in $arg could not be found!\n";
            }
        }
    }    
    return $arg;
}

#start main program
{
    my @components = @ARGV;

    if ( @components == 0 ) {
        FindUsage();
        exit 0;
    }

    CheckEnvironment();

    foreach my $comp (@components) {
        my $fullcomppath = ExpandCompPath($comp);
        my @requires = RequiresTransitive($fullcomppath);

        if ( @requires ) {
            print "\n$comp transitively requires:\n";
        }
        else {
            print "No REQUIRES sections found\n";
            exit 1;
        }

        @requires = reverse sort map {FindFileLocation($_) } @requires;
        
        foreach (@requires) {
            print $_ . "\n";
        }
    }
    exit 0;
}
