#!/usr/bin/perl
use strict;

our $version = "3.1";           # Version number

our $mode;                      # Controls operation of this wrapper.pl
                                # Value is "PROJECT_CREATOR_MODE" or 
								# "PROJECT_CREATOR_AND_MAKE_MODE" or
                                # "MAKEFILE_INTEGRATION_MODE"

our $wqp_list_file_path;        # Path to location of wqp_list.  Only relevant
                                # in PROJECT_CREATOR_MODE or PROJECT_CREATOR_AND_MAKE_MODE mode.
                                # NOTE: FILE $wqp_list_file_path MUST BE
                                # DELETED BY THE PROGRAM WHICH INVOKES make
                                # PRIOR TO INVOKING make.

our $compilerExecutable;        # The path to the compiler used by the wrapper.

our $compilerSettingsFile;      # This is usally the compiler personality file

our $integrationType;           # integrationType describes the mode of operation
                                # of the wrapper.  It can be either
                                # nothing "", STDERR, TEXT, HTML, STDERR_AND_TEXT,
                                # STDERR_AND_HTML, or STDERR_TEXT_AND_HTML

our $useLogging;                # Set to 1 if you want to log the analysis/compilation
                                # otherwise it is sent to stderr

our $logFile;                   # This is the output file used when $useLogging is 1.

our $debug = 0;                 # This gives extra debug information if set

our $stopOnFail;                # stopOnFail determines whether a failed analysis 
                                # should return a non-zero exit code

our $analyser;                  # This is either QAC or QACPP
our $analyserBase;              # This is the path where QAC/QAC++ is installed
our $analyserFlags;             # These are the flags for the analyser
our $errdspFlags;               # These are the flags for errdsp
our $postAnalysis;              # This is the post-analysis program
our @postAnalysisList;			# This is the list of post-analysis programs to run - deprecated
our @secondaryAnalysisList;		# This is the list of secondary-analysis programs to run.
								# (replaces post-analysis list)
our @complianceModuleList;		# List of compliance modules and parameters
our $complianceModule;			# Compliance module in use, if any.
our $complianceModuleBase;		# path to where the compliance module is installed, defaults to analyserBase
our $complianceModuleExecutable;
our $complianceModuleEntry;		# compliance module config selected.
our $sleepAfterAnalysis;        # Optional pause after each analysis
our $fileList;                  # Names the LOF file which is optionally created
                                # NOTE: FILE $fileList MUST BE
                                # DELETED BY THE PROGRAM WHICH INVOKES make
                                # PRIOR TO INVOKING make.

our $altOutputDir;              # This is the name of the alternate output dir for
                                # analysis op CR 12501

our %defineOnOption;            # Table of defines for analysis if compiler option
                                # set CR 12501

our $includeFlag;               # Set to allowable include flags CR 12501

# derived settings
#

our $analyserExecutable;        # This is the path to the analyser
our $errdspExecutable;          # This is the path to errdsp

our $outputDirectory;           # This is set to the same as the source file

our @compilerCommandLine;

our @qa_options;                # These are the options that are produced
                                # from parseCommandLine

our @qa_files    = ();    	  # source files
our $sourceFile;                # These are the source files that are found
                                # from parseCommandLine

our $cygpath_available;         # True if which( cygpath ) returns success

our @filename_extensions = ();  # list of filename extensions to recognize on compiler commandline

our @non_compile_options = ();	# list of compiler command line options that tell the 
								# compiler to do something other than compile. Wrapper
								# skips analyzing the files as well. (example: -MM for GCC)
our $LOGFILE;

our $unix;
our $windows_or_cygwin;
our $pure_windows;
our $cygwin;
our $relativeDir;
our $pwdAtStart;
our $file_locking;              # whether or not we are locking files while writing them
our $remoteHost;                # if set the host to run the analysis commands on
our $remoteViewerScript;        # if set Wrapper will write a script to launch the remote MB
our $remoteMessagePersonality;  # using this personality if set, otherwise the default one

# ------------------------------------------------------------------------------
#
# main
#
# ------------------------------------------------------------------------------

  $| = 1; # flush stdout after each print

  my $analysisResult = 0;
  my $exitValue = 0;
  
  checkUnixOrWindows();
  
  $pwdAtStart = PWD();
  $relativeDir = $pwdAtStart;

  # $cygpath_available     # True if which( cygpath ) returns success
  #
  $cygpath_available = 0;  # False

  if ( $windows_or_cygwin )
  {
      if ( WINDOWS_ONLY_WHICH_PROGRAM( "cygpath" ) ne "" )
      {
          $cygpath_available = 1;  # True
      }
  }

  # read the settings
  
  my $parse_successful = parseCommandLine();

  if ( $parse_successful == 1)      # all options/settings OK
  {
    setupEnvironmentVariables();

	foreach my $qa_file (@qa_files)
	{
	    $sourceFile = GET_CORRECT_PATH( $qa_file );
         if ( ($mode eq "PROJECT_CREATOR_MODE" ) ||
              ($mode eq "PROJECT_CREATOR_AND_MAKE_MODE" ) )
        {
            # Create a file named "$sourceFile_<random_chars>.wqp", write the sourceFile
            # and options to it.
            #
            Write_WQP_File( $sourceFile, @qa_options );
            $exitValue = 0;         # set flag so we compile 
        }
        else    # otherwise we must analyse the file
        {

	        $analysisResult = runAnalysis();
    
		    if ( ( $analysisResult == 0 ) &&
			    ( $complianceModule ne "" )
			    )
		    {
			    runComplianceModule();
		    }
		    elsif ( $complianceModule ne "" )
		    {
	            # Analysis failed, log skipping of compliance module
	            #
	            logMessage( "*** Analysis Failed: No Compliance Module analysis performed ***\n" );
		    }
                # post/SA comes after 'normal' analysis is case the post/sec analysis
                # is applying the baseline
            if ( ( $analysisResult == 0 ) &&
	            ( @postAnalysisList != 0 )
	        )
            {
                runPostAnalysis();
	        } 
	        elsif ( @postAnalysisList != 0 )
	        {
	            # Analysis failed, log skipping of post analysis
	            #
	            logMessage( "*** Analysis Failed: No post-analysis performed ***\n" );
	        }
		
	        if ( ( $analysisResult == 0 ) &&
	            ( @secondaryAnalysisList != 0 )
	        )
            {
                runSecondaryAnalysis();
	        } 
	        elsif ( @secondaryAnalysisList != 0 )
	        {
	            # Analysis failed, log skipping of secondary analysis
	            #
	            logMessage( "*** Analysis Failed: No secondary-analysis performed ***\n" );
	        }
      
      	    # Generate output so that hard errors can be seen
	        #
	        generateOutput();  
 
		    if ( $analysisResult )
            {
		        if ( $stopOnFail )
		        {
			        logMessage( "*** ABORTING AFTER ANALYSIS FAILURE (STOP_ON_FAIL=1) ***\n" );
			        $exitValue = $analysisResult;
			        last;
		        }
                else
                {
                    logMessage( "CONTINUING COMPILATION AFTER ANALYSIS FAILURE (STOP_ON_FAIL=0)\n" );
                }
            }
        }
	} # close foreach loop
    # Check whether we need to run compliation: based on analysis success
    # and stop on fail flag - if it is set and analysis failed
    # then we will already have had a message, so act silently
    if ( $exitValue )
    {
            # analysis failed - only run compliation if not stopping
      if ( !$stopOnFail)
      {
        $exitValue = runCompilation();	
      }
    }
    else   # analysis ran OK
    {
        $exitValue = runCompilation();	
    }
  }      # close if parse command line OK
  elsif ( $parse_successful == 2)      # invalid settings - can stop on this
  {
     logMessage( "*** No analysis performed ***\n" );
         # only run compiler if stop on fail not set
     if ( !$stopOnFail)
     {
       $exitValue = runCompilation();
     }
  }
  else                                  # no files or non compile settings
  {
     logMessage( "*** No analysis performed ***\n" );
        # we have done nothing that could fail, so launch compiler unconditionally
     $exitValue = runCompilation();
  }

  # Close the log file
  #
  closeLogFile();
  
  if ( $exitValue )
  {
      exit 255;
  }
  else
  {
      exit 0;
  }
    
# ------------------------------------------------------------------------------

sub checkUnixOrWindows
{
    $cygwin = ( $^O =~ /cygwin/o );
    $pure_windows = ( $^O =~ /MSWin/o );

    if ( $pure_windows || $cygwin )
    {
        $unix = 0;
        $windows_or_cygwin = 1;
    }
    else
    {
        $unix = 1;
        $windows_or_cygwin = 0;
    }
}

# ------------------------------------------------------------------------------

sub usage
{
    logMessage( "Command Line Wrapper version $version for QAC/QAC++\n" );
    logMessage( "Copyright Programming Research Ltd 2009\n" );
    logMessage( "\n" );
    logMessage( "Usage:\n" );
    logMessage( "wrapper [-wcf<FILE>|-wcf <FILE>]+ " .
                "[-setOPTION=VALUE|-set OPTION=VALUE]+ " .
                "<compiler> <compiler command line> <file>\n"
              );
    logMessage( "\n" );
    logMessage( "For detailed information, consult PDF manual\n" );
    logMessage( "For support, please contact support\@programmingresearch.com\n" ); 
    exit;
}

# ------------------------------------------------------------------------------

sub setDefaultSettings
{
    $mode                   = "MAKEFILE_INTEGRATION_MODE"; # default value
    $wqp_list_file_path     = ""; # must be set up
    $compilerSettingsFile   = ""; # default nothing (must be set)
    $integrationType        = ""; # default value
    $useLogging             = 0;  # default value
    $logFile                = ""; # default value
    $stopOnFail             = 0;  # default value
    $analyser               = ""; # must be set up
    $analyserBase           = ""; # must be set up
    $analyserFlags          = ""; # default value
    $errdspFlags            = ""; # default value
    $postAnalysis           = ""; # default value
    @postAnalysisList		= (); # default value
    @secondaryAnalysisList	= (); # default value
    $complianceModule		= ""; # default value
    $complianceModuleBase	= ""; # defaults to $analyserBase/Compliance module name
    $complianceModuleEntry	= ""; # default value
    $sleepAfterAnalysis     = 0;  # default no sleep
    $fileList               = ""; # default value
    $altOutputDir           = ""; # default is for op to go where make puts it's op
    %defineOnOption         = (); # no extra defines by default
    $includeFlag            = "[I|i]"; # default is upper or lower case i
    @filename_extensions    = ("c", "C", "cc", "CC", "cC", "Cc", "cxx", "CXX", "Cxx", "cXX", "cxX", "cpp", "Cpp", "cPP", "cpP");
	@non_compile_options	= ("MM");  # default flag 

	# format of complianceModuleList: 
    # COMPLIANCE_MODULE name, exec name, analyzer name, analyser personality, options
	@complianceModuleList	= ( ["hicppcm", "qaccuser_hiccpcm", "QACPP", "hicppcm.p_a", "-afe*"],
							  ["m2cm", "qacsa_m2cm", "QAC", "m2cm.p_a", "-forget", "cmaf"],
							  ["mcppcm", "qacppsa_mcpp", "QACPP", "mcpp.p_a", "-forget" ,"cmaf"],
							  ["mcm", "qacsa_mcm", "QAC", "mcm.p_a", "-forget", "cmaf"],
							  ["jcm", "qacppsa_jcm", "QACPP", "jcm.p_a", "-forget", "cmaf", "-po", "extension::header=['h','cpp','']", "-po", "extension::implementation=['h','cpp']"]
							 );
							  
    $file_locking           = 0; # no file locking
    $remoteHost             = ""; # no remote host
    $remoteViewerScript     = ""; # no script to be written
    $remoteMessagePersonality=""; # use default personality
    # derived

    $analyserExecutable     = ""; # will be set up
    $errdspExecutable       = ""; # will be set up
}

# ------------------------------------------------------------------------------
#
# Configuration options can contain environment variables, which must be 
# expanded.  
# Also, where the option ( ~ OUTPUT_DIR ) is a directory or file, this can be
# in the form of a relative path.  These paths are relative to the current
# directory, or if the option is set in a configuration file it is relative
# to the location of the configuration file.
#
sub expandOption
{
    my $baseOption = shift;

    # expand $(X) type environment variables
    #
    my $t = EXPAND_ENV_VARS( $baseOption );

    # expand relative paths
    #
    $t = EXPAND_RELATIVE_PATHS( $t );

    return $t;
}

