diff options
Diffstat (limited to 'mesonbuild/build.py')
-rw-r--r-- | mesonbuild/build.py | 310 |
1 files changed, 237 insertions, 73 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c60f37f..b610bb8 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,38 @@ 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): + for source in sources: + if source.endswith('.' + suffix): + return True + return False + +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': + return True + return False + + class InvalidArguments(MesonException): pass @@ -209,6 +242,10 @@ class BuildTarget(): raise InvalidArguments('Build target %s has no sources.' % name) self.validate_sources() + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.filename) + def get_id(self): # This ID must also be a valid file name on all OSs. # It should also avoid shell metacharacters for obvious @@ -593,6 +630,10 @@ class Generator(): self.exe = exe self.process_kwargs(kwargs) + def __repr__(self): + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.exe) + def get_exe(self): return self.exe @@ -670,15 +711,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 +731,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 +896,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() - - 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 + Returns None if there is no import library required for this platform + """ + return self.import_filename - 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): @@ -815,6 +964,10 @@ class CustomTarget: mlog.log(mlog.bold('Warning:'), 'Unknown keyword arguments in target %s: %s' % (self.name, ', '.join(unknowns))) + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + def get_id(self): return self.name + self.type_suffix() @@ -851,7 +1004,7 @@ class CustomTarget: for i, c in enumerate(cmd): if hasattr(c, 'held_object'): c = c.held_object - if isinstance(c, str): + if isinstance(c, str) or isinstance(c, File): final_cmd.append(c) elif isinstance(c, dependencies.ExternalProgram): if not c.found(): @@ -867,8 +1020,6 @@ class CustomTarget: if not isinstance(s, str): raise InvalidArguments('Array as argument %d contains a non-string.' % i) final_cmd.append(s) - elif isinstance(c, File): - final_cmd.append(os.path.join(c.subdir, c.fname)) else: raise InvalidArguments('Argument %s in "command" is invalid.' % i) self.command = final_cmd @@ -944,6 +1095,10 @@ class RunTarget: self.dependencies = dependencies self.subdir = subdir + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + def get_id(self): return self.name + self.type_suffix() @@ -994,6 +1149,12 @@ class ConfigureFile(): self.targetname = targetname self.configuration_data = configuration_data + def __repr__(self): + repr_str = "<{0}: {1} -> {2}>" + src = os.path.join(self.subdir, self.sourcename) + dst = os.path.join(self.subdir, self.targetname) + return repr_str.format(self.__class__.__name__, src, dst) + def get_configuration_data(self): return self.configuration_data @@ -1011,6 +1172,9 @@ class ConfigurationData(): super().__init__() self.values = {} + def __repr__(self): + return repr(self.values) + def get(self, name): return self.values[name] |