#! /bin/bash

###########################################################################
#   Copyright (C) 2009 by Hessel Hoogendorp                               #
#   bugs.ccc@gmail.com                                                    #
#                                                                         #
#   This program is free software; you can redistribute it and/or modify  #
#   it under the terms of the GNU General Public License as published by  #
#   the Free Software Foundation; either version 2 of the License, or     #
#   (at your option) any later version.                                   #
#                                                                         #
#   This program is distributed in the hope that it will be useful,       #
#   but WITHOUT ANY WARRANTY; without even the implied warranty of        #
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
#   GNU General Public License for more details.                          #
#                                                                         #
#   You should have received a copy of the GNU General Public License     #
#   along with this program; if not, write to the                         #
#   Free Software Foundation, Inc.,                                       #
#   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
###########################################################################

# -----------------------------------------------------------------------------
# This script wraps ld.
#
# First, it makes the original call to ld, and, if that succeeds, then calls
# the call information extractor with the same parameters.
# -----------------------------------------------------------------------------


# -----------------------------------------------------------------------------
# Call the original gcc compiler.
# -----------------------------------------------------------------------------

# Find the installation path of ld.
LD=$(whereis ld)
LD=${LD#*: }         # Strip off 'ld: '.
LD=${LD%% *}         # Strip off any secondary paths.

# Test whether we found an existing, executable program.
if [ ! -x $LD ]; then
	echo "ERROR: The ld wrapper script could not find a usable linker."
	exit 1
fi

# Find the installation path of ar.
AR=$(whereis ar)
AR=${AR#*: }         # Strip off 'ar: '.
AR=${AR%% *}         # Strip off any secondary paths.

# Test whether we found an existing, executable program.
if [ ! -x $AR ]; then
	echo "ERROR: The ld wrapper script could not find a usable archiver."
	exit 1
fi


# Try to find the target name of this link.
SETTARGET=0
TARGET="a.out"
for ARG in "$@"
do
	if [ $SETTARGET -eq 1 ]; then
		TARGET=$ARG
		break
	fi

	if [ $ARG = '-o' ]; then
		SETTARGET=1
	fi
done

# Now construct the name of the cci-target file, based on the target file.
CCI_TARGET="$TARGET.ccia"

# Supply the linker with all arguments passed to this script, but add the
# --trace switch. This will cause the linker to print out the names of the
# object files it is linking. We intercept these object files and process them.
OBJECTS=$($LD "$@" --trace)

# Check whether the call to ld was successful. If not, return with ld's exit code.
exitCode=$?
if [ $exitCode -ne 0 ]; then
	exit $exitCode
fi

# Randomly generate the name of a temporary directory.
TMPDIR="ccc-$RANDOM.$RANDOM.$RANDOM-ccc"

# Process each of the objects that were emitted by ld and add it to the target file.
CCI_OBJECTS=
OBJECT_ARRAY=($OBJECTS)
set -- ${OBJECT_ARRAY[@]}
while [ $# -gt 0 ]; do

	# Retrieve the first object from the array and shift it off.
	OBJ=$1
	shift

	# Find out what sort of object this is.
	case $OBJ in

		-l*)
			# This is a link to a shared library.

			# Compose the name of the .ccia archive for the dynamic library.
			DYNLIB=$1
			DYNLIB=${DYNLIB#(}
			DYNLIB=${DYNLIB%)}
			DYNLIB="$(readlink -m $DYNLIB).ccia"
			if [ ! -e $DYNLIB ]; then
				continue;
			fi

			# Make sure the temporary directory exists within the current directory.
			if [ ! -d $TMPDIR ]; then
				mkdir $TMPDIR
			fi

			# List all objects present in the archive
			AR_OBJS=$($AR t $DYNLIB)
			for AR_OBJ in $AR_OBJS; do
				$AR p $DYNLIB $AR_OBJ > "$TMPDIR/$AR_OBJ"

				# If the extracted file does not exist, skip.
				if [ ! -e $TMPDIR/$AR_OBJ ]; then
					continue;
				fi

				# If the size of the extracted file is zero, skip
				SIZE=$(stat -c%s "$TMPDIR/$AR_OBJ")
				if [ $SIZE -eq 0 ]; then
					continue
				fi

				# The file was extracted successfully, so add the file  to the
				# string of ccc-object files.
				CCI_OBJECTS="$CCI_OBJECTS $TMPDIR/$AR_OBJ"
			done
			
			shift
			;;

		(*\)*)
			# This seems to be a file in a library.

			LIB=${OBJ#(}
			LIB=${LIB%%)*}

			# Test whether the library file exists. If not, move on to the next object.
			if [ ! -e $LIB ]; then
				continue
			fi

			# It might be that this is actually a link to a library, not the library
			# itself. Therefore, resolve the file to its canonical form (the actual file).
			LIB=$(readlink -m $LIB)

			# Construct the cci-library name from the library name and test whether it
			# exists. If not, move on to the next object.
			CCI_LIB="$LIB.ccia"
			if [ ! -e $CCI_LIB ]; then
				continue
			fi

			# Determine the name of the file that must be extracted from the cci-library.
			OBJECT=${OBJ##*)}
			CCI_OBJECT_C=${OBJECT%.*}.i.cci
			CCI_OBJECT_CPP=${OBJECT%.*}.ii.cci

			# Make sure the temporary directory exists within the current directory.
			if [ ! -d $TMPDIR ]; then
				mkdir $TMPDIR
			fi

			# Attempt to extract the c-version of the object file. If that succeeds,
			# add that file to the string of cci-object files.
			$AR p $CCI_LIB $CCI_OBJECT_C > "$TMPDIR/$CCI_OBJECT_C"
			# Make sure the extracted file is larger than 0 bytes. If not, there
			# was no such file (and if there was, it is not interesting anyway).
			SIZE=$(stat -c%s "$TMPDIR/$CCI_OBJECT_C")
			if [ $SIZE -gt 0 ]; then
				CCI_OBJECTS="$CCI_OBJECTS $TMPDIR/$CCI_OBJECT_C"
				continue
			fi

			# Attempt to extract the cpp-version of the object file. If that succeeds,
			# add that file to the string of cci-object files.
			$AR p $CCI_LIB $CCI_OBJECT_CPP > "$TMPDIR/$CCI_OBJECT_CPP"
			SIZE=$(stat -c%s "$TMPDIR/$CCI_OBJECT_CPP")
			if [ $SIZE -gt 0 ]; then
				CCI_OBJECTS="$CCI_OBJECTS $TMPDIR/$CCI_OBJECT_CPP"
				continue
			fi

			;;
	    *)
	    	# This seems to be a plain file name.
			# Test whether the file exists. If not, move on to the next object.
			if [ ! -e $OBJ ]; then
				continue
			fi

			# It might be that this is actually a link to an object file, not the
			# object file itself. Therefore, resolve the file to its canonical form
			# (the actual file).
			OBJ=$(readlink -m $OBJ)
			
			# Test whether the dereferenced file exists. If not, move on to the next object.
			if [ ! -e $OBJ ]; then
				continue
			fi

			# Attempt to find the .i.cci or .ii.cci equivalent of the object file.
			# The first one that is found is added to the string of cci-object files.
			CCI_OBJECT_C=${OBJ%.*}.i.cci
			CCI_OBJECT_CPP=${OBJ%.*}.ii.cci

			CCI_OBJECT=
			if [ -e $CCI_OBJECT_C ]; then
				CCI_OBJECT=$CCI_OBJECT_C
			elif [ -e $CCI_OBJECT_CPP ]; then
				CCI_OBJECT=$CCI_OBJECT_CPP
			else
				continue
			fi
			CCI_OBJECTS="$CCI_OBJECTS $CCI_OBJECT"

			;;
	esac
done

# Archive the members of the target
$AR cru $CCI_TARGET $CCI_OBJECTS

# If a temporary directory has been created, delete it now.
if [ -d $TMPDIR ]; then
	rm -r $TMPDIR
fi

# The linker returned success, so return success.
exit 0