sub READ_FILENAME_EXTENSIONS
{
	# Usage @filename_extension_list = READ_FILENAME_EXTENSIONS( $var );
	# extensions, separated by ';'
	# List of individual extensions to compare against compiler command line.

	my $var = shift;
	@filename_extensions = ();  #erase defaults first

	# regexp: zero or more whitespace before extension, may start with a '.',
	# zero or more whitespace after extension, followed by ';'
	while ( $var =~ /^(\s*)(\.?)(\w*)(\s*);(.*)$/o )
	{
		push @filename_extensions, $3;

		$var = $5;
	}

	# get the last extension if it is not terminated with a ';'
	if ( $var =~ /^(\s*)(\.?)(\w*)(\s*)$/o )
	{				
		push @filename_extensions, $3;
	}
}
# ------------------------------------------------------------------------------

sub EXPAND_ENV_VARS
{
    # Usage $expanded_var = EXPAND_ENV_VARS( $var );
    #
    my $var = shift;

    # expand $(X) type environment variables
    #

    while ( $var =~ /^(.*?)\$\((.*?)\)(.*)$/o )
    {
        my $env_var = $2;

        if ( ! exists( $ENV{ $env_var } ) )
        {
            logMessage( "Environment variable $env_var is not set\n" );
            $var = $1 . $3;
        }
        else
        {
            $var = $1 . $ENV{ $env_var } . $3;
        }
    }

    return $var;
}

# ------------------------------------------------------------------------------

sub EXPAND_RELATIVE_PATHS
{
    # Usage: $expanded_path = EXPAND_RELATIVE_PATHS( $path );
    #
    my $path = shift;

    if ( $path =~ /^\./o )
    {
        return APPLY_FINAL_SEP( $relativeDir ) . $path;
    }
    else
    {
        return $path;
    }
}

# ------------------------------------------------------------------------------
#
# This function sets a wrapper option based on a single setting
#
sub setupOption
{
    my $line = shift;

    if ( $line =~ /^MODE=(.*)$/o )
    {
        $mode = expandOption( $1 );
    }
    elsif ( $line =~ /^WQP_OUTPUT_DIR=(.*)$/o )
    {
        $wqp_list_file_path = expandOption( $1 );
    }
    elsif ( $line =~ /^INCLUDE_OPTION=(.*)$/o )  # CR 12501
    {
        $includeFlag = expandOption( $1 );
    }
    elsif ( $line =~ /^DEFINE_ON_OPTION=(.*)$/o )  # CR 12501
    {
        %defineOnOption = PARSE_DEFINE_OPTIONS( $1 );
    }
    elsif ( $line =~ /^OUTPUT_DIR=(.*)$/o )  # CR 12501
    {
        $altOutputDir = EXPAND_ENV_VARS( $1 );
    }
    elsif ( $line =~ /^SLEEP_AFTER_ANALYSIS=(.*)$/o )
    {
        $sleepAfterAnalysis = expandOption( $1 ); # overrides previous settings
    }
    elsif ( $line =~ /^POST_ANALYSIS(=|\+=)(.*)$/o )
    {
        $postAnalysis = expandOption( $2 ); # appends to previous settings
        push @postAnalysisList, $postAnalysis;
    }
    elsif ( $line =~ /^SECONDARY_ANALYSIS(=|\+=)(.*)$/o )
    {
        push @secondaryAnalysisList, expandOption( $2 ); # appends to previous settings
    }
    elsif ( $line =~ /^COMPLIANCE_MODULE=(.*)$/o )
    {
        $complianceModule = expandOption( $1 ); # overrides previous settings
    }
    elsif ( $line =~ /^COMPLIANCE_MODULE_BASE=(.*)$/o )
    {
        $complianceModuleBase = expandOption( $1 ); # overrides previous settings
    }
    elsif ( $line =~ /^FILELIST=(.*)$/o )
    {
        $fileList = expandOption( $1 ); # overrides previous settings
    }
    elsif ( $line =~ /^COMPILER_SETTINGS_FILE=(.*)$/o )
    {
        $compilerSettingsFile = expandOption( $1 ); # overrides previous settings
    }
    elsif ( $line =~ /^INTEGRATION_TYPE=(.*)$/o )
    {
        $integrationType = expandOption( $1 );
    }
    elsif ( $line =~ /^LOGFILE=(.*)$/o )
    {
        # once set, this cannot be unset by subsequent 
        # wrapper config files
        $useLogging = 1;
        $logFile = expandOption( $1 );
    }
    elsif ( $line =~ /^ANALYSER=(.*)$/o )
    {
        # setting ANALYSER overrides previous settings
        #
        if ( uc( $1 ) eq "QAC" )
        {
            $analyser = "QAC";
        }
        else
        {
            $analyser = "QACPP";
        }
    }
    elsif ( $line =~ /^ANALYSER_BASE=(.*)$/o )
    {
        # setting ANALYSER_BASE overrides previous settings
        #
        $analyserBase = expandOption( $1 );
    }
    elsif ( $line =~ /^ANALYSER_FLAGS=(.*)$/o )
    {
        # As can have: ANALYSER_FLAGS=$ANALYSER_FLAGS flag flag ...
        # we need to replace $ANALYSER_FLAGS with $analyserFlags
        #
        my $newAnalyserFlags = expandOption( $1 );
        $newAnalyserFlags =~ s/\$ANALYSER_FLAGS/$analyserFlags/g;
        $analyserFlags = $newAnalyserFlags;
    }
    elsif ( $line =~ /^ERRDSP_FLAGS=(.*)$/o )
    {
        # As can have: ERRDSP_FLAGS=$ERRDSP_FLAGS flag flag ...
        # we need to replace $ERRDSP_FLAGS with $errdspFlags
        #
        my $newErrdspFlags = expandOption( $1 );
        $newErrdspFlags =~ s/\$ERRDSP_FLAGS/$errdspFlags/g;
        $errdspFlags = $newErrdspFlags;
    }
    elsif ( $line =~ /^DEBUG=(.*)$/o )
    {
        # setting DEBUG overrides previous settings
        #
        if ( expandOption( $1 ) eq "1" )
        {
            $debug = 1;
        }
        else
        {
            $debug = 0;
        }
    }
    elsif ( $line =~ /^STOP_ON_FAIL=(.*)$/o )
    {
        # setting STOP_ON_FAIL overrides previous settings
        #
        if ( expandOption( $1 ) eq "1" )
        {
            $stopOnFail = 1;
        }
        else
        {
            $stopOnFail = 0;
        }
    }
    elsif ( $line =~ /^FILENAME_EXTENSIONS=(.*)$/o )
    {
	  READ_FILENAME_EXTENSIONS( $1 );
    }
    elsif ( $line =~ /^NON_COMPILE_OPTIONS=(.*)$/o )
    {
	  @non_compile_options = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $1 );
    }
    elsif ($line =~ /^LOCK_FILES=(.*)$/o )
    {
        $file_locking = expandOption( $1 );
    }
    elsif ($line =~ /^REMOTE_HOST=(.*)$/o )
    {
        $remoteHost = expandOption( $1 );
    }
    elsif ($line =~ /^REMOTE_VIEWER_SCRIPT=(.*)$/o )
    {
        $remoteViewerScript = expandOption( $1 );
    }
    elsif ($line =~ /^REMOTE_MESSAGE_PERSONALITY=(.*)$/o )
    {
        $remoteMessagePersonality = expandOption( $1 );
    }
    
}

# ------------------------------------------------------------------------------
#
# This function reads a settings file
#
sub readSettingsFile
{
    my $settingsFile = shift;

    $settingsFile = GET_CORRECT_PATH( $settingsFile );

    open ( SETTINGS, "<$settingsFile" ) || DIE( "Cannot open $settingsFile\n$!" );
    
    $relativeDir = SHORT_PATH_NAME( DIR_NAME( $settingsFile ) );

    while ( my $line = <SETTINGS> )
    {
        $line = CHOMP( $line );
        setupOption( $line );
    }

    close SETTINGS;

    $relativeDir = $pwdAtStart;
}

# ------------------------------------------------------------------------------

sub validateSettings
{
    my $returnValue = 1;
    my $ext="";
        # set up extension for executable - this may be need for Windows as DOS
        # seems to run a .bat file before a .exe file
        # If a remote host is set, assume it is UNIX and no exe required.
    if ( $remoteHost eq "" )
    {
        if ( $windows_or_cygwin )
        {    
            $ext=".exe";
        }
    }

    openLogFile();

    logMessage( "Command Line Wrapper version $version for QAC/QAC++\n" );
    logMessage( "Copyright Programming Research Ltd 2009\n" );

    if ( $analyser eq "QACPP" )
    {
        # windows it is qacpp, unix it is qac++.        
        if ( $windows_or_cygwin )
        {
            $analyserExecutable = $analyserBase . "/bin/qacpp";
        }
        else
        {
            $analyserExecutable = $analyserBase . "/bin/qac++";
        }
    }
    else 
    {
        $analyserExecutable = $analyserBase . "/bin/qac";
    }
    $analyserExecutable = $analyserExecutable . $ext;
    $errdspExecutable = $analyserBase . "/bin/errdsp" . $ext;

    $analyserExecutable   = SHORT_PATH_NAME( $analyserExecutable );
    $errdspExecutable     = SHORT_PATH_NAME( $errdspExecutable );
    $compilerSettingsFile = SHORT_PATH_NAME( $compilerSettingsFile );

	# set complianceModuleBase if not set in config file
	if ( $complianceModuleBase eq "" )
	{
		$complianceModuleBase = $analyserBase . "/" . $complianceModule;
	}
	
	$complianceModuleExecutable = "";
	
	if ($complianceModule ne "")
	{
		# check that complianceModule is valid and for correct analyser QAC/QACPP
		# setup the complianceModuleExecutable	 
		SEARCHLOOP: foreach my $localComplianceModuleEntry (@complianceModuleList)
		{
			if ( uc($complianceModule) eq uc($localComplianceModuleEntry->[0]) )
			{
				$complianceModuleEntry = $localComplianceModuleEntry;

				# check correct analyser for complianceModule
				if ( uc($complianceModuleEntry->[2]) ne uc ($analyser) )
				{
				    logMessage( "*** Compliance Module configuration error: wrong analyser specified ***\n" );
				    logMessage( "*** Expected ($complianceModuleEntry->[2]), Received ($analyser) ***\n" );
					$returnValue = 0;
				
					last SEARCHLOOP;
				}
			
				# setup executable name
              	$complianceModuleExecutable = $complianceModuleBase . "/bin/" . $complianceModuleEntry->[1] . $ext;
			    $complianceModuleExecutable = SHORT_PATH_NAME( $complianceModuleExecutable);
	
                # add -via <analyser personality> to analyser flags
                my $analyserPerson = $complianceModuleEntry->[3];
                if ( $analyserPerson ne "" )
                {
                    $analyserFlags = $analyserFlags . " -via \"" . $complianceModuleBase . "/personalities/" .  $analyserPerson. "\"";
                }
				last SEARCHLOOP;	
			}	
		}

		if ($complianceModuleEntry eq "")
		{
			logMessage("*** Compliance Module configuration error: unknown compliance module ($complianceModule) *** \n");
            logMessage("*** Supported modules are: hicppcm, m2cm, mcppcm, mcm, jcm. ***\n");
			$returnValue = 0;
		}	
	}
	
    foreach my $localPostAnalysis (@postAnalysisList)
    {
		$localPostAnalysis = SHORT_PATH_NAME( $localPostAnalysis );
	}

        # CR 13227 multi SA processes - expand name_check and baseline
        # for other task, get first entry and get short path
    foreach my $localSecondaryAnalysis (@secondaryAnalysisList)
    {
		my @sa = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $localSecondaryAnalysis );
        if (@sa->[0] eq "name_check")
        {
            my $exe=$analyserBase . "/bin/pal" . $ext;
            $exe = SHORT_PATH_NAME($exe);
            @sa[0] = $exe;
            splice @sa, 1, 0, $analyser;        # CR 12192 add product name
		    $localSecondaryAnalysis = join( " ", @sa );
        }
        elsif (@sa->[0] eq "baseline")
        {
            my $exe=$analyserBase . "/bin/patchTransform" . $ext;
            $exe = SHORT_PATH_NAME($exe);
            @sa[0] = $exe;
            splice @sa, 1, 0, $analyser;        # CR 12192 add product name
		    $localSecondaryAnalysis = join( " ", @sa );
        }
        else
        {
            # NOTE: CR 12192 (add product name) does not seem appropriate here
            # since the SA program is explicitly specified
		    my $exe = SHORT_PATH_NAME( @sa->[0] );
            @sa[0] = $exe;
		    $localSecondaryAnalysis = join( " ", @sa );

        }
	}

	

    if ( (( $mode eq "MAKEFILE_INTEGRATION_MODE" ) ||
		  ( $mode eq "PROJECT_CREATOR_AND_MAKE_MODE")) &&
         ( $compilerSettingsFile eq "" ) 
       )
    {
        logMessage( "*** Required setting for COMPILER_SETTINGS_FILE is not set ***\n" );
        $returnValue = 0;
    }
    else
    {
        debugMessage( "COMPILER_SETTINGS_FILE=$compilerSettingsFile\n" );
    }

    if ( $analyser eq "" )
    {
        logMessage( "*** Required setting for ANALYSER is not set ***\n" );
        $returnValue = 0;
    }
    else
    {
        debugMessage( "ANALYSER=$analyser\n" );
    }

    if ( $analyserBase eq "" )
    {
        logMessage( "*** Required setting for ANALYSER_BASE is not set ***\n" );
        $returnValue = 0;
    }
    else
    {
        debugMessage( "ANALYSER_BASE=$analyserBase\n" );
    }

    if ( ( $mode ne "MAKEFILE_INTEGRATION_MODE" ) &&
         ( $mode ne "PROJECT_CREATOR_MODE" ) &&
         ( $mode ne "PROJECT_CREATOR_AND_MAKE_MODE" )
       )
    {
        logMessage( "*** MODE must be MAKEFILE_INTEGRATION_MODE or " .
                    "PROJECT_CREATOR_MODE or PROJECT_CREATOR_AND_MAKE_MODE ***\n" );
        $returnValue = 0;
    }

    if ( ( $mode eq "PROJECT_CREATOR_MODE" ) || ( $mode eq "PROJECT_CREATOR_AND_MAKE_MODE" ) ) 
    {
            # check output directory is set and it exists
        if ( $wqp_list_file_path eq "" )
        {
            logMessage( "*** WQP_OUTPUT_DIR must be specified when in " .
                        "PROJECT_CREATOR_MODE ***\n" );
            $returnValue = 0;
        }
        if (-e $wqp_list_file_path && -d $wqp_list_file_path && -W $wqp_list_file_path)
        {
            # OK, exists and writable, now check that on cyqwin it is a WINDOWS directory
	        if ( $windows_or_cygwin )
            {
                if (substr($wqp_list_file_path, 1,1) ne ":")
                {
                    logMessage( " *** WQP_OUTPUT_DIR ($wqp_list_file_path) must be a Windows" .
                                " path. e.g. c:\\temp ***\n");
                    $returnValue = 0;
                }
            }
        }
        else
        {
            logMessage( " *** WQP_OUTPUT_DIR ($wqp_list_file_path) may not exist or be writable ***\n");
            $returnValue = 0;
        }
    }

	# Log message for deprecated POST_ANALYSIS use
	if ( @postAnalysisList != 0 )
	{
		logMessage( "*** POST_ANALYSIS parameter deprecated. Please use SECONDARY_ANALYSIS ***\n" );
	}
		
    if ( $returnValue )
    {
        debugMessage( "INTEGRATION_TYPE=$integrationType\n" );
        debugMessage( "DEBUG=$debug\n" );
        
        if ( $useLogging )
        {
            debugMessage( "LOGFILE=$logFile\n" );
        }

        debugMessage( "ANALYSER_FLAGS=$analyserFlags\n" );
        debugMessage( "ERRDSP_FLAGS=$errdspFlags\n" );
        debugMessage( "COMPLIANCE_MODULE=$complianceModule\n" );
        debugMessage( "POST_ANALYSIS=@postAnalysisList\n" );
        debugMessage( "SECONDARY_ANALYSIS=@secondaryAnalysisList\n" );
        if ($remoteHost ne "")
        {
            debugMessage( "REMOTE_HOST=$remoteHost\n" );
            if($remoteViewerScript ne "")
            {
                debugMessage( "REMOTE_VIEWER_SCRIPT=$remoteViewerScript\n" );
            }
            if($remoteMessagePersonality ne "")
            {
                debugMessage( "REMOTE_MESSAGE_PERSONALITY=$remoteMessagePersonality\n" );
            }
        }
        debugMessage( "Analyser is $analyserExecutable\n" );
        debugMessage( "Errdsp is $errdspExecutable\n" );
        debugMessage( "Errdsp is $errdspExecutable\n" );
    }

    return $returnValue;
}

