aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/build.py
diff options
context:
space:
mode:
authorNirbheek Chauhan <nirbheek@centricular.com>2016-07-01 14:43:51 +0530
committerNirbheek Chauhan <nirbheek@centricular.com>2016-07-01 20:50:47 +0530
commit0143c32c7cd82872e42f57216bb94a26191f2824 (patch)
tree7b6030e63de5283548ce234dd4d96a36a7380f1b /mesonbuild/build.py
parentbc347aed0be4d8ea6210a546fb350f7bc1eee529 (diff)
downloadmeson-0143c32c7cd82872e42f57216bb94a26191f2824.zip
meson-0143c32c7cd82872e42f57216bb94a26191f2824.tar.gz
meson-0143c32c7cd82872e42f57216bb94a26191f2824.tar.bz2
Overhaul versioning and naming of libraries
This commit contains several changes to the naming and versioning of shared and static libraries. The details are documented at: https://github.com/mesonbuild/meson/pull/417 Here's a brief summary: * The results of binary and compiler detection via environment functions are now cached so that they can be called repeatedly without performance penalty. This is necessary because every build.SharedLibrary object has to know whether the compiler is MSVC or not (output filenames depend on that), and so the compiler detection has to be called for each object instantiation. * Linux shared libraries don't always have a library version. Sometimes only soversions are specified (and vice-versa), so support both. * Don't use versioned filenames when generating DLLs, DLLs are never versioned using the suffix in the way that .so libraries are. Hence, they don't use "aliases". Only Linux shared libraries use those. * OS X dylibs do not use filename aliases at all. They only use the soversion in the dylib name (libfoo.X.dylib), and that's it. If there's no soversion specified, the dylib is called libfoo.dylib. Further versioning in dylibs is supposed to be done with the -current_version argument to clang, but this is TBD. https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/DynamicLibraryDesignGuidelines.html#//apple_ref/doc/uid/TP40002013-SW23 * Install DLLs into bindir and import libraries into libdir * Static libraries are now always called libfoo.a, even with MSVC * .lib import libraries are always generated when building with MSVC * .dll.a import libraries are always generated when building with MinGW/GCC or MinGW/clang * TODO: Use dlltool if available to generate .dll.a when .lib is generated and vice-versa. * Library and executable suffix/prefixes are now always correctly overriden by the values of the 'name_prefix' and 'name_suffix' keyword arguments.
Diffstat (limited to 'mesonbuild/build.py')
-rw-r--r--mesonbuild/build.py278
1 files changed, 208 insertions, 70 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index bfdbca8..302f824 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -16,8 +16,9 @@ from . import coredata
from . import environment
from . import dependencies
from . import mlog
-import copy, os
+import copy, os, re
from .mesonlib import File, flatten, MesonException
+from .environment import for_windows, for_darwin
known_basic_kwargs = {'install' : True,
'c_pch' : True,
@@ -71,6 +72,35 @@ We are fully aware that these are not really usable or pleasant ways to do
this but it's the best we can do given the way shell quoting works.
'''
+def sources_are_suffix(sources, suffix):
+ return len(sources) > 0 and sources[0].endswith('.' + suffix)
+
+def compiler_is_msvc(sources, is_cross, env):
+ """
+ Since each target does not currently have the compiler information attached
+ to it, we must do this detection manually here.
+
+ This detection is purposely incomplete and will cause bugs if other code is
+ extended and this piece of code is forgotten.
+ """
+ compiler = None
+ if sources_are_suffix(sources, 'c'):
+ try:
+ compiler = env.detect_c_compiler(is_cross)
+ except MesonException:
+ return False
+ elif sources_are_suffix(sources, 'cxx') or \
+ sources_are_suffix(sources, 'cpp') or \
+ sources_are_suffix(sources, 'cc'):
+ try:
+ compiler = env.detect_cpp_compiler(is_cross)
+ except MesonException:
+ return False
+ if compiler and compiler.get_id() == 'msvc' and compiler.can_compile(sources[0]):
+ return True
+ return False
+
+
class InvalidArguments(MesonException):
pass
@@ -670,15 +700,19 @@ class GeneratedList():
class Executable(BuildTarget):
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
- self.prefix = ''
- self.suffix = environment.get_exe_suffix()
- suffix = environment.get_exe_suffix()
- if len(self.sources) > 0 and self.sources[0].endswith('.cs'):
- suffix = 'exe'
- if suffix != '':
- self.filename = self.name + '.' + suffix
- else:
- self.filename = self.name
+ # Unless overriden, executables have no suffix or prefix. Except on
+ # Windows and with C#/Mono executables where the suffix is 'exe'
+ if not hasattr(self, 'prefix'):
+ self.prefix = ''
+ if not hasattr(self, 'suffix'):
+ # Executable for Windows or C#/Mono
+ if for_windows(is_cross, environment) or sources_are_suffix(self.sources, 'cs'):
+ self.suffix = 'exe'
+ else:
+ self.suffix = ''
+ self.filename = self.name
+ if self.suffix:
+ self.filename += '.' + self.suffix
def type_suffix(self):
return "@exe"
@@ -686,52 +720,161 @@ class Executable(BuildTarget):
class StaticLibrary(BuildTarget):
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
- if len(self.sources) > 0 and self.sources[0].endswith('.cs'):
+ if sources_are_suffix(self.sources, 'cs'):
raise InvalidArguments('Static libraries not supported for C#.')
+ # By default a static library is named libfoo.a even on Windows because
+ # MSVC does not have a consistent convention for what static libraries
+ # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses
+ # it and GCC only looks for static libraries called foo.lib and
+ # libfoo.a. However, we cannot use foo.lib because that's the same as
+ # the import library. Using libfoo.a is ok because people using MSVC
+ # always pass the library filename while linking anyway.
if not hasattr(self, 'prefix'):
- self.prefix = environment.get_static_lib_prefix()
- self.suffix = environment.get_static_lib_suffix()
- if len(self.sources) > 0 and self.sources[0].endswith('.rs'):
- self.suffix = 'rlib'
+ self.prefix = 'lib'
+ if not hasattr(self, 'suffix'):
+ # Rust static library crates have .rlib suffix
+ if sources_are_suffix(self.sources, 'rs'):
+ self.suffix = 'rlib'
+ else:
+ self.suffix = 'a'
self.filename = self.prefix + self.name + '.' + self.suffix
- def get_import_filename(self):
- return self.filename
-
- def get_osx_filename(self):
- return self.get_filename()
-
def type_suffix(self):
return "@sta"
class SharedLibrary(BuildTarget):
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
- self.version = None
self.soversion = None
+ self.ltversion = None
self.vs_module_defs = None
- super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs);
- if len(self.sources) > 0 and self.sources[0].endswith('.cs'):
+ # 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
+ super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
+ if not hasattr(self, 'prefix'):
+ self.prefix = None
+ if not hasattr(self, 'suffix'):
+ self.suffix = None
+ self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
+ self.determine_filenames(is_cross, environment)
+
+ def determine_filenames(self, is_cross, env):
+ """
+ See https://github.com/mesonbuild/meson/pull/417 for details.
+
+ First we determine the filename template (self.filename_tpl), then we
+ set the output filename (self.filename).
+
+ The template is needed while creating aliases (self.get_aliaslist),
+ which are needed while generating .so shared libraries for Linux.
+
+ Besides this, there's also the import library name, which is only used
+ on Windows since on that platform the linker uses a separate library
+ called the "import library" during linking instead of the shared
+ library (DLL). The toolchain will output an import library in one of
+ two formats: GCC or Visual Studio.
+
+ When we're building with Visual Studio, the import library that will be
+ generated by the toolchain is self.vs_import_filename, and with
+ MinGW/GCC, it's self.gcc_import_filename. self.import_filename will
+ always contain the import library name this target will generate.
+ """
+ prefix = ''
+ suffix = ''
+ self.filename_tpl = self.basic_filename_tpl
+ # If the user already provided the prefix and suffix to us, we don't
+ # need to do any filename suffix/prefix detection.
+ # NOTE: manual prefix/suffix override is currently only tested for C/C++
+ if self.prefix != None and self.suffix != None:
+ pass
+ # C# and Mono
+ elif sources_are_suffix(self.sources, 'cs'):
+ prefix = ''
+ suffix = 'dll'
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
+ # Rust
+ elif sources_are_suffix(self.sources, 'rs'):
+ # Currently, we always build --crate-type=rlib
prefix = 'lib'
+ suffix = 'rlib'
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
+ # C, C++, Swift, Vala
+ # Only Windows uses a separate import library for linking
+ # For all other targets/platforms import_filename stays None
+ elif for_windows(is_cross, env):
suffix = 'dll'
+ self.vs_import_filename = '{0}.lib'.format(self.name)
+ self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name)
+ if compiler_is_msvc(self.sources, is_cross, env):
+ # Shared library is of the form foo.dll
+ prefix = ''
+ # Import library is called foo.lib
+ self.import_filename = self.vs_import_filename
+ # Assume GCC-compatible naming
+ else:
+ # Shared library is of the form libfoo.dll
+ prefix = 'lib'
+ # Import library is called libfoo.dll.a
+ self.import_filename = self.gcc_import_filename
+ # Shared library has the soversion if it is defined
+ if self.soversion:
+ self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
+ else:
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
+ elif for_darwin(is_cross, env):
+ prefix = 'lib'
+ suffix = 'dylib'
+ if self.soversion:
+ # libfoo.X.dylib
+ self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}'
+ else:
+ # libfoo.dylib
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
else:
- prefix = environment.get_shared_lib_prefix()
- suffix = environment.get_shared_lib_suffix()
- if not hasattr(self, 'prefix'):
- self.prefix = prefix
- if not hasattr(self, 'suffix'):
- if len(self.sources) > 0 and self.sources[0].endswith('.rs'):
- self.suffix = 'rlib'
+ prefix = 'lib'
+ suffix = 'so'
+ if self.ltversion:
+ # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional)
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}'
+ elif self.soversion:
+ # libfoo.so.X
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}'
else:
- self.suffix = suffix
- self.importsuffix = environment.get_import_lib_suffix()
- self.filename = self.prefix + self.name + '.' + self.suffix
+ # No versioning, libfoo.so
+ self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
+ if self.prefix == None:
+ self.prefix = prefix
+ if self.suffix == None:
+ self.suffix = suffix
+ self.filename = self.filename_tpl.format(self)
def process_kwargs(self, kwargs, environment):
super().process_kwargs(kwargs, environment)
+ # Shared library version
if 'version' in kwargs:
- self.set_version(kwargs['version'])
+ self.ltversion = kwargs['version']
+ if not isinstance(self.ltversion, str):
+ raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__)
+ if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion):
+ raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion))
+ # Try to extract/deduce the soversion
if 'soversion' in kwargs:
- self.set_soversion(kwargs['soversion'])
+ self.soversion = kwargs['soversion']
+ if isinstance(self.soversion, int):
+ self.soversion = str(self.soversion)
+ if not isinstance(self.soversion, str):
+ raise InvalidArguments('Shared library soversion is not a string or integer.')
+ try:
+ int(self.soversion)
+ except ValueError:
+ raise InvalidArguments('Shared library soversion must be a valid integer')
+ elif self.ltversion:
+ # library version is defined, get the soversion from that
+ self.soversion = self.ltversion.split('.')[0]
+ # Visual Studio module-definitions file
if 'vs_module_defs' in kwargs:
path = kwargs['vs_module_defs']
if (os.path.isabs(path)):
@@ -742,46 +885,41 @@ class SharedLibrary(BuildTarget):
def check_unknown_kwargs(self, kwargs):
self.check_unknown_kwargs_int(kwargs, known_shlib_kwargs)
- def get_shbase(self):
- return self.prefix + self.name + '.' + self.suffix
-
def get_import_filename(self):
- return self.prefix + self.name + '.' + self.importsuffix
+ """
+ The name of the import library that will be outputted by the compiler
- def get_all_link_deps(self):
- return [self] + self.get_transitive_link_deps()
+ Returns None if there is no import library required for this platform
+ """
+ return self.import_filename
- def get_filename(self):
- '''Works on all platforms except OSX, which does its own thing.'''
- fname = self.get_shbase()
- if self.version is None:
- return fname
- else:
- return fname + '.' + self.version
-
- def get_osx_filename(self):
- if self.version is None:
- return self.get_shbase()
- return self.prefix + self.name + '.' + self.version + '.' + self.suffix
-
- def set_version(self, version):
- if not isinstance(version, str):
- raise InvalidArguments('Shared library version is not a string.')
- self.version = version
+ def get_import_filenameslist(self):
+ if self.import_filename:
+ return [self.vs_import_filename, self.gcc_import_filename]
+ return []
- def set_soversion(self, version):
- if isinstance(version, int):
- version = str(version)
- if not isinstance(version, str):
- raise InvalidArguments('Shared library soversion is not a string or integer.')
- self.soversion = version
+ def get_all_link_deps(self):
+ return [self] + self.get_transitive_link_deps()
def get_aliaslist(self):
- aliases = []
- if self.soversion is not None:
- aliases.append(self.get_shbase() + '.' + self.soversion)
- if self.version is not None:
- aliases.append(self.get_shbase())
+ """
+ If the versioned library name is libfoo.so.0.100.0, aliases are:
+ * libfoo.so.0 (soversion)
+ * libfoo.so (unversioned; for linking)
+ """
+ # Aliases are only useful with .so libraries. Also if the .so library
+ # ends with .so (no versioning), we don't need aliases.
+ if self.suffix != 'so' or self.filename.endswith('.so'):
+ return []
+ # Unversioned alias: libfoo.so
+ aliases = [self.basic_filename_tpl.format(self)]
+ # If ltversion != soversion we create an soversion alias: libfoo.so.X
+ if self.ltversion and self.ltversion != self.soversion:
+ if not self.soversion:
+ # This is done in self.process_kwargs()
+ raise AssertionError('BUG: If library version is defined, soversion must have been defined')
+ alias_tpl = self.filename_tpl.replace('ltversion', 'soversion')
+ aliases.append(alias_tpl.format(self))
return aliases
def type_suffix(self):