#!/usr/bin/env perl
# apbuild - a GCC wrapper for creating portable binaries
# Copyright (c) 2003,2004,2005 Hongli Lai
# Distributed under the GNU General Public License

use warnings;
use strict;
use FindBin qw($RealBin);
use File::Spec;
use lib $RealBin;
use lib "$RealBin/../share/apbuild";
use IPC::Open2;
use POSIX;
use Cwd;

use Apbuild::GCC;
use Apbuild::Utils;

our $APBUILD_VERSION = "2.0.0";


######## Initialization ########

# In C mode:
#   $gcc is the default compiler.
#   $gxx2 is not set.
#
# In C++ mode:
#   $gcc is g++ 3.2.
#   $gxx2 is g++ 3.4, and it might not be set.
#   Double compiling is only enabled if both are set.
#
# Note that $APBUILD_CXX_MODE is an internal environment variable
# which should not be set by the user.
our ($gcc, $gxx2);

if ($ENV{APBUILD_CXX_MODE}) {
	# C++ support; user can:
	# - Not set APBUILD_CXX1/2 at all -> use g++ as default, and don't double compile C++ sources.
	# - Set APBUILD_CXX1 and APBUILD_CXX2 -> enable double compiling.
	# - Set only APBUILD_CXX1 -> don't double compile.
	# - Set only APBUILD_CXX2 -> use 'g++' as default for APBUILD_CXX1, and enable double compiling.

	if (empty($ENV{APBUILD_CXX1}) && empty($ENV{APBUILD_CXX2})) {
		$gcc = new Apbuild::GCC('g++');

	} elsif (!empty($ENV{APBUILD_CXX1}) && !empty($ENV{APBUILD_CXX2})) {
		$gcc = new Apbuild::GCC($ENV{APBUILD_CXX1});
		$gxx2 = new Apbuild::GCC($ENV{APBUILD_CXX2});

	} elsif (!empty($ENV{APBUILD_CXX1})) {
		$gcc = new Apbuild::GCC($ENV{APBUILD_CXX1});

	} elsif (!empty($ENV{APBUILD_CXX2})) {
		$gcc = new Apbuild::GCC('g++');
		$gxx2 = new Apbuild::GCC($ENV{APBUILD_CXX2});
	}

} else {
	my $gcc_command = 'gcc';
	$gcc_command = $ENV{APBUILD_CC} if (!empty($ENV{APBUILD_CC}));
	$gcc = new Apbuild::GCC($gcc_command);
}


our $appath;
our $libgcc = $ENV{APBUILD_STATIC_LIBGCC} ? '-static-libgcc' : '-shared-libgcc';

