Support implibs for executables on Windows

Add a boolean 'implib' kwarg to executable().  If true, it is permitted to
use the returned build target object in link_with:

On platforms where this makes sense (e.g. Windows), an implib is generated
for the executable and used when linking.  Otherwise, it has no effect.

(Rather than checking if it is a StaticLibrary or SharedLibary, BuildTarget
subclasses gain the is_linkable_target method to test if they can appear in
link_with:)

Also install any executable implib in a similar way to a shared library
implib, i.e. placing the implib in the appropriate place

Add tests of:
- a shared_module containing a reference to a symbol which is known (at link
time) to be provided by the executable
- trying to link with non-implib executables (should fail)
- installing the implib

(This last one needs a little enhancement of the installed file checking as
this is the first install test we have which needs to work with either
MSVC-style or GCC-style implib filenames)
pull/1955/head
Jon Turney 8 years ago
parent b43f4841ba
commit 3fa3922cea
  1. 1
      docs/markdown/Reference-manual.md
  2. 16
      mesonbuild/backend/backends.py
  3. 6
      mesonbuild/backend/ninjabackend.py
  4. 4
      mesonbuild/backend/vs2010backend.py
  5. 56
      mesonbuild/build.py
  6. 16
      run_project_tests.py
  7. 24
      test cases/common/154 shared module resolving symbol in executable/meson.build
  8. 16
      test cases/common/154 shared module resolving symbol in executable/module.c
  9. 60
      test cases/common/154 shared module resolving symbol in executable/prog.c
  10. 4
      test cases/failing/57 link with executable/meson.build
  11. 4
      test cases/failing/57 link with executable/module.c
  12. 5
      test cases/failing/57 link with executable/prog.c
  13. 4
      test cases/windows/12 exe implib/installed_files.txt
  14. 7
      test cases/windows/12 exe implib/meson.build
  15. 6
      test cases/windows/12 exe implib/prog.c

@ -248,6 +248,7 @@ Executable supports the following keyword arguments. Note that just like the pos
- `name_suffix` the string that will be used as the extension for the target by overriding the default. By default on Windows this is `exe` and on other platforms it is omitted.
- `build_by_default` causes, when set to true, to have this target be built by default, that is, when invoking plain `ninja`, the default value is true for all built target types, since 0.38.0
- `override_options` takes an array of strings in the same format as `project`'s `default_options` overriding the values of these options for this target only, since 0.40.0
- `implib` when set to true, an import library is generated for the executable, used when the returned build target object appears elsewhere in `link_with:`, on platforms where this is meaningful (e.g. Windows), since 0.42.0
The list of `sources`, `objects`, and `dependencies` is always flattened, which means you can freely nest and add lists while creating the final list. As a corollary, the best way to handle a 'disabled dependency' is by assigning an empty list `[]` to it and passing it like any other dependency to the `dependencies:` keyword argument.

@ -140,7 +140,12 @@ class Backend:
return os.path.join(self.get_target_dir(target), link_lib)
elif isinstance(target, build.StaticLibrary):
return os.path.join(self.get_target_dir(target), target.get_filename())
raise AssertionError('BUG: Tried to link to something that\'s not a library')
elif isinstance(target, build.Executable):
if target.import_filename:
return os.path.join(self.get_target_dir(target), target.get_import_filename())
else:
return None
raise AssertionError('BUG: Tried to link to {!r} which is not linkable'.format(target))
def get_target_dir(self, target):
if self.environment.coredata.get_builtin_option('layout') == 'mirror':
@ -463,12 +468,13 @@ class Backend:
def build_target_link_arguments(self, compiler, deps):
args = []
for d in deps:
if not isinstance(d, (build.StaticLibrary, build.SharedLibrary)):
if not (d.is_linkable_target()):
raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename())
d_arg = self.get_target_filename_for_linking(d)
if not d_arg:
continue
if isinstance(compiler, (compilers.LLVMDCompiler, compilers.DmdDCompiler)):
d_arg = '-L' + self.get_target_filename_for_linking(d)
else:
d_arg = self.get_target_filename_for_linking(d)
d_arg = '-L' + d_arg
args.append(d_arg)
return args