# ------------------------------------------------------------------------------
#
# setup environment variables
#
sub setupEnvironmentVariables
{ 
    # set up the bin directory QACBIN or QACPPBIN
    #
    $ENV{ $analyser . "BIN" } = SHORT_PATH_NAME( $analyserBase . "/bin" );

    # set up  QACOUTPATH or QACPPOUTPATH
    #
    $ENV{ $analyser . "OUTPATH" } = PWD();
    
    # set up QACHELPFILES or QACPPHELPFILES
    #
    $ENV{ $analyser . "HELPFILES" } = SHORT_PATH_NAME( $analyserBase . "/help" );
}

# ------------------------------------------------------------------------------
#
# openLogFile
#
sub openLogFile
{
    if ( $useLogging )
    {
        open LOGFILE, ">>$logFile";
    }
}

# ------------------------------------------------------------------------------
#
# logMessage
#
sub logMessage
{
    my $message = shift;

    if ( $useLogging )
    {
        print LOGFILE ">> " . $message;
    }
    else
    {
        print STDERR ">> " . $message;
    }
}

# ------------------------------------------------------------------------------
#
# debugMessage
#
sub debugMessage
{
    my $message = shift;

    if ( $debug )
    {
        logMessage( $message );
    }
}

# ------------------------------------------------------------------------------
#
# closeLogFile
#
sub closeLogFile
{
    if ( $useLogging )
    {
        close LOGFILE;
    }
}

# ------------------------------------------------------------------------------
#
# parseCommandLine parses the command line and sets up @qa_options
# It will return 1 if the command line was deemed a compilation line
# i.e some analysis was required.
#
sub parseCommandLine
{
    my $nextArgumentIsDefine            = 0; # set to 1 if the argument is a sole -D|-d
    my $nextArgumentIsInclude           = 0; # set to 1 if the argument is a sole -I|-i
    my $nextArgumentIsObjectFile        = 0; # set to 1 if the argument is a sole -o
    my $nextArgumentIsOptionsFile       = 0; # set to 1 if the argument is a sole -f
    my $nextArgumentIsWrapperConfigFile = 0; # set to 1 if the argument is a sole -wcf
    my $nextArgumentIsSetting           = 0; # set to 1 if the argument is a sole -set
    my @qa_includes = ();    # the includes
    my @qa_defines  = ();    # the defines
    my @CARGS       = ();    # the compiler arguments

    # Set up flagRegex to represent a regular expression
    # for options on windows/unix
    #
    my $flagRegex;
    if ( $windows_or_cygwin )
    {
        $flagRegex = "[-\/]";
    }
    else
    {
        $flagRegex = "-";
    }

    # Set the default settings prior to reading the wrapper
    # config files
    #
    setDefaultSettings();

    # First, parse the command line in order to pick
    # up the -wcf & -set options and the compiler
    #
    foreach my $arg ( @ARGV )
    {
        if ( $nextArgumentIsWrapperConfigFile )
        {
            # Read the settings from the wrapper config file
            #
            readSettingsFile( $arg );
            $nextArgumentIsWrapperConfigFile = 0;
        }
        elsif ( $nextArgumentIsSetting )
        {
            # Set the setting as specified 
            #
            setupOption( $arg );
            $nextArgumentIsSetting = 0;
        }
        elsif ( $arg =~ /^${flagRegex}wcf$/ )
        {
            $nextArgumentIsWrapperConfigFile = 1;
        }
        elsif ( $arg =~ /^${flagRegex}wcf(.+)$/ )
        {
            readSettingsFile( $1 );
        }
        elsif ( $arg =~ /^${flagRegex}set$/ )
        {
            $nextArgumentIsSetting = 1;
        }
        elsif ( $arg =~ /^${flagRegex}set(.+)$/ )
        {
            setupOption( $1 );
        }
        elsif ( $arg =~ /^${flagRegex}v$/ ||
                $arg =~ /^${flagRegex}version$/ ||
                $arg =~ /^${flagRegex}\?$/ )
        {
            usage();
        }
        else
        {
            push @CARGS, $arg;
        }
    }

    # Now we chop off the first argument (the compiler executable)
    #
    ( $compilerExecutable, @CARGS ) = @CARGS;

    # Set the output directory to the current directory
    # (this is the default behaviour for most compilers).
    # If we see a "-o" option we will use that directory instead.
    #
    $outputDirectory = PWD();

    # Construct the compiler command line (this must be done b4 the call
    # to validateSettings)
    # 
    @compilerCommandLine = @CARGS;

    # validate that the settings are ok
    #
    if ( validateSettings() == 0 )
    {
        logMessage( "*** Incomplete settings ***\n" );
        return 2;
    }

    my $index = 0;

    CARG:
    while ( $index <= $#CARGS )
    {
        my $arg = $CARGS[ $index ];

        my $next_arg;
        if ( $index < $#CARGS )
        {
            $next_arg = $CARGS[ $index + 1 ];
        }
        else
        {
            $next_arg = "";
        }

        if ( $nextArgumentIsDefine ) 
        {
            push @qa_defines, $arg;

            $nextArgumentIsDefine = 0;
        }
        elsif ( $nextArgumentIsInclude ) 
        {
            # CR12032 -I<space>path convert UNIX paths to windows paths on cygwin
            #
            push @qa_includes, GET_CORRECT_PATH( $arg );

            $nextArgumentIsInclude = 0;
        }
        elsif ( $nextArgumentIsObjectFile )
        {
            my $output_directory_supplied = DIR_NAME( GET_FILE_PATH_ABSOLUTE( $arg ) );

            # CR 12501 only change op dir if option selected 

            if ( $altOutputDir ne "" )
            {
                $outputDirectory = MODIFY_OUT_PATH( $output_directory_supplied, $altOutputDir );
            }
            else
            {
                $outputDirectory = $output_directory_supplied;
            }

            $outputDirectory = GET_CORRECT_PATH( $outputDirectory );

            MK_PATH( $outputDirectory );

            $nextArgumentIsObjectFile = 0;
        }
        elsif ( $nextArgumentIsOptionsFile )
        {
            @CARGS = LOAD_OPTIONS_FILE( $arg, $index, @CARGS );
            
            $nextArgumentIsOptionsFile = 0;
        }            

        elsif ( $arg =~ /^\@(.+)$/o )
        {
            # Microsoft CL @command-file (like QAC -via)
            #
            @CARGS = LOAD_OPTIONS_FILE( $1, $index, @CARGS );
        }            
        elsif ( $arg =~ /^${flagRegex}f$/ )
        {
            $nextArgumentIsOptionsFile = 1;
        }
        elsif ( $arg =~ /^${flagRegex}o$/ ) 
        {
            $nextArgumentIsObjectFile = 1;
        }
        elsif ( $windows_or_cygwin && 
                ( $arg =~ /^${flagRegex}Fo$/ )
              )
        {
            $nextArgumentIsObjectFile = 1;
        }
        elsif ( $arg =~ /^${flagRegex}[D|d]$/ ) 
        {
            # CR10233 missing path after include - employ fix for defines as well
            if ( $next_arg =~ /^${flagRegex}/ )
            {
                logMessage( "Empty define directive ignored\n" );
            }
            else  # the next argument is a macro   
            { 
                $nextArgumentIsDefine = 1;
            }
        }
        elsif ( $arg =~ /^${flagRegex}$includeFlag.*$/ ) 
        {
            # CR12501 allow specification of include flags
            #
            if ( $arg !~ /^${flagRegex}$includeFlag$/ )
            {
                my $new_arg = "";
                my $nch = length( $arg );

                while ( substr( $arg, 0, $nch ) !~ /^${flagRegex}$includeFlag$/ )
                {
                    --$nch;
                    $new_arg = substr( $arg, $nch );
                }
                push @qa_includes, GET_CORRECT_PATH( $new_arg );
            }
            else
            {
                # CR10233 missing path after include.
                # Check that argument is not an option.
                # Can't check -I <blank> <file to compile>
                #
                if ( $next_arg =~ /^${flagRegex}/ )
                {
                    logMessage( "Empty include directive ignored\n" );
                }
                else
                {
                    # Next argument is a path
                    #
                    $nextArgumentIsInclude = 1;  # true
                }
            }
        }

        elsif ( $arg =~ /^${flagRegex}[D|d](.+)$/ ) 
        {
            push @qa_defines, $1;
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}Fo(.+)$/ ) )
        {
            my $output_directory_supplied = DIR_NAME( GET_FILE_PATH_ABSOLUTE( $1 ) );
            # CR 12501 only change op dir if option selected 

            if ( $altOutputDir ne "" )
            {
                $outputDirectory = MODIFY_OUT_PATH( $output_directory_supplied, $altOutputDir );
            }
            else
            {
                $outputDirectory = $output_directory_supplied;
            }

            MK_PATH( $outputDirectory );
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}MD$/ ) )
        {
            push @qa_defines, "_MT";
            push @qa_defines, "_DLL";
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}MDd$/ ) )
        {
            push @qa_defines, "_MT";
            push @qa_defines, "_DLL";
            push @qa_defines, "_DEBUG";
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}MT$/ ) )
        {
            push @qa_defines, "_MT";
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}MTd$/ ) )
        {   
            push @qa_defines, "_MT";
            push @qa_defines, "_DEBUG";
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}MLd$/ ) )
        {   
            push @qa_defines, "_DEBUG";
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}LDd$/ ) )
        {   
            push @qa_defines, "_DEBUG";
        }
        elsif ( $windows_or_cygwin && ( $arg =~ /^${flagRegex}GR$/ ) )
        {
            push @qa_defines, "_CPPRTTI";
        }

		foreach my $skipflag (@non_compile_options)
		{
			if ( $arg =~ /^${flagRegex}${skipflag}$/ )
			{
		     # it is not a real compilation, so we skip the analysis
		     # by returning zero
				return 0; 
			}
		}
	  	
		foreach my $fext (@filename_extensions)
		{
			if ( $arg =~ /^[^-].*\.$fext$/ )
			{
				push @qa_files, $arg;
				last;
			}
		}

        # Run through the list of keys in the option list to see 
        # if there is a replacement defined for this $arg option.
        #
        while ( my ( $key, $val ) = each( %defineOnOption ) )
        {
            if ( $arg =~ /${flagRegex}$key$/ )
            {
                push @qa_defines, $val;
            }
        }
    }
    continue
    {
        ++$index;
    }

    if ( scalar( @qa_files ) == 0 )
    {
        # Not handled support for no files
        #
        logMessage( "*** No analysis required ***\n" );
        return 0;
    }

    push @qa_options, ( "-op", $outputDirectory );
    
    # Expand fileList
    #
    if ( $fileList ne "" )
    {
        $fileList = APPLY_FINAL_SEP( $outputDirectory ) . $fileList;
    }
    #  CR 13483 put these before all the make and compiler personality includes
    if ( $analyserFlags ne "" )
    {
        push @qa_options, SPLIT_STRING_ALLOWING_QUOTED_TERMS( $analyserFlags );
    }
    for my $include ( @qa_includes )
    {
        # make all include paths absolute
        #
        if ( $include ne "" )
        {
                # CR 13338 remove any surviving quotes
            $include=UNQUOTE($include);         
            if ( ! IS_FILE_PATH_ABSOLUTE( $include ) )
            {
                $include = SHORT_PATH_NAME( GET_FILE_PATH_ABSOLUTE( $include ) );
            }

            push @qa_options, ( "-I", $include );
        }
    }

    foreach my $define ( @qa_defines )
    {
        push @qa_options, ( "-D", $define );
    }


    # The compiler settings file *must* come after the includes
    # specified on the command line
    #
    if ( $compilerSettingsFile ne "" )
    {
        push @qa_options, ( "-via", $compilerSettingsFile );
    }


    return 1;
}


