aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/build.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/build.py')
-rw-r--r--mesonbuild/build.py310
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]