Subversion Repositories DevTools

Rev

Rev 6177 | Blame | Compare with Previous | Last modification | View Log | RSS feed

# -*- mode: perl; tabs: 8; indent-width: 4; show-tabs: yes; -*-
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
#
# Module name   : Makelib.pl
# Module type   : Makefile system
#
# Description:
#       This modules builds the primary makefile for each directory,
#       used in conjunction with Makelib.pl2.  The produced makefile
#       acts as a frontend to platform builds.
#
# Notes:
#
#                       *** DO NOT DETAB ***
#
#       Beware the use of space v's tab characters within the
#       makefile generation sessions.
#
#.........................................................................#

require 5.006_001;
use strict;
use warnings;
use Getopt::Long;
use JatsError;
use JatsEnv;
use JatsMakeInfo qw(:create);
use ToolsetFiles;

our $MakelibVersion         = "2.33";           # makelib.pl version

our $ScmRoot;
our $ScmSrcDir              = "";
our $ScmMakelib             = "";
our @ScmDepends             = ();
our $ScmExpert              = 0;
our $ScmAll                 = 0;
our $ProjectBase            = "";               # Base of the user's project
our $ScmInterface           = "interface";

our @SUBDIRS                = ();
our @PLATFORMS              = ();
our %PLATFORMARGS           = ();
our @DEFINES                = "";
our @RULES                  = ();

our %PACKAGE_DIST           = ();

our $ROOTMAKEFILE           = 0;
our $PLATFORMINCLUDED       = 0;
our @BUILDPLATFORMS         = ();

our $GBE_CONFIG;
our $GBE_TOOLS;
our $GBE_PERL;

#.. Running under 'buildlib.pl' ?
#       This will be the 'require .../makelib.pl' that is present in
#       the build.pl files. Yes, its overly complicated, but it was
#       the way it was done.
#
unless ( $::ScmBuildlib )
{
    MakeLibInit();
}

sub MakeLibInit
{
    #.. Test environment
    #
    EnvImport( "GBE_BIN" );
    EnvImport( "GBE_PERL" );
    EnvImport( "GBE_TOOLS" );
    EnvImport( "GBE_CONFIG" );
    EnvImport( "GBE_MACHTYPE" );

    #.. Common stuff
    #
    require "$::GBE_TOOLS/common.pl";

    CommonInit( "makelib " );
    Debug( "version:   $MakelibVersion" );

    #.. Parse command line
    #       makefile.pl  rootdir Makelib.pl [options ...]
    #
    Verbose ("Command Line: @ARGV");
    my $opt_help = 0;
    my $result = GetOptions (
                "help+"         => \$opt_help,
                "interface=s"   => \$::ScmInterface,
                );

    MLUsage() if ( $opt_help || !$result );

    #
    #   Needs 2 command line arguments
    #
    $::ScmRoot    = StripDrive( ${ARGV[0]} );
    $::ScmRoot    = '/./' if ( $::ScmRoot eq '/'  );    # Prevent leading '//'
    $::ProjectBase= $::ScmRoot;
    $::ScmMakelib = ${ARGV[1]};

    Debug( "ARGV:      @ARGV" );
    Debug( "Root:      $::ScmRoot" );
    Debug( "Makelib:   $::ScmMakelib" );

    #.. Get the stuff from the platform definition file
    #
    ConfigLoad();

    #.. Get the stuff from the package definition file
    #
    require "$::ScmRoot/package.pl"
        if ( -f "$::ScmRoot/package.pl" );
}

#   MLUsage ---
#       Makelib command line usage.
#..

sub MLUsage
{
    Error ( "Usage: perl makefile.pl <ROOTDIR> <makelib.pl> [options ...]",
            "Valid options:",
            "   --help            Display Help",
            "   --interface=name  Set interface directory",
            );
}

