#!/usr/bin/perl
#############################################################################
## Crytek Source File
## Copyright (C) 2006, Crytek Studios
##
## Creator: Sascha Demetrio
## Date: Wed Jun 27, 2007
## Description: GNU-make based build system
#############################################################################

use warnings;
use strict;

use Config;
use Getopt::Std;

# Set to non-zero when compiling with -fno-exceptions.
my $optionNoExceptions = 1;

# Set to non-zero when compiling without RTTI (-fno-rtti).
my $optionNoRTTI = 1;

# We'll collect all output file names in the array '@tempFiles' for cleanup.
my @tempFiles = ( );
END
{
  my $tempFile;
  for $tempFile (@tempFiles) { unlink $tempFile; }
}

getopts('o:cb:d:C:G:snhv');
our($opt_o, $opt_d, $opt_C, $opt_c, $opt_b,
		$opt_G, $opt_s, $opt_n, $opt_h, $opt_v);

sub help
{
  print <<EOF
link_prx.pl: Script for linking ELF object files into a PRX.
Synopsis:
  \$PERL link_prx.pl (Options) -- (Input)
Options:
-o (FILE).prx
  The PRX output file.  If this option is omitted, then no PRX output file is
  generated.
-c
  Cygwin mode.  Convert file names of the form '/cygdrive/(DRIVE)/...' to
	'(DRIVE):/...'.  The pattern is recognized for stand alone options _and_ for
	paths follwing a -I option.
-b BASEDIR
  Used in Cygwin mode only.  The base directory in Windows notation
	(drive:path).  If this option is specified, then the path prefix '/base' is
	converted to the specified base directory.
-d (DIR)
  The location of the output directory.  If -o is specified and this option is
  omitted, then the output directory is derived from the PRX output file path.
-C (DIR)
  Specify the location of the Cell SDK.  If this option is omitted, then a
  warning message is printed and the "CELL_SDK" environment variable is read.
-G (DIR)
  Specify the location of the GCC library directory.
-n
  Disable the export-pickup step.  If this option is specified, then no C++
  classes/methods/namespaces can be exported from a PRX.
-s
  Generate the stub libraries.
-h
  Display this help screen and exit.
-v
  Verbose operation.
Input:
This is a list of input files combined with -l and -L options to be passed to
the linker.
EOF
  ;
}

if ($opt_h)
{
  help;
  exit 0;
}

