From d152c1b5d549769c7977688fbb8c4cde037192e7 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sun, 2 Apr 2017 19:06:16 +0300 Subject: [PATCH] Add option to link the entire contents of a static library to a target. --- mesonbuild/backend/ninjabackend.py | 9 ++++++ mesonbuild/build.py | 29 ++++++++++++++++++- mesonbuild/compilers.py | 19 ++++++++++++ test cases/common/145 whole archive/dylib.c | 7 +++++ test cases/common/145 whole archive/libfile.c | 7 +++++ .../common/145 whole archive/meson.build | 14 +++++++++ test cases/common/145 whole archive/mylib.h | 21 ++++++++++++++ test cases/common/145 whole archive/prog.c | 5 ++++ 8 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 test cases/common/145 whole archive/dylib.c create mode 100644 test cases/common/145 whole archive/libfile.c create mode 100644 test cases/common/145 whole archive/meson.build create mode 100644 test cases/common/145 whole archive/mylib.h create mode 100644 test cases/common/145 whole archive/prog.c diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 5a9462fa0..4bfddc2d4 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2126,6 +2126,10 @@ rule FORTRAN_DEP_HACK raise RuntimeError('Unknown build target type.') return commands + def get_link_whole_args(self, linker, target): + target_args = self.build_target_link_arguments(linker, target.link_whole_targets) + return linker.get_link_whole_for(target_args) + def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]): if isinstance(target, build.StaticLibrary): linker_base = 'STATIC' @@ -2165,6 +2169,11 @@ rule FORTRAN_DEP_HACK # Add link args specific to this BuildTarget type, such as soname args, # PIC, import library generation, etc. commands += self.get_target_type_link_args(target, linker) + # Archives that are copied wholesale in the result. Must be before any + # other link targets so missing symbols from whole archives are found in those. + if not isinstance(target, build.StaticLibrary): + commands += self.get_link_whole_args(linker, target) + if not isinstance(target, build.StaticLibrary): # Add link args added using add_project_link_arguments() commands += self.build.get_project_link_args(linker, target.subproject) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 537c91bde..f379a6896 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -37,6 +37,7 @@ known_basic_kwargs = {'install': True, 'link_args': True, 'link_depends': True, 'link_with': True, + 'link_whole': True, 'include_directories': True, 'dependencies': True, 'install_dir': True, @@ -314,6 +315,7 @@ class BuildTarget(Target): self.external_deps = [] self.include_dirs = [] self.link_targets = [] + self.link_whole_targets = [] self.link_depends = [] self.name_prefix_set = False self.name_suffix_set = False @@ -560,6 +562,15 @@ class BuildTarget(Target): if hasattr(linktarget, "held_object"): linktarget = linktarget.held_object self.link(linktarget) + lwhole = kwargs.get('link_whole', []) + if not isinstance(lwhole, list): + lwhole = [lwhole] + for linktarget in lwhole: + # Sorry for this hack. Keyword targets are kept in holders + # in kwargs. Unpack here without looking at the exact type. + if hasattr(linktarget, "held_object"): + linktarget = linktarget.held_object + self.link_whole(linktarget) c_pchlist = kwargs.get('c_pch', []) if not isinstance(c_pchlist, list): c_pchlist = [c_pchlist] @@ -698,7 +709,7 @@ class BuildTarget(Target): def get_dependencies(self): transitive_deps = [] - for t in self.link_targets: + for t in self.link_targets + self.link_whole_targets: transitive_deps.append(t) if isinstance(t, StaticLibrary): transitive_deps += t.get_dependencies() @@ -790,6 +801,22 @@ You probably should put it in link_with instead.''') raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name)) self.link_targets.append(t) + def link_whole(self, target): + if not isinstance(target, list): + target = [target] + for t in target: + if hasattr(t, 'held_object'): + t = t.held_object + if not isinstance(t, StaticLibrary): + raise InvalidArguments('{!r} is not a static library.'.format(t)) + if isinstance(self, SharedLibrary) and not t.pic: + msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name) + msg += "Use the 'pic' option to static_library to build with PIC." + raise InvalidArguments(msg) + if self.is_cross != t.is_cross: + raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name)) + self.link_whole_targets.append(t) + def add_pch(self, language, pchlist): if len(pchlist) == 0: return diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index e6be8b1b2..94ee2f1be 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -707,6 +707,11 @@ class Compiler: def get_std_shared_module_link_args(self): return self.get_std_shared_lib_link_args() + def get_link_whole_for(self, args): + if isinstance(args, list) and len(args) == 0: + return [] + raise EnvironmentException('Language %s does not support linking whole archives.' % self.language) + class CCompiler(Compiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): # If a child ObjC or CPP class has already set it, don't set it ourselves @@ -2274,6 +2279,11 @@ class VisualStudioCCompiler(CCompiler): pdbarr += ['pdb'] return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] + def get_link_whole_for(self, args): + # Only since VS2015 + return ['/WHOLEARCHIVE:' + x for x in args] + + class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): self.language = 'cpp' @@ -2425,6 +2435,10 @@ class GnuCompiler: return ['-bundle'] return ['-shared'] + def get_link_whole_for(self, args): + return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive'] + + class GnuCCompiler(GnuCompiler, CCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) @@ -2460,6 +2474,7 @@ class GnuCCompiler(GnuCompiler, CCompiler): def get_std_shared_lib_link_args(self): return ['-shared'] + class GnuCPPCompiler(GnuCompiler, CPPCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrap, defines): @@ -2589,6 +2604,10 @@ class ClangCompiler: return ['-bundle', '-Wl,-undefined,dynamic_lookup'] return ['-shared'] + def get_link_whole_for(self, args): + return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive'] + + class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) diff --git a/test cases/common/145 whole archive/dylib.c b/test cases/common/145 whole archive/dylib.c new file mode 100644 index 000000000..9e287a4db --- /dev/null +++ b/test cases/common/145 whole archive/dylib.c @@ -0,0 +1,7 @@ +#define BUILDING_DLL + +#include + +int func2() { + return 42; +} diff --git a/test cases/common/145 whole archive/libfile.c b/test cases/common/145 whole archive/libfile.c new file mode 100644 index 000000000..b2690a07f --- /dev/null +++ b/test cases/common/145 whole archive/libfile.c @@ -0,0 +1,7 @@ +#define BUILDING_DLL + +#include + +int func1() { + return 42; +} diff --git a/test cases/common/145 whole archive/meson.build b/test cases/common/145 whole archive/meson.build new file mode 100644 index 000000000..64a0669b6 --- /dev/null +++ b/test cases/common/145 whole archive/meson.build @@ -0,0 +1,14 @@ +project('whole archive', 'c') + +stlib = static_library('allofme', 'libfile.c') + +# Nothing in dylib.c uses func1, so the linker would throw it +# away and thus linking the exe would fail. +dylib = shared_library('shlib', 'dylib.c', + link_whole : stlib) + +exe = executable('prog', 'prog.c', + link_with : dylib) + +test('prog', exe) + diff --git a/test cases/common/145 whole archive/mylib.h b/test cases/common/145 whole archive/mylib.h new file mode 100644 index 000000000..813cc6783 --- /dev/null +++ b/test cases/common/145 whole archive/mylib.h @@ -0,0 +1,21 @@ +#pragma once + +/* Both funcs here for simplicity. */ + +#if defined _WIN32 || defined __CYGWIN__ +#if defined BUILDING_DLL + #define DLL_PUBLIC __declspec(dllexport) +#else + #define DLL_PUBLIC __declspec(dllimport) +#endif +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +int DLL_PUBLIC func1(); +int DLL_PUBLIC func2(); diff --git a/test cases/common/145 whole archive/prog.c b/test cases/common/145 whole archive/prog.c new file mode 100644 index 000000000..9111ad45a --- /dev/null +++ b/test cases/common/145 whole archive/prog.c @@ -0,0 +1,5 @@ +#include + +int main(int argc, char **argv) { + return func1() - func2(); +}