#-------------------------------------------------------------------------------
# Function        : SubDir
#
# Description     : Recurse into the specified sub directories
#                   This is one of the very few directives in a 'makefile.pl'
#                   that is processed by this script - all the others are
#                   processed by makelib.pl2.
#
#                   This directive MUST appear before the 'Platform' directive
#
# Inputs          : List of sub directories to visit
#
# Returns         : Nothing
#
sub SubDir
{
    my( @NewDirs );
    Debug( "SubDir(@_)" );
    Error ("Directive 'SubDir' not allowed in this context") if ( $::ScmBuildlib  );

    #
    #   Support different constructs:
    #       'dir1 dir2'
    #       'dir1','dir2'
    #
    @NewDirs = map { split /\s+/ } @_;
    @NewDirs = grep { defined $_ } @NewDirs;

    foreach my $ThisDir ( @NewDirs )
    {
        $ThisDir =~ s~/+$~~;
        Warning ("SubDir contains a '\\' character: $ThisDir" )
            if ( $ThisDir =~ m~\\~);

        if ( grep /^$ThisDir$/, @::SUBDIRS )
        {
            Warning( "Duplicate SubDir '$ThisDir' -- ignored." );
            next;
        }
        if ( ! ( -e $ThisDir and -d $ThisDir ) )
        {
            Error( "SubDir(): Subdirectory not found: '$ThisDir'",
                   "Current directory: $::Cwd" );
        }
        if ( ! -f $ThisDir . '/makefile.pl' )
        {
            Error( "SubDir(): makefile.pl not found in subdirectory: '$ThisDir'",
                   "Current directory: $::Cwd" );
        }

        push(@::SUBDIRS, $ThisDir);
        ToolsetFiles::AddDir($ThisDir, 'SubDir');
    }
}


#-------------------------------------------------------------------------------
# Function        : RootMakefile
#
# Description     : This function is called from buildlib.pl prior to the
#                   generation of the root makefile. The Root Makefile is
#                   different to the others in this it does not have any platform
#                   specific makefiles associated with it. It is simply used to
#                   invoke the makefile in the 'src' subdirectory
#
# Inputs          : None
#
# Returns         : Nothing
#
sub RootMakefile
{
    Error ("Directive 'RootMakefile' not allowed in this context") if ( $::ScmBuildlib  );
    $::ROOTMAKEFILE = 1;
}


sub PackageDist
{
    my( $name, @elements ) = @_;
    Error ("Directive 'PackageDist' not allowed in this context") if ( $::ScmBuildlib  );

    foreach ( @elements ) {
        HashJoin( \%::PACKAGE_DIST, $;, $name, "$_" );
    }
}


sub Define
{
    Error ("Directive 'Define' not allowed in this context") if ( $::ScmBuildlib  );
    push( @::DEFINES, @_ );
}


sub Defines
{
    my( $path, $script ) = @_;
    my( $line );
    Error ("Directive 'Defines' not allowed in this context") if ( $::ScmBuildlib  );

    $script = Exists( $path, $script, "Defines" );
    open( my $fh, '<' ,$script ) ||
        Error( "cannot open '$script'" );
    while (<$fh>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @::DEFINES, $_ );
    }
    push( @::ScmDepends, "$script" );           # makefile dependencies
    close( $fh );
}


sub Rule
{
    Error ("Directive 'Rule' not allowed in this context") if ( $::ScmBuildlib  );
    push( @::RULES, @_ );
}


sub Rules
{
    Error ("Directive 'Rules' not allowed in this context") if ( $::ScmBuildlib  );
    my( $path, $script ) = @_;
    my( $line );

    $script = Exists( $path, $script, "Rules" );
    open( my $fh, '<' ,$script ) ||
        Error( "cannot open '$script'" );
    while (<$fh>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @::RULES, $_ );
    }
    push( @::ScmDepends, "$script" );           # makefile dependencies
    close( $fh );
}

