#!/bin/sh
#!/usr/bin/perl
#
# Usage: prlgcc.pl -personalities <personality_file_dir> [OPTION] -- COMPILER COMMAND LINE
# Example: prlgcc.pl -personalities /tmp/prlgcc -- g++ -std=c++11 
# 
# Additional options:
#         -cct                               PRLGCC will create a CCT rather than Personalities
#         -headers <substitute_header_dir>   Alternative location for substitute headers 
#         -macrofilter macro1[,...macroN]    List of macro identifiers to be ignored
#         -pcpaths                           Use \ as directory separator in generated personality
#         -manual                            Request manual selection of available configurations 
#         -display                           Print full name of created personality
#
# Devious magic to start Perl
eval 'exec perl -S -x "$0" ${1+"$@"}'
    if 0;

#######################################################################
# Change history
#
# 11/03/2015 Auto generate CCTs
#            
# 06/02/2015 Match GCC/G++ overwrite macro replacement lists with
#            subsequent macro definitions.  Create compiler_info.xml
#            to help with automatic generation of CCTs.
#            
# 23/09/2014 Support for gcc versions 4.9 onwards.  Command line
#            changed to pass additoinal parameters to the call of the
#            gcc/g++ binary.
#            
# 19/09/2012 Adding specification of the intrinsic type for 'size_t' and
#            'ptrdiff_t'
#            
# 12/03/2003 -c added to command line to suppress .o file
#            Separation of header files into g++-headers and 
#              gcc-headers
#            Remove checks for omitted include directories
#            switch to shortened versions of QA C[++] directives.
#
# 06/05/2003 New command line option to translate Unix paths to PC paths
#            Useful for analysing Unix source with PC GUI.
#
# 06/05/2003 New function to extract more comprehensive implicit defs
#            from compiler using gcc|g++ -E -dM <source>
# 
# 19/05/2003 makeHeaders only copies files / creates directories.
#            New function getSubSearchPaths builds a list of search
#            paths for substitute headers.
#
# 08/09/2003 New directory structure to allow different configurations
#            between product versions.
#
# 05/03/2004 Incorporate suggested improvements from Dedicated Systems
#            inc.
#            Support for cygwin mount points
#            Simplify capture of compiler details
#            Simplify generation of cannonical paths
#            Simplify path separator translation
#
# 01/01/2010 Added compiler flag back into script to accept command line comiler option
#            (Added support for later versions of gcc/g++ 4.0->4.4)
#            Added loop for multiple force include files exiting in the prlforcecinclude folder
#
# 29/11/2016 Prefix required commands with -Wp to allow use with
#            compiler wrappers.  Dropping -c as not required with -E.
#            Using __GNUC__ and __GNUC_MINOR__ to determine version of
#            compiler to use.  Many additional updates have been added
#            such as support for CCT generation since 2010.
# 
# 02/12/2016 Switching to use a single c++config.h header that
#            determines the correct libstdc++ version based on the
#            DATESTAMP specified in __GLIBCXX__.  Adding a second run
#            of the tool, this time with -v at the top level to
#            determine the target.  The processing of the command line
#            has been switched to keep the argv array.  This allows
#            for, amongst other things surrounding commands in quotes
#            if necessary.
#            
# 06/12/2016 Ver 5.0.  Handling of -include command line option.
#            Adding multiple passes to remove explicitly specified
#            command line -I and -D options to be removed.
# 
#######################################################################
use strict;
use warnings;
use FileHandle;
use Cwd;
use Cwd 'chdir';    # Keep $PWD up to date.
use File::Basename; # OS independent way to extract basename from path.
use File::Copy;     # OS independent file copy.
use File::Path;     # For makepath.
use Getopt::Long;   # To parse command line parameters
use File::Temp  qw/ tempfile tempdir /;     # For temporary filename

#####################################################################
# local scope variables visible to subroutines.
#####################################################################
our $prlgcc_version = "5.0";

&main();
exit 0;

sub main {
  #####################################################################
  # private scope variables not visible to subroutines.
  #####################################################################

  my @commandLine;         # full compiler command line
  my $subHeadDir;          # Directory for substitute header files.
  my $macroFilter;         # Commmand line string for the macro filter
  my %macroFilters;        # Hash of macro filters (split based on ,)
  my $personDir;           # Directory for personality files.
  my $usePCPaths;          # Translate Unix paths to PC format ?
  my $cygwin;              # Cygwin environment ?  Implies usePCPaths.
  my $createCCT;           # Create a CCT rather than Personalities
  my $manual;              # decide on compiler versions automatically instead of asking user
  my $display;             # display full generated personality file name at end
  my $createGeneric;       # specifies that we should use generic compiler names (eg. g++/gcc)

  my %includeDirPaths;     # Location of prlforceinclude and 3rdparty
  my @substituteHeaders;   # Paths for substitute headers.

  my @gccTargetQueryOptions=("-v", "-E");         # Options to gather target and host information.
  my @gccPreProQueryOptions=("-Wp,-v,-dM", "-E"); # Options to gather preprocessor information.

  &initialise(\@commandLine
              , \$personDir
              , \$subHeadDir
              , \$macroFilter
              , \$usePCPaths
              , \$createCCT
              , \$cygwin
              , \$manual
              , \$display
              , \$createGeneric);

  # If there's a macro filter then split it into our macroFilters hash
  if ($macroFilter) {
    foreach my $flt (split(',', $macroFilter)) {
      chomp($flt);
      $macroFilters{$flt} = 1;
    }
  }

  if ( $createCCT == 1 ) {
    # For CCTs, we detect the __GNUC_PATCHLEVEL__ and __VERSION__ macro
    # as part of the CIP generation so we add that macro name to our
    # filter.
    $macroFilters{'__GNUC_PATCHLEVEL__'} = 1;
    $macroFilters{'__VERSION__'} = 1;
  }

  my $tempFile = File::Temp->new(TEMPLATE => 'prlgccTempXXXXX'
                                 , SUFFIX => &getFileExt(\@commandLine) );
  ($tempFile)
      || die ("*** ERROR: Cannot create temporary file: $!\n");
  $tempFile->unlink_on_destroy(1);
  print ($tempFile "");
  my $compilerInfo = &getCompilerImplicitInfo (\@commandLine
                                               , \@gccPreProQueryOptions
                                               , $tempFile->filename
                                               , $createGeneric
                                               , \%macroFilters);


  {
    #
    # A final pass accesses the 'Target' reported by gcc and some variants to determine the host and platform.
    
    my $ph = FileHandle->new();  # request a new file handle.
    # Pipe output to file handle.
    my $prlgccCommandLine = &formCommandLine (\@commandLine, \@gccTargetQueryOptions, $tempFile->filename) ;
    if ( open ($ph, $prlgccCommandLine . " 2>&1 |") ) {
      &scanTargetOutput($ph, $compilerInfo);

      # The file handle and temp file are not needed anymore
      close($ph);
    }
  }

  # Create the output name (stored in $compilerInfo{'OUTPUT_NAME'}
  &createDestName ($createCCT, $compilerInfo);


  # From now on we use the above information to create output
  my $compiler = $$compilerInfo->getCompilerInformation ()->{'COMPILER'};
  my $compilerFullVersion = $$compilerInfo->getCompilerInformation ()->{'COMPILER_FULL_VERSION'};
  my $compilerOutputPrefix = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'};
  my $compilerOutputExt = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_EXT'};
  my $compilerDataPrefix = $$compilerInfo->getCompilerInformation ()->{'DATA_PREFIX'};

  print("Creating configuration for $compiler($compilerFullVersion) using ".
        "$compiler template.\n");


  # Copy and create links to the substitute headers
  &copySupportFiles ($subHeadDir
                     , $createCCT
                     , \%includeDirPaths
                     , \@substituteHeaders
                     , $compilerInfo);
  
  # Process command line extentions
  my $extensions = &processExtensions ($compilerInfo);

  if ($display != 0)
  {
    # User has asked for the output filename so provide it
    print("[[$personDir/$compilerOutputPrefix.$compilerOutputExt]]\n");
  }

  print("Configuring compiler personality.\n");
  $personDir=File::Spec->canonpath($personDir);

  # Create our personality or CCT file
  &configurePersonality($compilerInfo
                        , \%includeDirPaths
                        , $personDir
                        , \@substituteHeaders
                        , \$extensions
                        , $usePCPaths
                        , $cygwin);


  if ( $createCCT != 1 ){
    # If we're creating personalities, then we include an XML descrition
    # with the CCT
    &createCompilerInfo($personDir, $compilerInfo);
  } else {
    # Otherwise, the final step is to copy the python search script into
    # the Scripts directory.
    &createSearchScript($personDir, $compilerInfo);
  }

  print("Compiler personality configuration complete.\n");
}

