aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/modules
diff options
context:
space:
mode:
authorJohn Ericson <git@JohnEricson.me>2020-08-03 11:48:27 -0400
committerJohn Ericson <git@JohnEricson.me>2020-08-03 11:48:27 -0400
commiteaf6343c065842b9719793066e765b2e5f1c2f3b (patch)
tree1bfeac5297ba489721e704e63c28f33d0fb98990 /mesonbuild/modules
parent87aa98c1787d800145853a8e84654e4c54ee1078 (diff)
parent70edf82c6c77902cd64f44848302bbac92d611d8 (diff)
downloadmeson-lang-enum.zip
meson-lang-enum.tar.gz
meson-lang-enum.tar.bz2
Merge remote-tracking branch 'upstream/master' into lang-enumlang-enum
Diffstat (limited to 'mesonbuild/modules')
-rw-r--r--mesonbuild/modules/__init__.py11
-rw-r--r--mesonbuild/modules/cmake.py113
-rw-r--r--mesonbuild/modules/gnome.py134
-rw-r--r--mesonbuild/modules/keyval.py (renamed from mesonbuild/modules/unstable_kconfig.py)10
-rw-r--r--mesonbuild/modules/pkgconfig.py126
-rw-r--r--mesonbuild/modules/python.py12
-rw-r--r--mesonbuild/modules/qt.py58
-rw-r--r--mesonbuild/modules/qt4.py3
-rw-r--r--mesonbuild/modules/qt5.py3
-rw-r--r--mesonbuild/modules/windows.py2
10 files changed, 297 insertions, 175 deletions
diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py
index dc86a1b..47be039 100644
--- a/mesonbuild/modules/__init__.py
+++ b/mesonbuild/modules/__init__.py
@@ -57,6 +57,17 @@ def get_include_args(include_dirs, prefix='-I'):
return dirs_str
+def is_module_library(fname):
+ '''
+ Check if the file is a library-like file generated by a module-specific
+ target, such as GirTarget or TypelibTarget
+ '''
+ if hasattr(fname, 'fname'):
+ fname = fname.fname
+ suffix = fname.split('.')[-1]
+ return suffix in ('gir', 'typelib')
+
+
class ModuleReturnValue:
def __init__(self, return_value, new_objects):
self.return_value = return_value
diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py
index 0283d11..e6587e4 100644
--- a/mesonbuild/modules/cmake.py
+++ b/mesonbuild/modules/cmake.py
@@ -14,12 +14,28 @@
import re
import os, os.path, pathlib
import shutil
+import typing as T
from . import ExtensionModule, ModuleReturnValue
from .. import build, dependencies, mesonlib, mlog
-from ..interpreterbase import permittedKwargs, FeatureNew, stringArgs, InterpreterObject, ObjectHolder, noPosargs
+from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args
from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder
+from ..interpreterbase import (
+ InterpreterObject,
+ ObjectHolder,
+
+ FeatureNew,
+ FeatureNewKwargs,
+ FeatureDeprecatedKwargs,
+
+ stringArgs,
+ permittedKwargs,
+ noPosargs,
+ noKwargs,
+
+ InvalidArguments,
+)
COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion']
@@ -82,42 +98,107 @@ class CMakeSubprojectHolder(InterpreterObject, ObjectHolder):
assert(all([x in res for x in ['inc', 'src', 'dep', 'tgt', 'func']]))
return res
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def get_variable(self, args, kwargs):
return self.held_object.get_variable_method(args, kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def dependency(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['dep']], kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def include_directories(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['inc']], kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def target(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['tgt']], kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def target_type(self, args, kwargs):
info = self._args_to_info(args)
return info['func']
@noPosargs
- @permittedKwargs({})
+ @noKwargs
def target_list(self, args, kwargs):
return self.held_object.cm_interpreter.target_list()
@noPosargs
- @permittedKwargs({})
+ @noKwargs
@FeatureNew('CMakeSubproject.found()', '0.53.2')
def found_method(self, args, kwargs):
return self.held_object is not None
+class CMakeSubprojectOptions(InterpreterObject):
+ def __init__(self) -> None:
+ super().__init__()
+ self.cmake_options = [] # type: T.List[str]
+ self.target_options = TargetOptions()
+
+ self.methods.update(
+ {
+ 'add_cmake_defines': self.add_cmake_defines,
+ 'set_override_option': self.set_override_option,
+ 'set_install': self.set_install,
+ 'append_compile_args': self.append_compile_args,
+ 'append_link_args': self.append_link_args,
+ 'clear': self.clear,
+ }
+ )
+
+ def _get_opts(self, kwargs: dict) -> SingleTargetOptions:
+ if 'target' in kwargs:
+ return self.target_options[kwargs['target']]
+ return self.target_options.global_options
+
+ @noKwargs
+ def add_cmake_defines(self, args, kwargs) -> None:
+ self.cmake_options += cmake_defines_to_args(args)
+
+ @stringArgs
+ @permittedKwargs({'target'})
+ def set_override_option(self, args, kwargs) -> None:
+ if len(args) != 2:
+ raise InvalidArguments('set_override_option takes exactly 2 positional arguments')
+ self._get_opts(kwargs).set_opt(args[0], args[1])
+
+ @permittedKwargs({'target'})
+ def set_install(self, args, kwargs) -> None:
+ if len(args) != 1 or not isinstance(args[0], bool):
+ raise InvalidArguments('set_install takes exactly 1 boolean argument')
+ self._get_opts(kwargs).set_install(args[0])
+
+ @stringArgs
+ @permittedKwargs({'target'})
+ def append_compile_args(self, args, kwargs) -> None:
+ if len(args) < 2:
+ raise InvalidArguments('append_compile_args takes at least 2 positional arguments')
+ self._get_opts(kwargs).append_args(args[0], args[1:])
+
+ @stringArgs
+ @permittedKwargs({'target'})
+ def append_link_args(self, args, kwargs) -> None:
+ if not args:
+ raise InvalidArguments('append_link_args takes at least 1 positional argument')
+ self._get_opts(kwargs).append_link_args(args)
+
+ @noPosargs
+ @noKwargs
+ def clear(self, args, kwargs) -> None:
+ self.cmake_options.clear()
+ self.target_options = TargetOptions()
+
+
class CmakeModule(ExtensionModule):
cmake_detected = False
cmake_root = None
@@ -252,8 +333,7 @@ class CmakeModule(ExtensionModule):
(ofile_path, ofile_fname) = os.path.split(os.path.join(state.subdir, '{}Config.cmake'.format(name)))
ofile_abs = os.path.join(state.environment.build_dir, ofile_path, ofile_fname)
- if 'install_dir' not in kwargs:
- install_dir = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'cmake', name)
+ install_dir = kwargs.get('install_dir', os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'cmake', name))
if not isinstance(install_dir, str):
raise mesonlib.MesonException('"install_dir" must be a string.')
@@ -287,16 +367,27 @@ class CmakeModule(ExtensionModule):
return res
@FeatureNew('subproject', '0.51.0')
- @permittedKwargs({'cmake_options', 'required'})
+ @FeatureNewKwargs('subproject', '0.55.0', ['options'])
+ @FeatureDeprecatedKwargs('subproject', '0.55.0', ['cmake_options'])
+ @permittedKwargs({'cmake_options', 'required', 'options'})
@stringArgs
def subproject(self, interpreter, state, args, kwargs):
if len(args) != 1:
raise InterpreterException('Subproject takes exactly one argument')
+ if 'cmake_options' in kwargs and 'options' in kwargs:
+ raise InterpreterException('"options" cannot be used together with "cmake_options"')
dirname = args[0]
subp = interpreter.do_subproject(dirname, 'cmake', kwargs)
if not subp.held_object:
return subp
return CMakeSubprojectHolder(subp, dirname)
+ @FeatureNew('subproject_options', '0.55.0')
+ @noKwargs
+ @noPosargs
+ def subproject_options(self, state, args, kwargs) -> ModuleReturnValue:
+ opts = CMakeSubprojectOptions()
+ return ModuleReturnValue(opts, [])
+
def initialize(*args, **kwargs):
return CmakeModule(*args, **kwargs)
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index 21360a2..de674db 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -32,8 +32,8 @@ from ..mesonlib import (
MachineChoice, MesonException, OrderedSet, Popen_safe, extract_as_list,
join_args, unholder,
)
-from ..dependencies import Dependency, PkgConfigDependency, InternalDependency
-from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs
+from ..dependencies import Dependency, PkgConfigDependency, InternalDependency, ExternalProgram
+from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs
# gresource compilation is broken due to the way
# the resource compiler and Ninja clash about it
@@ -44,20 +44,6 @@ gresource_dep_needed_version = '>= 2.51.1'
native_glib_version = None
-@functools.lru_cache(maxsize=None)
-def gir_has_option(intr_obj, option):
- try:
- g_ir_scanner = intr_obj.find_program_impl('g-ir-scanner')
- # Handle overridden g-ir-scanner
- if isinstance(getattr(g_ir_scanner, "held_object", g_ir_scanner), interpreter.OverrideProgram):
- assert option in ['--extra-library', '--sources-top-dirs']
- return True
-
- opts = Popen_safe(g_ir_scanner.get_command() + ['--help'], stderr=subprocess.STDOUT)[1]
- return option in opts
- except (MesonException, FileNotFoundError, subprocess.CalledProcessError):
- return False
-
class GnomeModule(ExtensionModule):
gir_dep = None
@@ -303,7 +289,7 @@ class GnomeModule(ExtensionModule):
link_command.append('-L' + d)
if include_rpath:
link_command.append('-Wl,-rpath,' + d)
- if gir_has_option(self.interpreter, '--extra-library') and use_gir_args:
+ if use_gir_args and self._gir_has_option('--extra-library'):
link_command.append('--extra-library=' + lib.name)
else:
link_command.append('-l' + lib.name)
@@ -321,6 +307,10 @@ class GnomeModule(ExtensionModule):
deps = mesonlib.unholder(mesonlib.listify(deps))
for dep in deps:
+ if isinstance(dep, Dependency):
+ girdir = dep.get_variable(pkgconfig='girdir', internal='girdir', default_value='')
+ if girdir:
+ gi_includes.update([girdir])
if isinstance(dep, InternalDependency):
cflags.update(dep.get_compile_args())
cflags.update(get_include_args(dep.include_directories))
@@ -371,11 +361,6 @@ class GnomeModule(ExtensionModule):
external_ldflags_nodedup += [lib, next(ldflags)]
else:
external_ldflags.update([lib])
-
- if isinstance(dep, PkgConfigDependency):
- girdir = dep.get_pkgconfig_variable("girdir", {'default': ''})
- if girdir:
- gi_includes.update([girdir])
elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
cflags.update(get_include_args(dep.get_include_dirs()))
depends.append(dep)
@@ -383,7 +368,7 @@ class GnomeModule(ExtensionModule):
mlog.log('dependency {!r} not handled to build gir files'.format(dep))
continue
- if gir_has_option(self.interpreter, '--extra-library') and use_gir_args:
+ if use_gir_args and self._gir_has_option('--extra-library'):
def fix_ldflags(ldflags):
fixed_ldflags = OrderedSet()
for ldflag in ldflags:
@@ -417,15 +402,37 @@ class GnomeModule(ExtensionModule):
return girtarget
def _get_gir_dep(self, state):
- try:
- gir_dep = self.gir_dep or PkgConfigDependency('gobject-introspection-1.0',
- state.environment,
- {'native': True})
- pkgargs = gir_dep.get_compile_args()
- except Exception:
- raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.')
-
- return gir_dep, pkgargs
+ if not self.gir_dep:
+ kwargs = {'native': True, 'required': True}
+ holder = self.interpreter.func_dependency(state.current_node, ['gobject-introspection-1.0'], kwargs)
+ self.gir_dep = holder.held_object
+ giscanner = state.environment.lookup_binary_entry(MachineChoice.HOST, 'g-ir-scanner')
+ if giscanner is not None:
+ self.giscanner = ExternalProgram.from_entry('g-ir-scanner', giscanner)
+ elif self.gir_dep.type_name == 'pkgconfig':
+ self.giscanner = ExternalProgram('g_ir_scanner', self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {}))
+ else:
+ self.giscanner = self.interpreter.find_program_impl('g-ir-scanner')
+ gicompiler = state.environment.lookup_binary_entry(MachineChoice.HOST, 'g-ir-compiler')
+ if gicompiler is not None:
+ self.gicompiler = ExternalProgram.from_entry('g-ir-compiler', gicompiler)
+ elif self.gir_dep.type_name == 'pkgconfig':
+ self.gicompiler = ExternalProgram('g_ir_compiler', self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {}))
+ else:
+ self.gicompiler = self.interpreter.find_program_impl('g-ir-compiler')
+ return self.gir_dep, self.giscanner, self.gicompiler
+
+ @functools.lru_cache(maxsize=None)
+ def _gir_has_option(self, option):
+ exe = self.giscanner
+ if hasattr(exe, 'held_object'):
+ exe = exe.held_object
+ if isinstance(exe, interpreter.OverrideProgram):
+ # Handle overridden g-ir-scanner
+ assert option in ['--extra-library', '--sources-top-dirs']
+ return True
+ p, o, e = Popen_safe(exe.get_command() + ['--help'], stderr=subprocess.STDOUT)
+ return p.returncode == 0 and option in o
def _scan_header(self, kwargs):
ret = []
@@ -688,11 +695,10 @@ class GnomeModule(ExtensionModule):
source.get_subdir())
if subdir not in typelib_includes:
typelib_includes.append(subdir)
- elif isinstance(dep, PkgConfigDependency):
- girdir = dep.get_pkgconfig_variable("girdir", {'default': ''})
+ if isinstance(dep, Dependency):
+ girdir = dep.get_variable(pkgconfig='girdir', internal='girdir', default_value='')
if girdir and girdir not in typelib_includes:
typelib_includes.append(girdir)
-
return typelib_includes
def _get_external_args_for_langs(self, state, langs):
@@ -715,11 +721,12 @@ class GnomeModule(ExtensionModule):
if f.startswith(('-L', '-l', '--extra-library')):
yield f
- @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
+ @FeatureNewKwargs('generate_gir', '0.55.0', ['fatal_warnings'])
+ @FeatureNewKwargs('generate_gir', '0.40.0', ['build_by_default'])
@permittedKwargs({'sources', 'nsversion', 'namespace', 'symbol_prefix', 'identifier_prefix',
'export_packages', 'includes', 'dependencies', 'link_with', 'include_directories',
'install', 'install_dir_gir', 'install_dir_typelib', 'extra_args',
- 'packages', 'header', 'build_by_default'})
+ 'packages', 'header', 'build_by_default', 'fatal_warnings'})
def generate_gir(self, state, args, kwargs):
if not args:
raise MesonException('generate_gir takes at least one argument')
@@ -731,42 +738,25 @@ class GnomeModule(ExtensionModule):
if len(girtargets) > 1 and any([isinstance(el, build.Executable) for el in girtargets]):
raise MesonException('generate_gir only accepts a single argument when one of the arguments is an executable')
- self.gir_dep, pkgargs = self._get_gir_dep(state)
- # find_program is needed in the case g-i is built as subproject.
- # In that case it uses override_find_program so the gobject utilities
- # can be used from the build dir instead of from the system.
- # However, GObject-introspection provides the appropriate paths to
- # these utilities via pkg-config, so it would be best to use the
- # results from pkg-config when possible.
- gi_util_dirs_check = [state.environment.get_build_dir(), state.environment.get_source_dir()]
- giscanner = self.interpreter.find_program_impl('g-ir-scanner')
- if giscanner.found():
- giscanner_path = giscanner.get_command()[0]
- if not any(x in giscanner_path for x in gi_util_dirs_check):
- giscanner = self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {})
- else:
- giscanner = self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {})
+ gir_dep, giscanner, gicompiler = self._get_gir_dep(state)
- gicompiler = self.interpreter.find_program_impl('g-ir-compiler')
- if gicompiler.found():
- gicompiler_path = gicompiler.get_command()[0]
- if not any(x in gicompiler_path for x in gi_util_dirs_check):
- gicompiler = self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {})
- else:
- gicompiler = self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {})
-
- ns = kwargs.pop('namespace')
- nsversion = kwargs.pop('nsversion')
+ ns = kwargs.get('namespace')
+ if not ns:
+ raise MesonException('Missing "namespace" keyword argument')
+ nsversion = kwargs.get('nsversion')
+ if not nsversion:
+ raise MesonException('Missing "nsversion" keyword argument')
libsources = mesonlib.extract_as_list(kwargs, 'sources', pop=True)
girfile = '%s-%s.gir' % (ns, nsversion)
srcdir = os.path.join(state.environment.get_source_dir(), state.subdir)
builddir = os.path.join(state.environment.get_build_dir(), state.subdir)
- depends = [] + girtargets
+ depends = gir_dep.sources + girtargets
gir_inc_dirs = []
langs_compilers = self._get_girtargets_langs_compilers(girtargets)
cflags, internal_ldflags, external_ldflags = self._get_langs_compilers_flags(state, langs_compilers)
deps = self._get_gir_targets_deps(girtargets)
deps += mesonlib.unholder(extract_as_list(kwargs, 'dependencies', pop=True))
+ deps += [gir_dep]
typelib_includes = self._gather_typelib_includes_and_update_depends(state, deps, depends)
# ldflags will be misinterpreted by gir scanner (showing
# spurious dependencies) but building GStreamer fails if they
@@ -781,7 +771,6 @@ class GnomeModule(ExtensionModule):
inc_dirs = self._scan_inc_dirs(kwargs)
scan_command = [giscanner]
- scan_command += pkgargs
scan_command += ['--no-libtool']
scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion]
scan_command += ['--warn-all']
@@ -806,10 +795,18 @@ class GnomeModule(ExtensionModule):
scan_command += self._scan_langs(state, [lc[0] for lc in langs_compilers])
scan_command += list(external_ldflags)
- if gir_has_option(self.interpreter, '--sources-top-dirs'):
+ if self._gir_has_option('--sources-top-dirs'):
scan_command += ['--sources-top-dirs', os.path.join(state.environment.get_source_dir(), self.interpreter.subproject_dir, state.subproject)]
scan_command += ['--sources-top-dirs', os.path.join(state.environment.get_build_dir(), self.interpreter.subproject_dir, state.subproject)]
+ if '--warn-error' in scan_command:
+ mlog.deprecation('Passing --warn-error is deprecated in favor of "fatal_warnings" keyword argument since v0.55')
+ fatal_warnings = kwargs.get('fatal_warnings', False)
+ if not isinstance(fatal_warnings, bool):
+ raise MesonException('fatal_warnings keyword argument must be a boolean')
+ if fatal_warnings:
+ scan_command.append('--warn-error')
+
scan_target = self._make_gir_target(state, girfile, scan_command, depends, kwargs)
typelib_output = '%s-%s.typelib' % (ns, nsversion)
@@ -846,6 +843,8 @@ class GnomeModule(ExtensionModule):
return ModuleReturnValue(target_g, [target_g])
@permittedKwargs({'sources', 'media', 'symlink_media', 'languages'})
+ @FeatureDeprecatedKwargs('gnome.yelp', '0.43.0', ['languages'],
+ 'Use a LINGUAS file in the source directory instead')
def yelp(self, state, args, kwargs):
if len(args) < 1:
raise MesonException('Yelp requires a project id')
@@ -860,11 +859,6 @@ class GnomeModule(ExtensionModule):
source_str = '@@'.join(sources)
langs = mesonlib.stringlistify(kwargs.pop('languages', []))
- if langs:
- mlog.deprecation('''The "languages" argument of gnome.yelp() is deprecated.
-Use a LINGUAS file in the sources directory instead.
-This will become a hard error in the future.''')
-
media = mesonlib.stringlistify(kwargs.pop('media', []))
symlinks = kwargs.pop('symlink_media', True)
diff --git a/mesonbuild/modules/unstable_kconfig.py b/mesonbuild/modules/keyval.py
index 6685710..3da2992 100644
--- a/mesonbuild/modules/unstable_kconfig.py
+++ b/mesonbuild/modules/keyval.py
@@ -21,9 +21,9 @@ from ..interpreter import InvalidCode
import os
-class KconfigModule(ExtensionModule):
+class KeyvalModule(ExtensionModule):
- @FeatureNew('Kconfig Module', '0.51.0')
+ @FeatureNew('Keyval Module', '0.55.0')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.snippets.add('load')
@@ -56,9 +56,7 @@ class KconfigModule(ExtensionModule):
s = sources[0]
is_built = False
if isinstance(s, mesonlib.File):
- if s.is_built:
- FeatureNew('kconfig.load() of built files', '0.52.0').use(state.subproject)
- is_built = True
+ is_built = is_built or s.is_built
s = s.absolute_path(interpreter.environment.source_dir, interpreter.environment.build_dir)
else:
s = os.path.join(interpreter.environment.source_dir, s)
@@ -70,4 +68,4 @@ class KconfigModule(ExtensionModule):
def initialize(*args, **kwargs):
- return KconfigModule(*args, **kwargs)
+ return KeyvalModule(*args, **kwargs)
diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py
index ac51e36..f81ee2f 100644
--- a/mesonbuild/modules/pkgconfig.py
+++ b/mesonbuild/modules/pkgconfig.py
@@ -36,6 +36,7 @@ class DependenciesHelper:
self.priv_reqs = []
self.cflags = []
self.version_reqs = {}
+ self.link_whole_targets = []
def add_pub_libs(self, libs):
libs, reqs, cflags = self._process_libs(libs, True)
@@ -76,7 +77,7 @@ class DependenciesHelper:
processed_reqs = []
for obj in mesonlib.unholder(mesonlib.listify(reqs)):
if not isinstance(obj, str):
- FeatureNew('pkgconfig.generate requirement from non-string object', '0.46.0').use(self.state.subproject)
+ FeatureNew.single_use('pkgconfig.generate requirement from non-string object', '0.46.0', self.state.subproject)
if hasattr(obj, 'generated_pc'):
self._check_generated_pc_deprecation(obj)
processed_reqs.append(obj.generated_pc)
@@ -130,10 +131,7 @@ class DependenciesHelper:
if obj.found():
processed_libs += obj.get_link_args()
processed_cflags += obj.get_compile_args()
- if public:
- self.add_pub_libs(obj.libraries)
- else:
- self.add_priv_libs(obj.libraries)
+ self._add_lib_dependencies(obj.libraries, obj.whole_libraries, obj.ext_deps, public)
elif isinstance(obj, dependencies.Dependency):
if obj.found():
processed_libs += obj.get_link_args()
@@ -148,12 +146,13 @@ class DependenciesHelper:
processed_libs.append(obj)
elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)):
processed_libs.append(obj)
- if isinstance(obj, build.StaticLibrary) and public:
- self.add_pub_libs(obj.get_dependencies(for_pkgconfig=True))
- self.add_pub_libs(obj.get_external_deps())
- else:
- self.add_priv_libs(obj.get_dependencies(for_pkgconfig=True))
- self.add_priv_libs(obj.get_external_deps())
+ # If there is a static library in `Libs:` all its deps must be
+ # public too, otherwise the generated pc file will never be
+ # usable without --static.
+ self._add_lib_dependencies(obj.link_targets,
+ obj.link_whole_targets,
+ obj.external_deps,
+ isinstance(obj, build.StaticLibrary) and public)
elif isinstance(obj, str):
processed_libs.append(obj)
else:
@@ -161,6 +160,31 @@ class DependenciesHelper:
return processed_libs, processed_reqs, processed_cflags
+ def _add_lib_dependencies(self, link_targets, link_whole_targets, external_deps, public):
+ add_libs = self.add_pub_libs if public else self.add_priv_libs
+ # Recursively add all linked libraries
+ for t in link_targets:
+ # Internal libraries (uninstalled static library) will be promoted
+ # to link_whole, treat them as such here.
+ if t.is_internal():
+ self._add_link_whole(t, public)
+ else:
+ add_libs([t])
+ for t in link_whole_targets:
+ self._add_link_whole(t, public)
+ # And finally its external dependencies
+ add_libs(external_deps)
+
+ def _add_link_whole(self, t, public):
+ # Don't include static libraries that we link_whole. But we still need to
+ # include their dependencies: a static library we link_whole
+ # could itself link to a shared library or an installed static library.
+ # Keep track of link_whole_targets so we can remove them from our
+ # lists in case a library is link_with and link_whole at the same time.
+ # See remove_dups() below.
+ self.link_whole_targets.append(t)
+ self._add_lib_dependencies(t.link_targets, t.link_whole_targets, t.external_deps, public)
+
def add_version_reqs(self, name, version_reqs):
if version_reqs:
if name not in self.version_reqs:
@@ -196,6 +220,32 @@ class DependenciesHelper:
return ', '.join(result)
def remove_dups(self):
+ # Set of ids that have already been handled and should not be added any more
+ exclude = set()
+
+ # We can't just check if 'x' is excluded because we could have copies of
+ # the same SharedLibrary object for example.
+ def _ids(x):
+ if hasattr(x, 'generated_pc'):
+ yield x.generated_pc
+ if isinstance(x, build.Target):
+ yield x.get_id()
+ yield x
+
+ # Exclude 'x' in all its forms and return if it was already excluded
+ def _add_exclude(x):
+ was_excluded = False
+ for i in _ids(x):
+ if i in exclude:
+ was_excluded = True
+ else:
+ exclude.add(i)
+ return was_excluded
+
+ # link_whole targets are already part of other targets, exclude them all.
+ for t in self.link_whole_targets:
+ _add_exclude(t)
+
def _fn(xs, libs=False):
# Remove duplicates whilst preserving original order
result = []
@@ -206,19 +256,21 @@ class DependenciesHelper:
cannot_dedup = libs and isinstance(x, str) and \
not x.startswith(('-l', '-L')) and \
x not in known_flags
- if x not in result or cannot_dedup:
- result.append(x)
+ if not cannot_dedup and _add_exclude(x):
+ continue
+ result.append(x)
return result
- self.pub_libs = _fn(self.pub_libs, True)
+
+ # Handle lists in priority order: public items can be excluded from
+ # private and Requires can excluded from Libs.
self.pub_reqs = _fn(self.pub_reqs)
- self.priv_libs = _fn(self.priv_libs, True)
+ self.pub_libs = _fn(self.pub_libs, True)
self.priv_reqs = _fn(self.priv_reqs)
+ self.priv_libs = _fn(self.priv_libs, True)
+ # Reset exclude list just in case some values can be both cflags and libs.
+ exclude = set()
self.cflags = _fn(self.cflags)
- # Remove from private libs/reqs if they are in public already
- self.priv_libs = [i for i in self.priv_libs if i not in self.pub_libs]
- self.priv_reqs = [i for i in self.priv_reqs if i not in self.pub_reqs]
-
class PkgConfigModule(ExtensionModule):
def _get_lname(self, l, msg, pcfile):
@@ -267,7 +319,6 @@ class PkgConfigModule(ExtensionModule):
def generate_pkgconfig_file(self, state, deps, subdirs, name, description,
url, version, pcfile, conflicts, variables,
uninstalled=False, dataonly=False):
- deps.remove_dups()
coredata = state.environment.get_coredata()
if uninstalled:
outdir = os.path.join(state.environment.build_dir, 'meson-uninstalled')
@@ -372,18 +423,18 @@ class PkgConfigModule(ExtensionModule):
if len(deps.priv_libs) > 0:
ofile.write('Libs.private: {}\n'.format(' '.join(generate_libs_flags(deps.priv_libs))))
- def generate_compiler_flags():
- cflags_buf = []
- for f in deps.cflags:
- cflags_buf.append(self._escape(f))
- return cflags_buf
-
- cflags = generate_compiler_flags()
- ofile.write('Cflags:')
+ cflags = []
if uninstalled:
- ofile.write(' '.join(generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs)))
- elif not dataonly and cflags:
- ofile.write('{}\n'.format(' '.join(cflags)))
+ cflags += generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs)
+ else:
+ for d in subdirs:
+ if d == '.':
+ cflags.append('-I${includedir}')
+ else:
+ cflags.append(self._escape(PurePath('-I${includedir}') / d))
+ cflags += [self._escape(f) for f in deps.cflags]
+ if cflags and not dataonly:
+ ofile.write('Cflags: {}\n'.format(' '.join(cflags)))
@FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['uninstalled_variables'])
@FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags'])
@@ -394,8 +445,6 @@ class PkgConfigModule(ExtensionModule):
'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions',
'dataonly', 'conflicts'})
def generate(self, state, args, kwargs):
- if 'variables' in kwargs:
- FeatureNew('custom pkgconfig variables', '0.41.0').use(state.subproject)
default_version = state.project_version['version']
default_install_dir = None
default_description = None
@@ -403,9 +452,9 @@ class PkgConfigModule(ExtensionModule):
mainlib = None
default_subdirs = ['.']
if not args and 'version' not in kwargs:
- FeatureNew('pkgconfig.generate implicit version keyword', '0.46.0').use(state.subproject)
+ FeatureNew.single_use('pkgconfig.generate implicit version keyword', '0.46.0', state.subproject)
elif len(args) == 1:
- FeatureNew('pkgconfig.generate optional positional argument', '0.46.0').use(state.subproject)
+ FeatureNew.single_use('pkgconfig.generate optional positional argument', '0.46.0', state.subproject)
mainlib = getattr(args[0], 'held_object', args[0])
if not isinstance(mainlib, (build.StaticLibrary, build.SharedLibrary)):
raise mesonlib.MesonException('Pkgconfig_gen first positional argument must be a library object')
@@ -450,11 +499,6 @@ class PkgConfigModule(ExtensionModule):
libraries = [mainlib] + libraries
deps = DependenciesHelper(state, filebase)
- for d in subdirs:
- if d == '.':
- deps.add_cflags(['-I${includedir}'])
- else:
- deps.add_cflags(self._escape(PurePath('-I${includedir}') / d))
deps.add_pub_libs(libraries)
deps.add_priv_libs(kwargs.get('libraries_private', []))
deps.add_pub_reqs(kwargs.get('requires', []))
@@ -467,6 +511,8 @@ class PkgConfigModule(ExtensionModule):
if compiler:
deps.add_cflags(compiler.get_feature_args({'versions': dversions}, None))
+ deps.remove_dups()
+
def parse_variable_list(stringlist):
reserved = ['prefix', 'libdir', 'includedir']
variables = []
diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py
index a5c58a2..ceabd76 100644
--- a/mesonbuild/modules/python.py
+++ b/mesonbuild/modules/python.py
@@ -285,7 +285,7 @@ print (json.dumps ({
class PythonInstallation(ExternalProgramHolder):
def __init__(self, interpreter, python, info):
- ExternalProgramHolder.__init__(self, python)
+ ExternalProgramHolder.__init__(self, python, interpreter.subproject)
self.interpreter = interpreter
self.subproject = self.interpreter.subproject
prefix = self.interpreter.environment.coredata.get_builtin_option('prefix')
@@ -361,7 +361,7 @@ class PythonInstallation(ExternalProgramHolder):
@permittedKwargs(['pure', 'subdir'])
def install_sources_method(self, args, kwargs):
- pure = kwargs.pop('pure', False)
+ pure = kwargs.pop('pure', True)
if not isinstance(pure, bool):
raise InvalidArguments('"pure" argument must be a boolean.')
@@ -514,7 +514,7 @@ class PythonModule(ExtensionModule):
if disabled:
mlog.log('Program', name_or_path or 'python', 'found:', mlog.red('NO'), '(disabled by:', mlog.bold(feature), ')')
- return ExternalProgramHolder(NonExistingExternalProgram())
+ return ExternalProgramHolder(NonExistingExternalProgram(), state.subproject)
if not name_or_path:
python = ExternalProgram('python3', mesonlib.python_command, silent=True)
@@ -561,11 +561,11 @@ class PythonModule(ExtensionModule):
if not python.found():
if required:
raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python'))
- res = ExternalProgramHolder(NonExistingExternalProgram())
+ res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject)
elif missing_modules:
if required:
raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules)))
- res = ExternalProgramHolder(NonExistingExternalProgram())
+ res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject)
else:
# Sanity check, we expect to have something that at least quacks in tune
try:
@@ -583,7 +583,7 @@ class PythonModule(ExtensionModule):
if isinstance(info, dict) and 'version' in info and self._check_version(name_or_path, info['version']):
res = PythonInstallation(interpreter, python, info)
else:
- res = ExternalProgramHolder(NonExistingExternalProgram())
+ res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject)
if required:
raise mesonlib.MesonException('{} is not a valid python or it is missing setuptools'.format(python))
diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py
index c7da530..c810df6 100644
--- a/mesonbuild/modules/qt.py
+++ b/mesonbuild/modules/qt.py
@@ -15,8 +15,8 @@
import os
from .. import mlog
from .. import build
-from ..mesonlib import MesonException, Popen_safe, extract_as_list, File, unholder
-from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency
+from ..mesonlib import MesonException, extract_as_list, File, unholder, version_compare
+from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency, NonExistingExternalProgram
import xml.etree.ElementTree as ET
from . import ModuleReturnValue, get_include_args, ExtensionModule
from ..interpreterbase import noPosargs, permittedKwargs, FeatureNew, FeatureNewKwargs
@@ -30,49 +30,34 @@ _QT_DEPS_LUT = {
class QtBaseModule(ExtensionModule):
tools_detected = False
+ rcc_supports_depfiles = False
def __init__(self, interpreter, qt_version=5):
ExtensionModule.__init__(self, interpreter)
self.snippets.add('has_tools')
self.qt_version = qt_version
- def _detect_tools(self, env, method):
+ def _detect_tools(self, env, method, required=True):
if self.tools_detected:
return
+ self.tools_detected = True
mlog.log('Detecting Qt{version} tools'.format(version=self.qt_version))
- # FIXME: We currently require QtX to exist while importing the module.
- # We should make it gracefully degrade and not create any targets if
- # the import is marked as 'optional' (not implemented yet)
- kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true', 'method': method}
+ kwargs = {'required': required, 'modules': 'Core', 'method': method}
qt = _QT_DEPS_LUT[self.qt_version](env, kwargs)
- # Get all tools and then make sure that they are the right version
- self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect(self.interpreter)
- # Moc, uic and rcc write their version strings to stderr.
- # Moc and rcc return a non-zero result when doing so.
- # What kind of an idiot thought that was a good idea?
- for compiler, compiler_name in ((self.moc, "Moc"), (self.uic, "Uic"), (self.rcc, "Rcc"), (self.lrelease, "lrelease")):
- if compiler.found():
- # Workaround since there is no easy way to know which tool/version support which flag
- for flag in ['-v', '-version']:
- p, stdout, stderr = Popen_safe(compiler.get_command() + [flag])[0:3]
- if p.returncode == 0:
- break
- stdout = stdout.strip()
- stderr = stderr.strip()
- if 'Qt {}'.format(self.qt_version) in stderr:
- compiler_ver = stderr
- elif 'version {}.'.format(self.qt_version) in stderr:
- compiler_ver = stderr
- elif ' {}.'.format(self.qt_version) in stdout:
- compiler_ver = stdout
- else:
- raise MesonException('{name} preprocessor is not for Qt {version}. Output:\n{stdo}\n{stderr}'.format(
- name=compiler_name, version=self.qt_version, stdo=stdout, stderr=stderr))
- mlog.log(' {}:'.format(compiler_name.lower()), mlog.green('YES'), '({path}, {version})'.format(
- path=compiler.get_path(), version=compiler_ver.split()[-1]))
+ if qt.found():
+ # Get all tools and then make sure that they are the right version
+ self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect(self.interpreter)
+ if version_compare(qt.version, '>=5.14.0'):
+ self.rcc_supports_depfiles = True
else:
- mlog.log(' {}:'.format(compiler_name.lower()), mlog.red('NO'))
- self.tools_detected = True
+ mlog.warning('rcc dependencies will not work properly until you move to Qt >= 5.14:',
+ mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460'), fatal=False)
+ else:
+ suffix = '-qt{}'.format(self.qt_version)
+ self.moc = NonExistingExternalProgram(name='moc' + suffix)
+ self.uic = NonExistingExternalProgram(name='uic' + suffix)
+ self.rcc = NonExistingExternalProgram(name='rcc' + suffix)
+ self.lrelease = NonExistingExternalProgram(name='lrelease' + suffix)
def parse_qrc(self, state, rcc_file):
if type(rcc_file) is str:
@@ -128,7 +113,7 @@ class QtBaseModule(ExtensionModule):
if disabled:
mlog.log('qt.has_tools skipped: feature', mlog.bold(feature), 'disabled')
return False
- self._detect_tools(state.environment, method)
+ self._detect_tools(state.environment, method, required=False)
for tool in (self.moc, self.uic, self.rcc, self.lrelease):
if not tool.found():
if required:
@@ -177,6 +162,9 @@ class QtBaseModule(ExtensionModule):
'output': name + '.cpp',
'command': [self.rcc, '-name', '@BASENAME@', '-o', '@OUTPUT@', rcc_extra_arguments, '@INPUT@'],
'depend_files': qrc_deps}
+ if self.rcc_supports_depfiles:
+ rcc_kwargs['depfile'] = name + '.d'
+ rcc_kwargs['command'] += ['--depfile', '@DEPFILE@']
res_target = build.CustomTarget(name, state.subdir, state.subproject, rcc_kwargs)
sources.append(res_target)
if ui_files:
diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py
index 112e3e4..e85a150 100644
--- a/mesonbuild/modules/qt4.py
+++ b/mesonbuild/modules/qt4.py
@@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .. import mlog
from .qt import QtBaseModule
@@ -23,6 +22,4 @@ class Qt4Module(QtBaseModule):
def initialize(*args, **kwargs):
- mlog.warning('rcc dependencies will not work properly until this upstream issue is fixed:',
- mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460'))
return Qt4Module(*args, **kwargs)
diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py
index 96a7964..873c2db 100644
--- a/mesonbuild/modules/qt5.py
+++ b/mesonbuild/modules/qt5.py
@@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .. import mlog
from .qt import QtBaseModule
@@ -23,6 +22,4 @@ class Qt5Module(QtBaseModule):
def initialize(*args, **kwargs):
- mlog.warning('rcc dependencies will not work reliably until this upstream issue is fixed:',
- mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460'))
return Qt5Module(*args, **kwargs)
diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py
index f939782..c154ab2 100644
--- a/mesonbuild/modules/windows.py
+++ b/mesonbuild/modules/windows.py
@@ -107,7 +107,7 @@ class WindowsModule(ExtensionModule):
'a MinGW bug: https://sourceware.org/bugzilla/show_bug.cgi?id=4933'
for arg in extra_args:
if ' ' in arg:
- mlog.warning(m.format(arg))
+ mlog.warning(m.format(arg), fatal=False)
res_targets = []