#! perl
########################################################################
# Copyright (C) 2006 ERG Limited, All rights reserved
#
# Module name   : jats_buildlinux.pl
# Module type   : Makefile system
# Compiler(s)   : n/a
# Environment(s): jats
#
# Description   : A utility to assist in building Linux Device Driver
#                 This script implemenst the runtime functionality of the
#                 MakeLinuxDriver directive
#
# Usage:
#
#......................................................................#

require 5.006_001;
use strict;
use warnings;
use Cwd;
use FileUtils;
use JatsSystem;
use JatsError;
use ArrayHashUtils;
use File::Path;
use File::Copy;
use Getopt::Long;
use Pod::Usage;                             # required for help support

#
#   Initialise and configure some of the used packages
#
ErrorConfig( 'name' => 'BuildLinuxDriver' );
SystemConfig ( 'ExitOnError' => 1);
InitFileUtils();

#
#   Global variables
#
my $VERSION = "1.0.0";                      # Update this
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_help = 0;
my $opt_manual = 0;
my $opt_output;
my $opt_kernel;
my $opt_gcc_path;
my $opt_arch;
my @opt_source;
my $opt_driver;
my $opt_leavetmp;
my $opt_show_cmds;
my $opt_type = 'P';
my $opt_platform;
my @opt_defs;
my @opt_incdirs;

#
#   Extract arguments
#
my $result = GetOptions (
                "help+"         => \$opt_help,              # flag, multiple use allowed
                "manual"        => \$opt_manual,            # flag, multiple use allowed
                "verbose+"      => \$opt_verbose,           # flag, multiple use allowed
                "Output=s"      => \$opt_output,
                "GccPath=s"     => \$opt_gcc_path,
                "Arch=s"        => \$opt_arch,
                "Driver=s"      => \$opt_driver,
                "LeaveTmp:s"    => \$opt_leavetmp,
                "Verbose:s"     => \$opt_show_cmds,
                "Type=s"        => \$opt_type,
                "Platform=s"    => \$opt_platform,
                "Define=s"      => \@opt_defs,
                "Define=s"      => \@opt_defs,
                "Incdir=s"      => \@opt_incdirs,
                );

#
#   Process help and manual options
#
pod2usage(-verbose => 0, -message => "Version: $VERSION") if ($opt_help == 1 || ! $result);
pod2usage(-verbose => 1) if ($opt_help == 2 );
pod2usage(-verbose => 2) if ($opt_manual || ($opt_help > 2));
#pod2usage(-verbose => 0, -message => "Version: $VERSION") if ( $#ARGV >= 0 );

#
#   Sanity Test user input
#
Error ("No Output File specified") unless ( $opt_output );

#
#   Locate the Kernel Headers
#   This will be provided by one of the external packages
#   Paths to the packages will be in @opt_incdirs
#   Look for a dir ectory with a 'Makefile' - use the first one
#
my $KERNELDIR;
foreach  ( @opt_incdirs )
{
    my $test_dir = $_ . '/' . $opt_platform;
    next unless ( -d $test_dir );
    next unless ( -f $test_dir . '/Makefile' );
    $KERNELDIR = $test_dir;
}
Error ("Could not find the Kernel Headers",
       "Need a package that contains include/$opt_platform/Makefile"
      ) unless ( $KERNELDIR );


#
#   Calculate required bits
#   The generated file. Based on the name of the desired output file and the
#   source path.
#
my $gen_file = StripDir($opt_output);

#
#   Cross-compiler toolchain root
#   Based on GccPath, remove the gcc bit
#
my $CROSS_COMPILE = $opt_gcc_path;
   $CROSS_COMPILE =~ s~-gcc$~-~;

#
#   The required architecture
#
my $ARCH;
   $ARCH = 'arm'  if ( $opt_arch =~ m/arm/i );
   $ARCH = 'i386' if ( $opt_arch =~ m/i386/i );
   $ARCH = 'powerpc' if ( $opt_arch =~ m/powerpc/i );
   Error ("Cannot determine architecture") unless ( $ARCH );