#-------------------------------------------------------------------------------
# Function        : Platform
#
# Description     : Within "makelib.pl" the Platform directive is processed
#                   in such a manner as to trigger the verification and
#                   generation of Makefile and xxxxx.mk files in the tree
#                   below the makefile.pl
#
# Inputs          : A list of platforms for which the body of the makefile.pl
#                   are to be processed + GBE_PLATFORM
#
#                   "*", ...            - A wildcard for all platforms
#                                         Options include
#                                           --Targets (Not Products)
#                                           --Products
#                                           --NoPlatformBuilds
#                                           !xxx - Exclude a platform
#
#                   "--NoPlatformBuilds"
#
#                   "xxx"[, "yyy"]*     - A list of platforms and aliases
#
#                   "!xxx"              - A platform to be excluded
#
# Returns         :
#
sub Platform
{
    my ( @platforms ) = @_;
    my $exitVal;
    my $platform;
    my $noplatforms = 0;
    my $defplatforms = 0;
    my %platforms;

    Debug( "Platform(@_)" );
    Error ("Directive 'Platform' not allowed in this context") if ( $::ScmBuildlib  );

    #
    #   Import user platform specification (GBE_PLATFORM)
    #   Note: This is also used by the Makefile and it cannot be
    #         alias expanded or sanitised. Its not really really useful
    #
    #   Only use GBE_PLATFORM if the user has not specified to build ALL makefiles.
    #
    my %filter;
    my $filter_present = 0;

    if (  $::ScmAll == 0  )
    {
        $filter{GENERIC} = 1;
        foreach ( split( ' ', $ENV{ "GBE_PLATFORM" } || '' ) )
        {
            $filter{$_} = 1;
            $filter_present = 1;
        }
    }

    #
    #   Expand out the directive platform list (and optional arguments)
    #   Expand wildcards and aliases
    #
    #   Handle format
    #       Platform ('*', '--Options', [!Name]);
    #       Platform ('--NoPlatformBuilds' );
    #       Platform ('Name', '--Options', [!Name] );
    #       Platform ('!Name' );
    #
    #
    if ($::ScmNoBuild)
    {
        @platforms = ();                        # zap list
        $noplatforms = 1;
    }
    elsif ( $platforms[0] && $platforms[0] eq '*' )
    {
        my( $targets, $products );              # options
        my( @args );
        my( @exclude );

        $targets = $products = 0;

        foreach ( @platforms ) {
            next if ( /^\*$/);
            next if ( /^--Targets$/ && ($targets = 1));
            next if ( /^--Products$/ && ($products = 1));
            next if ( /^--NoPlatformBuilds/ && ($noplatforms = 1));
            next if ( /^--/ && push @args, $_ );
            next if ( /^!/  && push @exclude, $_ );
            Warning( "Platform: unknown option $_ -- ignored\n" );
        }

        #
        #   Determine the list of platforms to expand the '*' into
        #   The result may be modified by optional arguments
        #       --Targets           # Expands to all targets
        #       --Products          # Expands to all products
        #       OtherWise           # Expands to all build platforms
        #
        @platforms = ();                        # zap list

                                                # 'all' platforms
        push( @platforms, @::DEFBUILDPLATFORMS )
            unless ( $targets | $products );

        #
        #   Expand the '*' into a list of platforms that are NOT products
        #
        if ( $targets && ( %::ScmBuildPlatforms ) )
        {                                       # targets
            foreach my $key (keys %::ScmBuildPlatforms) {
                push( @platforms, $key )
                    if (! ( %::ScmBuildProducts ) ||
                            ! scalar $::ScmBuildProducts{ $key } );
            }
        }

        #
        #   Expand the '*' into a list of platforms that are 'products'
        #
        if ( $products && ( %::ScmBuildProducts ) )
        {                                       # products
            foreach my $key (keys %::ScmBuildProducts) {
                push( @platforms, $key );
            }
        }

        #
        #   Distribute arguments over all expanded platforms
        #
        if ( @args )
        {
            my @baseplatforms;
            foreach  ( @platforms )
            {
                push @baseplatforms, $_, @args;
            }
            @platforms = @baseplatforms;
        }

        #
        #   Place the excluded platforms at the end of the list
        #
        push @platforms, ExpandPlatforms( @exclude );

    }
    elsif ( scalar @platforms == 1 && $platforms[0] eq "--NoPlatformBuilds" )
    {                                           # short-cut
        $noplatforms = 1;
        @platforms = @::DEFBUILDPLATFORMS;
    }
    else
    {                                           # aliasing
        @platforms = ExpandPlatforms( @platforms );
        #
        #   Process excluded platform lists
        #   Migrate excluded platforms to the end of the list
        #
        my (@include, @exclude);

        foreach ( @platforms )
        {
            next if ( m/^!/ && push @exclude, $_ );
            push @include, $_;
        }

        #
        #   If no included platforms have been found then assume that the
        #   list is an exclusion list and seed the platform list with
        #   a set of defualt platforms - like '*'
        #
        #
        @include = @::DEFBUILDPLATFORMS unless @include;
        @platforms = ( @include, @exclude );
    }

    $platform = "";                             # current platform

    #
    #   Process the directives expanded list of platforms
    #

    #
    #   Generate a HASH of lowercase known platform names
    #   This will be used to quickly validate platform names
    #
    my %lc_platforms;
    foreach  ( @::BUILDPLATFORMS )
    {
        $lc_platforms{ lc($_) } = $_;
    }

FILTER:
    foreach $_ ( @platforms )
    {
        if ( ! /^--(.*)/ )
        {
            $_ =~ s/^\s*//g;                    # leading white space
            $_ =~ s/\s*(\n|$)//;                # trailing white space


            #
            #   Remove specific platforms from the list
            #
            $defplatforms = 1;
            if ( m/!(.*)/ )
            {
                Verbose( "Excluded platform removed: $1" );
                delete $platforms{$1};
                next FILTER;
            }


            if ( exists $platforms{$_}  )
            {
                Warning( "duplicate platform '$_' -- ignored." );
                $platform = "";
                next FILTER;
            }

            #
            #   validate 'platform'
            #   Allow the user to have a bad case match ( Fred == fred == FRED )
            #
            my $lc_platform = lc($_);
            unless ( exists( $lc_platforms{$lc_platform}  ) )
            {
                Warning( "Platform '$_' not contained within BuildPlatforms -- ignored." )
                    unless ( exists( $::ScmBuildMatrix{$_} ));
                $platform = "";
                next FILTER;
            }

            $lc_platform = $lc_platforms{$lc_platform};
            if ( $_ ne $lc_platform )
            {
                Warning( "Mixed case usage of platform '$_' -- corrected." );
                $_ = $lc_platform;
            }

                                                # filter 'platform'
            if ( $filter_present  )
            {
                if ( ! exists $filter{$_} )
                {
                    Verbose( "GBE_PLATFORM override $_ -- ignored" );
                    $platform = "";
                    next FILTER;
                }
            }

            #
            #   Platform not filtered out - must be using it.
            #
            Verbose( "Platform ... $_" );
            $platforms{$_} = 1;                 # Add to platform list
            $platform = $_;                     # new platform
        }

        elsif ( /^--NoPlatformBuilds$/ )
        {
            $noplatforms = 1;
        }

        elsif ( $platform ne "" )
        {                                       # other arguments
            Verbose( "          .. $_" );

            HashUniqueJoin( \%::PLATFORMARGS, $; , $platform, $1 ) ||
                Warning( "Duplicate argument '$platform=$_' -- ignored." );
        }
    }
    #
    #   Sort the platforms to ensure that the ordering is consistient throughout
    #
    @::PLATFORMS = sort keys %platforms;

    #
    #   Trap makefiles that don't have any platforms
    #   The user 'should' mark these as --NoPlatformBuilds
    #
    unless ( @::PLATFORMS )
    {
        Warning( "No platform definitions." )
            unless( $noplatforms || $defplatforms);
        $noplatforms = 1;
    }

#.. Common rules
#
    my( $file ) = Require( "$::GBE_CONFIG", "Rules", "Common rules " );
    push( @::ScmDepends, "$file" );


#.. Generate primary makefile
#
    $exitVal = Generate( $noplatforms || $::ROOTMAKEFILE );
    if ($::ROOTMAKEFILE == 1)
    {
        Warning ("WARNING: Problem generating Makefile ($exitVal)")
            if ($exitVal);
        return ($exitVal);
    }

    #
    #   Not creating the ROOT Makefile
    #   Exit the phase. All the work has been done
    #
    Debug( "Platform ExitVal:   $exitVal" );
    exit( $exitVal );
}