#####################################################################
# Function : initialise
# Parameters : args - a copy of the command line arguments
# Description : Check command line, extract parameters
#               and prompt if improperly used etc.
# Returns : Com
#           and personality dir.
#               
#####################################################################

sub initialise {
  my $commandLine = shift;
  my $personDir = shift;
  my $headerDir = shift;
  my $macroFilter = shift;
  my $usePCPaths = shift;
  my $createCCT = shift;
  my $cygwin = shift;
  my $manual = shift;
  my $display = shift;
  my $generic = shift;

  $$headerDir="";
  $$usePCPaths = 0;
  $$createCCT = 0;
  $$cygwin = 0;
  $$manual = 0;        
  $$display = 0;        
  $$generic = 0;        

  GetOptions("personalities=s" => $personDir,    # compulsory string output path
             "headers=s"       => $headerDir,    # optional alternative substitute header path
             "macrofilter=s"   => $macroFilter,  # optional filter for macro identifiers
             "pcpaths"         => $usePCPaths,   # optional boolean flag to use \ on windows
             "cct"             => $createCCT,    # create a CCT rather than a personality
             "manual"          => $manual,       # allow manual selection of the compiler version
             "display"         => $display,      # display personality filename
             "generic"         => $generic);     # if true then we are being run for generic builds (use gcc/g++)

  &filterCommandLine (\@ARGV, $commandLine);

  # Test for compulsory parameters
  if ( (! @$commandLine ) || ! defined ($$personDir) ) {
    usage();
  }

  chomp($$headerDir);
  chomp($$personDir);
  chomp($$usePCPaths); 
  chomp($$cygwin);

  # if not header dir specified, use the personality dir
  if ( $$headerDir eq "" )
  {
    $$headerDir = $$personDir;
  }
  
  #Check the substitute header directory.
  # must be absolute path
  if ( (substr($$headerDir,0,1) ne "/") &&
       (substr($$headerDir,1,1) ne ":") )
  { 
    print(STDERR "The substitute header directory must be".
          " an absolute path.\n");
    usage();
  }
  checkDirs($$headerDir, "Header");

  #Check the personality directory.
  # must be absolute path
  if ( (substr($$personDir,0,1) ne "/") &&
       (substr($$personDir,1,1) ne ":") ) 
  { 
    print(STDERR "The personality file directory must be".
          " an absolute path.\n");
    usage();
  }
  checkDirs($$personDir, "Personality");

  # if OS is cygwin then ensure -pcpaths set
  if ($^O =~ /cygwin/) {
    $$cygwin = 1;
    $$usePCPaths = 1;
  }
}

# if the header or personality dirs given exist, check they are writable dirs
sub checkDirs() {
  my $path = $_[0];
  my $name = $_[1];

  if (-e "$path") {
    if(-d "$path" && -W "$path") {
      #do nothing
    } else {
      print(STDERR "The $name directory specified exists ".
            "but it is not a writable directory.\n");
      usage();
    }
  }
}

#####################################################################
# Function : usage
# Parameters : None
# Description : Display usage message and terminate
# Returns : Nothing.
#               
#####################################################################

sub usage {
  print "Usage: prlgcc.pl -personalities <personality_file_dir> [OPTION] -- COMPILER COMMAND LINE\n";
  print "Example: prlgcc.pl -personalities /tmp/prlgcc -- g++ -std=c++11 \n\n";
  print "Additional options:\n";
  print "\t-cct                               Generate a CCTs\n";
  print "\t-headers <substitute_header_dir>   Alternative location for substitute headers\n";
  print "\t-macrofilter macro1[,...macroN]    List of macro identifiers to be ignored\n";
  print "\t-pcpaths                           Use \\ as directory separator in generated personality\n";
  print "\t-manual                            Request manual selection of available configurations \n";
  print "\t-display                           Print full name of created personality\n";
  print "PRLGCC Version $prlgcc_version\n";
  print "Report bugs to: support\@programmingresearch.com\n";
  print "Home Page: www.programmingresearch.com\n";
  exit 1;
}


#####################################################################
# Function : getFileExt
# Parameters : commandLine - command line for gcc
# Description : Searches the command line for an -x option to use as
#               the explicit language
# Returns : .c for -xc and .cpp for -xc++
#               
#####################################################################

sub getFileExt {
  my $commandLine = shift;
  my $fileExt = '.c';
  if ( join(' ', @$commandLine) =~ m/\-x\s*c\+\+($|[^\-])/ ) {
    $fileExt = '.cpp';
  }
  return $fileExt;
}

#####################################################################
# Function    : fixIntrinsicType
# Parameters  : type
# Description : Change 'short int' to 'int' and 'long int' to 'long'
#               as the GUIs cannot handle this.
# Returns     : An intrinsic type compatible with the GUI
#####################################################################

sub fixIntrinsicType {
  my $type = shift;
  $type =~ s/short\s*int/short/;
  $type =~ s/long\s*long\s*unsigned/unsigned long long/;
  $type =~ s/long\s*unsigned/unsigned long/;
  $type =~ s/long\s*int/long/;
  return $type;
}

#####################################################################
# Function : scanPreProOutput
# Parameters : ph                    - file handle to preprocessor output.
#              commandLine           - an array holding the gcc command and arguments
#              prlgccCommandLine     - a string storing the command used to create ph
#              gccPreProQueryOptions - the options to use for querying settings
#              createGeneric         - true when CCT genertion is for generic CCT creation
#              macroFiltersRef       - macros to exclude from the .cct
#
# Description : Scan gcc compatible preprocessor output and extract
#                include paths (if configured), system include paths
#                and macros (-Dmacro...)
#
# Returns : A CompilerInfo object for this scan
#               
#####################################################################