#
#   Source files
#   These may be comma seperated. Expand the array
#
@opt_source = @ARGV;
Error ("No source files specified" ) unless ( @opt_source );

#
#   Must have a nice name for the driver
#
Error ("No driver name specified") unless ( $opt_driver );


Message    "======================================================================";
Message    "Build Linux Device Driver using the native build method";
Message    "         Driver : $opt_driver";
Message    "          Arch  : $ARCH";
Message    "          Type  : $opt_type";
Message    "         Output : $opt_output";
Message    "       Platform : $opt_platform";
Message    " Cross Compiler : $CROSS_COMPILE";
Message    " Kernel Headers : $KERNELDIR";
Message    "         Source : @opt_source";
foreach  ( @opt_defs )
{
    Message    "         Define : $_";
}
foreach  ( @opt_incdirs )
{
    Message    "         Incdir : $_";
}

Message    "======================================================================";

#
#   Convert the list of source files into object files
#   Needed for cleaning
#
my @obj_files = map { $_ =~ s~\.\w+$~.o~; $_ } @opt_source;

#
#   Clean any existing build artifacts
#   The build options to place things in a different diractory are broken.
#   We must be able to build for multiple targets without confusion so we
#   clean, then build, then copy out the bits we need.
clean();

#
#   Generate a Makefile for use by the driver Builder
#   This encapsulates the complete driver build mechanism
#
create_makefile();

#
#   Remove some 'make' environment variables
#   The JATS make and the PACKAGE make must not know about each other
#
foreach my $var (qw ( MAKE MAKEFLAGS MAKEOVERRIDES MAKELEVEL MAKE_MODE
                     CC CXX CPP EXTRA_CFLAGS CFLAGS ASFLAGS LDFLAGS
                     DEBFLAGS LDDINC DEBUG KERNELRELEASE LDDINC
                     ))
{
    delete $ENV{$var};
}

#
#   Build up the make command
#   Create an array of arguments - Don't need to escape funny bits
#
my @make_args = ();
push @make_args, "ARCH=$ARCH";
push @make_args, "CROSS_COMPILE=$CROSS_COMPILE";
push @make_args, "KERNELDIR=$KERNELDIR";
push @make_args, "-f", "Kbuild";
push @make_args, "V=1" unless ( $opt_show_cmds );

System ( 'make', @make_args );

Error  ("Generated output module file not found: $gen_file") unless ( -f $gen_file );
copy ( $gen_file, $opt_output ) || Error ("Cannot copy output file: $opt_output");
clean() unless $opt_leavetmp;

Message "Script complete";
exit 0;

#-------------------------------------------------------------------------------
# Function        : create_makefile
#
# Description     : Generate a makefile called Kbuild
#                   This will be used by the kernel builder to do the hard work
#
#                   The makefile is created via a template embedded in this file
#
# Inputs          : None
#
# Returns         : Creates a file called Kbuild
#
sub create_makefile
{
    #
    #   Create variables for the data that will be substituted
    #
    my $driver_line = "obj-m	:= $opt_driver.o";

    my $file_list = "${opt_driver}-objs := @obj_files";
    my $debug_line = "DEBUG = y";
       $debug_line = "# " . $debug_line unless ( $opt_type eq 'D' );


    my $filename = "Kbuild";
    unlink ($filename);
    open (KBUILD, ">", $filename ) || Error ("Cannot generate file: $filename");
    while ( <DATA> )
    {
        $_ =~ s~\s+$~~;

        #
        #   Replace the entire line
        #
        $_ = $debug_line if ( m~__DEBUG_LINE__~ );
        $_ = $driver_line if ( m~__DEVICE_NAME__~ );
        $_ = $file_list if ( m~__FILE_LIST__~ );
        last if ( m~__END__~ );

        if ( m~__DEFINES__~ )
        {
            print KBUILD "# User definitions\n";
            foreach my $line ( @opt_defs )
            {
                print KBUILD "EXTRA_CFLAGS += -D$line\n";
            }
            print KBUILD "# End of User definitions\n";
            next;
        }

        if ( m~__INCDIR__~ )
        {
            print KBUILD "# Search Include Directories\n";
            foreach my $line ( @opt_incdirs )
            {
                if ( ! -d $line )
                {
                    Verbose("Incdir does not exist: $line");
                    next;
                }

                if ( $line =~ m~^/~ )
                {
                    print KBUILD "EXTRA_CFLAGS += -I$line\n";
                }
                else
                {
                    print KBUILD "EXTRA_CFLAGS += -I\$(PWD)/$line\n";
                }
            }
            print KBUILD "# End of User Include Directories\n";
            next;
        }
        

        print KBUILD "$_\n";
    }

    close (KBUILD);

}

