Subversion Repositories DevTools

Rev

Rev 227 | Rev 255 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

# -*- mode: perl; tabs: 8; indent-width: 4; show-tabs: yes; -*-
# Copyright (C) 1998-2004 ERG Transit Systems, 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.6.1;
use strict;
use warnings;
use JatsEnv;
use JatsMakeInfo qw(:create);

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

our $Generated              = 10;               # Generated error code ..

our $ScmRoot                = "";
our $ScmSrcDir              = "";
our $ScmMakelib             = "";
our @ScmDepends             = ();
our $ScmForce               = 0;
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         = ();


#.. Running under 'buildlib.pl' ?
#
unless ( $::ScmBuildlib )
{
    MakeLibInit();
}

sub MakeLibInit
{
    my ( $argc );                               # argument count

#.. 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 ...]
#
    $::ScmRoot    = StripDrive( ${ARGV[0]} );
    $::ProjectBase= $::ScmRoot;
    $::ScmMakelib = ${ARGV[1]};

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

    $argc = 2;
    while ($_ = $ARGV[ $argc++ ])
    {
        last if ( ! /^-/ );                     # end of options

        Debug( "option:    $_" );

        /^-h$|^-help$|^--help$|^--usage$|^-?$/ && MLUsage();

                                                # long arguments
        if (/^--interface=(.*)/) {
            $::ScmInterface = $1;
            Debug( "Interface: $::ScmInterface" );
            next;
        }

        if (/^--expert(.*)/) {
            if ($1) {
                $::ScmExpert = $1;
            } else {
                $::ScmExpert = 1;
            }
            Debug( "Expert:    $::ScmExpert" );
            next;
        }

        if (/^--all/) {
            $::ScmAll = 1;
            Debug( "All:       $::ScmAll" );
            next;
        }

        last if (/^--$/);                       # end of arguments

        if (/^-D(.*)/) {
            if ($1) {
                $::ScmDebug = $1;
            } else {
                $::ScmDebug = $ARGV[0];
                $argc++;
            }
            Debug( "ScmDebug:  $::ScmDebug" );
            next;
        }

        next if (/^-V/ && ($::ScmVerbose = 1));

        next if (/^-F/ && ($::ScmForce   = 1));

        if (/^-.*/) {                           # unknown option
            MLUsage( "unknown option '$_'\n\n" );
        }
    }


#.. 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
{
    my( $msg ) = @_;

    print "makefile $msg\n\n"
        if ( $msg ne "" );

    print "Usage: perl makefile.pl <ROOTDIR> <Makelib.pl> [options]\n
Valid options:
\t-V                Verbose mode.
\t-D<debug-level>   Set the debug level.
\t                  except all SCM support files are removed (eg CVS).
\n";
    exit(42);
}

#-------------------------------------------------------------------------------
# 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 )
    {
        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);
    }
}


#-------------------------------------------------------------------------------
# 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( SCRIPT, $script ) ||
        Error( "cannot open '$script'" );
    while (<SCRIPT>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @::DEFINES, $_ );
    }
    push( @::ScmDepends, "$script" );           # makefile dependencies
    close( SCRIPT );
}


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( SCRIPT, $script ) ||
        Error( "cannot open '$script'" );
    while (<SCRIPT>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @::RULES, $_ );
    }
    push( @::ScmDepends, "$script" );           # makefile dependencies
    close( SCRIPT );
}

#-------------------------------------------------------------------------------
# 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 ( $dir, $exitVal, $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 GB_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 ( $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 && defined( %::ScmBuildPlatforms ) )
        {                                       # targets
            foreach my $key (keys %::ScmBuildPlatforms) {
                push( @platforms, $key )
                    if (! defined( %::ScmBuildProducts ) ||
                            ! scalar $::ScmBuildProducts{ $key } );
            }
        }

        #
        #   Expand the '*' into a list of platforms that are 'products'
        #
        if ( $products && defined( %::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( $::ScmBuildPlatforms{$_} ));
                $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 );
    if ($::ROOTMAKEFILE == 1) {
        print STDERR "WARNING: problem generating Makefile ($exitVal)\n"
            unless (($exitVal == $::Generated) || ($exitVal == 0));
        return ($exitVal);
    }

    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
#
# Returns         : $exitVal
#
sub Generate
{
    my( $noplatforms ) = @_;
    my( $exitVal ) = 0;

    #.. Dont build platforms within root directory
    #
    $noplatforms = 1
        if ($::ROOTMAKEFILE == 1);

    #.. Build 'makefile'
    #
    $exitVal = GeneratePlatforms()
        unless ($noplatforms );

    GenerateMakefile( $noplatforms );
    $exitVal = $::Generated if ( $exitVal == 0);

    return $exitVal;
}


#
#   Note:
#   Currently this function will create all .mk files in the current directory
#
#   Note: Cleanup of unused .mk files is done when the WriteCommonInfo  is
#         processed. Makefiles that are no lonker used will be purged
#
sub GeneratePlatforms
{
    my( $exitVal, $exitVal2 ) = 0;

    foreach my $platform ( @::PLATFORMS )
    {
        my( $Cmdline ) = "$::GBE_PERL $0 $::ScmRoot " . $::ScmMakelib . "2" . " $platform";

        $Cmdline .= " --interface=$::ScmInterface"
            if ( $::ScmInterface ne "" );

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

        $Cmdline .= " --expert"
            if ( $::ScmExpert );

        $Cmdline .= " --all"
            if ( $::ScmAll );

        $exitVal2 = System( $Cmdline );

        Warning ("Problem generating $platform.mk in $::Cwd ($exitVal2)")
            unless (($exitVal2 == $::Generated) || ($exitVal2 == 0));

        if ($exitVal == 0) {
            $exitVal = $exitVal2;
        } elsif ($exitVal == $::Generated) {
            $exitVal = $exitVal2
                if ($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 offeres 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;