diff options
Diffstat (limited to 'tools/apbuild/apgcc')
-rwxr-xr-x | tools/apbuild/apgcc | 1023 |
1 files changed, 1023 insertions, 0 deletions
diff --git a/tools/apbuild/apgcc b/tools/apbuild/apgcc new file mode 100755 index 00000000..2aba0071 --- /dev/null +++ b/tools/apbuild/apgcc @@ -0,0 +1,1023 @@ +#!/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; + +# Don't forget to bump the version in Makefile too. +our $APBUILD_VERSION = "2.0.9"; + + +######## 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}) { + if ($dir =~ /=/) { + # allow people to force changing a path with oldpath=newpath, e.g.,: + # $echo $APBUILD_INCLUDE + # /opt/kde/include=/home/taj/kde-headers/kde-3.3.2/kde/include + # would change -I/opt/kde/include to my KDE 3.3 headers. + # this works around problems where the order of the include + # directories is important. Added for amarok 1.4.3 which has a + # header named scriptmanager.h. KDE also has a header named + # scriptmanager. Order of -I flags forces amarok header to be + # included. + my @splitpaths = split(/=/, $dir); + if (-d $splitpaths[1]) { + foreach (@ARGV) { + s/-I$splitpaths[0]$/-I$splitpaths[1]/g; + } + } + } + else { + 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; +if ($capabilities{hash_style}) { + # By default FC6 only generates a .gnu.hash section, not + # .hash (which is the only thing that FC5 and most other + # distros understand). + # Force generation of both sections if the linker supports it + # so that the binary will run on other systems. + # See http://fcp.surfsite.org/modules/newbb/viewtopic.php?topic_id=29929&forum=10&post_id=128939 + # for more details + push @linking, '-Wl,--hash-style=both'; +} + +if ($capabilities{stack_protector}) { + # gcc-4.1 introduced stack protection to help check for + # buffer overflows. This introduces a silent dependency on + # glibc 2.4 (__stack_chk_fail@@GLIBC_2.4) + # Not many people have glibc 2.4 yet, so disable it. + push @include, "-fno-stack-protector"; +} + +if ($capabilities{fortify_source}) { + # Some distros (e.g. Ubuntu 8.10) activate a bufferoverflow protection + # which is only available since glibc 2.3.4. Apbuild removes all symbols + # newer then 2.3, so this can't be used. + push @include, "-U_FORTIFY_SOURCE"; +} + +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 'precompiled header') { + # 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) + { + compileSource($file, \@ARGV); + } +} 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], [@linking, @options], 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, [@params, '-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_STATIC_X=1 Force static linking of some X extension libraries\n" . + " Don't use this unless you know what you're doing.\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-separated 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 from the argument list. This causes trouble when +# double compiling, unless the -nostdlib option is specified and we're not double compiling. +# libtool can put us in this configuration. + +# 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(.[56])?/) { + # drop this in double compile mode as it can cause issues + # in single-compile mode leave it alone, otherwise, libtool can break + # ^^^ That used to be true + # Now drop it and add the compilers stdc++ + # Works around libtool problems where the wrong libstdc++.so is + # picked up from .la files of dependencies + my @command = ($gcc->command, "--print-file-name=libstdc++.so"); + chomp($_[0] = `@command`); + + } 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. XRender needs to be +# after XCursor for some reason, so push it to the end too. +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" || $_[0] eq "-lXrender") { + 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); + if (defined $ENV{'APBUILD_NO_STATIC_X'}) { + warn "WARNING: APBUILD_NO_STATIC_X is no longer used because it became the default behaviour.\n"; + } + if (defined $ENV{'APBUILD_STATIC_X'} && $ENV{'APBUILD_STATIC_X'} eq "1") { + foreach (qw(Xrender Xcursor Xfixes Xi Xinerama Xrandr Xv Xxf86dga Xxf86misc Xxf86vm)) { + my $file = searchLib("lib$_.a", \@searchPaths); + $staticList{$_} = $file if (defined $file); + $xlibs{$_} = 1; + } + } + + my @static_deps; + 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); + + if (defined $file) { + # find the DT_NEEDED entries that this library-to-be-made-static + # has so that final linking works (the deps need to be added after the static lib so the bogus stripper doesn't remove them) + my ($r, $w); + my $abslib = searchLib("lib$lib.so", \@searchPaths); + if (!defined $abslib) { + warn "WARNING: Failed to find lib$lib.so in " . join(":", @searchPaths) . ".\n"; + next; + } + my $pid = open2 ($r, $w, 'objdump', '-p', $abslib); + close ($w); + foreach (<$r>) { + next unless (/^ NEEDED\s+lib(.+?)\.so/); + s/^ NEEDED\s+lib(.+?)\.so(.*)/$1/; + s/\n//; + push(@static_deps, "-l$_"); + } + close ($r); + waitpid ($pid, 0); + } + } + } + + push (@_, @static_deps); + + # 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}); + + if ($libname eq "Xcursor") { + # With some versions of X11 (on Slack 11, + # xorg 6.9.0, anyway), Xcursor links against Xfixes. + # If we switch Xcursor to being linked statically, + # force linking of Xfixes too so we don't get + # undefined symbol errors from Xcursor. + push @args, $staticList{"Xfixes"}; + } + + } 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); +} |