#-------------------------------------------------------------------------------
# Function        : clean
#
# Description     : Clean up build artifacts
#                   Need to remove stuff that we build as the the kernel builder
#                   doesn't handle multiple platforms
#
# Inputs          : Globals: @obj_files
#
# Returns         : Nothing
#
sub clean
{
    foreach my $files (qw (*.o *~ core .depend .*.cmd *.ko *.mod.c Kbuild ), @obj_files )
    {
        unlink ( glob ( $files ));
    }
    rmtree  ( '.tmp_versions' );
}

__DATA__
#
#   This is a generated file
#   Do not edit or check into version control
################################################################################

# Comment/uncomment the following line to disable/enable debugging
DEBUG = y __DEBUG_LINE__

__DEFINES__

__INCDIR__

# Add your debugging flag (or not) to EXTRA_CFLAGS
ifeq ($(DEBUG),y)
  DEBFLAGS = -O -g -DDEV_DEBUG # "-O" is needed to expand inlines
else
  DEBFLAGS = -O2
endif

EXTRA_CFLAGS += $(DEBFLAGS)
EXTRA_CFLAGS += -I$(LDDINC)

ifneq ($(KERNELRELEASE),)
# call from kernel build system

llcd-objs := __FILE_LIST__
obj-m	:= __DEVICE_NAME__

else

KERNELDIR ?= "-- Must be defined by calling makefile"
PWD       := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules

endif

depend .depend dep:
	$(CC) $(EXTRA_CFLAGS) -M *.c > .depend


ifeq (.depend,$(wildcard .depend))
include .depend
endif

__END__

#-------------------------------------------------------------------------------
#   Documentation
#

=pod

=for htmltoc    MAKEUTIL::

=head1 NAME

jats_buildlinux - Build a Linux Device Driver

=head1 SYNOPSIS

perl jats_buildlinux [options] source_files+

 Options:
    -help              - brief help message
    -help -help        - Detailed help message
    -man               - Full documentation
    -Output=path       - Path to the output file
    -Kernel=path       - Path to the Kernel Headers
    -GccPath=path      - Path to the gcc compiler to use
    -Arch=name         - Architecture name
    -Driver=name       - Base name of the driver to build
    -LeaveTmp=num      - Leave tmp files after build
    -Verbose=num       - Verbose command output
    -Type=[P|D]        - Type of build
    -Platform=name     - Target Platform
    -Define=text       - Definitions to pass to compiler
    -Incdir=path       - Extend the header file search path
    

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exits.

=item B<-help -help>

Print a detailed help message with an explanation for each option.

=item B<-man>

Prints the manual page and exits.

=back

=head1 DESCRIPTION

This program is used internally by Jats to implement the body of the MakeLinuxDriver
directive. The command is not intended to be called by the user.

=head1 EXAMPLE

    MakeLinuxDriver ('*', 'llcd.ko',
                          '--KernelPackage=linux_kernel_headers', @CSRCS );
 

=cut