# ------------------------------------------------------------------------------

sub LOAD_OPTIONS_FILE
{
    # Usage: @CARGS = LOAD_OPTIONS_FILE( $option_filename, $index, @CARGS );
    #
    # Called in middle of parsing $index'th element of @CARGS options 
    # to insert options immediately after the $index element of @CARGS.
    # Inserts options found in $option_filename into @CARGS.
    # No options are removed from @CARGS as preceding @CARGS options
    # are no longer of any interest in the calling parseCommandLine loop.
    #
    my $option_filename = shift;
    my $index           = shift;
    my @CARGS           = @_;

    my @opt_lines = LOAD_FILE_AS_ARRAY( $option_filename );

    my @olist = ();
    for my $line ( @opt_lines )
    {
        # Trim eol
        #
        $line =~ s/[\n\r]+$//og;

        push @olist, SPLIT_STRING_ALLOWING_QUOTED_TERMS( $line );
    }

    splice ( @CARGS, $index + 1, 0, @olist );

    return @CARGS;
}

# ------------------------------------------------------------------------------
#
# runPostAnalysis - only called if there is post-analysis to run
#
sub runPostAnalysis
{
    # Require here $mode is "MAKEFILE_INTEGRATION_MODE" or "PROJECT_CREATOR_AND_MAKE_MODE"
    # 
    if ( ( $mode ne "MAKEFILE_INTEGRATION_MODE" ) &&
		 ( $mode ne "PROJECT_CREATOR_AND_MAKE_MODE" )) 
    {
        return 0;
    }

	my $analysisExitValue= 0;
	
	foreach my $localpostAnalysis (@postAnalysisList)
	{
		my @commandLine = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $localpostAnalysis );
		@commandLine = (@commandLine, "-op", $outputDirectory, $sourceFile );

		debugMessage( join( " ", @commandLine ) . "\n" );

		logMessage( "Post-Analysing $sourceFile ...\n" );
    
		$analysisExitValue = EXECUTE_ARGS_WITH_RESULT( @commandLine );

		if ( $analysisExitValue != 0 ) 
		{
	        logMessage( "Post Analysis FAILED\n" );
	        logMessage( "*** Post-analysis of $sourceFile failed ($analysisExitValue). ***\n" );
	        last;
		}
	    else 
	    {
			logMessage( "Post Analysis OK\n" );
		}
	}
	
    return $analysisExitValue;
}

# ------------------------------------------------------------------------------
#
# runSecondaryAnalysis - only called if there is secondary-analysis to run
#
sub runSecondaryAnalysis
{
    # Require here $mode is "MAKEFILE_INTEGRATION_MODE" or "PROJECT_CREATOR_AND_MAKE_MODE"
    # 
    if ( ( $mode ne "MAKEFILE_INTEGRATION_MODE" ) &&
		 ( $mode ne "PROJECT_CREATOR_AND_MAKE_MODE" )) 
    {
        return 0;
    }

	my $analysisExitValue= 0;
	
	foreach my $localSecondaryAnalysis (@secondaryAnalysisList)
	{
		my @commandLine = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $localSecondaryAnalysis );
		@commandLine = (@commandLine, "-op", $outputDirectory, $sourceFile );

		debugMessage( join( " ", @commandLine ) . "\n" );

		logMessage( "Secondary-Analysing $sourceFile ...\n" );
    
		$analysisExitValue = EXECUTE_ARGS_WITH_RESULT( @commandLine );

		if ( $analysisExitValue != 0 ) 
		{
	        logMessage( "Secondary Analysis FAILED\n" );
	        logMessage( "*** Secondary-analysis of $sourceFile failed ($analysisExitValue). ***\n" );
            #last;
		}
	    else 
	    {
			logMessage( "Secondary Analysis OK\n" );
		}
	}
	
    return $analysisExitValue;
}

# ------------------------------------------------------------------------------
#
# runComplianceModule - only called if there is compliance module to run
#
sub runComplianceModule
{
    # Require here $mode is "MAKEFILE_INTEGRATION_MODE" or "PROJECT_CREATOR_AND_MAKE_MODE"
    # 
    if ( ( $mode ne "MAKEFILE_INTEGRATION_MODE" ) &&
		 ( $mode ne "PROJECT_CREATOR_AND_MAKE_MODE" )) 
    {
        return 0;
    }

	my $analysisExitValue= 0;
	my @commandLine;
	
	my @complianceParameters = @{$complianceModuleEntry}[4..@{$complianceModuleEntry}];

	push @commandLine, $complianceModuleEntry->[2];
	push @commandLine, "-op";
	push @commandLine, $outputDirectory;
	push @commandLine, $sourceFile;	
	foreach my $cmentry (@complianceParameters)
	{
		push @commandLine, $cmentry;
	} 

	debugMessage( join( " ", $complianceModuleExecutable, @commandLine ) . "\n" );

	logMessage( "Compliance analysis: $sourceFile ..." );
    
	$analysisExitValue = EXECUTE_ARGS_WITH_RESULT( $complianceModuleExecutable, @commandLine );

	if ( $analysisExitValue != 0 ) 
	{	
		logMessage( "FAILED\n" );
	}
    else 
	{
		logMessage( "OK\n" );
	}		
		
    return $analysisExitValue;
}

# ------------------------------------------------------------------------------
#
# runAnalysis
#
sub runAnalysis
{
    logMessage( "Analysing $sourceFile ...\n" );

    debugMessage( "$analyserExecutable " . join( " ", @qa_options ) . " $sourceFile\n" );

    my $analysisExitValue = EXECUTE_ARGS_WITH_RESULT( $analyserExecutable, @qa_options, $sourceFile );

    my @errdsp_flags = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $errdspFlags );

    my $temp_file = APPLY_FINAL_SEP( $outputDirectory ) . BASE_NAME( $sourceFile ) . ".tmp";

        # Remote host constrain: for QAC > 6.2 - otherwise is is another ssh connection
    if ( $analyser eq "QAC" && $remoteHost eq "" )
    {
        # QAC before 6.2 is broke!
        # it always returns an exit value of 0 regardless of whether it had
        # any hard errors.
        # the only to work out if we have hard errors is to grep through
        # the output from errdsp!
        # NOTE: errdsp may not be licensed!
        #
        # QAC 6.2 onwards does return the correct exit value
        
        # get QAC version - don't care about version after minor version
        # note that on differnt systems stdout/stderr can come out in different 
        # order, so need to parse all output, not just first line
        my @qacVer;
        if ( $pure_windows )
        {
            @qacVer = `$analyserExecutable -version 2>NUL`;
        }
        else
        {
            @qacVer = `$analyserExecutable -version 2>/dev/null`;
        }
        my $major = 0;
        my $minor = 0;
        foreach my $line (@qacVer)
        {
            if($line =~ "^QAC Deep Flow Static Analyser ([0-9]+)\.([0-9]+)")
            {
                $major = $1;
                $minor = $2;
                if( ($major <= 6) && ($minor < 2) )
                {
                    my $hardErrorFormat = "%?h%(HARDERROR%)";
    
                    my $exitValue = EXECUTE_ARGS_WITH_RESULT
                            ( $errdspExecutable, $analyser, "-HDR-", "-SU0-8",
                                "-op", $outputDirectory, @errdsp_flags,
                                "-messagesonly+", "-format", $hardErrorFormat,
                                $sourceFile, "-file", $temp_file );
    
                    my @temp_file = LOAD_FILE_AS_ARRAY( $temp_file );
    
                    if ( grep( /HARDERROR/o, @temp_file ) > 0 )
                    {
                        $analysisExitValue = 1;
                    }
                }
            }
        }
            # check we managed to find a version
        if($major == 0)
        {
            logMessage("Cannot get QAC version: STOP_ON_FAIL may not work\n");
        }
    }

    if ( $analysisExitValue != 0 ) 
    {
        logMessage( "Analysis FAILED\n" );
    }
    else 
    {
        logMessage( "Analysis OK\n" );
    }

    # Optional sleep after analysis completed
    #
    if ( $sleepAfterAnalysis != 0 )
    {
        my $mess;
        if ( $sleepAfterAnalysis == 1 )
        {
            $mess = "Sleep for 1 second ...\n";
        }
        else
        {
            $mess = "Sleep for $sleepAfterAnalysis seconds ...\n";
        }

        debugMessage( $mess );

        sleep $sleepAfterAnalysis;
    }  
 
    return $analysisExitValue;
}

# ------------------------------------------------------------------------------
#
# runCompilation
#
sub runCompilation
{
    # Require here $mode is "MAKEFILE_INTEGRATION_MODE" or "PROJECT_CREATOR_AND_MAKE_MODE"
    # 
    if ( $mode eq "PROJECT_CREATOR_MODE" ) 
    {
        return 0;
    }

    # Run the compiler

    logMessage( "Compiling ...\n" );

    # Run the compiler and check to see whether we get a non-zero
    # exit code. If we do, exit and return that code.
    #
    debugMessage( "$compilerExecutable " . join( " ", @compilerCommandLine ) . "\n" );

    my $exitValue = LOCAL_EXECUTE_ARGS_WITH_RESULT( $compilerExecutable, @compilerCommandLine );

    if ( $exitValue != 0 ) 
    {
        logMessage( "Compilation FAILED\n" );
    }
    else
    {
        logMessage( "Compilation OK\n" );
    }

    return $exitValue;
}

