diff options
author | John Ericson <git@JohnEricson.me> | 2020-08-03 11:48:27 -0400 |
---|---|---|
committer | John Ericson <git@JohnEricson.me> | 2020-08-03 11:48:27 -0400 |
commit | eaf6343c065842b9719793066e765b2e5f1c2f3b (patch) | |
tree | 1bfeac5297ba489721e704e63c28f33d0fb98990 /mesonbuild/modules | |
parent | 87aa98c1787d800145853a8e84654e4c54ee1078 (diff) | |
parent | 70edf82c6c77902cd64f44848302bbac92d611d8 (diff) | |
download | meson-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__.py | 11 | ||||
-rw-r--r-- | mesonbuild/modules/cmake.py | 113 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 134 | ||||
-rw-r--r-- | mesonbuild/modules/keyval.py (renamed from mesonbuild/modules/unstable_kconfig.py) | 10 | ||||
-rw-r--r-- | mesonbuild/modules/pkgconfig.py | 126 | ||||
-rw-r--r-- | mesonbuild/modules/python.py | 12 | ||||
-rw-r--r-- | mesonbuild/modules/qt.py | 58 | ||||
-rw-r--r-- | mesonbuild/modules/qt4.py | 3 | ||||
-rw-r--r-- | mesonbuild/modules/qt5.py | 3 | ||||
-rw-r--r-- | mesonbuild/modules/windows.py | 2 |
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 = [] |