sub scanPreProOutput {
  my $ph                      = shift;
  my $commandLine             = shift;
  my $prlgccCommandLine       = shift;
  my $createGeneric           = shift;
  my $macroFiltersRef         = shift;

  
  my $canonicalPath;   #absolute path to directory
  my $gccHdrVersion;
  my %gccVersion;
  my $cLangVersion = undef;
  my $cppLangVersion = undef;
  
  my $has_macro__GXX_EXPERIMENTAL_CXX0X__ = 0;
  my $has_macro__arch64__ = 0;

  my $compilerInfo = new CompilerInfo ('prlgccCommandLine' => $prlgccCommandLine);

  while (defined(my $line = <$ph>)) {
    my $macroName;
    my $macroBody;
    my $macroDef;     # PRL style macro definition

    # Forward any command line error information
    if ( "$line" =~ /error:/ ) {
      die ("*** ERROR: Problem running: '$prlgccCommandLine'\n\t$line\n\nCommand: $prlgccCommandLine\n");
    }

    chomp($line);
    
    # Extract # defines from output and convert to macro=body
    # command line options -E -dM generate implicit defines as
    # #define directives

    # macro with body
    if ( "$line" =~ /^#define\s+(\w[\w\(\)]*)\s+(.*)\s*$/) {

      # $1 = macro name, $2 = macro body
      $macroName = $1;
      $macroBody = $2;

      # remove trailing spaces
      $macroBody =~ s/\s+$//;

      if ($macroName =~ m/\b__GNUC__\b/) {
        if ($macroBody =~ m/([\.0-9]+)/) {
          $gccVersion{$macroName} = $1;
        }
      } elsif ($macroName =~ m/\b__GNUC_MINOR__\b/) {
        if ($macroBody =~ m/([\.0-9]+)/) {
          $gccVersion{$macroName} = $1;
        }
      } elsif ($macroName =~ m/\b__GNUC_PATCHLEVEL__\b/) {
        if ($macroBody =~ m/([\.0-9]+)/) {
          $gccVersion{$macroName} = $1;
        }
      }

      # some compilers define strings that we're not interested in, so
      # we ignore these lines
      next if( (%$macroFiltersRef) && defined ($$macroFiltersRef{$macroName}));

      if ("$macroName" =~ m/__cplusplus/) {
        $compilerInfo->getCompilerInformation ()->{'COMPILER'} = "g++";
        $compilerInfo->getCompilerInformation ()->{'SOURCE_LANGUAGE'} = "C++";
        my %cppVersions = ( "201103L" => "c++11",
                            "201300L" => "c++14",
                            "201402L" => "c++14" );
        if ( defined ($cppVersions{$macroBody} ) ) {
          # Only interested in 201103 or 201402
          $cppLangVersion = $cppVersions{$macroBody};
        }
      } elsif ( "$macroName" =~ m/__STDC_VERSION__/ ) {
        my %cVersions = ( "199901L" => "c99",
                          "201112L" => "c11" );
        if ( defined ($cVersions{$macroBody} ) ) {
          # Currently only getting 199901L and 201112L
          $cLangVersion = $cVersions{$macroBody};
        }
      } elsif ( "$macroName" =~ m/__sparc/ ) {
        $compilerInfo->getCompilerInformation ()->{'PLATFORM'} = 'PLATFORM_SOL32';
      } elsif ( "$macroName" =~ m/__linux/ ) {
        $compilerInfo->getCompilerInformation ()->{'PLATFORM'} = 'PLATFORM_LIN32';
      } elsif ( "$macroName" =~ m/__x86_64/ ) {
        $compilerInfo->getCompilerInformation ()->{'PLATFORM'} =~ s/32/64/;
      } elsif ( "$macroName" =~ m/__GXX_EXPERIMENTAL_CXX0X__/ && "$macroBody" =~ m/1/ ) {
        $has_macro__GXX_EXPERIMENTAL_CXX0X__ = 1;
      } elsif ( "$macroName" =~ m/__arch64__/ && "$macroBody" =~ m/1/ ) {
        $has_macro__arch64__ = 1;
      } 

      if ("$macroBody" =~ m/\".*\"/ ) {
        # remove the quotes.  __PRL_STRINGIFY will add later.
        $macroBody =~ s/\"//g;
        $macroDef = "${macroName}=_PRL_STRINGIFY(${macroBody})";
        
      } else {
        $macroDef = "$macroName=$macroBody";
      }

      push(@{$compilerInfo->getSystemDefines ()}, $macroDef);

      if ("$macroName" =~ m/__SIZE_TYPE__/) {
        # Special handling to update intrinsic type for
        # 'size_t'
        $compilerInfo->getIntrinsicTypes ()->{'size_t'} = fixIntrinsicType($macroBody);
      } elsif  ("$macroName" =~ m/__PTRDIFF_TYPE__/) {
        # Special handling to update intrinsic type for
        # 'ptrdiff_t'
        $compilerInfo->getIntrinsicTypes ()->{'ptrdiff_t'} = fixIntrinsicType($macroBody);
      } elsif  ("$macroName" =~ m/__WCHAR_TYPE__/) {
        # Special handling to update intrinsic type for
        # 'wchar_t'
        $compilerInfo->getIntrinsicTypes ()->{'wchar_t'} = fixIntrinsicType($macroBody);
      } elsif  ("$macroName" =~ m/__CHAR_BIT__/) {
        # Number of bits in char
        $compilerInfo->getTypeSizes ()->{'char'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_SHORT__/) {
        # Number of bits in short
        $compilerInfo->getTypeSizes ()->{'short'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'short'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_INT__/) {
        # Number of bits in int
        $compilerInfo->getTypeSizes ()->{'int'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'int'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_LONG__/) {
        # Number of bits in long
        $compilerInfo->getTypeSizes ()->{'long'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'long'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_LONG_LONG__/) {
        # Number of bits in long long
        $compilerInfo->getTypeSizes ()->{'longlong'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'longlong'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_FLOAT__/) {
        # Number of bits in float
        $compilerInfo->getTypeSizes ()->{'float'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'float'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_DOUBLE__/) {
        # Number of bits in double
        $compilerInfo->getTypeSizes ()->{'double'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'double'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_LONG_DOUBLE__/) {
        # Number of bits in long double
        $compilerInfo->getTypeSizes ()->{'ldouble'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeAligns ()->{'ldouble'} = $macroBody;
      } elsif  ("$macroName" =~ m/__SIZEOF_POINTER__/) {
        # Number of bits in pointers
        $compilerInfo->getTypeSizes ()->{'dataptr'} = $compilerInfo->getTypeSizes ()->{'char'} * $macroBody;
        $compilerInfo->getTypeSizes ()->{'codeptr'} = $compilerInfo->getTypeSizes ()->{'dataptr'};
        $compilerInfo->getTypeAligns ()->{'dataptr'} = $macroBody;
        $compilerInfo->getTypeAligns ()->{'codeptr'} = $compilerInfo->getTypeAligns ()->{'dataptr'};
      } elsif  ("$macroName" =~ m/__CHAR_(UN|)SIGNED__/) {
        # Sign of char
        if ( defined ($1) && $macroBody =~ /1/ ) {
          ${$compilerInfo->getSignOfChar ()} = "-u+";
        }
      }
    } elsif ( "$line" =~ /^#define\s+(\w+)\s*$/ ) {
      # macro without body
      my $macroName = $1;
      push(@{$compilerInfo->getSystemDefines ()}, "$macroName=");
    }

    # Look for search paths. @ is match operator.
    if ("$line" =~ m@^#include [<"]\.\.\.[>"] search starts here@) {
      # should be paths until "End of search list"
      while ((defined($line = <$ph>)) &&
             !("$line" =~ m/^End of search list/)) {

        # Identify paths and store.  Paths are indented.
        if ("$line" =~ m#^\s+([/\.\w\\].*)#) {
          my $path = $1;
          if ( $path =~ /.*\/include\/c\+\+\/([0-9\.]*)$/ ) {
            $gccHdrVersion = $1;
          } elsif ( ! defined ($gccHdrVersion) && $path =~ m@/gcc/.*/([0-9]*\.[0-9]*\.[0-9]*)/include$@ ) {
            $gccHdrVersion = $1;
          }

          if (-d "$path") {
            $canonicalPath = File::Spec->canonpath($path);
            push(@{$compilerInfo->getSystemIncludes ()}, $canonicalPath);
          } else {
            push(@{$compilerInfo->getSystemIncludes ()}, $path);
          }
        }

      }

      # if we've reached the end of file something is wrong.
      if (!defined($line)) {
        die ("*** ERROR: End of file reached while processing search path list.  "
             . "The output of gcc is an unexpected format.\n");
      }
    }
  }

  if ( ! (@{$compilerInfo->getSystemIncludes ()}) || ! (@{$compilerInfo->getSystemDefines ()}) ) {
    # Failed to find includes or defines
    undef $compilerInfo;
  } else {
    if (defined ($gccHdrVersion)) {
      if ($gccHdrVersion =~ /([0-9]+)\.([0-9]+)\.([0-9]+)/) {
        $gccVersion{'__GNUC__'} =  $1;
        $gccVersion{'__GNUC_MINOR__'} =  $2;
        $gccVersion{'__GNUC_PATCHLEVEL__'} =  $3;
      }
    }
    defined ($gccVersion{'__GNUC__'})
        && defined ($gccVersion{'__GNUC_MINOR__'})
        && defined ($gccVersion{'__GNUC_PATCHLEVEL__'})
        || die ("*** ERROR: Unable to determine version of GCC/G++ compiler\n");

    $compilerInfo->getCompilerInformation ()->{'COMPILER_VERSION'} = $gccVersion{'__GNUC__'}
    . '.'
        . $gccVersion{'__GNUC_MINOR__'};

    $compilerInfo->getCompilerInformation ()->{'COMPILER_FULL_VERSION'} = $gccVersion{'__GNUC__'}
    . '.' 
        . $gccVersion{'__GNUC_MINOR__'}
    . '.' 
        . $gccVersion{'__GNUC_PATCHLEVEL__'};
    
    if ( $compilerInfo->getCompilerInformation ()->{'COMPILER'} eq 'gcc' && defined ($cLangVersion) ) {
      # It's for C
      $compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'} = $cLangVersion;
    } elsif ( $compilerInfo->getCompilerInformation ()->{'COMPILER'} eq 'g++' ) {
      # It's for C++
      if ( defined ($cppLangVersion) ) {
        $compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'} = $cppLangVersion;
      } elsif ( $has_macro__GXX_EXPERIMENTAL_CXX0X__ == 1 ) {
        # For early versions of g++ setting --std=c++0x would do nothing but add
        #   #define __GXX_EXPERIMENTAL_CXX0X__ 1
        $compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'} .= "c++0x";
      }
    }

    if ($compilerInfo->getCompilerInformation ()->{'PLATFORM'} eq 'PLATFORM_SOL32' && $has_macro__arch64__ == 1 ) {
      $compilerInfo->getCompilerInformation ()->{'PLATFORM'} = 'PLATFORM_SOL64';
    }
    
    if ( $createGeneric != 0 ) {
      (my $shortVersion = $compilerInfo->getCompilerInformation ()->{'COMPILER_VERSION'}) =~ s/([0-9]*)\.([0-9]*)/$1$2/;
      $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'} = $compilerInfo->getCompilerInformation ()->{'COMPILER'};
      $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY_ALTERNATIVE'} = 
          $compilerInfo->getCompilerInformation ()->{'COMPILER'} . $shortVersion;
    } elsif (@$commandLine) {
      $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'} = $$commandLine[0];
      $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY_ALTERNATIVE'} =
          $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'};
      if ( -l $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'} ) {
        $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY_ALTERNATIVE'} =
            readlink ($compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'});
      }
    } else {
      $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'} = $compilerInfo->getCompilerInformation ()->{'COMPILER'};
      $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY_ALTERNATIVE'} = 
          $compilerInfo->getCompilerInformation ()->{'COMPILER_BINARY'};
    }

    my @additionalArgs = @$commandLine;
    shift (@additionalArgs);
    if (@additionalArgs && $createGeneric == 0) {
      my $sep = "";
      $compilerInfo->getCompilerInformation ()->{'ADDITIONAL_ARGS'} = '[';
      foreach my $arg (@additionalArgs) {
        $compilerInfo->getCompilerInformation ()->{'ADDITIONAL_ARGS'} .= $sep . "'" . $arg . "'";
        $sep = ',';
      }
      $compilerInfo->getCompilerInformation ()->{'ADDITIONAL_ARGS'} .= ']';
    }
  }

  return \$compilerInfo;
}

#####################################################################
# Function : formCommandLine
# Parameters : args          - an array holding the arguments
# Description : Adds quotes around strings with spaces if necessary
#
# Returns : A string with arguments quoted as necessary
#
#####################################################################
sub formCommandLine {
  my $commandLine = shift;
  my $options = shift;
  my $filename = shift;

  my $result = "";

  my $sep = "";
  my @args = ( @$commandLine, @$options, $filename );
  for my $arg ( @args ) {
    if ($arg =~ / /) {
      $arg = "\"$arg\"";
    }
    $result .= $sep . $arg;
    $sep = " ";
  }
  return $result;
}

#####################################################################
# Function : getCompilerImplicitInfo
# Parameters : commandLine          - an array holding the gcc command and arguments
#              gccQueryOptions      - the options to use for querying settings
#              filename             - the filename being scanned
#              createGeneric        - true when CCT genertion is for generic CCT creation
#
# Description : Determines from the tools preprocessor output the
#               implicit include paths (if configured), system include
#               paths and macros (-Dmacro...)
#
# Returns : A CompilerInfo object with only implicit macros and includes.
#
#####################################################################

sub getCompilerImplicitInfo {
  my $commandLine             = shift;
  my $gccPreProQueryOptions   = shift;
  my $filename                = shift;
  my $createGeneric           = shift;
  my $macroFiltersRef         = shift;


  my $ph = FileHandle->new();  # request a new file handle.
  # Pipe output to file handle.
  my $prlgccCommandLine = &formCommandLine ($commandLine, $gccPreProQueryOptions, $filename) ;
  if ( ! open ($ph, $prlgccCommandLine . " 2>&1 |" ) ) {
    die ("*** ERROR: Could not scan compiler output.\n\t$!\n");
  }

  my $compilerInfo = &scanPreProOutput($ph
                                       , $commandLine
                                       , $prlgccCommandLine
                                       , $createGeneric
                                       , $macroFiltersRef);

  # The file handle and temp file are not needed anymore
  close($ph);

  my @filteredCommandLine;
  if ( defined ($$compilerInfo) && &filterIncludesAndDefines ($commandLine, \@filteredCommandLine, $compilerInfo) ) {
    $ph = FileHandle->new();  # request a new file handle.

    # Pipe output to file handle.
    $prlgccCommandLine = &formCommandLine (\@filteredCommandLine, $gccPreProQueryOptions, $filename) ;
    if ( open ($ph, $prlgccCommandLine  . " 2>&1 |" ) ) {
      my $filteredPass = &scanPreProOutput($ph
                                           , \@filteredCommandLine
                                           , $prlgccCommandLine
                                           , $createGeneric
                                           , $macroFiltersRef);
      if ( defined ($$filteredPass) )
      {
        # If the filtered 2nd pass succeeded then we use it, otherwise
        # we stick with whatever the first pass produced.
        $compilerInfo = $filteredPass;
      }
    }

    # The file handle and temp file are not needed anymore
    close($ph);
  }

  ( defined $$compilerInfo )
      || die ("*** ERROR: No include paths found during scan of compiler output, please check command line:\n\t$prlgccCommandLine\n");

  return $compilerInfo;
}

#####################################################################
# Function : checkIsListedAsSystemInclude
# Parameters : path            - include path discovered
#              compilerInfo    - reference, compiler information
#
# Description : Searches the system include paths to see if this path
#               was defined in the configuration.
#
# Returns : 1 if the include path is listed or 0 otherwise.
#
#####################################################################
sub checkIsListedAsSystemInclude {
  my $path = shift;
  my $compilerInfo = shift;
  my $result = 0;

  chomp($path);
  for my $systemInclude ( @{$$compilerInfo->getSystemIncludes ()} ) {
    if ($path eq $systemInclude) {
      $result = 1;
    }
  }
  return $result;
}

#####################################################################
# Function : checkIsListedAsSystemMacro
# Parameters : macro           - The macro text
#              sysDefineHash   - reference to hash of defined macros
#
# Description : Checks the command line for a system macro, needs to
#               be recursive to handle both -DX=Y and -D X=Y
#
# Returns : 1 if the macro should be skipped or 0 otherwise.
#
#####################################################################
sub checkIsListedAsSystemMacro {
  my $macro = shift;
  my $sysDefineHash = shift;
  my $result = 0;

  if ( $macro =~ /([^=]*)=.*/ ) {
    $result = &checkIsListedAsSystemMacro ($1, $sysDefineHash);
  } else {
    $result = (defined ($sysDefineHash->{$macro}));
  }
  return $result;
}

#####################################################################
# Function : filterIncludesAndDefines
# Parameters : commandLine          - command line presented to prlgcc
#              filteredCommandLine  - command line with explicit -I/-D and -include removed
#              forceIncludes        - list of headers force included
#              compilerInfo         - reference, compiler information
#
# Description : Walks through the command line looking for any
#               explicit -include, -I or -D options that may have
#               impacted the generated CCT/personality.  These are not
#               copied into the filteredCommandLine.
#
# Returns : 1 if the command line has been filtered, 0 otherwise
#
#####################################################################
sub filterIncludesAndDefines {
  my $commandLine = shift;
  my $filteredCommandLine = shift;
  my $compilerInfo = shift;
  my $result = 0;

  my $index = 0;

  my %sysDefineHash;
  for my $sysDefine (@{$$compilerInfo->getSystemDefines ()}) {
    my @entry=split('=', $sysDefine);
    $sysDefineHash{$entry[0]} = $entry[1];
  }

  while ($index <= $#$commandLine) {
    if ( $commandLine->[$index] =~ /^\-I(.+)/ ) {
      if ( &checkIsListedAsSystemInclude ($1, $compilerInfo) ) { 
        $result = 1;
      } else {
        push (@$filteredCommandLine, $commandLine->[$index]);
      }
    } elsif ( $commandLine->[$index] =~ /^\-I$/ ) {
      if ( &checkIsListedAsSystemInclude ( $commandLine->[$index + 1], $compilerInfo) ) { 
        ++$index;
        $result = 1;
      } else {
        push (@$filteredCommandLine, $commandLine->[$index]);
      }
    } elsif ( $commandLine->[$index] =~ /^\-D(.+)/ ) {
      if ( &checkIsListedAsSystemMacro ($1, \%sysDefineHash) ) {
        $result = 1;
      } else {
        push (@$filteredCommandLine, $commandLine->[$index]);
      }
    } elsif ( $commandLine->[$index] =~ /^\-D$/ ) {
      if ( &checkIsListedAsSystemMacro ($commandLine->[$index + 1], \%sysDefineHash) ) {
        ++$index;
        $result = 1;
      } else {
        push (@$filteredCommandLine, $commandLine->[$index]);
      }
    } else {
      push (@$filteredCommandLine, $commandLine->[$index]);
    }
    ++$index;
  }

  return $result;
}

#####################################################################
# Function : filterCommandLine
# Parameters : commandLine          - command line presented to prlgcc
#              filteredCommandLine  - command line with explicit -I/-D and -include removed
#
# Description : Walks through the command line looking for any
#               explicit -include, -imacros and depenency options that
#               could cause the command to fail if filenames are not
#               found.
#
# Returns : Nothing.
#
#####################################################################
sub filterCommandLine {
  my $commandLine = shift;
  my $filteredCommandLine = shift;

  my $index = 0;
  while ($index <= $#$commandLine) {
    if ( $commandLine->[$index] =~ /^\-include$/ ) {
      # This option 'force includes' the file into the source.  We
      # remove it to avoid macros being included from it which may be
      # specified to a particular file or directory.
      ++$index;
    } elsif ( $commandLine->[$index] =~ /^\-imacros$/ ) {
      # This option 'force includes' the file but only stores macros.
      # For for the -include option, we remove this to avoid macros
      # being included from it which may be specified to a particular
      # file or directory.
      ++$index;
    } elsif ( $commandLine->[$index] =~ /^\-M(.*)$/ ) {
      my $subOption = $1;
      if ( $subOption =~ /^$/ ) {
        # -M
      } elsif ($subOption =~ /[FTQ]/ ) {
        # -MF file
        ++$index;
      } else {
        ($subOption =~ /[MGPD][M]*/ ) 
            || die ("*** ERROR: Unexpected dependency option\n\t" . $commandLine->[$index] . "\n");
      }
    } elsif ( $commandLine->[$index] =~ /^\-O(|[0-3]|s|fast|g)$/ 
              || $commandLine->[$index] =~ /^\-c$/
              || $commandLine->[$index] =~ /^\-g$/
              || $commandLine->[$index] =~ /^\-p[g]*$/ ) {
      # Options that shouldn't impact preprocessor includes or defines
    } else {
      push (@$filteredCommandLine, $commandLine->[$index]);
    }
    ++$index;
  }
}

#####################################################################
# Function : scanTargetOutput
# Parameters : ph                   - file handle to tool output.
#              compilerInfo         - reference, compiler information
#
# Description : Scan the -v output of the tool and extract the target
#               and host if present
#
# Returns : Nothing.
#
#####################################################################

sub scanTargetOutput {
  my $ph                      = shift;
  my $compilerInfo            = shift;

  while (defined(my $line = <$ph>)) {
    chomp($line);
    next if ( "$line" =~ /error:/ );

    # Look for the machine that the compiler was built for 
    if ( "$line" =~ m/Target:\s+([^\s]*)/) {
      $$compilerInfo->getCompilerInformation ()->{'TARGET'} = $1;
      if ($$compilerInfo->getCompilerInformation ()->{'TARGET'} =~ m/([^\-]*)-/) {
        $$compilerInfo->getCompilerInformation ()->{'HOST'} = $1;
      }
    }
  }
}

#####################################################################
# Function : copyIfDifferent
# Parameters : sourceFile        - The file to copy from
#              destinationFile   - The file to copy to
#
#####################################################################
sub copyIfDifferent {
  my $sourceFile = shift;
  my $destination = shift;

  my $destinationFile = $destination;
  if ( -d $destination ) {
    $destinationFile = "$destination/" . basename ($sourceFile);
  }

  my $doCopy = 1;
  if ( -e $destinationFile ) {
    system("diff"
            , $sourceFile
            , $destinationFile);

    ( $? != -1 )
        || die ("*** ERROR: Failed to compare files: $!\n");

    $doCopy = ($? >> 8) ;
  }

  if ( $doCopy ) {
    &copy ($sourceFile, $destinationFile)
        || die ("*** ERROR: Failed while copying $sourceFile to $destinationFile: $!\n");
  }
}

#####################################################################
# Function : makeHeaders
# Parameters : source - Source directory
#              dest - Destination directory
#
# Description : Recursively copy header file directories from source
#               to dest in a OS independent manner.
#
#               An implicit -i/-q is added for the top most directory.
#               
#               THE SOURCE AND DESTINATION DIRECTORIES MUST BE ABSOLUTE PATHS
#               
# Returns : Nothing.
#               
#####################################################################

sub makeHeaders {

  ######  The source and destination paths must be ABSOLUTE PATHS ######
  # This function relies on the fact that "/" is portable as
  # directory separator for internal functions.
  my $source         = shift;    # Source directory
  my $dest           = shift;    # destination directory

  my @files;                     # all of the files in the directory

  # have to glob files differently depending on if there
  # are spaces in the path...
  if (index($source, ' ') == -1) {
    @files = <$source/*>;       # no space, no quotes
  } else {
    @files = <"$source/*">;     # spaces, use quotes
  }
  
  foreach my $file (@files) {
    next if ( $file =~ m/\/CVS\b/ );                              # Explicitly ignore CVS directories for testing
    next if ( $file =~ m/\~$/ );                                  # Ignore backup files (ie. ending in ~)

    #parse filename
    (my $base, my $path, my $ext) = fileparse($file,'\..*');
      
    # if the $file is a directory and we can access...
    if ( (-d $file) && (-X $file) ) {
      # Create the same directory at the destination and recursively
      # call this function.
      if ( !(-d "$dest/$base$ext" ) ) {
        &mkpath(["$dest/$base$ext"],0,0777)
            || die ("*** ERROR: Failed to make dir $dest/$base$ext\n\t$!\n");
      }
      &makeHeaders("$file","$dest/$base$ext");
    } elsif (! -d "$file") {
      # Copy the file to the destination directory
      &copyIfDifferent ("$file","$dest/$base$ext");
    }
  }
}

#####################################################################
# Function : getSearchPaths
# Parameters : subHeadDir - root level directory for substiture headers
#    
#              substituteHeaders - Reference to array in which substitute
#              headers will be stored
#
#              skipLeafDirs - if true ignore directories which contain
#                             other directories.
#
# Description : Recursively scan the substitute header directories and build
#               a list of non-leaf level directories.
#               
#               THE DIRECTORY MUST BE AN ABSOLUTE PATH
#               
# Returns : Nothing.
#               
#####################################################################

sub getSearchPaths {
  my $subHeadDir           = shift;
  my $substituteHeadersRef = shift;
  my $skipLeafDirs         = shift;

  my $file;           # The current file.
  my $currentPath;    # The current directory
  my $canonicalPath;  # platform dependent canonical path.
  my @files;          # all files

  # handle spaces in dir name
  if (index($subHeadDir, ' ') == -1)
  {
    @files = <$subHeadDir/*>;       # no space, no quotes
  }
  else
  {
    @files = <"$subHeadDir/*">;     # spaces, use quotes
  }

  foreach $file (@files) {
    next if ( $file =~ m/\/CVS\b/ );                         # Explicitly ignore CVS directories for testing
    next if ( $file =~ m/\~$/ );                             # Ignore backup files (ie. ending in ~)
    if ( (-d "$file") && (-X "$file") ) {
      # If this dir does not contain files put it in the search path
      if ((!$skipLeafDirs) ||                                # we don't care if the dir is a leaf or not
          ($skipLeafDirs && (! &isLeafDirectory ("$file")))) {  # or we do care and this directory is not a leaf
        $canonicalPath = File::Spec->canonpath("$file");
        push(@$substituteHeadersRef, $canonicalPath);
      } else {
        getSearchPaths("$file", $substituteHeadersRef,1); # Note only dirs without files from now on
      }
    }
  }
}

#####################################################################
# Function : isLeafDirectory
# Parameters : directory - path to directory.
#
# Description : True if directory does not contain any subdirectories
#               
#               THE DIRECTORY MUST BE AN ABSOLUTE PATH
#               
# Returns : Non-zero if the directory contains a non-directory file
#               
#####################################################################

sub isLeafDirectory {
  my $dir  = shift;
  my $file;
  
  foreach $file (<$dir/*>) {
    next if ( $file =~ m/\/CVS\b/ );                         # Explicitly ignore CVS directories for testing
    if ( (-d $file) ) {
      return 0;
    }
  }
  
  return 1;
}

#####################################################################
# Function : createDestName
# Parameters : createCCT         - 1 if we're creating CCT
#              compilerInfo      - information deduced from running the compiler
#
#####################################################################
sub createDestName {
  my $createCCT = shift;
  my $compilerInfo = shift;

  if ($createCCT == 1) {
    $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'} = $$compilerInfo->getCompilerInformation ()->{'COMPILER_HIERARCHY'} .
        "_" . $$compilerInfo->getCompilerInformation ()->{'COMPILER_NAME'} .
        "-" . $$compilerInfo->getCompilerInformation ()->{'COMPILER'} .
        "_" . $$compilerInfo->getCompilerInformation ()->{'COMPILER_VERSION'} .
        "-" . $$compilerInfo->getCompilerInformation ()->{'TARGET'} .
        "-" . $$compilerInfo->getCompilerInformation ()->{'SOURCE_LANGUAGE'};

    # The stub headers go into a directory without the .PATCH
    $$compilerInfo->getCompilerInformation ()->{'DATA_PREFIX'} = $$compilerInfo->getCompilerInformation ()->{'COMPILER_HIERARCHY'} .
        "_" . $$compilerInfo->getCompilerInformation ()->{'COMPILER_NAME'} .
        "-" . $$compilerInfo->getCompilerInformation ()->{'COMPILER'};

    if (defined ($$compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'})) {
      $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'} .= "-" .
          $$compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'};
    }

    $$compilerInfo->getCompilerInformation ()->{'OUTPUT_EXT'} = "cct";
  } else {
    my $tool = ($$compilerInfo->getCompilerInformation ()->{'COMPILER'} eq "gcc") ? "qac" : "qacpp";
    $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'} = $$compilerInfo->getCompilerInformation ()->{'COMPILER'} . "_${tool}";
    $$compilerInfo->getCompilerInformation ()->{'OUTPUT_EXT'} = "p_c";
  }
}      

#####################################################################
# Function : copySupportFiles
# Parameters : subHeadDir        - reference to the location the target location to write to
#              createCCT         - 1 if we're creating CCT
#              includeDirPaths   - reference to paths for prlforceinclude and 3rdparty header directories
#              substituteHeaders - reference to search paths
#              compilerInfo      - reference to information relating to the compiler
#
#####################################################################
sub copySupportFiles {
  my $subHeadDir = shift;
  my $createCCT = shift;
  my $includeDirPaths = shift;
  my $substituteHeaders = shift;
  my $compilerInfo = shift;

  my $currentDir = cwd();

  my $compilerOutputPrefix = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'};
  my $compilerOutputExt = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_EXT'};
  my $compiler = $$compilerInfo->getCompilerInformation ()->{'COMPILER'};

  print("Creating substitute headers.\n");
  $subHeadDir = File::Spec->canonpath($subHeadDir); # Absolute path.

  my $cctNestingOpt = "";
  if ( $createCCT == 1 ) {
    my $compilerDataPrefix = $$compilerInfo->getCompilerInformation ()->{'DATA_PREFIX'};
    $cctNestingOpt = "/DATA/$compilerDataPrefix/Stub";
  }

  $$includeDirPaths{'prlforceinclude'} = "$subHeadDir${cctNestingOpt}/prlforceinclude";
  $$includeDirPaths{'gcc'} = "$subHeadDir${cctNestingOpt}";
  $$includeDirPaths{'3rdParty'} = "$subHeadDir${cctNestingOpt}/3rdParty";

  #
  # COPY PRLFORCEINCLUDE (both from the product specific and common locations):
  #
  &mkpath([$$includeDirPaths{'prlforceinclude'}], 0, 0777) ;
  &makeHeaders($currentDir . "/$compiler-headers/prlforceinclude", $$includeDirPaths{'prlforceinclude'});

  my $builtinsSource = $currentDir . "/common-headers/prlforceinclude/builtins_all.h";
  &copyIfDifferent($builtinsSource, $$includeDirPaths{'prlforceinclude'}) ;

  #
  # COPY STUB HEADERS
  #
  &makeHeaders($currentDir . "/$compiler-headers/headers", $$includeDirPaths{'gcc'}); 
  &makeHeaders($currentDir . "/common-headers/headers", $$includeDirPaths{'gcc'}); 

  # Build a list of search paths
  &getSearchPaths($subHeadDir, $substituteHeaders, 0);

  # copy 3rdParty headers
  &makeHeaders($currentDir . "/$compiler-headers/3rdParty", $$includeDirPaths{'3rdParty'});

  # Build a list of search paths for 3rdParty headers
  &getSearchPaths($$includeDirPaths{'3rdParty'}, $substituteHeaders, 0);
  
  print("Substitute headers completed.\n");
}

#####################################################################
# Function : processExtensions
# Parameters : compilerInfo      - information deduced from running the compiler
#
#####################################################################
sub processExtensions {
  my $compilerInfo = shift;
  my $extensions = "";

  my $compiler = $$compilerInfo->getCompilerInformation ()->{'COMPILER'};

  while ( my $ext_file = < $compiler-headers/person/ver-*.ext > ) {
    if ($ext_file =~ /ver-(.*).ext/) {
      my $version = $1;
      if ($version =~ /all/ || $version =~ $compiler ) {
        my $fh;
        open ($fh, "<$ext_file")
            || die ("*** ERROR: Cannot open $ext_file for reading: $!\n");
        local $/ = undef;
        $extensions .= <$fh>;
        close ($fh);
      }
    }
  }
  return $extensions;
}


#####################################################################
# Function : configurePersonality
# Parameters : compilerInfo - information about the compiler
#    
#              includeDirPaths - reference to hash of relevant include paths
#                  Reference to Hash of Absolute paths.
#              personDir - location to place the compiler personality.
#                  Absolute path.
#              subHeadRef - Reference. Array of substitute header paths.
#              extensions - Reference to additional configuration options appropriate for this version
#              usePCPaths - True if we are to use paths for Windows
#              cygwin - True for a cygwin environment
#
# Description : configure a compiler personality based on a template.
#               The template is copied as is, with the retrieved
#               information inserted at the indicated points.
#               
#               
# Returns : Nothing.
#               
#####################################################################

sub configurePersonality {
  my $compilerInfo       = shift;
  my $includeDirPaths    = shift;
  my $personDir          = shift;
  my $subheadRef         = shift;
  my $extensions         = shift;
  my $usePCPaths         = shift;
  my $cygwin             = shift;

  my $compiler = $$compilerInfo->getCompilerInformation ()->{'COMPILER'};
  my $compilerOutputExt = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_EXT'};
  my $compilerOutputPrefix = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'};
  my $compilerSourceLanguage = $$compilerInfo->getCompilerInformation ()->{'SOURCE_LANGUAGE'};

  my $line;        # Current line of template.
  my $item;        # Used to iterate over lists.
  my $pcPath;      # paths converted to PC format.
  my $cygwinRoot = ""; # cygwin root path
  my $useCygpath = 1;  # flag for wheather to use cygpath or mount method
  my %cygwinMountPoints;
  my @mounts;
  
  ######################################################
  # The following variables are set to the appropriate
  # syntax depending on if the personality is for QAC
  # or QAC++.
  ######################################################

  my $forceInclude;
  my $quiet;
  my $stubInclude;
  my $systemInclude;
  my $systemDefine;
  my $optionalQuote;  # Quote system define for QAC, not for QAC++

  my $pathSeparator;  # Use \ on PC systems where --pcpaths is specified.

  if ($cygwin) {
    # see if we have cygpath and use that if possible as this
    # gives best results, otherwise check mounts
    my $tmp=`which cygpath`;
    chomp($tmp);
    if ( $tmp eq "" ) {
      $useCygpath = 0;

      # If the OS is cygwin replace cygwin mount points
      # Thanks to Dedicated Systems for this improvement
      %cygwinMountPoints = &getCygwinMountPoints(\$cygwinRoot);
      @mounts = sort {$b cmp $a } keys %cygwinMountPoints;
      

      # translate all cygwin mount points to PC path entries
      foreach my $mount ( @mounts ) {
        my $cygwinPath = $mount;
        my $PCpath = $cygwinMountPoints{$mount};
        
        foreach $item (@{$$compilerInfo->getSystemIncludes()}) {
          $item =~ s/^$cygwinPath/$PCpath/;
        }
      }
      
      # If any paths still start with "/" prepend $cygwinRoot
      # $cygwinRoot should be returned as a reference from 
      # getCygwinMountPoints()
      foreach $item (@{$$compilerInfo->getSystemIncludes()}) {
        $item =~ s#^/#$cygwinRoot#;
      }

      # and the same for sub headers
      foreach $item (@$subheadRef) {
        $item =~ s#^/#$cygwinRoot#; 
      }
      
    } else {
      
      # use cypath
      foreach $item (@{$$compilerInfo->getSystemIncludes()}) {
        $item=`cygpath -w "$item"`;
        chomp($item);
      }

      foreach $item (@$subheadRef) {
        $item=`cygpath -w "$item"`;
        chomp($item);
      }
    }
  }  # if ($cygwin)

  if ($usePCPaths) {
    $pathSeparator = "\\";
    
    # convert all path entries to PC format.  (Match op is #)
    foreach $item (@$subheadRef) 
    {
      $item =~ s#/#$pathSeparator#g;
    }

    foreach $item (@{$$compilerInfo->getSystemIncludes()}) 
    {
      $item =~ s#/#$pathSeparator#g;
    }
  } else {
    $pathSeparator = "/";
  } 

  if ($compiler eq "gcc") {
    # QAC personality
    $forceInclude="-fi";
    $quiet="-q";
    $stubInclude="-i";
    $systemInclude="-i";
    $systemDefine="-d";
    $optionalQuote="\"";
  } else {
    # QAC++ personality
    $forceInclude="-fi";
    $quiet="-q";
    $stubInclude="-i";
    $systemInclude="-si";
    $systemDefine="-sd";
    $optionalQuote="\"";
  }

  my $templatePath =  "$compiler-headers/person/$compiler.$compilerOutputExt.tpl";
  if (! open (SOURCE,"<$templatePath")) {
    die("*** ERROR: Failed to open template $templatePath\n");
  }

  my $languageStandardOptionOpt = "";
  if (defined ($$compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'}) ) {
    $languageStandardOptionOpt = " --std=$compilerSourceLanguage" .
        $$compilerInfo->getCompilerInformation ()->{'LANGUAGE_STANDARD_VERSION'};
    $languageStandardOptionOpt =~ s/C/c/;
  }

  my $destFilename = "$personDir/$compilerOutputPrefix.$compilerOutputExt";
  if (! open (DEST, ">$destFilename")) {
    die("*** ERROR: Failed to create $destFilename: $!\n");
  }

  while (defined ($line = <SOURCE>)) {
    # copy each line of the template and check for indicators.
    $line =~ s/__PRLGCC_LANGUAGE_STANDARD__/$languageStandardOptionOpt/;
      
    for my $replKey ( keys %{$$compilerInfo->getCompilerInformation ()} ) {
      if (defined ($$compilerInfo->getCompilerInformation ()->{$replKey}) ) {
        my $replSearch = "__PRLGCC_${replKey}__";
        my $replReplace = $$compilerInfo->getCompilerInformation ()->{$replKey};
        $line =~ s/${replSearch}/${replReplace}/g;
      }
    }

    ( my $compilerVersion = $$compilerInfo->getCompilerInformation ()->{'COMPILER_VERSION'} );
    $line =~ s/__PRLGCC_CONFIG_VERSION__/$compilerVersion/;
    ( my $compilerCmd = $$compilerInfo->getCompilerInformation ()->{'COMPILER_CMD'} ) =~ s/ [^ ]*\.c[p]*$/ /;
    $line =~ s/__PRLGCC_COMMAND_LINE__/$compilerCmd/;
    print(DEST $line);

    # substitute headers
    if ($line =~ /^\*\s*substitute\s*headers.*/i) {
      # if a forceinclude file exists in the root of the
      # header file destination.  Then generate a forceinclude.
      # add force includes for all files in this dir.

      # insert the substitute header list.
      foreach $item (@$subheadRef) {
        # Don't repeat the prlforceinclude directory
        if (!($item =~ "prlforceinclude")) {
          print(DEST "$stubInclude \"$item\"\n");
        }
      }

      # again for -quiet includes
      # This could be done in the loop above but kept separate
      # to make the personality file easier to read.
      foreach $item (@$subheadRef) {
        # The prlforceinclude directory will be added later
        if (!($item =~ "prlforceinclude")) {
          print(DEST "$quiet \"$item\"\n");
        }
      }            

    }

    # force include files
    if ($line =~ /^\*\s*prlforceinclude.*/i) {
      # if any other headers live in the prlforceinclude list then add these as well
      # not just the prlgcc/g++ file
      my $writeQuietOption=0;
      my $forceIncFile;
      
      my @forceIncludeFiles;          # all files
      
      # handle spaces in dir name
      if (index($$includeDirPaths{'prlforceinclude'}, ' ') == -1) {
        @forceIncludeFiles = <$$includeDirPaths{'prlforceinclude'}/*.h>;       # no space, no quotes
      } else {
        @forceIncludeFiles = <"$$includeDirPaths{'prlforceinclude'}/*.h">;     # spaces, use quotes
      }

      # add all files in the force include directory
      foreach $forceIncFile (@forceIncludeFiles) {
        # translate all cygwin mount points to PC path entries
        if ($cygwin) {
          if($useCygpath == 1)
          {
            $forceIncFile=`cygpath -w "$forceIncFile"`;
            chomp($forceIncFile);
          }
          else
          {
            foreach my $mount ( @mounts )
            {
              my $cygwinPath = $mount;
              my $PCpath = $cygwinMountPoints{$mount};
              
              $forceIncFile =~ s/^$cygwinPath/$PCpath/;
            }
          }
        } 

        if ($usePCPaths) {
          $forceIncFile =~ s#/#$pathSeparator#g;
          # set path parsing rules for creating quiet directory
          # directly from force include file
          fileparse_set_fstype("MSWin32");
        }
        # write quiet option once, 
        if ($writeQuietOption == 0 ) { 
          # get path component, write and set flag
          my $quietDir = dirname($forceIncFile);
          print(DEST "$quiet \"$quietDir\"\n");
          $writeQuietOption = 1;
        }  
        # write the force include option last, so they all appear together
        print(DEST "$forceInclude \"$forceIncFile\"\n");
      }

      foreach $forceIncFile (@{$$compilerInfo->getForceIncludes ()}) {
        # write the force include option last, so they all appear together
        print(DEST "$forceInclude \"$forceIncFile\"\n");
      }
    }

    # gcc include directories or CR 965 #include_next difficiency
    if (($line =~ /^\*\s*gcc\s*include\s*directories.*/i) ||
        ($line =~ /^\*\s*cr\s*965.*/i)) {

      foreach $item (@{$$compilerInfo->getSystemIncludes()}) {
        print(DEST "$systemInclude \"$item\"\n");
      }

      # do -quiet includes for include directories.
      if ($line =~ /^\*\s*gcc\s*include\s*directories.*/i) {
        foreach $item (@{$$compilerInfo->getSystemIncludes()}) {

          print(DEST "$quiet \"$item\"\n");
        }

      }
    }

    # Implicit macros
    if ($line =~ /^\*\s*implicit\s*macros.*/i) {
      print(DEST "$systemDefine ${optionalQuote}_PRL_STRINGIFY(x)=#x".
            "$optionalQuote\n");
      foreach $item (@{$$compilerInfo->getSystemDefines ()}) {

        print(DEST "$systemDefine $optionalQuote" . &convertUnsupportedConstantSuffix($item) .
              "$optionalQuote\n");
      }
    }

    # Type Sizes (and alignment)
    if ($line =~ /^\*\s*type\s*sizes.*/i) {
      foreach $item (sort keys %{$$compilerInfo->getTypeSizes ()}) {
        print(DEST "-size " . $item . "=" . $$compilerInfo->getTypeSizes ()->{$item} . "\n");
      }

      foreach $item (sort keys %{$$compilerInfo->getTypeAligns ()}) {
        print(DEST "-align " . $item . "=" . $$compilerInfo->getTypeAligns ()->{$item} . "\n");
      }
    }

    # Intrinsic types
    if ($line =~ /^\*\s*intrinsic\s*types.*/i) {
      print(DEST "-it \"ptrdiff_t=" . $$compilerInfo->getIntrinsicTypes ()->{'ptrdiff_t'} . "\"\n");
      print(DEST "-it \"size_t=" . $$compilerInfo->getIntrinsicTypes ()->{'size_t'} . "\"\n");
    }

    # Intrinsic wchar type (only for gcc personalities)
    if ($line =~ /^\*\s*intrinsic\s*wchar_t\s*type.*/i) {
      print(DEST "-it \"wchar_t=" . $$compilerInfo->getIntrinsicTypes ()->{'wchar_t'} . "\"\n");
    }

    # Sign of char type (only for gcc personalities)
    if ($line =~ /^\*\s*sign\s*of\s*char.*/i) {
      print(DEST ${$$compilerInfo->getSignOfChar ()} . "\n");
    }

    # Extensions
    if ($line =~ /^\*\s*additional\s*options/i) {
      print (DEST $$extensions);
    }    
  }

  close(SOURCE);
  close(DEST);
}

#####################################################################
# Function : createCompilerInfo
# Parameters : personDir    - location to place the compiler personality.
#                             Absolute path.
#
#              compilerInfo - Information gathered relating to the compiler
#
#####################################################################
sub createCompilerInfo {
  my $personDir          = shift;
  my $compilerInfo       = shift;

  my %xmlKeys = ("PLATFORM" => "1",
                 "COMPILER_HIERARCHY" => "1",
                 "COMPILER_NAME" => "1",
                 "COMPILER_VERSION" => "1",
                 "HOST" => "1",
                 "TARGET" => "1",
                 "SOURCE_LANGUAGE" => "1",
                 "COMPILER_CMD" => "1");
  
  my $destFilename = "$personDir/compiler_info.xml";
  if (! open (DEST, ">$destFilename")) {
    die("*** ERROR: Failed to create $destFilename: $!\n");
  }
  print DEST "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  print DEST "<info>\n";
  foreach my $key ( sort keys %{$$compilerInfo->getCompilerInformation ()} ) {
    if ( defined ( $xmlKeys {$key} ) ) {
      print DEST "\t<$key>" . $$compilerInfo->getCompilerInformation ()->{$key} . "</$key>\n";
    }
  }
  print DEST "</info>\n";

  close (DEST);
}

#####################################################################
# Function : createSearchScript
# Parameters : personDir    - location to place the compiler personality.
#                             Absolute path.
#
#              compilerInfo - Information gathered relating to the compiler
#
#####################################################################
sub createSearchScript {
  my $personDir          = shift;
  my $compilerInfo       = shift;

  my $compilerDataPrefix = $$compilerInfo->getCompilerInformation ()->{'DATA_PREFIX'};
  my $compilerOutputPrefix = $$compilerInfo->getCompilerInformation ()->{'OUTPUT_PREFIX'};
  
  my $currentDir = cwd();
  my $srcFilename = "$currentDir/common-headers/Script/gcc_script.py.tpl";
  my $destDirectory = "$personDir/DATA/$compilerDataPrefix/Script";
  my $destFilename = "$destDirectory/$compilerOutputPrefix.py";

  my $systemInclude="i";
  my $systemDefine="d";
  if ($$compilerInfo->getCompilerInformation ()->{'COMPILER'} eq "g++") {
    $systemInclude="si";
    $systemDefine="sd";
  }

  # Create the script path if necessary
  &mkpath([$destDirectory], 0, 0777);
  
  if (! open (SRC, "<$srcFilename")) {
    die("*** ERROR: Failed to open $srcFilename: $!\n");
  }

  my $tempFile = File::Temp->new(TEMPLATE => 'prlgccTempXXXXX'
                                 , SUFFIX => '.py' );
  ($tempFile)
      || die ("*** ERROR: Cannot create temporary file: $!\n");
  $tempFile->unlink_on_destroy(1);

  while (my $line = <SRC>) {
    # copy each line of the template and check for indicators.
    for my $replKey ( keys %{$$compilerInfo->getCompilerInformation ()} ) {
      if (defined ($$compilerInfo->getCompilerInformation ()->{$replKey}) ) {
        my $replSearch = "__PRLGCC_${replKey}__";
        my $replReplace = $$compilerInfo->getCompilerInformation ()->{$replKey};
        $line =~ s/${replSearch}/$replReplace/g;
      }
    }
    $line =~ s/__PRLGCC_SYSTEM_INCLUDE__/$systemInclude/g;
    $line =~ s/__PRLGCC_SYSTEM_DEFINE__/$systemDefine/g;

    print $tempFile $line;
  }
  close ($tempFile);
  close (SRC);

  # Copy our temporary file to the destionation
  &copyIfDifferent ($tempFile->filename, $destFilename);

  # Copy helper scripts
  my $gnuScript = "$currentDir/common-headers/Script/gnu_gcc.py";
  &copyIfDifferent ($gnuScript, $destDirectory);

  my $cygwinScript = "$currentDir/common-headers/Script/cygpathHandler.py";
  &copyIfDifferent($cygwinScript, $destDirectory);

}


#####################################################################
# Function : convertUnsupportedConstantSuffix
# Parameters : A string containing a macro definition, e.g.
#              "__DEC128_MAX__=9.999999999999999999999999999999999E6144DL"
#
# Description : Translates the decimal constant notation 
#               into a form that can be parsed
#               by QAC and QACPP. The suffixes of intereset 
#               are DF, DL, DD.
# Test cases :
#               "ahoj" -> "ahoj"
#               "-D=12F" -> "-D=12F"
#               "-D=12DF" -> "-D=12F"
#               "-D=12DL" -> "-D=12L"
#               "-D=12DD" -> "-D=12"
#               "-D=\"12DD\"" -> "-D=\"12DD\""
#               "-D=12.0e-15DF" -> "-D=12.0e-15F"
#               "__DEC128_MAX__=9.999999999999999999999999999999999E6144DL" 
#                   -> "__DEC128_MAX__=9.999999999999999999999999999999999E6144L"
#
# Return : Decimal constant is conveted to ordinary constant.
#          Other macros are returned unchanged.
#
#####################################################################
sub convertUnsupportedConstantSuffix {
  my $macro = shift;
  
  if ($macro =~ /^(\S*)=(\S+)$/) {
    my $name = $1;
    my $value = $2;
    if ($value =~ /^[-+]?\d*\.?\d+(e[-+]?\d+)?(df|dl|dd)$/i) { # floating poit number with an unsupported suffix
      $value =~ /(.*)d(f|l|d)/i; # skip the first "d"
      my $suffix = $2;
      if (lc $suffix eq "d") { $suffix = ""; } # there is no "d" suffix for double constant
      return "$name=$1$suffix";
    }
  }
  
  return $macro;
}

#####################################################################
# Function : getCygwinMountPoints
# Parameters : cygwinRootRef - when the pc path to the cygwin root is
#               found it is stored here for use later in
#               mapping cygwin paths to pc paths
#
# Description : Map cygwin mount points by capturing the output of 
#               the cygwin mount command
#               information inserted at the indicated points.
#               Extracts paths from lines such as
#               c: on /cygdrive/c type user (textmode,noumount)
#               C:\cygwin\bin on /usr/bin type ntfs (binary,auto)
#               Note from Cygwin 1.7 the mount command reports:
#               C:/cygwin/bin on /usr/bin type ntfs (binary,auto)
#               i.e. with forward slashes, so the regex now does both
#               
# Returns : Map of cygwin mounts.  Key is cygwin path
#               
#####################################################################
sub  getCygwinMountPoints
{
  my $cygwinRootRef = $_[0];
  my %cygwinMountPoints = ();
  my @mountOutput = `mount`;
  my $line;

  foreach $line (@mountOutput)
  {
    # Look for search paths. @ is match operator.
    
    if ( "$line" =~ m@^([a-z:\\/]+)\s+on\s+([a-zA-Z0-9/]*)\s@  ||
         "$line" =~ m@^([a-zA-Z0-9:\\/]+)\s+on\s+([/])\s@) # root 
    {
      my $PCpath = $1;
      my $cygwinPath = $2;
      
      # the root mount needs a "\" added
      if ( $cygwinPath =~ m@^[/]$@ )
      {
        # Make a note of this path and pass back as ref
        $PCpath = $PCpath."\\";
        $$cygwinRootRef = $PCpath;
      }  
      $cygwinMountPoints{ $cygwinPath } = $PCpath;
    }
  }

  return %cygwinMountPoints;
}

package CompilerInfo;

sub new
{ 
  my $type = shift;
  my %parm = @_;
  my @empty;
  my $this = { 'INTRINSIC_TYPE' => &defaultIntrinsicType ()
               , 'TYPE_SIZES' => &defaultSizes ()
               , 'TYPE_ALIGNS' => &defaultAligns ()
               , 'SIGN_OF_CHAR' => "-u-"
               , 'SYSTEM_DEFINES' => undef
               , 'SYSTEM_INCLUDES' => undef
               , 'FORCE_INCLUDES' => undef
               , 'COMPILER_INFORMATION' => &defaultCompilerInfo ( $parm{'prlgccCommandLine'} ) };

  @{$this->{'SYSTEM_DEFINES'}} = ();
  @{$this->{'SYSTEM_INCLUDES'}} = ();

  bless $this, $type;
}

sub getIntrinsicTypes ()
{
  my $this = shift;
  return \%{$this->{'INTRINSIC_TYPE'}};
}

sub getTypeSizes ()
{
  my $this = shift;
  return \%{$this->{'TYPE_SIZES'}};
}

sub getTypeAligns ()
{
  my $this = shift;
  return \%{$this->{'TYPE_ALIGNS'}};
}

sub getSignOfChar ()
{
  my $this = shift;
  return \$this->{'SIGN_OF_CHAR'};
}

sub getSystemDefines ()
{
  my $this = shift;
  return \@{$this->{'SYSTEM_DEFINES'}};
}

sub getSystemIncludes ()
{
  my $this = shift;
  return \@{$this->{'SYSTEM_INCLUDES'}};
}

sub getForceIncludes ()
{
  my $this = shift;
  return \@{$this->{'FORCE_INCLUDES'}};
}

sub getCompilerInformation ()
{
  my $this = shift;
  return \%{$this->{'COMPILER_INFORMATION'}};
}

sub defaultIntrinsicType {
  my $intrinsicTypes = shift;
  return { 'size_t' => "unsigned int"
               , 'ptrdiff_t'=> "int"
               , 'wchar_t' => "unsigned short" };
}

sub defaultSizes {
  return { 'char' => 8
               , 'short' => 16
               , 'int' => 32
               , 'long' => 32
               , 'longlong' => 64
               , 'float' => 32
               , 'double' => 64
               , 'ldouble' => 96
               , 'dataptr' => 32
               , 'codeptr' => 32 };
}

sub defaultAligns {
  return { 'short' => 2
               , 'int' => 4
               , 'long' => 4
               , 'longlong' => 8
               , 'float' => 4
               , 'double' => 8
               , 'ldouble' => 12
               , 'dataptr' => 4
               , 'codeptr' => 4 };
}

sub defaultCompilerInfo {
  my $prlgccCommandLine = shift;
  return { 'PLATFORM' => 'PLATFORM_WIN32'
               , 'COMPILER' => 'gcc'
               , 'COMPILER_CMD' => $prlgccCommandLine
               , 'COMPILER_HIERARCHY' => 'GNU'
               , 'COMPILER_NAME' => 'GCC'
               , 'COMPILER_VERSION' => 'unknown'
               , 'COMPILER_FULL_VERSION' => 'unknown'
               , 'HOST' => 'unknown'
               , 'TARGET' => 'unknown'
               , 'SOURCE_LANGUAGE' => 'C'
               , 'LANGUAGE_STANDARD_VERSION' => undef
               , 'ADDITIONAL_ARGS' => "[]" };
}