# ------------------------------------------------------------------------------
#
# generateOutput - creates annotated source and FILELIST
#
sub generateOutput
{
    my @errdsp_flags = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $errdspFlags );

    if ( $integrationType =~ /STDERR/o )
    {
        my $stderrFile = APPLY_FINAL_SEP( $outputDirectory ) . BASE_NAME( $sourceFile ) . ".stderr";

        my $exitValue = EXECUTE_ARGS_WITH_RESULT
                           ( $errdspExecutable, $analyser, $sourceFile, "-hdr-",
                             "-op", $outputDirectory, @errdsp_flags, "-messagesonly+", 
                             "-file", $stderrFile );

        my @errors = LOAD_FILE_AS_ARRAY( $stderrFile );

        print STDERR @errors;
    }

    if ( $integrationType =~ /TEXT/o )
    {
        my $textFile = APPLY_FINAL_SEP( $outputDirectory ) . BASE_NAME( $sourceFile ) . ".txt";

        my $exitValue = EXECUTE_ARGS_WITH_RESULT
                           ( $errdspExecutable, $analyser, $sourceFile, "-hdr-",
                             "-op", $outputDirectory, @errdsp_flags,
                             "-file", $textFile );
    }

    if ( $integrationType =~ /HTML/o )
    {
        my $htmlFile = APPLY_FINAL_SEP( $outputDirectory ) . BASE_NAME( $sourceFile ) . ".html";

        my $exitValue = EXECUTE_ARGS_WITH_RESULT
                           ( $errdspExecutable, $analyser, $sourceFile, "-hdr-", "-html+",
                             "-op", $outputDirectory, @errdsp_flags,
                             "-file", $htmlFile );
    }

    if ( $fileList ne "" )
    {
        addToFileList();
    }
}

# ------------------------------------------------------------------------------