@ -693,7 +693,8 @@ int dummy;
# On toolchains/platforms that use an import library for
# linking (separate from the shared library with all the
# code), we need to install that too (dll.a/.lib).
if isinstance(t, build.SharedLibrary) and t.get_import_filename():
if (isinstance(t, build.SharedLibrary) or
isinstance(t, build.Executable)) and t.get_import_filename():
if custom_install_dir:
# If the DLL is installed into a custom directory,
# install the import library into the same place so
@ -2256,6 +2257,9 @@ rule FORTRAN_DEP_HACK
# If gui_app, and that's significant on this platform
if target.gui_app and hasattr(linker, 'get_gui_app_args'):
commands += linker.get_gui_app_args()
# If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio)
if target.import_filename:
commands += linker.gen_import_library_args(os.path.join(target.subdir, target.import_filename))
elif isinstance(target, build.SharedLibrary):
if isinstance(target, build.SharedModule):
commands += linker.get_std_shared_module_link_args()

@ -951,10 +951,12 @@ class Vs2010Backend(backends.Backend):
ofile.text = '$(OutDir)%s' % target.get_filename()
subsys = ET.SubElement(link, 'SubSystem')
subsys.text = subsystem
if isinstance(target, build.SharedLibrary):
if (isinstance(target, build.SharedLibrary) or
isinstance(target, build.Executable)) and target.get_import_filename():
# DLLs built with MSVC always have an import library except when
# they're data-only DLLs, but we don't support those yet.
ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename()
if isinstance(target, build.SharedLibrary):
# Add module definitions file, if provided
if target.vs_module_defs:
relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src))

@ -74,6 +74,9 @@ known_lib_kwargs.update({'version': True, # Only for shared libs
'rust_crate_type': True, # Only for Rust libs
})
known_exe_kwargs = known_basic_kwargs.copy()
known_exe_kwargs.update({'implib': True,
})
class InvalidArguments(MesonException):
pass
@ -841,8 +844,8 @@ You probably should put it in link_with instead.''')
for t in flatten(target):
if hasattr(t, 'held_object'):
t = t.held_object
if not isinstance(t, (StaticLibrary, SharedLibrary)):
raise InvalidArguments('Link target {!r} is not library.'.format(t))
if not t.is_linkable_target():
raise InvalidArguments('Link target {!r} is not linkable.'.format(t))
if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) 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."
@ -986,6 +989,9 @@ You probably should put it in link_with instead.''')
return True
return False
def is_linkable_target(self):
return False
class Generator:
def __init__(self, args, kwargs):
@ -1122,9 +1128,49 @@ class Executable(BuildTarget):
self.filename += '.' + self.suffix
self.outputs = [self.filename]
# The import library this target will generate
self.import_filename = None
# The import library that Visual Studio would generate (and accept)
self.vs_import_filename = None
# The import library that GCC would generate (and prefer)
self.gcc_import_filename = None
# if implib:true appears, this target is linkwith:-able, but that only
# means something on Windows platforms.
self.is_linkwithable = False
if 'implib' in kwargs and kwargs['implib']:
self.is_linkwithable = True
if for_windows(is_cross, environment) or for_cygwin(is_cross, environment):
self.vs_import_filename = '{0}.lib'.format(self.name)
self.gcc_import_filename = 'lib{0}.exe.a'.format(self.name)
if self.get_using_msvc():
self.import_filename = self.vs_import_filename
else:
self.import_filename = self.gcc_import_filename
def type_suffix(self):
return "@exe"
def check_unknown_kwargs(self, kwargs):
self.check_unknown_kwargs_int(kwargs, known_exe_kwargs)
def get_import_filename(self):
"""
The name of the import library that will be outputted by the compiler
Returns None if there is no import library required for this platform
"""
return self.import_filename
def get_import_filenameslist(self):
if self.import_filename:
return [self.vs_import_filename, self.gcc_import_filename]
return []
def is_linkable_target(self):
return self.is_linkwithable
class StaticLibrary(BuildTarget):
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options:
@ -1176,6 +1222,9 @@ class StaticLibrary(BuildTarget):
else:
raise InvalidArguments('Invalid rust_crate_type "{0}": must be a string.'.format(rust_crate_type))
def is_linkable_target(self):
return True
class SharedLibrary(BuildTarget):
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
self.soversion = None
@ -1405,6 +1454,9 @@ class SharedLibrary(BuildTarget):
def type_suffix(self):
return "@sha"
def is_linkable_target(self):
return True
# A shared library that is meant to be used with dlopen rather than linking
# into something else.
class SharedModule(SharedLibrary):