###############################################################################
# Private function section.
#       The following functions are used INTERNALLY by makelib.pl.
#
###############################################################################

#-------------------------------------------------------------------------------
# Function        : Generate
#
# Description     : Build makefiles ...
#                   Creates the command data files and the per-target .MK files
#
# Inputs          : $noplatforms    - 1 if this makefile does not have
#                                       any platforms. Also set for ROOTMAKEFILE
#
# Returns         : $exitVal
#
sub Generate
{
    my( $noplatforms ) = @_;
    my $exitVal = 0;

    #.. Build all the per-taget makefiles, if any exist
    #
    $exitVal = GeneratePlatforms()
        unless ($noplatforms );

    #.. Build the common Makefile.gbe
    #
    GenerateMakefile( $noplatforms );
    return $exitVal;
}

#-------------------------------------------------------------------------------
# Function        : GeneratePlatforms
#
# Description     : Generate all the per-taget makefiles
#                   Create all .mk files in the current directory
#
#                   Cleanup of unused .mk files is done when the WriteCommonInfo
#                   is processed. Makefiles that are no longer used will be purged
#                   
#                   Invoke makelib.pl2 as
#                       perl makefile.pl <root> makelib.pl2 <platform> <interface>
#
# Inputs          : None
#
# Returns         : exitVal
#
sub GeneratePlatforms
{
    my  $exitVal = 0;
    my  $exitVal2;

    foreach my $platform ( @::PLATFORMS )
    {
        my @CmdLine;
        push @CmdLine, $::GBE_PERL, $0, $::ScmRoot;
        push @CmdLine, $::ScmMakelib . "2";
        push @CmdLine, $platform;
        push @CmdLine, "--interface=$::ScmInterface" if ( $::ScmInterface ne "" );

        #
        #   Insert platform local arguments
        #
        if ($::PLATFORMARGS{ $platform })
        {
            foreach my $arg (split( /$;/, $::PLATFORMARGS{ $platform } )) {
                push @CmdLine,"--arg=$arg";
            }
        }

        #
        #   Invoke the command
        #   Don't use a Shell. Don't need the overhead
        #
        $exitVal2 = System( '--NoShell', @CmdLine );

        #
        #   Warn on any error
        #   Overall return code will be set if any platform fails, but we
        #   will process all of them first
        #
        if ( $exitVal2 )
        {
            Warning ("Problem generating $platform.mk in $::Cwd ($exitVal2)");
            $exitVal = $exitVal2;
        }
    }

    return $exitVal;
}