sub addToFileList
{
    if ($file_locking)
    {
        addToFileListLock();
    }
    else
    {
        addToFileListNoLock();
    }
    if($remoteHost ne "" && $remoteViewerScript ne "")
    {
        writeViewerScript();
    }
}
sub addToFileListLock()
{
    # cannot simply touch this file: if the file does not exist we must write
    # the -op path to it first. 
    my $full_source_filename = LONG_PATH_NAME( GET_FILE_PATH_ABSOLUTE( $sourceFile ) );

    my $source_directory_path = LONG_PATH_NAME( DIR_NAME( $full_source_filename ) );

    # open for append 
    # lock file
    # test size
    # if zero, implement WRITE_FILE here, unlock close
    # otherise close and APPEND
    
    if ( open( FILE, ">>$fileList" ) )
    {
        flock FILE, 2;      
        if ( !(-s $fileList) )
        {
                # file is zero length, so write -op line first
                # $fileList is already an absolute path at this point
            my $filelist_directory_path = LONG_PATH_NAME( DIR_NAME( $fileList ) );
                # Trim off trailing separator(s)
            $filelist_directory_path =~ s/[\\\/]+$//o;
            print FILE "-op \"" . $filelist_directory_path . "\"\n";
            print FILE  "\"" . $full_source_filename . "\"\n";
            flock FILE, 8;
            close FILE;
        }
        else
        {
                # file has data in it already, close file and append
            flock FILE, 8;
            close FILE;
            my @file_data = LOAD_FILE_AS_ARRAY( $fileList );

            if ( grep( /^\Q\"${full_source_filename}\"\E$/, @file_data ) == 0 )
            {
                # Here when not found
                #
                APPEND_TO_FILE( $fileList, "\"" . $full_source_filename . "\"\n" );
            }
        }
    }
    else
    {
        DIE( "Cannot open $fileList\n$!" );
    }
}

sub addToFileListNoLock()
{
    my $full_source_filename = LONG_PATH_NAME( GET_FILE_PATH_ABSOLUTE( $sourceFile ) );

    my $source_directory_path = LONG_PATH_NAME( DIR_NAME( $full_source_filename ) );

    if ( ! ( -e $fileList ) )
    {
        # $fileList is already an absolute path at this point

        my $filelist_directory_path = LONG_PATH_NAME( DIR_NAME( $fileList ) );

        # Trim off trailing separator(s)
        #
        $filelist_directory_path =~ s/[\\\/]+$//o;

        WRITE_FILE( $fileList, ( "-op \"" . $filelist_directory_path . "\"\n",
                                 "\"" . $full_source_filename . "\"\n" )
                  );
    }
    else
    {
        # Test whether this file has already been written:

        my @file_data = LOAD_FILE_AS_ARRAY( $fileList );

        if ( grep( /^\Q\"${full_source_filename}\"\E$/, @file_data ) == 0 )
        {
            # Here when not found
            #
            APPEND_TO_FILE( $fileList, "\"" . $full_source_filename . "\"\n" );
        }
    }
}

# ------------------------------------------------------------------------------

sub add_To_WQP_List
{
    # Usage: $added_bool = add_To_WQP_List( $full_source_filename );
    #
    my $full_source_filename = shift;

    my $added_bool = 0;  # false

    my $wqp_master = APPLY_FINAL_SEP($wqp_list_file_path) . "master.wqp"; 

    $full_source_filename = EVALUATE_RELATIVE_PATHS( $full_source_filename );

    APPEND_TO_FILE( $wqp_master, $full_source_filename . "\n" );

    $added_bool = 1;  # true

    return $added_bool;
}

# ------------------------------------------------------------------------------

sub Write_WQP_File
{
    # Creates a random file named "$sourceFile_XXXXXXXX.wqp" in the WQP 
    # output directory and writes the sourceFile and options to it.
    #
    my $sourceFile = shift;
    my @options = @_;

    # First remove the -op option
    #
    my @options_less_op = ();
    my $index = 0;
    my $output_dir = PWD();

    while ( $index <= $#options )
    {
        if ( $options[ $index ] =~ /^\-(op|outputpath)$/oi )
        {
            $output_dir = $options[ $index + 1 ];

            $index += 2;
        }
        else
        {
            push @options_less_op, $options[ $index ];

            ++$index;
        }
    }
    # remove -via compiler.p_c from the end of the list
    # otherwise the compiler options get added to the analyer personality
    # during the back end process.
    if ( $options_less_op[$#options_less_op -1] eq "-via" )
    {
        pop(@options_less_op);
        pop(@options_less_op);
    }

    # Now, add eol to each option.
    #
    foreach my $opt ( @options_less_op )
    {
        $opt .= "\n";
    }

    my $source_directory_path = DIR_NAME( GET_FILE_PATH_ABSOLUTE( $sourceFile ) );

    my $output_directory_path = GET_FILE_PATH_ABSOLUTE( $output_dir );

        #CR 13224 write wqp files to seperate location - the WQP_OUTPUT_DIR
        # create temporary file with .wqp ext based on source file name
        # so that the file is opened on creation, so name cannot be used by
        # another process.
    my $fh;
    my $template=BASE_NAME( $sourceFile ) . "_XXXXXXXX";
    my $wqp_file;
    use File::Temp qw/ tempfile tempdir /;

    ($fh, $wqp_file) = tempfile($template, SUFFIX => ".wqp", DIR => $wqp_list_file_path);
    logMessage( "Writing " . $wqp_file . "\n" );

    my $added_bool = add_To_WQP_List( $wqp_file );

    close($fh);
    if ( $added_bool )
    {
        # can write here as $wqp_file name is unique to each process
        WRITE_FILE( "$wqp_file", 
           APPLY_FINAL_SEP( $source_directory_path ) . BASE_NAME( $sourceFile ) . "\n", 
           "-op\n",
           "$output_directory_path\n",
            @options_less_op );
    }
}
 
# ------------------------------------------------------------------------------
# CR 12032. When running on cygwin, convert UNIX paths to full Windows paths
# NOTE: on cygwin running wrapper.exe the OSTYPE is MSWin32 as we are using
# ActiveState perl in a DOS subshell.
# Otherise, return (ie UNIX) just return path (ie no-op)
# Only do absolute paths - relative paths are expanded 
# to Windows paths correctly
#
sub GET_CORRECT_PATH
{
    my $path = shift;

    debugMessage( "GET_CORRECT_PATH of $path\n" );
    if( $windows_or_cygwin )
    {
            # CR 13666 if the remote host is set, we need a UNIX path, so don't alter it
            # this test strictly isn't needed as both sides really should be UNIXes
            # but this is useful for testing from Cygwin
        if ($remoteHost eq "")
        {
            # Need cygpath here.  Execute it if it is installed.
            # If not installed, then assume that Cygwin is not installed
            # and if Cygwin isn't installed then $path cannot be a 
            # Cygwin path, so no need to translate it.
            #
            if ( $cygpath_available )
            {
                # See if $path begins with a /
                #
                if ( $path =~ /^\//o )
                {
                    $path = `cygpath -m "$path"`;
        
                    $path = CHOMP( $path );
        
                    return $path;
                }
            }
        }
    }

    return $path;
}

# ------------------------------------------------------------------------------
# CR 12501 Function to combine the output path with the name specified in the
# OUTPUT_DIR option.
#
sub MODIFY_OUT_PATH
{
    # Usage: $outputDirectory = MODIFY_OUT_PATH( $outputDirectory, $altOutputDir );
    #
    # $altOutputDir is either relative or absolute.
    #
    # If $altOutputDir is relative it is simply appended to the supplied
    # $outputDirectory path.
    #
    # If $altOutputDir is absolute, it is compared, element by element, with
    # $outputDirectory, starting at the lhs.  The first element that is different
    # (and any trailing elements) are appended to $altOutputDir.
    # e.g.
    # $altOutputDir is C:\A\B\QAC\M\N
    # $outputDirectory is C:\A\B\C\D
    # Then $outputDirectory becomes C:\A\B\QAC\M\N\C\D
    #
    # Similarly..
    # $altOutputDir is C:\A\B\QAC\M\N
    # $outputDirectory is C:\Fred\Blogs
    # Then $outputDirectory becomes C:\A\B\QAC\M\N\Fred\Blogs
    #
    # Note that the drive letter is treated as a directory element (if different)
    # e.g.
    # $altOutputDir is C:\A\B\QAC\M\N
    # $outputDirectory is D:\A\B\C\D
    # Then $outputDirectory becomes C:\A\B\QAC\M\N\D\A\B\C\D
    # 
    my $outputDirectory = shift;
    my $altOutputDir    = shift;

    # Test whether $altOutputDir is a relative path.
    #
    my $path_is_absolute = IS_FILE_PATH_ABSOLUTE( $altOutputDir );

    if ( ! $path_is_absolute )
    {
        # Here with relative path
        #
        return APPLY_FINAL_SEP( $outputDirectory ) . $altOutputDir;
    }

    # Here with absolute path

    # Ensure correct Cygwin path where necessary
    #
    $altOutputDir = GET_CORRECT_PATH( $altOutputDir );

    # For Cygwin and Unix platform, expand any symbolic links in the paths
    # NB wrapper.exe running on Cygwin can have symbolic links.
    #
    $outputDirectory = EXPAND_LINKS( $outputDirectory );
    $altOutputDir    = EXPAND_LINKS( $altOutputDir );

    # Decompose the paths, including the drive letter if present
    #
    my @o_path = split( /[\\\/\:]+/o, $outputDirectory );
    my @a_path = split( /[\\\/\:]+/o, $altOutputDir );

    # Force the drive letter to upper case (if present)
    #

    if ( $outputDirectory =~ /^.\:/o )
    {
        $o_path[ 0 ] = uc( $o_path[ 0 ] );
    }
   
    if ( $altOutputDir =~ /^.\:/o )
    {
        $a_path[ 0 ] = uc( $a_path[ 0 ] );
    }

    # Find first element which is different between paths
    #

    my $n_elements = scalar( @o_path );
    if ( $n_elements > scalar( @a_path ) )
    {
        $n_elements = scalar( @a_path );
    }

    my $first_different_element = 0;

    ELEMENT:
    for ( my $i_element = 0; $i_element < $n_elements; ++$i_element )
    {
        if ( $o_path[ $i_element ] ne $a_path[ $i_element ] )
        {
            $first_different_element = $i_element;

            last ELEMENT;
        }
    }

    # Now append first different element and trailing elements of
    # OutputDirectory to altOutputDir.
    #
    for ( my $i_element = $first_different_element;
          $i_element <= $#o_path;
          ++$i_element )
    {
        push @a_path, $o_path[ $i_element ];
    }

    # @a_path now has the right tokens

    # Reconstitute the path 

    my $separator = SEPARATOR();

    my $result_path = join( $separator, @a_path );

    # If $altOutputDir has a drive spec, insert the colon

    if ( $altOutputDir =~ /^.\:/o )
    {
        substr( $result_path, 1, 0 ) = ":";
    }

    return $result_path;
}

# ------------------------------------------------------------------------------

sub EXPAND_LINKS
{
    # Usage: $path_expanded = EXPAND_LINKS( $path );
    # $path is a Cygwin or Unix path
    #
    my $path = shift;

    # Get absolute version of path
    #
    $path = GET_FILE_PATH_ABSOLUTE( $path );

    # Get the drive prefix (if present)
    #

    my $drive = "";

    if ( $path =~ /^(.\:)/o )
    {
        $drive = $1;
        $path =~ s/^.\://o;
    }

    my @elements = split( /[\\\/]/o, $path );

    # Now go through element by element replacing preceding path
    # with link evaluations.
    #

    my $new_path = $drive;

    # Count from 1 because first @elements is always empty (because path is abs)
    #
    for ( my $i_el = 1; $i_el <= $#elements; ++$i_el )
    {
        my $element = $elements[ $i_el ];

        $new_path .= "/". $element;

        while ( -l $new_path )
        {
            $new_path = readlink( $new_path );

            $new_path = GET_FILE_PATH_ABSOLUTE( $new_path );
        }
    }

    return $new_path;
}

# ------------------------------------------------------------------------------
# chop line and put options and defines into hash
# Option is
#   DEFINE_ON_OPTION=fr:TEST_DEFINE=FRED mt:_REENTRANT ppc:POWERPC 
# etc
#
sub PARSE_DEFINE_OPTIONS
{
    my $line = shift;

    my %defopts;
  
    # Chop up on spaces first
    #
    my @items = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $line );

    # Then put into hash if correctly formed
    #
    for my $i ( @items )
    {
        my @opt = split( /:/, $i );
        if ( @opt == 2 )
        {
            $defopts{ $opt[ 0 ] } = $opt[ 1 ];
        }    
    }

    return %defopts;
}

# ------------------------------------------------------------------------------

# This function will always run on the local machine, what ever the remoteHost
# is set to - this is for compilation, so cache the remote host and set it to null
# and then restore it once the non-analysis command is done
sub LOCAL_EXECUTE_ARGS_WITH_RESULT
{
    my $tmp=$remoteHost;
    $remoteHost="";
    my $ret=EXECUTE_ARGS_WITH_RESULT (@_);
    $remoteHost=$tmp;
    return $ret;
}

# run the command on $remoteHost if set, otherwise run directly on localhost
sub EXECUTE_ARGS_WITH_RESULT
{
    # Usage: $result_code = EXECUTE_ARGS_WITH_RESULT( @args );
    #
    my $prog_name = shift;
    my @args = @_;

        # check we have a program to run
    if ( $prog_name eq "" )
    {
        return 99;
    }
    if ( $pure_windows )
    {
        if($remoteHost ne "" )
        {
            DIE ("Cannot use REMOTE_HOST on Windows, try Cygwin");
        }
        my $a_parameter_has_space = 0;   # false

        foreach my $arg ( @args )
        {
            if ( index( $arg, " " ) != -1 )
            {
                $a_parameter_has_space = 1;   # true
            }

            $arg = QUOTE_ARG( $arg );
        }
  
        my $path = DIR_NAME( $prog_name );
        my $prog = BASE_NAME( $prog_name );

        if ( $path ne "" )
        {
            my $short_path = SHORT_PATH_NAME( $path );
  
            # Note that $short_path can still contain spaces
            # (see SHORT_PATH_NAME documentation), if this is the
            # case die here.
            # 
            if ( ( index( $short_path, " " ) != -1 ) &&
                  $a_parameter_has_space
               )
            {
                DIE( "Windows system limitation - " .
                     "Cannot execute \"$prog_name\" " . join( " ", @args ) );
            }
  
            $prog_name = APPLY_FINAL_SEP( $short_path )  . $prog;
        }
    }
    my $return_code;
    if($remoteHost eq "")
    {
        $return_code = system( $prog_name, @args );
        $return_code = $return_code >> 8;
    }
    else
    {
            # run command on remote machine. Use two temporary files to store results in
            # one from the ssh command, the other from the qac/qac++ command
            # if either command fails, cat the file contents to debug
            # temporary files are then deleted.
        my $location=`pwd`;
        chomp($location);
        my $tempAnalFile;
        my $tempSSHFile;
        my $afh;
        my $sfh;
        use File::Temp qw/ tempfile tempdir /;
        ($afh, $tempAnalFile) = tempfile("analysis_XXXXXXX", SUFFIX => ".log", DIR => $location);
        ($sfh, $tempSSHFile) = tempfile("ssh_XXXXXXX", SUFFIX => ".log", DIR => $location);
        close($afh);
        close($sfh);
            # the remote command
        my $remoteCommand = "\"cd $location; . $analyserBase/.profile ; $prog_name @args; echo \\\$? > $tempAnalFile; \"";
        debugMessage( "Run $remoteCommand on $remoteHost\n" );
        `ssh $remoteHost $remoteCommand > $tempSSHFile 2>&1`;
            # first check the ssh worked: file may contain more than one line, so load as array
        my @mess=LOAD_FILE_AS_ARRAY($tempSSHFile);
        my $messLen=@mess;
        if($messLen != 0)
        {
                # got an error, send to debug
            logMessage("*** Error in ssh to $remoteHost ***\n");
            for my $line (@mess) 
            {
                logMessage($line . "\n");
            }
            $return_code=@mess[0];
        }
        unlink($tempSSHFile);
        my @mess=LOAD_FILE_AS_ARRAY($tempAnalFile);
            # this should have data in it - the return code from QAC/QAC++
        $messLen=@mess;
        if($messLen == 0)
        {
            logMessage("*** Error in getting analysis result from $remoteHost ***\n");
            $return_code=1;
        }
        else
        {
            # get first line, convert to numbner, OK if zero
            $return_code=@mess[0];
            chomp($return_code);
            if ($return_code =~ /-?[0-9]+/) 
            {
                if ($return_code != 0)
                {
                    logMessage("*** Remote Analysis Error ***\n");
                    for my $line (@mess)            # dump message from file
                    {
                        logMessage($line . "\n");
                    }
                }
            }
            else
            {
                logMessage("*** Remote Analysis Error ***\n");
                for my $line (@mess)            # dump message from file
                {
                    logMessage($line . "\n");
                }
                $return_code =1;
            }
        }
        unlink($tempAnalFile);
    }
 
    return $return_code;
}

# ------------------------------------------------------------------------------

sub PWD
{
    # Usage: $CWD = pwd();
    #
    require Cwd;
 
    my $pwd = Cwd::getcwd();

    $pwd = GET_CORRECT_PATH( $pwd );
 
    return $pwd;
}

# ------------------------------------------------------------------------------

sub GET_FILE_PATH_ABSOLUTE
{
    # Usage: $path_adjusted = GET_FILE_PATH_ABSOLUTE( $path );
    #
    # If $path is relative, returns as absolute. 
    # If absolute returns unchanged.
    #
    my $path_param = shift;
 
    # Make sure uniform separators are used.
    #
    my $path = NORMALISE_PATH_SEPARATORS( $path_param );
 
    # Test path is legal
    #
    if ( ( $path eq "" ) ||
         ( substr( $path, 0, 1 ) eq ":" )
       )
    {
       DIE( "Illegal path \"$path_param\"." );
    }
   
    if ( IS_FILE_PATH_ABSOLUTE( $path ) )
    {
       # Have absolute path
       #
       return $path;
    }
 
    my $ch1 = substr( $path, 0, 1 );
 
    if ( $windows_or_cygwin )
    {
        my $ch2_3 = substr( $path, 1, 2 );
  
        if ( ( $ch2_3 eq ":\\" ) ||
             ( $ch2_3 eq ":/" )
           )
        {
            # Have absolute path
            #
            return $path;
        }
        else
        {
            # Here when path is relative.
            # Calc absolute path.
            #
            
            # First check case like  A:fred
            # Check that drive is the drive of the current dir.
            # If same then make relative to current dir
            # Otherwise don't know what current dir is for specified
            # drive, so fail program
            #
            
            my $current_dir_path = PWD();
            
            my $abs_path;
            
            if ( substr( $path, 1, 1 ) eq ":" )
            {
                # Here with drive spec in supplied parameter ( $path_param ).
                #
             
                # Get that drive spec.
                #
                my ( $drive_specified, $path_specified, $junk ) = split( ":", $path, 3 );
             
                # Get the currently logged drive.
                #
                my ( $current_drive, $rest ) = split( ":", $current_dir_path, 2 );
             
                # Can only resolve a relative path of a drive which is the current drive.
                # e.g. can do:
                #   current drive is C:
                #   path is          C:fred
                # Cannot do
                #   current drive is C:
                #   path is          D:fred
                # as don't know the current directory on D:
                #
             
                if ( lc( $drive_specified ) eq lc( $current_drive ) )
                {
                    $abs_path = APPLY_FINAL_SEP( $current_dir_path ) . $path_specified;
                }
                else
                {
                    DIE( "Cannot derive absolute path for \"$path_param\"." );
                }
            }
            else
            {
                # Here with no drive spec in $path, i.e. $path_param.
                #

                # Remove any leading ./ pairs
                #
                $path =~ s/^(\.\/)*//o;
     
                $abs_path = APPLY_FINAL_SEP( $current_dir_path ) . $path;
            }
           
            return $abs_path;
        }
    }
    else
    {
        # Platform is Unix ( possibly Cygwin )
        #
  
        # Here when path is relative.
        # Calc absolute path.
        #
  
        # Test for path relative to login directory,
        # i.e. starts with '~'
        #
        if ( $ch1 eq "~" )
        {
           # Here with path relative to login directory
           # Get username e.g. ~fred/dir... or ~/dir... etc
           #
           my $home;
           ( $home = $path ) =~ s/^(~.*?)\/.*/$1/o;
  
           my $remainder = substr( $path, length( $home ) );
  
           # Evaluate the $home path
           #
           my $expanded_home_path = `ls -d $home`;

           # Trim
           $expanded_home_path =~ s/^\s*(.*)\s*$/$1/;
  
           # Build the absolute path
           #
 
           # Remove final sep
           #
           $expanded_home_path =~ s/[\\\/]+$//o;

           return $expanded_home_path . $remainder;
        }
        else
        {
            my $current_dir_path = PWD();
     
            # Remove any leading ./ pairs
            #
            $path =~ s/^(\.\/)*//o;
     
            return APPLY_FINAL_SEP( $current_dir_path ) . $path;
        }
    }
}

# ------------------------------------------------------------------------------

sub SHORT_PATH_NAME
{
    # Usage: $windows_or_cygwin_short_path = SHORT_PATH_NAME( $path );
    #
    my $path = shift;
 
    if ( ! defined( $path ) ||
         ( $path eq "" )
       )
    {
        return "";
    }

    if ( $cygwin )
    {
        # Allow GetShortPathName to work on Cygwin paths as well as result is
        # ok if path starts with c drive specification (eg C:
        #
        if ( $path =~ /^.\:/o )
        {
            # Here with what looks like a DOS drive spec and path

            # Remove any trailing separator as Win32::GetShortPathName
            # objects.
            #
            $path =~ s/[\\\/]+$//o;

            require File::Spec::Win32;

            my $spn = Win32::GetShortPathName( $path );

            if ( defined( $spn ) &&
                 ( $spn ne "" )
               )
            {
                # Here with short path name produced a result
                # 

                # Force to Cygwin / Unix separators
                #
                $spn =~ s/\\/\//go;

                return $spn;
            }
        }

        # Here with short path name failed so return original path
        #

        # Force to Cygwin / Unix separators
        #
        $path =~ s/\\/\//go;

        return $path;
    }
    elsif ( $pure_windows )
    {
        # Here with what should be a DOS drive spec and path

        # Remove any trailing separator as Win32::GetShortPathName
        # objects.
        #
        $path =~ s/[\\\/]+$//o;

        require File::Spec::Win32;

        my $spn = Win32::GetShortPathName( $path );

        if ( defined( $spn ) &&
             ( $spn ne "" )
           )
        {
            # Here with short path name produced a result
            # 

            # Force to Cygwin / Unix separators
            #
            $spn =~ s/\\/\//go;

            return $spn;
        }

        # Here with short path name failed so return original path
        #

        # Force to Cygwin / Unix separators
        #
        $path =~ s/\\/\//go;

        return $path;
    }
    else
    {
        # Force to Cygwin / Unix separators
        #
        $path =~ s/\\/\//go;

        return $path;
    }
}

# ------------------------------------------------------------------------------

sub LONG_PATH_NAME
{
    # Usage: $windows_or_cygwin_long_path = LONG_PATH_NAME( $path );
    #
    my $path = shift;
 
    if ( ! defined( $path ) ||
         ( $path eq "" )
       )
    {
        return "";
    }

    # Windows (ActiveState) function getLongPathName wont handle embedded ..\
    # so remove them
    #
    $path = EVALUATE_RELATIVE_PATHS( $path );

    if ( $cygwin )
    {
        # Allow GetLongPathName to work on Cygwin paths as well as
        # Win32::GetShortPathName result is ok if path starts with c drive
        # specification (eg C:, and assume same applies to Win32::GetLongPathName
        #
        if ( $path =~ /^.\:/o )
        {
            # Here with what looks like a DOS drive spec and path

            # Remove any trailing separator as Win32::GetLongPathName
            # objects.
            #
            $path =~ s/[\\\/]+$//o;

            require File::Spec::Win32;

            my $lpn = Win32::GetLongPathName( $path );

            if ( defined( $lpn ) &&
                 ( $lpn ne "" )
               )
            {
                # Here with long path name produced a result
                # 

                # Force to Cygwin / Unix separators
                #
                $lpn =~ s/\\/\//go;

                return $lpn;
            }
        }

        # Here with long path name failed so return original path
        #

        # Force to Cygwin / Unix separators
        #
        $path =~ s/\\/\//go;

        return $path;
    }
    elsif ( $pure_windows )
    {
        # Here with what should be a DOS drive spec and path

        # Remove any trailing separator as Win32::GetShortPathName
        # objects.
        #
        $path =~ s/[\\\/]+$//o;

        require File::Spec::Win32;

        my $lpn = Win32::GetLongPathName( $path );

        if ( defined( $lpn ) &&
             ( $lpn ne "" )
           )
        {
            # Here with long path name produced a result
            # 

            # Force to Cygwin / Unix separators
            #
            $lpn =~ s/\\/\//go;

            return $lpn;
        }

        # Here with long path name failed so return original path
        #

        # Force to Cygwin / Unix separators
        #
        $path =~ s/\\/\//go;

        return $path;
    }
    else
    {
        # Force to Cygwin / Unix separators
        #
        $path =~ s/\\/\//go;

        return $path;
    }
}

# ------------------------------------------------------------------------------

sub DIR_NAME
{
    # Usage: $directory_of_file_path = DIR_NAME( $path );
    #
    my $path = shift;
    
    if ( $path eq "" )
    {
       return "";
    }
 
    my $n_pos = -1;
    my $keep_value;
    my $separator = SEPARATOR();
  
    # Allow both Unix and DOS separators
    #
    $path = NORMALISE_PATH_SEPARATORS( $path );

    LOOP:
    for (;;)
    {
        $keep_value = $n_pos;
        $n_pos      = index( $path, $separator, $n_pos + 1 );
        last LOOP if ( $n_pos == -1 );
    }
    
    return substr( $path, 0, $keep_value + 1 );
}

# ------------------------------------------------------------------------------

sub IS_FILE_PATH_ABSOLUTE
{
    # Usage: $is_absolute_bool = IS_FILE_PATH_ABSOLUTE( $path );
    #
    # Returns true if path starts with \ or / or c:\
    #
    my $path = shift;
 
    if ( $path eq "" )
    {
        return 0;   # false
    }
 
    if ( $windows_or_cygwin )
    {
        # Does $path start with '\' or '/'?
        #
        my $ch_1 = substr( $path, 0, 1 );

        if ( ( $ch_1 eq "\\" ) ||
             ( $ch_1 eq "/" )
           )
        {
            # Have absolute path
            #
            return 1;   # true
        }
  
        # Does $path have drive specifier and has '\' ?
        #
        my $ch2_3 = substr( $path, 1, 2 );
  
        if ( ( $ch2_3 eq ":\\" ) ||
             ( $ch2_3 eq ":/" )
           )
        {
            # Have absolute path
            #
            return 1;    # true
        }
        else
        {
            # Here when path is relative.
            #
            return 0;  # false
        }
    }
    else
    {
        # Platform is Unix
        #
        if ( index( "~/", substr( $path, 0, 1 ) ) != -1 )
        {
            # Have absolute path
            #
            return 1;   # true
        }
        else
        {
            # Here when path is relative.
            #
            return 0;  # false
        }
    }
}

# ------------------------------------------------------------------------------

sub MK_PATH
{
    # Usage:  MK_PATH( $path_to_ensure_created );
    #
    my $path = shift;
 
    if ( $path eq "" )
    {
        return;
    }

    use File::Path;

    File::Path::mkpath( $path, 0, 0777 );

    return;
}

# ------------------------------------------------------------------------------

sub NORMALISE_PATH_SEPARATORS
{
    # Usage: $path_normalised = NORMALISE_PATH_SEPARATORS( $path );
    #
    # Swaps all occurences of \ or / for true path separator.
    #
    my $path = shift;
   
    my $separator = SEPARATOR();

    $path =~ s/\\/$separator/g;
    $path =~ s/\//$separator/g;

    return $path;
}

# ------------------------------------------------------------------------------

sub BASE_NAME
{
    # Usage: $stem_and_extension = BASE_NAME( $path );
    #
    my $path = shift;
    # CR 13338 Fix sepearators - needed if you pass in backslashes
    # in Cygwin
	$path=NORMALISE_PATH_SEPARATORS($path);	
    require File::Basename;

    return File::Basename::basename( $path );
}

# ------------------------------------------------------------------------------

sub STEM_NAME
{
   # Usage: $stem = STEM_NAME( $path );
   #
   my $base_name = BASE_NAME( shift );

   my $dot_pos = rindex( $base_name, "." );

   if ( $dot_pos == -1 )
   {
      return $base_name;
   }
   elsif ( $dot_pos == 0 )
   {
      return "";
   }
   else
   {
      return substr( $base_name, 0, $dot_pos );
   }
}

# ------------------------------------------------------------------------------

sub QUOTE_ARG
{
    # Usage: $quoted_arg = QUOTE_ARG( $arg );
    #
    # Escapes any embedded quotes.
    # If $arg has embedded spaces or embedded \ , and $arg not already quoted,
    # quotes $arg.
    #
    my $arg_supplied = shift;
 
    if ( $arg_supplied eq "" )
    {
        return "";
    }
 
    # If $arg already quoted, remove quotes.
    #
    my $arg = UNQUOTE( $arg_supplied );
 
    my $need_quotes = $arg ne $arg_supplied;
    
    # Test for embedded \ in string that is NOT quoted.
    #
    if ( ( $arg eq $arg_supplied ) &&
         ( index( $arg_supplied, "\\" ) != -1 )
       )
    {
        # Here with arg_supplied is not quoted and has embedded \
        #
        # Set $need_quotes to true to force generation of surrounding quotes
        #
        $need_quotes = 1;  # true
    }
    
    # Here with $arg as bare string that may need quotes if it has
    # embedded spaces.
    #
 
    # Need to maintain any \\ or \" already in string.
    # Hence separate $arg on \\ and \"
    #
 
    my @args;
    if ( $arg eq "" )
    {
        @args = ( "" );
    }
    else
    {
        @args = split( (/(\\\\|\\\")/o), $arg );
    }
 
    # Cannot allow $arg to end in ...\ as this will escape the 
    # applied quote and corrupt the command-line.
    # Similarly settings.via file does not allow paths that end in a
    # bare \
    # e.g. can't have -opC:\prqa\qac-5.0\temp\
    # settings.via can have -op"C:\prqa\qac-5.0\temp\" but this is not
    # suitable as an argument to a Perl program.
    # Achieve this by removing last term of @args if it is '\'.
    #
 
    my $last_arg = $args[ $#args ];
 
    if ( ( $last_arg ne "\\\\" ) &&
         ( $last_arg =~ /\\$/o )
       )
    {
        ( $args[ $#args ] = $last_arg ) =~ s/\\$//o;
    }
 
    SUB_ARG:
    foreach my $sub_arg ( @args )
    {
        next SUB_ARG if ( ( $sub_arg eq "\\\\" ) ||
                          ( $sub_arg eq "\\\"" )
                        );
 
        # Escape all embedded quotes
        #
        $sub_arg =~ s/\"/\\\"/go;
    }
 
    # Reinstate $arg by reassembling the components
    #
    $arg = join( "", @args );
    
    # Now quote if necessary.
    #
    if ( $need_quotes ||
         ( $arg =~ /[\s\"]/oi ) ||
         # Add quotes if $arg contains ( or )
         ( $arg =~ /[\(\)]/o )
       )
    {
        # If $arg starts with -, put the first quote after it
        #
        if ( $arg =~ /^\-/o )
        {
            $arg =~ s/^\-([a-zA-Z0-9_=]+)(.*)$/\-$1"$2"/o;

            return $arg;
        }
        else
        {
            return "\"$arg\"";
        }
    }
    else
    {   
        # Here when no quoting necessary
        #
        return $arg;
    }
}

# ------------------------------------------------------------------------------

sub UNQUOTE
{
    # Usage: $unquoted_string = UNQUOTE( $arg );
    #
    my $arg = shift;
 
    if ( IS_QUOTED( $arg, "\"" ) )
    {
        return substr( $arg, 1, length( $arg ) - 2 );
    }
    else
    {
        return $arg;
    }
}

# ------------------------------------------------------------------------------

sub IS_QUOTED
{
    # Usage: $bool = IS_QUOTED( $item, "\'\"" );
    #
    my $item   = shift;
    my $quotes = shift;
    
    if ( length( $item ) < 2 )
    {
        return 0;  # False
    }
 
    my $first_ch = substr( $item, 0, 1 );
    my $last_ch  = substr( $item, length( $item ) - 1, 1 );
    my @quote_chars = split( //, $quotes );
 
    foreach my $ch ( @quote_chars )
    {
        if ( ( $first_ch eq $ch ) && ( $last_ch eq $ch ) )
        {
            return 1;  # true
        }
    }
 
    return 0;   # false
}

# ------------------------------------------------------------------------------

sub SPLIT_STRING_ALLOWING_QUOTED_TERMS
{
    # Subroutine to split up a string of terms separated by space characters.
    # Where some of the terms may be quoted strings that include spaces.
    # Note that escaped spaces may be embedded.  To do this the space is 
    # prefixed with \ (backslash).
    #
    # Usage:
    #  @list_of_terms = SPLIT_STRING_ALLOWING_QUOTED_TERMS( $str );
    #
    # Handling of escape char \ (backslash) is OS specific.
    # For the following 4 cases Unix and Windows produce 2 different sets of 
    # results.   (NB For the purposes of this sub Cygwin is treated as the same
    # as Unix as Cygwin uses the Unix version of Perl.  Our approach to Cygwin
    # is to use the wrapper.exe file which uses Windows Perl conventions.):
    #
    # 1. "abc\"def\\ghi\ jk\'l\x\y\z"
    # 2. 'abc\"def\\ghi\ jk\'l\x\y\z'
    # 3. 'abc\"def\\ghi\ jkl\x\y\z'  
    # 4. abc\"def\\ghi\ jk\'l\x\y\z  
    #
    # On Unix:
    # 1. "abc\"def\\ghi\ jk\'l\x\y\z" becomes -> abc"def\ghi\ jk\'l\x\y\z
    # 2. 'abc\"def\\ghi\ jk\'l\x\y\z' is illegal (unterminated)
    # 3. 'abc\"def\\ghi\ jkl\x\y\z'   becomes -> abc\"def\\ghi\ jkl\x\y\z
    # 4. abc\"def\\ghi\ jk\'l\x\y\z   becomes -> abc"def\ghi jk'lxyz
    #
    # On Windows:
    # 1. "abc\"def\\ghi\ jk\'l\x\y\z" becomes -> abc"def\\ghi\ jk\'l\x\y\z
    # 2. 'abc\"def\\ghi\ jk\'l\x\y\z' becomes -> 'abc"def\\ghi\
    #                                            jk\'l\x\y\z
    # 3. 'abc\"def\\ghi\ jkl\x\y\z'   becomes -> 'abc"def\\ghi\
    #                                            jkl\x\y\z
    # 4. abc\"def\\ghi\ jk\'l\x\y\z   becomes -> abc"def\\ghi\
    #                                            jk\'l\x\y\z
    #
    # Therefore rules are:
    # On Unix act on \\ and \" inside "
    # On Unix act on nothing inside '
    # On Unix act on \ all outside " and '
    #
    # On Windows act on \" inside "
    # On Windows act on \" inside '
    # On Windows act on \" outside " and '
    #

    my $str = shift;

    if ( $str eq "" )
    {
        return ();
    }
 
    my $separator_char = " ";                             # Constant
    my $tab_char       = "\t";                            # Constant
    my $escape_char    = "\\";                            # Constant
    my $double_quote   = "\"";                            # Constant
    my $single_quote   = "'";                             # Constant
    my $quoting_chars  = $double_quote . $single_quote;   # Constant
    my $escaped_chars  = $double_quote . $escape_char;    # Constant
    
 
    my $in_quoted_term = 0;   # FALSE
    my $term           = "";
    my @list_of_terms  = ();
    my @characters     = split( //, $str );
 
    my $quote_char = "";
    my $i_ch = 0;
 
    I_CH:
    while ( $i_ch <= $#characters )
    {
        my $ch = $characters[ $i_ch ];
 
        my $next_ch;
        if ( $i_ch < $#characters )
        {
            $next_ch = $characters[ $i_ch + 1 ];
        }
        else
        {
            $next_ch = "";
        }
 
        if ( index( $quoting_chars, $ch ) != -1 )
        {
            if ( $in_quoted_term )
            {
                if ( $ch eq $quote_char )
                {
                    $in_quoted_term = 0;  # False
                }
                else
                {
                    $term .= $ch;
                }
            }
            else
            {
                $in_quoted_term = 1;   # True
                $quote_char = $ch;
            }
        }
        elsif ( $ch eq $escape_char )
        {
            # Here with escape character, handle according to platform
            #
            if ( $pure_windows )
            {
                if ( $next_ch eq $double_quote )
                {
                    # Drop the escape char and use the next char ie double quote
                    #
                    $term .= $next_ch;
                    ++$i_ch;
                }
                else
                {
                    # Treat as ordinary char
                    #
                    $term .= $ch;
                }
            }
            else
            {
                # Here when OS has Unix behaviour
                #
                if ( $in_quoted_term )
                {
                    if ( ( $quote_char eq $double_quote ) &&
                         ( index( $escaped_chars, $next_ch ) != -1 )
                       )
                    {
                        # Drop the escape char and use the next char
                        #
                        $term .= $next_ch;
                        ++$i_ch;
                    }
                    else
                    {
                        # Treat as ordinary char
                        #
                        $term .= $ch;
                    }
                }
                else
                {
                    # Here when escape is outside quotes (on Unix)
                    # Remove escape char in each case.
                    #   
                    # Drop the escape char and use the next char
                    #
                    $term .= $next_ch;
                    ++$i_ch;
                }
            }
        }
        elsif ( ( $ch eq $separator_char ) ||
                ( $ch eq $tab_char )
              )
        {
            if ( $in_quoted_term )
            {
                $term .= $ch;
            }
            else
            {
                if ( $term ne "" )
                {
                    push @list_of_terms, $term;
                    $term = "";
                }
            }
        }
        else
        {
            $term .= $ch;
        }
 
        ++$i_ch;
    }
 
    if ( $term ne "" )
    {
        push @list_of_terms, $term;
    }
 
    return @list_of_terms;
}

# ------------------------------------------------------------------------------

sub APPLY_FINAL_SEP
{
    # e.g. $qabin = APPLY_FINAL_SEP( $QACBIN );
    #
    my $path = shift;

    my $path_separator = SEPARATOR();

    if ( $path eq "" )
    {
        return $path;
    }

    # Trim any existing trailing separators
    #
    $path =~ s/[\/\\]*$//o;

    # Append separator
    #
    $path .= $path_separator;

    # Force separators to '/'
    #
    $path = NORMALISE_PATH_SEPARATORS( $path );
    
    return $path;
}

# ------------------------------------------------------------------------------

sub SEPARATOR
{
    # Usage: $separator = SEPARATOR();
    #

    # Always return '/' as this works for Windows QAC as well as Cygwin and Unix.
    #
    return "/";

    if ( $pure_windows )
    {
        return "\\";
    }
    else
    {
        return "/";
    }
}

# ------------------------------------------------------------------------------

sub CHOMP
{
   # Usage: $line = CHOMP( $line );
   # chomp implemented such that it will work for Unix and Windows eol
   #
   my $line = shift;

   $line =~ s/[\n\r]+$//o;

   return $line;
}

# ------------------------------------------------------------------------------

sub LOAD_FILE_AS_ARRAY
{
   # Usage @array_of_lines_of_file = LOAD_FILE_AS_ARRAY( $file_name );
   #
   my $file_name = shift;

   open FILE, "<$file_name" or DIE( "Cannot open file $file_name\n$!" );
   
   my @file_lines = <FILE>;
 
   close( FILE );
   
   return @file_lines;
}

# ------------------------------------------------------------------------------

sub APPEND_TO_FILE
{
    if ($file_locking)
    {
        APPEND_TO_FILE_LOCK (@_);
    }
    else
    {
        APPEND_TO_FILE_NO_LOCK (@_);
    }
}

sub APPEND_TO_FILE_LOCK
{
    # Usage: APPEND_TO_FILE_LOCK( $file_name, $data );
    #
    my $file_name = shift;
    my $data      = shift;

    if ( open( FILE, ">>$file_name" ) )
    {   
            # if we are implementing locking,
            # lock exclusively while file is open
        flock FILE, 2;      
        print FILE $data;
        flock FILE, 8;
        close FILE;
    }
    else
    {
        DIE( "Cannot open $file_name\n$!" );
    }
}

sub APPEND_TO_FILE_NO_LOCK
{
    # Usage: APPEND_TO_FILE_NO_LOCK( $file_name, $data );
    #
    my $file_name = shift;
    my $data      = shift;

    if ( open( FILE, ">>$file_name" ) )
    {   
        print FILE $data;
        close FILE;
    }
    else
    {
        DIE( "Cannot open $file_name\n$!" );
    }
}

# ------------------------------------------------------------------------------

sub WRITE_FILE
{
   # Usage: WRITE_FILE( $file_name, @text );
   #
   my $file_name = shift;
   my @text      = @_;

   open FILE, ">$file_name" or DIE( "Cannot open $file_name\n$!" );

   foreach my $line ( @text )
   {
       print FILE $line;
   }

   close FILE;
}

# ------------------------------------------------------------------------------

sub DIE
{
    # Usage: DIE( $str );
    #
    my $str = shift;

    print STDERR ">> Error - $str\n";

    exit 255;
}

# ------------------------------------------------------------------------------

sub WINDOWS_ONLY_WHICH_PROGRAM
{
    # Usage: $full_filename = WINDOWS_ONLY_WHICH_PROGRAM( $program_name );
    #
    # Returns absolute path to $program_name or "" if not found.
    #
    # NB this WHICH() is identical to GEN::WHICH() and has the limitation that
    # for Windows, if the supplied file is not an executable file, e.g. fred.txt,
    # then WHICH() will return the first full path of fred.txt in
    # PATH, whether it is executable or not.  It is the responsibility
    # of the user of WHICH() to ensure that a truely executable
    # filename is supplied.
    #
    # For Windows:
    #    Gets all paths on PATH and for each path:
    #       Looks for $program_name . ( '.exe' | '.com' | '' ).
    #
    # NB For Windows, this sub will return the first file path in PATH
    # that matches $program_name . ( '.exe' | '.com' | '' )
    # This may be a non-executable file.
  
    my $program_name = shift;
 
    if ( ! $windows_or_cygwin )
    {
        # This sub cannot be called for Unix

        DIE( "Program error in WINDOWS_ONLY_WHICH_PROGRAM" );
    }
    
    if ( $program_name eq "" )
    {
        return "";
    }
 
    # No need to lookup if $program_name is absolute.
    #
    elsif ( IS_FILE_PATH_ABSOLUTE( $program_name ) )
    {
        return EXECUTABLE_FULL_NAME( $program_name );
    }

    # Here when on Windows.
    #
    else
    {
        my $prog       = BASE_NAME( $program_name );
        my $prog_stem  = STEM_NAME( $program_name );
  
        my @exe_names = ( "$prog.exe", "$prog.com", $prog );
  
        my $path_delimiter = ";";
        if ( $cygwin )
        {
            $path_delimiter = ":";
        }

        my $path_list = $ENV{ 'PATH' };
        $path_list    = "." . $path_delimiter . $path_list;
  
        my @paths = split( $path_delimiter, $path_list );
    
        foreach my $path ( @paths )
        {
            $path = APPLY_FINAL_SEP( $path );

            foreach my $exe_name ( @exe_names )
            {
                my $try_name = $path . $exe_name;
    
                if ( -f $try_name )
                {
                    return EXECUTABLE_FULL_NAME( $try_name );
                }
            }
        }
    }
 
    return "";
}

# ------------------------------------------------------------------------------

sub EXECUTABLE_FULL_NAME
{
   # Usage: $full_program_name = EXECUTABLE_FULL_NAME( $program_name );
   #
   my $program_name = shift;

   my $executable_full_name = "";

   if ( $unix )
   {
      $executable_full_name = $program_name;
   }
   else
   {
      if ( -e "$program_name.exe" )
      {
         $executable_full_name = "$program_name.exe";
      }
      if ( -e "$program_name.com" )
      {
         $executable_full_name = "$program_name.com";
      }
      elsif ( -e $program_name )
      {
         $executable_full_name = $program_name;
      }
   }

   if ( $executable_full_name eq "" )
   {
      return "";
   }

   ### $executable_full_name = GET_FILE_PATH_ABSOLUTE( $executable_full_name );
   ### $executable_full_name = EVALUATE_RELATIVE_PATHS( $executable_full_name );

   return $executable_full_name;
}

# ------------------------------------------------------------------------------

sub EVALUATE_RELATIVE_PATHS
{
    # Usage: $simple_path = EVALUATE_RELATIVE_PATHS( $path );
    #
    # Removes embedded .. (ie up one level) specifiers from $path, and
    # . (i.e. current directory)
    #
    my $path = shift;
 
    # Allow both Unix and DOS separators
    #
    my @path_elements = split( /[\\\/]/o, $path );
 
    # For case of empty path, return empty path
    #
    if ( $#path_elements == -1 )
    {
        return "";
    }
 
    my @result         = ();
    my $result_cursor  = 0;   # - points to one beyond last @result element.
    my $element_cursor = 0;   # - points to current @path_elements to process.
 
    # Always include leading ".."
    #
    LEADING_DOT_DOT:
    while ( $path_elements[ $element_cursor ] eq ".." )
    {
        $result[ $result_cursor ] = "..";
        ++$result_cursor;
  
        ++$element_cursor;
        last LEADING_DOT_DOT if ( $element_cursor > $#path_elements );
    }
 
    # Process the remaining elements of the path.
    # Append each non ".." element,
    # Remove from result for each "..".
    #
    REMAINDER:
    while ( $element_cursor <= $#path_elements )
    {
        my $element = $path_elements[ $element_cursor ];
 
        if ( $element eq ".." )
        {
            --$result_cursor;
        }
        elsif ( $element ne "." )
        {
            $result[ $result_cursor ] = $element;
          ++$result_cursor;
        }
 
        ++$element_cursor;
    }
 
    my $separator = SEPARATOR();
    my $result_path = $result[ 0 ];
 
    for ( my $i = 1; $i < $result_cursor; ++$i )
    {
        $result_path .= $separator . $result[ $i ];
    }
 
    return $result_path;
}

# write a suitbale script for launching the Messge Browser from the remote machine
# and make it executable
sub writeViewerScript
{
    my $filename = APPLY_FINAL_SEP( $outputDirectory ) . $remoteViewerScript;
        # don't worry if we can't open this file: not serious
    open FILE, ">$filename" or return;

    print FILE "#!/usr/bin/perl\n";
    print FILE "my \$remoteHost = \"$remoteHost\";\n";
    print FILE "my \$analyserBase =  \"$analyserBase\";\n";
    print FILE "my \$analyser = \"$analyser\";\n";
    if ($remoteMessagePersonality eq "")
    {
        #default 
        if ($analyser eq "QAC")
        {
            print FILE "my \$personality = \"$analyserBase/personalities/start.p_s\";\n";
        }
        else
        {
            print FILE "my \$personality = \"$analyserBase/personalities/default.p_s\";\n";
        }
    }
    else
    {
        print FILE "my \$personality = \"$remoteMessagePersonality\";\n";
    }
    print FILE "my \$fileList = \"$fileList\";\n";
    print FILE "my \$commandString;\n";
    print FILE "my \$location = \"$outputDirectory\";\n";
    print FILE "my \$remoteCommand = \"\\\"cd \$location; . \$analyserBase/.profile; viewer \$analyser -via \$personality -list \$fileList\\\"\";\n";
    print FILE "`ssh -X \$remoteHost \$remoteCommand`;\n";
    close FILE;  
    chmod(0755, $filename);

}
# ------------------------------------------------------------------------------