@ -183,7 +183,7 @@ def get_relative_files_list_from_dir(fromdir):
paths.append(path)
return paths
def platform_fix_name(fname):
def platform_fix_name(fname, compiler):
if '?lib' in fname:
if mesonlib.is_cygwin():
fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname)
@ -195,6 +195,16 @@ def platform_fix_name(fname):
if mesonlib.is_windows() or mesonlib.is_cygwin():
return fname + '.exe'
if fname.startswith('?msvc:'):
fname = fname[6:]
if compiler != 'cl':
return None
if fname.startswith('?gcc:'):
fname = fname[5:]
if compiler == 'cl':
return None
return fname
def validate_install(srcdir, installdir, compiler):
@ -210,7 +220,9 @@ def validate_install(srcdir, installdir, compiler):
elif os.path.exists(info_file):
with open(info_file) as f:
for line in f:
expected[platform_fix_name(line.strip())] = False
line = platform_fix_name(line.strip(), compiler)
if line:
expected[line] = False
# Check if expected files were found
for fname in expected:
file_path = os.path.join(installdir, fname)

@ -0,0 +1,24 @@
project('shared module resolving symbol in executable', 'c')
# The shared module contains a reference to the symbol 'func_from_executable',
# which is always provided by the executable which loads it. This symbol can be
# resolved at run-time by an ELF loader. But when building PE/COFF objects, all
# symbols must be resolved at link-time, so an implib is generated for the
# executable, and the shared module linked with it.
#
# See testcase 125 for an example of the more complex portability gymnastics
# required if we do not know (at link-time) what provides the symbol.
link_flags = []
if host_machine.system() != 'windows'
# Needed to export dynamic symbols from the executable
link_flags += ['-rdynamic']
# Need to add this manually because Meson won't add it automatically because
# it doesn't know that we are loading a module from the build directory.
link_flags += ['-Wl,-rpath,' + meson.current_build_dir()]
endif
dl = meson.get_compiler('c').find_library('dl', required: false)
e = executable('prog', 'prog.c', dependencies: dl, implib: true, link_args: link_flags)
m = shared_module('module', 'module.c', link_with: e)
test('test', e, args: m)

@ -0,0 +1,16 @@
#if defined _WIN32 || defined __CYGWIN__
#define DLL_PUBLIC __declspec(dllexport)
#else
#if defined __GNUC__
#define DLL_PUBLIC __attribute__ ((visibility("default")))
#else
#pragma message ("Compiler does not support symbol visibility.")
#define DLL_PUBLIC
#endif
#endif
extern int func_from_executable(void);
int DLL_PUBLIC func(void) {
return func_from_executable();
}

@ -0,0 +1,60 @@
#include <stdio.h>
#include <assert.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#if defined _WIN32 || defined __CYGWIN__
#define DLL_PUBLIC __declspec(dllexport)
#else
#if defined __GNUC__
#define DLL_PUBLIC __attribute__ ((visibility("default")))
#else
#pragma message ("Compiler does not support symbol visibility.")
#define DLL_PUBLIC
#endif
#endif
typedef int (*fptr) (void);
int DLL_PUBLIC
func_from_executable(void)
{
return 42;
}
int
main (int argc, char **argv)
{
int expected, actual;
fptr importedfunc;
#ifdef _WIN32
HMODULE h = LoadLibraryA(argv[1]);
#else
void *h = dlopen(argv[1], RTLD_NOW);
#endif
assert(h != NULL);
#ifdef _WIN32
importedfunc = (fptr) GetProcAddress (h, "func");
#else
importedfunc = (fptr) dlsym(h, "func");
#endif
assert(importedfunc != NULL);
assert(importedfunc != func_from_executable);
actual = (*importedfunc)();
expected = func_from_executable();
assert(actual == expected);
#ifdef _WIN32
FreeLibrary(h);
#else
dlclose(h);
#endif
return 0;
}

@ -0,0 +1,4 @@
project('link with exe', 'c')
e = executable('prog', 'prog.c')
m = shared_module('module', 'module.c', link_with: e)

@ -0,0 +1,4 @@
int func(void) {
return 42;
}

@ -0,0 +1,5 @@
int
main (int argc, char **argv)
{
return 0;
}

@ -0,0 +1,4 @@
usr/bin/prog.exe
usr/bin/prog.pdb
?gcc:usr/lib/libprog.exe.a
?msvc:usr/lib/prog.lib

@ -0,0 +1,7 @@
project('wintest', 'c')
# Test that we can produce an implib for an executable on Windows, and that it
# is installed along with the executable
prog = executable('prog', 'prog.c', install: true, implib: true)
test('wintest', prog)

@ -0,0 +1,6 @@
#include <windows.h>
int __declspec(dllexport)
main(int argc, char **argv) {
return 0;
}
Loading…
Cancel
Save