#-------------------------------------------------------------------------------
# Function        : GenerateMakefile
#
# Description     : Generate the non-platform specific makefile
#                   Once upon a time this was a real makefile
#                   Now its a simple file and a database as this offers greater
#                   flexability.
#
#                   The file, "Makefile.gbe" contains enough info to get to the
#                   interface directory.
#
#                   The "database" is held in the interface directory as
#                       Makfile.cfg     - index to locate Makefile_nn.cfg
#                       Makefile_nn.cfg - data from a makefile.pl
#
# Inputs          : $noplatforms        - 1: no platform makefiles in this dir
#
# Returns         : Nothing
#
sub GenerateMakefile
{
    my( $noplatforms ) = @_;
    my %platform_info;

    #
    #   Determine targets that are limited to either debug or production builds
    #   This information may come from several places
    #
    foreach my $key ( @PLATFORMS )
    {
        my @args;
        UniquePush (\@args, split ( /$;/, $::PLATFORMARGS{$key} ))
            if ($::PLATFORMARGS{$key});

        UniquePush (\@args, map { s/^--//; $_} split ( /$;/, $::ScmBuildPlatforms{$key} ))
            if($::ScmBuildPlatforms{$key});

        my $hasOnlyProd  =  grep /^OnlyProd/, @args;
        my $hasOnlyDebug =  grep /^OnlyDebug/, @args;

        if ( $hasOnlyProd && $hasOnlyDebug )
        {
            Warning ("Target \"$key\" has both OnlyDebug and OnlyProd options",
                     "Both options ignored. Target will be built" );

            $hasOnlyProd = $hasOnlyDebug = 0;
        }
        $platform_info{$key}{prod}  = 1 unless $hasOnlyDebug;
        $platform_info{$key}{debug} = 1 unless $hasOnlyProd;
    }

    #
    #   Update common information in the Makefile_x.cfg file
    #
    WriteCommonInfo( \@::SUBDIRS, \%platform_info, $noplatforms, $::ROOTMAKEFILE );

    #
    #.. Maintain local information in Makefile.gbe
    #   This allows us to locate the interface directory
    #
    Message "[Control] $::Cwd";
    CreateMakeInfo();
}

1;