diff options
author | Nirbheek Chauhan <nirbheek@centricular.com> | 2016-07-01 14:43:51 +0530 |
---|---|---|
committer | Nirbheek Chauhan <nirbheek@centricular.com> | 2016-07-01 20:50:47 +0530 |
commit | 0143c32c7cd82872e42f57216bb94a26191f2824 (patch) | |
tree | 7b6030e63de5283548ce234dd4d96a36a7380f1b /mesonbuild/build.py | |
parent | bc347aed0be4d8ea6210a546fb350f7bc1eee529 (diff) | |
download | meson-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.py | 278 |
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): |