# Find out where apgcc is located and determine the apbuild header
# path relative to the apgcc script location.
$appath = $ENV{APBUILD_PATH} if (!empty($ENV{APBUILD_PATH}));
if (!defined $appath) {
	$appath = $FindBin::Bin;
	$appath =~ s/\/*$//g;
	$appath =~ s/^(.*)\/.*?$/$1/;
	$appath .= '/include/apbuild';
	if (! -f "$appath/apsymbols.h" && -f "$FindBin::Bin/apsymbols.h") {
		$appath = $FindBin::Bin;
	}
}

# Special constants
our @linking = ('-Wl,--enable-new-dtags,--rpath,${ORIGIN}/../lib,--rpath,${ORIGIN}/../lib/autopackage');
our @include = ("-I$appath", '-include', "$appath/apsymbols.h", "-DAPBUILD_VERSION=\"$APBUILD_VERSION\"");
our $extraTypes = $Apbuild::GCC::extraTypes;
our $srcTypes = $Apbuild::GCC::srcTypes;


@linking = () if ($ENV{APBUILD_NO_RPATH});
if (!empty($ENV{APBUILD_INCLUDE})) {
	foreach my $dir (split /:+/, $ENV{APBUILD_INCLUDE}) {
		push @include, "-I$dir" if (-d $dir);
	}
}

# Include the apbuild include folder, and all folders inside that folder.
if (opendir D, $appath) {
	foreach my $dir (readdir D) {
		if ($dir !~ /^\./ && -d "$appath/$dir") {
			push @include, "-I$appath/$dir";
		}
	}
	closedir D;
}

if (!empty($ENV{APBUILD_PROJECTNAME})) {
	push @linking, '-Wl,--rpath,${ORIGIN}/../lib/' . $ENV{APBUILD_PROJECTNAME};
}


our %capabilities = $gcc->capabilities;
our %capabilities_gxx2;
%capabilities_gxx2 = $gxx2->capabilities if ($gxx2);
push @linking, $libgcc if ($capabilities{libgcc});


############# Detect compilation situation #############

our $situation = $gcc->situation(\@ARGV);
if ($ENV{APBUILD_CXX_MODE}) {
	debug "apg++ @ARGV\n";
} else {
	debug "apgcc @ARGV\n";
}
debug "Situation: $situation\n";


# Handle each situation
# Only situations that involve compiling or linking need to be treated specially
if ($situation eq 'compile') {
	# Extract the parameters and files
	my (@files, @params);
	$gcc->splitParams(\@ARGV, \@files, \@params);

	# Compile each source file to an object file.
	# Force GCC to compile C/C++ source files with older glibc symbols.
	debug "\@files: is @files\n";
	foreach my $file (@files) {
		if ($gxx2 && $gxx2->isCxxSource($file)) {
			# This is a C++ source file. Compile the file twice with different ABIs.
			my $src = $file;
			my $old_gcc;
			my %old_cap;

			compileSource($src, \@ARGV);

			$old_gcc = $gcc;
			%old_cap = %capabilities;
			$gcc = $gxx2;
			%capabilities = %capabilities_gxx2;

			beginDoubleCompiling();
			compileSource($src, \@ARGV, '.GCCABI2');
			endDoubleCompiling();

			$gcc = $old_gcc;
			%capabilities = %old_cap;

		} else {
			compileSource($file, \@ARGV);
		}
	}
	exit;

} elsif ($situation eq 'linking') {
	linkObjects(\@ARGV);

	if ($gxx2) {
		# Check whether there are .GCCABI2 objects. Link them if there are.
		my @options;
		my $doubleCompile;

		my $callback = sub {
			my $type = shift;

			if ($type eq 'param' && ($gxx2->isLibrary($_[0]) || $gxx2->isObject($_[0])) && !$gxx2->isStaticLib($_[0]) && -f "$_[0].GCCABI2") {
				push @options, "$_[0].GCCABI2";
				$doubleCompile = 1;
			} else {
				push @options, @_;
			}
		};

		$gcc = $gxx2;
		%capabilities = %capabilities_gxx2;

		$gcc->foreach(\@ARGV, $callback);

		if ($doubleCompile) {
			debug "Double compiling.\n";
			beginDoubleCompiling();
			linkObjects(\@options, ".GCCABI2");
			endDoubleCompiling();
		}
	}
	exit;

} elsif ($situation eq 'compile and link') {
	# Extract the parameters and files
	my (@params, @files, @linking2);
	my @command;
	my $status;

	# Seperate files and linker options.
	# @params are all options except linker options, and is used for compilation of each individual source file.
	# @files are the source files.
	$gcc->splitParams(\@ARGV, \@files, \@params);
	$gcc->stripLinkerParams(\@params, \@linking2);

	# Compile & link only one source file
	if (@files == 1) {
		my @options = modifyLinkerOptions(@params);
		push @options, @linking2;

		if ($gxx2 && $gxx2->isCxxSource($files[0])) {
			# This is a C++ file. Compile twice with different ABIs.
			compileSource($files[0], \@ARGV);
			manipulateDeps(@options);

			$gcc = $gxx2;
			%capabilities = %capabilities_gxx2;

			beginDoubleCompiling();
			compileSource($files[0], \@ARGV, '.GCCABI2');
			manipulateDeps(@options);
			endDoubleCompiling();

		} else {
			compileSource($files[0], [@ARGV, @linking], undef);
			manipulateDeps($files[0], @options);
		}

		exit;
	}

	# Compile individual files into objects
	my $cxx;
	debug "Multiple source files: @files\n";
	foreach my $file (@files) {
		my $out = $file;
		$out =~ s/^(.*)\..*?$/$1.o/;

		if ($gxx2 && $gxx2->isCxxSource($file)) {
			# This is a C++ file. Compile twice with different ABIs.
			my $old_gcc;
			my %old_cap;

			$cxx = 1;
			compileSource($file, [@ARGV, '-c']);

			$old_gcc = $gcc;
			%old_cap = %capabilities;
			$gcc = $gxx2;
			%capabilities = %capabilities_gxx2;

			beginDoubleCompiling();
			compileSource($file, [@ARGV, '-c'], '.GCCABI2');
			endDoubleCompiling();

			$gcc = $old_gcc;
			%capabilities = %old_cap;

		} else {
			compileSource($file, [@ARGV, '-c'], undef);
		}

		$file = $out;
	}

	# Finally, link all objects together.
	my @options = (@params, @linking2);
	linkObjects([@files, @options]);

	if ($cxx) {
		$gcc = $gxx2;
		%capabilities = %capabilities_gxx2;

		# Also link the objects with ABI 2 together
		foreach (@files) {
			$_ .= ".GCCABI2";
		}
		beginDoubleCompiling();
		linkObjects([@files, @options], '.GCCABI2');
		endDoubleCompiling();
	}
	exit;

} else {
	my $ret = run($gcc->command, @ARGV);
	if (defined $ARGV[0] && $ARGV[0] eq '--help') {
		print	"\napbuild environment variables:\n";
		print	"  APBUILD_PATH=path         Specifies the include path for apsymbols.h\n" .
			"                            (like: /usr/local/include/apbuild)\n" .
			"  APBUILD_DEBUG=1           Enable debugging messages\n" .
			"  APBUILD_BOGUS_DEPS=deps   Specify a list of whitespace-seperated bogus\n" .
			"                            library dependancies (like: X11 ICE png). These\n" .
			"                            libraries will not be linked.\n" .
			"  APBUILD_STATIC=deps       Specify a list of whitespace-seperated libraries\n" .
			"                            to statically link to (like: popt z). You can also\n" .
			"                            explicitly specify a filename to the static library.\n" .
			"                            Example: popt=/usr/lib/libpopt.a\n" .
			"  APBUILD_DISABLE_BOGUS_DETECTOR=1   Disable the automatic bogus dependancy\n" .
			"                            detector. This is useful when linking to libraries\n" .
			"                            don't have correct DT_NEEDED entries, like GTK 1.2.\n" .
			"  APBUILD_NOT_BOGUS=deps    If you want to use the automatic bogus dependancy\n" .
			"                            dectector anyway, then you can specify a list of\n" .
			"                            dependancies here that are not bogus.\n" .
			"  APBUILD_STATIC_LIBGCC=1   Link all binaries with -static-libgcc. See the gcc\n" .
			"                            info page for more info about this option.\n" .
			"  APBUILD_PROJECTNAME       If non-empty, apbuild will add\n" .
			"                            \$ORIGIN/../lib/\$APBUILD_PROJECTNAME to the library\n" .
			"                            search path.\n" .
			"  APBUILD_INCLUDE           Prepend the specified directory to the compiler's\n" .
			"                            header search path. The compiler will search this\n" .
			"                            directory first, before searching any other\n" .
			"                            directory. This is useful in combination with the\n" .
			"                            older GTK headers package (see the autopackage\n" .
			"                            website). You can specify multiple directories,\n" .
			"                            seperated by a ':', just like the \$PATH environment\n" .
			"                            variable.\n" .
			"  APBUILD_NO_RPATH          Do not add rpath entries during linking.\n" .
			"  APBUILD_CC                Use the specified C compiler. Default value: gcc\n" .
			"  APBUILD_CXX1,APBUILD_CXX2 Use the specified C++ compiler. Default value: g++\n" .
			"                            Set both variables to enable double compiling. The\n" .
			"                            first should be set to the g++ 3.2 compiler and the\n" .
			"                            second should be set to the g++ 3.4 (or newer)\n" .
			"                            compiler.\n" .
			"  APBUILD_RESOLVE_LIBPATH   A whitespace-separed list of regular expressions which\n" .
			"                            specify the libraries whose path must be resolved into\n" .
			"                            an absolute path.\n";
	}
	exit $ret;
}


######## Functions ########


sub modifyLinkerOptions {
	my @argv = @_;

	# Remove manually specified bogus library dependancies
	my @bogusDeps;
	@bogusDeps = split / +/, $ENV{APBUILD_BOGUS_DEPS} if (!empty($ENV{APBUILD_BOGUS_DEPS}));

	# We call removeLibraries() twice because it may detect
	# some dependancies after we've resolved the library names
	@argv = removeLibraries(\@bogusDeps, @argv);
	@argv = translateLibNames(@argv);
	@argv = removeLibraries(\@bogusDeps, @argv);

	@argv = removeStaticGlibc(@argv);
	if ($capabilities{as_needed}) {
		@argv = rearrangeForAsNeeded(@argv);
		@argv = forceStatic(@argv);
	}

	return @argv;
}


sub removeLibraries {
	my $blacklist = shift;
	return @_ if (@{$blacklist} == 0);

	my @args;
	my $callback = sub {
		my $type = shift;
		if ($type ne "param") {
			push @args, @_;
			return;
		}

		my $lib;
		$_ = $_[0];
		if (/^-l(.+)/) {
			$lib = $1
		} elsif (/(.*)\/lib(.+)\.so/) {
			$gcc->addSearchPaths($1);
			$lib = $2;
		} else {
			push @args, @_;
			return;
		}

		# We now have a library parameter; remove this parameter
		# if the library's in the blacklist
		foreach my $dep (@{$blacklist}) {
			return if ($lib eq $dep);
		}
		push @args, @_;
	};

	$gcc->foreach(\@_, $callback);
	return @args;
}


# This function translates library linker options to something saner.
# - On my system, -lpng links to libpng.so. However, libpng.so is a symlink to libpng12.so.
#   This function translates -lpng to -lpng12 so that the automatic bogus dependancy stripper
#   can detect this as a bogus dependancy.
# - Translate /usr/lib/libpng.so to /usr/lib/libpng12.so because the soname is different.
# - Translates -pthread to -lpthread.
# - When in C++ mode, removes libstdc++.so.x from the argument list. This causes trouble when
#   double compiling.
#
# TODO: correctly handle static libraries.
# apg++ ... -L/usr/lib -Wl,-Bstatic -lphysfs -Wl,-Bdynamic 
# -> /usr/lib/gcc-lib/i686-pc-linux-gnu/3.3.5/../../../../i686-pc-linux-gnu/bin/ld: cannot find -lphysfs-1.0
sub translateLibNames {
	my (@args, @searchPaths);

	# Get a list of search paths
	$gcc->getSearchPaths(\@_, \@searchPaths);

	my $staticMode = 0;
	my $callback = sub {
		my $type = shift;
		my $dontAdd;

		if ($type ne 'param') {
			push @args, @_;
			return;
		}

		$_ = $_[0];
		if (/^-Wl,(.+)/) {
			# Detect whether the next library will be linked statically or dynamically
			foreach my $arg (split /,/, $1) {
				if ($arg eq '-Bdynamic') {
					$staticMode = 0;
				} elsif ($arg eq '-Bstatic') {
					$staticMode = 1;
				}
			}

		} elsif ($staticMode) {
			# Don't try to resolve library name if it's linked statically

		} elsif (/^-l(.+)/ || /--library=(.+)/) {
			my $libname = $1;

			# Resolve libname if explicitely asked to, through APBUILD_RESOLVE_LIBPATH.
			my @libtosolve = split / +/, $ENV{APBUILD_RESOLVE_LIBPATH} if (!empty($ENV{APBUILD_RESOLVE_LIBPATH}));
			foreach (@libtosolve) {
				my $regexp = $_;
				if ($libname =~ /($regexp)/) {
					my $file = searchLib("lib$libname.a", \@searchPaths);
					if ($file && -f $file) {
						debug "resolved", $_[0], "as", $file;
						# Replace -lXXX with the absolute path for libXXX.a
						$_[0] = $file;
						last;
					}
				}
			}

			# Library is a symlink; check whether the sonames match
			my $lib = searchLib("lib$libname.so", \@searchPaths);
			if ($lib && -l $lib) {
				my ($soname1) = $lib =~ /.*\/lib(.+)\.so/;
				my $lib2 = soname($lib);
				my ($soname2) = $lib2 =~ /lib(.+)\.so/;

				if ($soname1 ne $soname2 && defined searchLib("lib$soname2.so", \@searchPaths)) {
					$_[0] = "-l$soname2";
				}
			}

		} elsif ($_ eq '-pthread') {
			$_[0] = "-lpthread";

		} elsif ($ENV{APBUILD_CXX_MODE} && /\/?libstdc\+\+\.so/) {
			$dontAdd = 1;

		} elsif (/(.*)\/?(lib.+\.so.*)/) {
			$gcc->addSearchPaths ($1);
			push @searchPaths, $1;
			my $lib = searchLib($2, \@searchPaths);
			if (defined $lib) {
				my $soname = soname($lib);
				$lib = searchLib($soname, \@searchPaths);
				$_[0] = $lib if (defined $lib);
			}
		}
		push @args, @_ if (!$dontAdd);
	};
	$gcc->foreach(\@_, $callback);

	return @args;
}


# Replace -static with something else. We can't statically link to glibc!
# So we statically link to everything but glibc.
sub removeStaticGlibc {
	my $hasStatic = 0;
	foreach (@_) {
		if ($_ eq '-static') {
			$hasStatic = 1;
			last;
		}
	}
	return @_ if (!$hasStatic);

	my @argv;
	foreach (@_) {
		if ((/^-l(.+)/ || /^--library=(.+)/) && defined $1 && $1 ne 'c') {
			push @argv, '-Wl,-Bstatic';
			push @argv, $_;
			push @argv, '-Wl,-Bdynamic';
		} elsif ($_ ne "-static") {
			push @argv, $_;
		}
	}
	return @argv;
}


# 'gcc -Wl,--as-needed foo.o -lpng' breaks the binary.
# 'gcc foo.o -Wl,--as-needed -lpng' doesn't.
# Move object files to before the first library flag.
#
# Furthermore, -lbfd and -liberty must be the last arguments, or
# an app won't link properly in some cases.
sub rearrangeForAsNeeded {
	my @args;
	my @nonParams;
	my @last;

	my $callback = sub {
		my $type = shift;

		if ($type ne "param") {
			push @nonParams, @_;

		} elsif ($_[0] eq "-lbfd" || $_[0] eq "-liberty") {
			push @last, @_;

		} elsif ($gcc->isLibrary($_[0])) {
			push @args, @_;

		} elsif ($gcc->isObject($_[0]) || $gcc->linkOrderIsImportant($_[0])) {
			push @nonParams, @_;

		} else {
			push @args, @_;
		}
	};

	$gcc->foreach(\@_, $callback);
	unshift @args, "-Wl,--as-needed";
	unshift @args, @nonParams;
	push @args, @last;
	return @args;
}


################ Automatic bogus dependancy stripper ################


# Automatically detecting bogus dependancies & force static linking to certain X libraries
sub manipulateDeps {
	return if ($capabilities{as_needed});
	my @searchPaths;
	my $output = 'a.out';
	my $i = 0;
	my @deps;
	my @argv;

	if ($ENV{APBUILD_DISABLE_BOGUS_DETECTOR}) {
		@argv = @_;
		goto FINAL;
	}

	# Get a list of search paths and the output filename
	for ($i = 0; $i < @_; $i++) {
		if ($_[$i] eq "-L") {
			push (@searchPaths, $_[$i + 1]) if (defined $_[$i + 1]);
			$i++;
		} elsif ($_[$i] =~ /^-L(.+)/ || $_[$i] =~ /^--library-path=(.+)/) {
			push (@searchPaths, $1);
		} elsif ($_[$i] eq "-o") {
			$output = $_[$i + 1] if (defined $_[$i + 1]);
			$i++;
		}
	}

	# Find out what libraries the executable needs
	my ($r, $w);
	my $pid = open2 ($r, $w, 'objdump', '-p', $output);
	close ($w);
	foreach (<$r>) {
		next unless (/^  NEEDED/);
		s/^  NEEDED[ \t]+//;
		s/\n//;
		my $lib = searchLib ($_, \@searchPaths);
		push (@deps, $lib) if (defined $lib);
	}
	close ($r);
	waitpid ($pid, 0);

	# Some -l options have no effect. For example, when linking apollon,
	# -lXinerama is passed, yet the resulting executable doesn't have a
	# DT_NEEDED entry for libXinerama.so. Remove those options so that
	# they won't interfere with forceStatic().
	foreach (@_) {
		if ((/^-l(.+)/ || /^--library=(.+)/)
		    && !searchLib("lib$1.a", \@searchPaths)
		    && !(/^-lpng$/) # Special case the libpng mess, bah
		   ) {
			# "Xinerama"
			my $arg = $1;
			# Only add to @argv if $arg is in @deps
			foreach my $dep (@deps) {
				# "/usr/X11R6/lib/libXinerama.so.1" -> "Xinerama"
				my ($soname) = $dep =~ /.*\/lib(.+)\.so/;
				if ($arg eq $soname) {
					push (@argv, "-l$arg");
					last;
				}
			}
		} else {
			push (@argv, $_);
		}
	}

	# Find out which symbols the executable needs, and which symbols are provided
	# by the libraries it's linked to.
	my %appsyms = extractSymbols ('UBV', $output);
	my @bogusDeps;

	foreach my $lib (@deps) {
		# Never remove libc, libgcc_s and libstdc++
		next if ($lib =~ /^\/lib\/lib(c|gcc_s|stdc\+\+)\.so/);

		my %libsyms = extractSymbols ('TBVRDSWG', $lib);
		my $bogus = 1;

		foreach my $sym (keys %libsyms) {
			if (defined $appsyms{$sym}) {
				debug ("Real dependancy $lib: $sym (lib: $libsyms{$sym} - app: $appsyms{$sym})\n");
				$bogus = 0;
				last;
			}
		}

		if ($bogus) {
			my ($soname) = $lib =~ /.*\/lib(.+)\.so/;
			push (@bogusDeps, $soname);
		}
	}

	FINAL: {
		# Don't strip dependancies that are explicitly marked as not bogus
		my %notBogus;
		if (!empty($ENV{APBUILD_NOT_BOGUS})) {
			foreach (split / +/, $ENV{APBUILD_NOT_BOGUS}) {
				$notBogus{$_} = 1;
			}
		}

		my @tmp;
		foreach (@bogusDeps) {
			push @tmp, $_ if (!$notBogus{$_});
		}
		@bogusDeps = @tmp;

		my @options = removeLibraries(\@bogusDeps, @argv);
		@options = forceStatic(@options);

		if ("@options" ne "@argv") {
			my @command = ($gcc->command, @include, @linking, @options);
			debug("Bogus dependancies: @bogusDeps\n") if (@bogusDeps);
			debug("Relinking: @command\n");
			my $status = run(@command);
			exit($status) if ($status != 0);
		}
	}
}

sub extractSymbols {
	my $types = shift;
	my %symbols = ();
	my ($r, $w);
	my $pid = open2 ($r, $w, 'nm', '-D', @_);

	close ($w);
	foreach (<$r>) {
		if (/^.{9}[$types]/) {
			s/\n//;
			s/^.{9}//;
			my ($type, $name) = split (/ /, $_, 2);
			$symbols{$name} = $type;
		}
	}
	close ($r);
	waitpid ($pid, 0);
	return %symbols;
}


# Force static linking against libraries in $APBUILD_STATIC and certain X libraries.
sub forceStatic {
	my (%xlibs, %staticList, $X11linked, $linkedToStaticX);
	my (@args, @searchPaths);

	# Create a list of libraries that we want to statically link
	$gcc->getSearchPaths(\@_, \@searchPaths);
	foreach (qw(Xrender Xcursor Xi Xinerama Xrandr Xv Xxf86dga Xxf86misc Xxf86vm)) {
		my $file = searchLib("lib$_.a", \@searchPaths);
		$staticList{$_} = $file if (defined $file);
		$xlibs{$_} = 1;
	}

	if (!empty($ENV{APBUILD_STATIC})) {
		foreach (split / +/, $ENV{APBUILD_STATIC}) {
			my ($lib, $file) = split /=/, $_, 2;
			$file = searchLib("lib$lib.a", \@searchPaths) if (!defined $file);
			$staticList{$lib} = $file if (defined $file);
		}
	}

	# Modify linker options for static linking
	my $callback = sub {
		my $type = shift;
		my $libname;
		if ($type eq "param" && $gcc->isLibrary($_[0], \$libname)) {
			$X11linked = 1 if (!$X11linked && $libname eq 'X11');
			if ($staticList{$libname}) {
				# This parameter is a library and is in the list of libraries
				# to statically link; replace parameter by a filename to the
				# static library
				push @args, $staticList{$libname};
				$linkedToStaticX = 1 if ($xlibs{$libname});

			} else {
				push @args, @_;
			}

		} else {
			push @args, @_;
		}
	};
	$gcc->foreach(\@_, $callback);

	# The app must be linked to libX11 if it has statically linked any the static X libraries
	push @args, "-lX11" if ($linkedToStaticX && !$X11linked);
	return @args;
}

##
# compileSource(source, argss, extension, extra...)
# source: the source filename.
# args: the full GCC arguments (may include other source files) used for compilation.
# extension: if not undef, $extension will be appended to the output object file's filename.
# extra: extra parameters to pass to the compiler.
sub compileSource {
	my ($source, $args, $ext) = @_;
	my (@command, @tmp, @params, @sourceParams, @otherParams);

	$gcc->splitParams($args, undef, \@tmp);
	push @tmp, $source;

	if (defined $ext) {
		# Change the output file's extension.
		$gcc->setOutputFile(\@tmp, $gcc->getOutputFile(\@tmp) . $ext);
	}
	$gcc->splitParams(\@tmp, undef, \@params);

	my $callback = sub {
		my $type = shift;
		if ($type eq 'param' && $gcc->sourceOrderIsImportant($_[0])) {
			push @sourceParams, @_;
		} else {
			push @otherParams, @_;
		}
	};
	$gcc->foreach(\@params, $callback);

	if ($source =~ /\.($srcTypes)$/) {
		@command = ($gcc->command, @include, @sourceParams, $source, @otherParams);
	} else {
		@command = ($gcc->command, @sourceParams, $source, @otherParams);
	}

	debug "@command\n";
	my $status = run(@command);
	exit($status) if ($status != 0);
}

# Checks whether there should be an ABI2 version of a certain static library.
sub checkStaticLibrary {
	my ($lib, $ext) = @_;
	my ($pid, $r, $w, @objects, $doubleCompile);
	my (undef, $libdir, $libname) = File::Spec->splitpath($lib);
	my $newlib = "$lib$ext";

	# Return the ABI2 version if already exists.
	return $newlib if (-f $newlib);

	# Check the content of the archive. Check whether
	# there are ABI2 versions of the object files inside.
	$pid = open2($r, $w, 'ar', 't', $lib);
	close($w);
	while ((my $file = <$r>)) {
		$file =~ s/\n//g;
		if (-f "$libdir/$file$ext") {
			push @objects, "$file$ext";
			$doubleCompile = 1;
		} else {
			push @objects, $file;
		}
	}
	close($r);
	waitpid ($pid, 0);

	if ($doubleCompile) {
		my $oldDir = getcwd();
		$newlib = "$libname$ext";
		debug "Creating static library $newlib\n";

		chdir($libdir);
		my @command = ("ar", "cru", $newlib, @objects);
		debug(@command);
		my $ret = run(@command);
		exit($ret) if ($ret != 0);

		@command = ("ranlib", $newlib);
		debug(@command);
		$ret = run(@command);
		exit($ret) if ($ret != 0);

		chdir($oldDir);
		return $newlib;
	} else {
		return undef;
	}
}

sub linkObjects {
	my ($args, $ext) = @_;
	my @options = modifyLinkerOptions(@{$args});

	if (defined $ext) {
		$gcc->setOutputFile(\@options, $gcc->getOutputFile(\@options) . $ext);

		# Check whether this object links to any static libraries.
		# If it does, check whether there should be an ABI2 version of that
		# static library, and attempt to create it.
		my @options2;

		my $callback = sub {
			my $type = shift;
			if ($type eq 'param' && $gcc->isStaticLib($_[0])) {
				my $newlib = checkStaticLibrary($_[0], $ext);
				if (defined $newlib) {
					push @options2, $newlib;
				} else {
					push @options2, @_;
				}
			} else {
				push @options2, @_;
			}
		};
		$gcc->foreach(\@options, $callback);
		@options = @options2;
	}

	my @command = ($gcc->command, @linking, @options);
	debug "@command\n";

	my $status = run(@command);
	exit ($status) if ($status != 0);

	my (@files, @options2);
	$gcc->splitParams(\@options, \@files, \@options2);
	manipulateDeps(@files, @options2);
}

##
# beginDoubleCompiling()
#
# Prepare the environment for double compiling.
sub beginDoubleCompiling {
	# Since g++ will be executed another time, we don't want it to
	# print output to stdout/stderr, because it can potentially
	# screw up some build systems such as libtool.
	# stderr will go to the console (/dev/tty), stdout will be
	# lost (/dev/null).

	our $stdout_fd = fileno(STDOUT);
	our $stderr_fd = fileno(STDERR);
	our $stdout_saved = POSIX::dup($stdout_fd);
	our $stderr_saved = POSIX::dup($stderr_fd);

	my $fd1 = POSIX::open("/dev/null", O_CREAT | O_WRONLY, 0644);
	my $fd2 = POSIX::open("/dev/tty", O_CREAT | O_WRONLY, 0644);
	POSIX::dup2($fd1, $stdout_fd);
	POSIX::dup2($fd2, $stderr_fd);
	POSIX::close($fd1);
	POSIX::close($fd2);
}

##
# endDoubleCompiling()
#
# Unprepare the environment for double compiling.
sub endDoubleCompiling {
	our ($stdout_fd, $stderr_fd, $stdout_saved, $stderr_saved);

	POSIX::dup2($stdout_saved, $stdout_fd);
	POSIX::dup2($stderr_saved, $stderr_fd);
	POSIX::close($stdout_saved);
	POSIX::close($stderr_saved);
}