# Extract the list of object files from the command line.
my @objects = ( );
{
  my $arg;
  foreach $arg (@ARGV)
  {
    next if ($arg =~ /^-[lLf]\S.*$/);
    if ($arg =~ /^-.*$/)
    {
      die "unrecognized linker option '$arg'";
    }
    if (not $arg =~ /\.[oa]$/ and not $arg =~ /^@/)
    {
      die "unrecognized linker input file '$arg'";
    }
		if ($opt_c)
		{
			if ($opt_b and $arg =~ /^(-L|)\/base\/(.*$)/i)
			{
				$arg = "$1$opt_b/$2";
			}
			if ($arg =~ /^(-L|)\/cygdrive\/([a-z])\/(.*$)/i)
			{
				$arg = "$1$2:/$3";
			}
			if ($opt_b and $arg =~ /^@\/base\/(.*$)/i)
			{
				$arg = "\@$opt_b/$1";
			}
			if ($arg =~ /^@\/cygdrive\/([a-z])\/(.*$)/i)
			{
				$arg = "\@$1:/$2";
			}
			$arg =~ s/\\/\//g;
			$arg =~ s/\/\/*/\//g;
		}
    push @objects, $arg;
  }
}

# Locate the Cell SDK and set up the tool variables.
my $CELL_SDK;
my $PRX_EXPORTPICKUP;
my $CC;
my $CXX;
my %GCCLIB;
{
  # Determine the OS name.
  my $osName = $Config{osname};
  if ($osName ne 'linux' and $osName ne 'cygwin')
  {
    print STDERR "OS must be either 'linux' or 'cygwin'\n";
    die "host OS '$osName' not supported";
  }
  $CELL_SDK = $opt_C;
  if (not $CELL_SDK)
  {
    $CELL_SDK = $ENV{CELL_SDK};
    if (not $CELL_SDK) { die "no CELL_SDK specified"; }
    warn "using CELL_SDK='$CELL_SDK' from the environment";
  }
  my $CELL_SDK_HOST;
  if ($osName eq 'linux')
  {
    $CELL_SDK_HOST = "$CELL_SDK/host-linux";
  }
  else
  {
    $CELL_SDK_HOST = "$CELL_SDK/host-win32";
  }
  $PRX_EXPORTPICKUP = "$CELL_SDK_HOST/bin/ppu-lv2-prx-exportpickup";
  $CC = "$CELL_SDK_HOST/ppu/bin/ppu-lv2-gcc";
  $CXX = "$CELL_SDK_HOST/ppu/bin/ppu-lv2-g++";

  if (not $opt_G)
  {
    die "the GCC libraries directory has not been specified (option -G)";
  }

  # Locate the system object files for global ctor/dtor support.
  my $gcc_libdir;
  if ($optionNoExceptions)
  {
    $gcc_libdir = "$opt_G/fno-exceptions/fno-rtti";
  }
  else
  {
    $gcc_libdir = "$opt_G";
  }
  if (not -d $gcc_libdir)
  {
    print STDERR "GCC library directory '$gcc_libdir' not found!\n";
    print STDERR "(maybe GCC_VERSION is not set correctly?)\n";
    die "GCC installation problem";
  }
  my $gcc_ecrti_o = "$gcc_libdir/ecrti.o";
  my $gcc_ecrtn_o = "$gcc_libdir/ecrtn.o";
  my $gcc_crtbegin_o = "$gcc_libdir/crtbegin.o";
  my $gcc_crtend_o = "$gcc_libdir/crtend.o";
  my $gcc_file;
  for $gcc_file ($gcc_ecrti_o, $gcc_ecrtn_o, $gcc_crtbegin_o, $gcc_crtend_o)
  {
    if (not -f $gcc_file)
    {
      die "GCC installation problem: '$gcc_file' not found";
    }
  }
  $GCCLIB{'libdir'} = $gcc_libdir;
  $GCCLIB{'ecrti.o'} = $gcc_ecrti_o;
  $GCCLIB{'ecrtn.o'} = $gcc_ecrtn_o;
  $GCCLIB{'crtbegin.o'} = $gcc_crtbegin_o;
  $GCCLIB{'crtend.o'} = $gcc_crtend_o;
}

# Locate the output file and directory.  All temporary output files go to the
# same output directory as the specified output file.
my $output;
my $outputDir;
my $outputBasename;
{
  $output = $opt_o;
  $outputDir = $opt_d;
  if ($output)
  {
    if ($output =~ /^(.*)[\/\\]([^\/\\]+)\.prx$/)
    {
      if (not $opt_d) { $outputDir = $1; }
      $outputBasename = $2;
    }
    else
    {
      if (not $opt_d) { $outputDir = '.'; }
      if ($output =~ /^([^\/\\]+)\.prx$/)
      {
	die "invalid output filename (extension '.prx' required)";
      }
      $outputBasename = $1;
    }
  }
  if (not $output and not $opt_s)
  {
    die "invocation error, must specify either -o (PRX) or -s";
  }
}

# Run a command.
#
# The subroutine is compatible to the "system LIST" call, but the command is
# executed in the output directory (as specified in the -o or -d option).
sub runCmd(@)
{
  my $pid = fork;
  if ($pid == 0)
  {
    chdir $outputDir or die "can not change to output directory '$outputDir'";
    exec(@_);
    die "exec failed: $!";
  }
  elsif (not $pid) { die "fork failed: $!"; }
  if (wait != $pid) { die "wait failed: $!"; }
  if ($?) { print STDERR "command terminated with exit code $?\n"; }
  return $?;
}

# Generate the export file (if requested).  Export files are generated only
# when building the stub libraries.
my $exportFileO;
if (not $opt_n and $opt_s)
{
  my $exportFileC;

  # Generate the export C file from the object files.
  $exportFileC = "$outputDir/${outputBasename}_export.c";
  push @tempFiles, $exportFileC;
  my @prxExportPickupCmd = ($PRX_EXPORTPICKUP, '-o', $exportFileC);
  my $object;
  foreach $object (@objects) { push @prxExportPickupCmd, $object; }
  if ($opt_v) { print join(' ', @prxExportPickupCmd), "\n"; }
  runCmd(@prxExportPickupCmd) == 0 or die "$PRX_EXPORTPICKUP failed: $!";

  # Compile the export file.
  $exportFileO = "$outputDir/${outputBasename}_export.o";
  push @tempFiles, $exportFileO;
  my @exportCompileCmd = ($CC, '-o', $exportFileO, '-c', $exportFileC);
  if ($opt_v) { print join(' ', @exportCompileCmd), "\n"; }
  runCmd(@exportCompileCmd) == 0 or die "export file compilation failed: $!";
}

# Link the PRX.
my @linkPrxCmd = ($CXX, '-mprx');
if ($optionNoExceptions)
{
  push @linkPrxCmd, (
      '-nostdlib',
      '-fno-exceptions',
      "-L$GCCLIB{'libdir'}",
      "-L${CELL_SDK}/target/ppu/lib/fno-exceptions/fno-rtti");
}
if ($optionNoRTTI) { push @linkPrxCmd, '-fno-rtti'; }
if ($output)
{
  push @linkPrxCmd,
    ('-o', $output, '-zgenprx', $GCCLIB{'ecrti.o'}, $GCCLIB{'crtbegin.o'});
  my $arg;
  foreach $arg (@ARGV) { push @linkPrxCmd, $arg; }
}
else
{
  my $object;
  foreach $object (@objects) { push @linkPrxCmd, $object; }
}
if (not $opt_n) { push @linkPrxCmd, $exportFileO; }
if ($opt_s) { push @linkPrxCmd, '-zgenstub'; }
if ($output)
{
  # Link libraries required only when linking the final PRX.
  push @linkPrxCmd, ('-lsupc++', '-lgcc');
  push @linkPrxCmd, ('-lc_stub', '-lstdc++_stub', '-llv2_stub');
  push @linkPrxCmd, ($GCCLIB{'crtend.o'}, $GCCLIB{'ecrtn.o'});
}
if ($opt_v)
{
  push @linkPrxCmd, '-zdump';
  print join(' ', @linkPrxCmd), "\n";
}
runCmd(@linkPrxCmd) == 0 or die "PRX linker failed: $!";

# Tools/link_prx.pl
# vim:ts=2:sw=2

