Merge pull request #1955 from jon-turney/exe-implib

Support implibs for executables on Windows
pull/2104/head
Jussi Pakkanen 7 years ago committed by GitHub
commit 979efce04a
  1. 1
      docs/markdown/Reference-manual.md
  2. 5
      docs/markdown/Release-notes-for-0.42.0.md
  3. 16
      mesonbuild/backend/backends.py
  4. 6
      mesonbuild/backend/ninjabackend.py
  5. 4
      mesonbuild/backend/vs2010backend.py
  6. 59
      mesonbuild/build.py
  7. 16
      run_project_tests.py
  8. 21
      test cases/common/154 shared module resolving symbol in executable/meson.build
  9. 16
      test cases/common/154 shared module resolving symbol in executable/module.c
  10. 60
      test cases/common/154 shared module resolving symbol in executable/prog.c
  11. 4
      test cases/failing/57 link with executable/meson.build
  12. 4
      test cases/failing/57 link with executable/module.c
  13. 5
      test cases/failing/57 link with executable/prog.c
  14. 8
      test cases/windows/12 exe implib/installed_files.txt
  15. 7
      test cases/windows/12 exe implib/meson.build
  16. 6
      test cases/windows/12 exe implib/prog.c
  17. 11
      test cases/windows/7 mingw dll versioning/installed_files.txt
  18. 55
      test cases/windows/7 mingw dll versioning/meson.build
  19. 0
      test cases/windows/8 dll versioning/copyfile.py
  20. 0
      test cases/windows/8 dll versioning/exe.orig.c
  21. 24
      test cases/windows/8 dll versioning/installed_files.txt
  22. 0
      test cases/windows/8 dll versioning/lib.c
  23. 8
      test cases/windows/8 dll versioning/meson.build
  24. 9
      test cases/windows/8 msvc dll versioning/exe.orig.c
  25. 13
      test cases/windows/8 msvc dll versioning/installed_files.txt
  26. 6
      test cases/windows/8 msvc dll versioning/lib.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 (the name of the import library is based on *exe_name*). Alternatively, when set to a string, that gives the base name for the import library. The import library is used when the returned build target object appears in `link_with:` elsewhere. Only has any effect on platforms where that 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.

@ -65,3 +65,8 @@ A new experimental module to compile code with many different SIMD
instruction sets and selecting the best one at runtime. This module
is unstable, meaning its API is subject to change in later releases.
It might also be removed altogether.
## Import libraries for executables on Windows
The new keyword `implib` to `executable()` allows generation of an import
library for the executable.

@ -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,52 @@ 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 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']:
implib_basename = self.name + '.exe'
if not isinstance(kwargs['implib'], bool):
implib_basename = 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(implib_basename)
self.gcc_import_filename = 'lib{0}.a'.format(implib_basename)
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 +1225,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 +1457,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,21 @@
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']
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.full_path())

@ -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,8 @@
usr/bin/prog.exe
usr/bin/prog.pdb
usr/bin/prog2.exe
usr/bin/prog2.pdb
?gcc:usr/lib/libprog.exe.a
?gcc:usr/lib/libburble.a
?msvc:usr/lib/prog.exe.lib
?msvc:usr/lib/burble.lib

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

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

@ -1,11 +0,0 @@
usr/bin/?libsome-0.dll
usr/lib/libsome.dll.a
usr/bin/?libnoversion.dll
usr/lib/libnoversion.dll.a
usr/bin/?libonlyversion-1.dll
usr/lib/libonlyversion.dll.a
usr/bin/?libonlysoversion-5.dll
usr/lib/libonlysoversion.dll.a
usr/libexec/?libcustomdir.dll
usr/libexec/libcustomdir.dll.a
usr/lib/?libmodule.dll

@ -1,55 +0,0 @@
project('mingw dll versioning', 'c')
cc = meson.get_compiler('c')
if cc.get_id() == 'msvc'
error('MESON_SKIP_TEST: test is only for MinGW')
endif
# Test that MinGW/GCC creates correctly-named dll files and dll.a files,
# and also installs them in the right place
some = shared_library('some', 'lib.c',
version : '1.2.3',
soversion : '0',
install : true)
noversion = shared_library('noversion', 'lib.c',
install : true)
onlyversion = shared_library('onlyversion', 'lib.c',
version : '1.4.5',
install : true)
onlysoversion = shared_library('onlysoversion', 'lib.c',
# Also test that int soversion is acceptable
soversion : 5,
install : true)
# Hack to make the executables below depend on the shared libraries above
# without actually adding them as `link_with` dependencies since we want to try
# linking to them with -lfoo linker arguments.
out = custom_target('library-dependency-hack',
input : 'exe.orig.c',
output : 'exe.c',
depends : [some, noversion, onlyversion, onlysoversion],
command : ['cp', '@INPUT@', '@OUTPUT@'])
# Manually test if the linker can find the above libraries
# i.e., whether they were generated with the right naming scheme
test('manually linked 1', executable('manuallink1', out,
link_args : ['-L.', '-lsome']))
test('manually linked 2', executable('manuallink2', out,
link_args : ['-L.', '-lnoversion']))
test('manually linked 3', executable('manuallink3', out,
link_args : ['-L.', '-lonlyversion']))
test('manually linked 4', executable('manuallink4', out,
link_args : ['-L.', '-lonlysoversion']))
shared_library('customdir', 'lib.c',
install : true,
install_dir : get_option('libexecdir'))
shared_module('module', 'lib.c', install : true)

@ -0,0 +1,24 @@
?msvc:usr/bin/some-0.dll
?msvc:usr/bin/some-0.pdb
?msvc:usr/lib/some.lib
?msvc:usr/bin/noversion.dll
?msvc:usr/bin/noversion.pdb
?msvc:usr/lib/noversion.lib
?msvc:usr/bin/onlyversion-1.dll
?msvc:usr/lib/onlyversion.lib
?msvc:usr/bin/onlysoversion-5.dll
?msvc:usr/lib/onlysoversion.lib
?msvc:usr/libexec/customdir.dll
?msvc:usr/libexec/customdir.lib
?msvc:usr/lib/module.dll
?gcc:usr/bin/?libsome-0.dll
?gcc:usr/lib/libsome.dll.a
?gcc:usr/bin/?libnoversion.dll
?gcc:usr/lib/libnoversion.dll.a
?gcc:usr/bin/?libonlyversion-1.dll
?gcc:usr/lib/libonlyversion.dll.a
?gcc:usr/bin/?libonlysoversion-5.dll
?gcc:usr/lib/libonlysoversion.dll.a
?gcc:usr/libexec/?libcustomdir.dll
?gcc:usr/libexec/libcustomdir.dll.a
?gcc:usr/lib/?libmodule.dll

@ -2,12 +2,8 @@ project('msvc dll versioning', 'c')
cc = meson.get_compiler('c')
if cc.get_id() != 'msvc'
error('MESON_SKIP_TEST: test is only for msvc')
endif
# Test that MSVC creates correctly-named dll files and .lib files,
# and also installs them in the right place
# Test that we create correctly-named dll and import lib files,
# and also install them in the right place
some = shared_library('some', 'lib.c',
version : '1.2.3',
soversion : '0',

@ -1,9 +0,0 @@
int myFunc (void);
int
main (int argc, char *argv[])
{
if (myFunc() == 55)
return 0;
return 1;
}

@ -1,13 +0,0 @@
usr/bin/some-0.dll
usr/bin/some-0.pdb
usr/lib/some.lib
usr/bin/noversion.dll
usr/bin/noversion.pdb
usr/lib/noversion.lib
usr/bin/onlyversion-1.dll
usr/lib/onlyversion.lib
usr/bin/onlysoversion-5.dll
usr/lib/onlysoversion.lib
usr/libexec/customdir.dll
usr/libexec/customdir.lib
usr/lib/module.dll

@ -1,6 +0,0 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int myFunc() {
return 55;
}
Loading…
Cancel
Save