aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/dependencies
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2021-03-22 15:25:29 -0700
committerDylan Baker <dylan@pnwbakers.com>2021-03-23 15:28:08 -0700
commitc211fea51389728783cf59ae41156a5e0de9c41a (patch)
tree3269d721c713be5e7cd9d0c3a30e14b20b58e46e /mesonbuild/dependencies
parent54c55f77a976deb0047a78468250b75525df6514 (diff)
downloadmeson-c211fea51389728783cf59ae41156a5e0de9c41a.zip
meson-c211fea51389728783cf59ae41156a5e0de9c41a.tar.gz
meson-c211fea51389728783cf59ae41156a5e0de9c41a.tar.bz2
Refactor Qt Dependency into proper split classes with factories
Currently the Qt Dependencies still use the old "combined" method for dependencies with multiple ways to be found. This is problematic as it means that `get_variable()` and friends don't work, as the dependency can't implement any of those methods. The correct solution is to make use of multiple Dependency instances, and a factory to tie them together. This does that. To handle QMake, I've leveraged the existing config-tool mechanism, which allows us to save a good deal of code, and use well tested code instead of rolling more of our own code. The one thing this doesn't do, but we probably should, is expose the macOS ExtraFrameworks directly, instead of forcing them to be found through QMake. That is a problem for another series, and someone who cares more about macOS than I do.
Diffstat (limited to 'mesonbuild/dependencies')
-rw-r--r--mesonbuild/dependencies/__init__.py8
-rw-r--r--mesonbuild/dependencies/base.py11
-rw-r--r--mesonbuild/dependencies/qt.py297
3 files changed, 172 insertions, 144 deletions
diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py
index 416325d..afd4adc 100644
--- a/mesonbuild/dependencies/__init__.py
+++ b/mesonbuild/dependencies/__init__.py
@@ -31,7 +31,7 @@ from .misc import (
shaderc_factory, threads_factory,
)
from .platform import AppleFrameworks
-from .qt import Qt4Dependency, Qt5Dependency, Qt6Dependency
+from .qt import qt4_factory, qt5_factory, qt6_factory
from .ui import GnuStepDependency, WxDependency, gl_factory, sdl2_factory, vulkan_factory
"""Dependency representations and discovery logic.
@@ -226,9 +226,9 @@ packages.update({
# From ui:
'gl': gl_factory,
'gnustep': GnuStepDependency,
- 'qt4': Qt4Dependency,
- 'qt5': Qt5Dependency,
- 'qt6': Qt6Dependency,
+ 'qt4': qt4_factory,
+ 'qt5': qt5_factory,
+ 'qt6': qt6_factory,
'sdl2': sdl2_factory,
'wxwidgets': WxDependency,
'vulkan': vulkan_factory,
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py
index 6ec74ce..8944780 100644
--- a/mesonbuild/dependencies/base.py
+++ b/mesonbuild/dependencies/base.py
@@ -18,6 +18,7 @@ import copy
import functools
import os
import re
+import itertools
import json
import shlex
import shutil
@@ -136,11 +137,21 @@ class Dependency:
return converted
return self.compile_args
+ def get_all_compile_args(self) -> T.List[str]:
+ """Get the compile arguments from this dependency and it's sub dependencies."""
+ return list(itertools.chain(self.get_compile_args(),
+ *[d.get_all_compile_args() for d in self.ext_deps]))
+
def get_link_args(self, raw: bool = False) -> T.List[str]:
if raw and self.raw_link_args is not None:
return self.raw_link_args
return self.link_args
+ def get_all_link_args(self) -> T.List[str]:
+ """Get the link arguments from this dependency and it's sub dependencies."""
+ return list(itertools.chain(self.get_link_args(),
+ *[d.get_all_link_args() for d in self.ext_deps]))
+
def found(self) -> bool:
return self.is_found
diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py
index 97a8a7f..1059871 100644
--- a/mesonbuild/dependencies/qt.py
+++ b/mesonbuild/dependencies/qt.py
@@ -17,25 +17,22 @@
"""Dependency finders for the Qt framework."""
import abc
-import collections
import re
import os
import typing as T
from . import (
- ExtraFrameworkDependency, ExternalDependency, DependencyException, DependencyMethods,
- PkgConfigDependency
+ ExtraFrameworkDependency, DependencyException, DependencyMethods,
+ PkgConfigDependency,
)
-from .base import ConfigToolDependency
+from .base import ConfigToolDependency, DependencyFactory
from .. import mlog
from .. import mesonlib
-from ..programs import find_external_program
if T.TYPE_CHECKING:
from ..compilers import Compiler
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..programs import ExternalProgram
def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> T.List[str]:
@@ -114,70 +111,78 @@ class QtExtraFrameworkDependency(ExtraFrameworkDependency):
return []
-class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta):
+class _QtBase:
- version: T.Optional[str]
+ """Mixin class for shared componenets between PkgConfig and Qmake."""
- def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
- super().__init__(name, env, kwargs, language='cpp')
+ link_args: T.List[str]
+ clib_compiler: 'Compiler'
+ env: 'Environment'
+
+ def __init__(self, name: str, kwargs: T.Dict[str, T.Any]):
self.qtname = name.capitalize()
self.qtver = name[-1]
if self.qtver == "4":
self.qtpkgname = 'Qt'
else:
self.qtpkgname = self.qtname
- self.root = '/usr'
- self.bindir: T.Optional[str] = None
+
self.private_headers = T.cast(bool, kwargs.get('private_headers', False))
- mods = mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules'))
- self.requested_modules = mods
- if not mods:
+
+ self.requested_modules = mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules'))
+ if not self.requested_modules:
raise DependencyException('No ' + self.qtname + ' modules specified.')
- self.from_text = 'pkg-config'
self.qtmain = T.cast(bool, kwargs.get('main', False))
if not isinstance(self.qtmain, bool):
raise DependencyException('"main" argument must be a boolean')
- # Keep track of the detection methods used, for logging purposes.
- methods: T.List[str] = []
- # Prefer pkg-config, then fallback to `qmake -query`
- if DependencyMethods.PKGCONFIG in self.methods:
- mlog.debug('Trying to find qt with pkg-config')
- self._pkgconfig_detect(mods, kwargs)
- methods.append('pkgconfig')
- if not self.is_found and DependencyMethods.QMAKE in self.methods:
- mlog.debug('Trying to find qt with qmake')
- self.from_text = self._qmake_detect(mods, kwargs)
- methods.append('qmake-' + self.name)
- methods.append('qmake')
- if not self.is_found:
- # Reset compile args and link args
+ def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool:
+ libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary
+ base_name = 'qtmaind' if is_debug else 'qtmain'
+ qtmain = self.clib_compiler.find_library(base_name, self.env, libdir)
+ if qtmain:
+ self.link_args.append(qtmain[0])
+ return True
+ return False
+
+ def get_exe_args(self, compiler: 'Compiler') -> T.List[str]:
+ # Originally this was -fPIE but nowadays the default
+ # for upstream and distros seems to be -reduce-relocations
+ # which requires -fPIC. This may cause a performance
+ # penalty when using self-built Qt or on platforms
+ # where -fPIC is not required. If this is an issue
+ # for you, patches are welcome.
+ return compiler.get_pic_args()
+
+ def log_details(self) -> str:
+ return f'modules: {", ".join(sorted(self.requested_modules))}'
+
+
+class QtPkgConfigDependency(_QtBase, PkgConfigDependency, metaclass=abc.ABCMeta):
+
+ """Specialization of the PkgConfigDependency for Qt."""
+
+ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
+ _QtBase.__init__(self, name, kwargs)
+
+ # Always use QtCore as the "main" dependency, since it has the extra
+ # pkg-config variables that a user would expect to get. If "Core" is
+ # not a requested module, delete the compile and link arguments to
+ # avoid linking with something they didn't ask for
+ PkgConfigDependency.__init__(self, self.qtpkgname + 'Core', env, kwargs)
+ if 'Core' not in self.requested_modules:
self.compile_args = []
self.link_args = []
- self.from_text = mlog.format_list(methods)
- self.version = None
-
- @abc.abstractmethod
- def get_pkgconfig_host_bins(self, core: PkgConfigDependency) -> T.Optional[str]:
- pass
- def _pkgconfig_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> None:
- # We set the value of required to False so that we can try the
- # qmake-based fallback if pkg-config fails.
- kwargs['required'] = False
- modules: T.MutableMapping[str, PkgConfigDependency] = collections.OrderedDict()
- for module in mods:
- modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env,
- kwargs, language=self.language)
- for m_name, m in modules.items():
- if not m.found():
+ for m in self.requested_modules:
+ mod = PkgConfigDependency(self.qtpkgname + m, self.env, kwargs, language=self.language)
+ if not mod.found():
self.is_found = False
return
- self.compile_args += m.get_compile_args()
if self.private_headers:
- qt_inc_dir = m.get_pkgconfig_variable('includedir', dict())
- mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m_name)
+ qt_inc_dir = mod.get_pkgconfig_variable('includedir', {})
+ mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m)
if not os.path.isdir(mod_private_dir):
# At least some versions of homebrew don't seem to set this
# up correctly. /usr/local/opt/qt/include/Qt + m_name is a
@@ -185,94 +190,93 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta):
# file points to /usr/local/Cellar/qt/x.y.z/Headers/, and
# the Qt + m_name there is not a symlink, it's a file
mod_private_dir = qt_inc_dir
- mod_private_inc = _qt_get_private_includes(mod_private_dir, m_name, m.version)
+ mod_private_inc = _qt_get_private_includes(mod_private_dir, m, mod.version)
for directory in mod_private_inc:
- self.compile_args.append('-I' + directory)
- self.link_args += m.get_link_args()
-
- if 'Core' in modules:
- core = modules['Core']
- else:
- corekwargs = {'required': 'false', 'silent': 'true'}
- core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs,
- language=self.language)
- modules['Core'] = core
+ mod.compile_args.append('-I' + directory)
+ self._add_sub_dependency([lambda: mod])
if self.env.machines[self.for_machine].is_windows() and self.qtmain:
# Check if we link with debug binaries
debug_lib_name = self.qtpkgname + 'Core' + _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], True)
is_debug = False
- for arg in core.get_link_args():
- if arg == '-l%s' % debug_lib_name or arg.endswith('%s.lib' % debug_lib_name) or arg.endswith('%s.a' % debug_lib_name):
+ for arg in self.get_link_args():
+ if arg == f'-l{debug_lib_name}' or arg.endswith(f'{debug_lib_name}.lib') or arg.endswith(f'{debug_lib_name}.a'):
is_debug = True
break
- libdir = core.get_pkgconfig_variable('libdir', {})
+ libdir = self.get_pkgconfig_variable('libdir', {})
if not self._link_with_qtmain(is_debug, libdir):
self.is_found = False
return
- self.is_found = True
- self.version = m.version
- self.pcdep = list(modules.values())
- # Try to detect moc, uic, rcc
- # Used by self.compilers_detect()
- self.bindir = self.get_pkgconfig_host_bins(core)
+ self.bindir = self.get_pkgconfig_host_bins(self)
if not self.bindir:
# If exec_prefix is not defined, the pkg-config file is broken
- prefix = core.get_pkgconfig_variable('exec_prefix', {})
+ prefix = self.get_pkgconfig_variable('exec_prefix', {})
if prefix:
self.bindir = os.path.join(prefix, 'bin')
- def search_qmake(self) -> T.Generator['ExternalProgram', None, None]:
- for qmake in ('qmake-' + self.name, 'qmake'):
- yield from find_external_program(self.env, self.for_machine, qmake, 'QMake', [qmake])
+ @staticmethod
+ @abc.abstractmethod
+ def get_pkgconfig_host_bins(core: PkgConfigDependency) -> T.Optional[str]:
+ pass
+
+ @abc.abstractmethod
+ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
+ pass
+
+ def log_info(self) -> str:
+ return 'pkg-config'
+
+
+class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta):
+
+ """Find Qt using Qmake as a config-tool."""
+
+ tool_name = 'qmake'
+ version_arg = '-v'
+
+ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
+ _QtBase.__init__(self, name, kwargs)
+ self.tools = [f'qmake-{self.qtname}', 'qmake']
+
+ # Add additional constraits that the Qt version is met, but preserve
+ # any version requrements the user has set as well. For exmaple, if Qt5
+ # is requested, add "">= 5, < 6", but if the user has ">= 5.6", don't
+ # lose that.
+ kwargs = kwargs.copy()
+ _vers = mesonlib.listify(kwargs.get('version', []))
+ _vers.extend([f'>= {self.qtver}', f'< {int(self.qtver) + 1}'])
+ kwargs['version'] = _vers
+
+ ConfigToolDependency.__init__(self, name, env, kwargs)
+ if not self.found():
+ return
- def _qmake_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> T.Optional[str]:
- for qmake in self.search_qmake():
- if not qmake.found():
- continue
- # Check that the qmake is for qt5
- pc, stdo = mesonlib.Popen_safe(qmake.get_command() + ['-v'])[0:2]
- if pc.returncode != 0:
- continue
- if not 'Qt version ' + self.qtver in stdo:
- mlog.log('QMake is not for ' + self.qtname)
- continue
- # Found qmake for Qt5!
- self.qmake = qmake
- break
- else:
- # Didn't find qmake :(
- self.is_found = False
- return None
- self.version = re.search(self.qtver + r'(\.\d+)+', stdo).group(0)
# Query library path, header path, and binary path
- mlog.log("Found qmake:", mlog.bold(self.qmake.get_path()), '(%s)' % self.version)
- stdo = mesonlib.Popen_safe(self.qmake.get_command() + ['-query'])[1]
- qvars = {}
- for line in stdo.split('\n'):
+ stdo = self.get_config_value(['-query'], 'args')
+ qvars: T.Dict[str, str] = {}
+ for line in stdo:
line = line.strip()
if line == '':
continue
- (k, v) = tuple(line.split(':', 1))
+ k, v = line.split(':', 1)
qvars[k] = v
# Qt on macOS uses a framework, but Qt for iOS/tvOS does not
xspec = qvars.get('QMAKE_XSPEC', '')
if self.env.machines.host.is_darwin() and not any(s in xspec for s in ['ios', 'tvos']):
mlog.debug("Building for macOS, looking for framework")
- self._framework_detect(qvars, mods, kwargs)
+ self._framework_detect(qvars, self.requested_modules, kwargs)
# Sometimes Qt is built not as a framework (for instance, when using conan pkg manager)
# skip and fall back to normal procedure then
if self.is_found:
- return self.qmake.name
+ return
else:
mlog.debug("Building for macOS, couldn't find framework, falling back to library search")
incdir = qvars['QT_INSTALL_HEADERS']
self.compile_args.append('-I' + incdir)
libdir = qvars['QT_INSTALL_LIBS']
- # Used by self.compilers_detect()
+ # Used by qt.compilers_detect()
self.bindir = get_qmake_host_bins(qvars)
- self.is_found = True
# Use the buildtype by default, but look at the b_vscrt option if the
# compiler supports it.
@@ -282,7 +286,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta):
is_debug = True
modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug)
- for module in mods:
+ for module in self.requested_modules:
mincdir = os.path.join(incdir, 'Qt' + module)
self.compile_args.append('-I' + mincdir)
@@ -292,7 +296,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta):
define_base = 'TESTLIB'
else:
define_base = module.upper()
- self.compile_args.append('-DQT_%s_LIB' % define_base)
+ self.compile_args.append(f'-DQT_{define_base}_LIB')
if self.private_headers:
priv_inc = self.get_private_includes(mincdir, module)
@@ -315,16 +319,15 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta):
if not self._link_with_qtmain(is_debug, libdir):
self.is_found = False
- return self.qmake.name
+ def _sanitize_version(self, version: str) -> str:
+ m = re.search(rf'({self.qtver}(\.\d+)+)', version)
+ if m:
+ return m.group(0).rstrip('.')
+ return version
- def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool:
- libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary
- base_name = 'qtmaind' if is_debug else 'qtmain'
- qtmain = self.clib_compiler.find_library(base_name, self.env, libdir)
- if qtmain:
- self.link_args.append(qtmain[0])
- return True
- return False
+ @abc.abstractmethod
+ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
+ pass
def _framework_detect(self, qvars: T.Dict[str, str], modules: T.List[str], kwargs: T.Dict[str, T.Any]) -> None:
libdir = qvars['QT_INSTALL_LIBS']
@@ -344,43 +347,36 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta):
qt_version=self.version)
self.link_args += fwdep.get_link_args()
else:
+ self.is_found = False
break
else:
self.is_found = True
# Used by self.compilers_detect()
self.bindir = get_qmake_host_bins(qvars)
- @staticmethod
- def get_methods() -> T.List[DependencyMethods]:
- return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE]
+ def log_info(self) -> str:
+ return 'qmake'
- @staticmethod
- def get_exe_args(compiler: 'Compiler') -> T.List[str]:
- # Originally this was -fPIE but nowadays the default
- # for upstream and distros seems to be -reduce-relocations
- # which requires -fPIC. This may cause a performance
- # penalty when using self-built Qt or on platforms
- # where -fPIC is not required. If this is an issue
- # for you, patches are welcome.
- return compiler.get_pic_args()
+
+class Qt4ConfigToolDependency(QmakeQtDependency):
def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
return []
- def log_details(self) -> str:
- module_str = ', '.join(self.requested_modules)
- return 'modules: ' + module_str
- def log_info(self) -> str:
- return f'{self.from_text}'
+class Qt5ConfigToolDependency(QmakeQtDependency):
+
+ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
+ return _qt_get_private_includes(mod_inc_dir, module, self.version)
+
+
+class Qt6ConfigToolDependency(QmakeQtDependency):
- def log_tried(self) -> str:
- return self.from_text
+ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
+ return _qt_get_private_includes(mod_inc_dir, module, self.version)
-class Qt4Dependency(QtBaseDependency):
- def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]):
- QtBaseDependency.__init__(self, 'qt4', env, kwargs)
+class Qt4PkgConfigDependency(QtPkgConfigDependency):
@staticmethod
def get_pkgconfig_host_bins(core: PkgConfigDependency) -> T.Optional[str]:
@@ -396,10 +392,11 @@ class Qt4Dependency(QtBaseDependency):
pass
return None
+ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
+ return []
+
-class Qt5Dependency(QtBaseDependency):
- def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]):
- QtBaseDependency.__init__(self, 'qt5', env, kwargs)
+class Qt5PkgConfigDependency(QtPkgConfigDependency):
@staticmethod
def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str:
@@ -409,9 +406,7 @@ class Qt5Dependency(QtBaseDependency):
return _qt_get_private_includes(mod_inc_dir, module, self.version)
-class Qt6Dependency(QtBaseDependency):
- def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]):
- QtBaseDependency.__init__(self, 'qt6', env, kwargs)
+class Qt6PkgConfigDependency(QtPkgConfigDependency):
@staticmethod
def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str:
@@ -419,3 +414,25 @@ class Qt6Dependency(QtBaseDependency):
def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
return _qt_get_private_includes(mod_inc_dir, module, self.version)
+
+
+qt4_factory = DependencyFactory(
+ 'qt4',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
+ pkgconfig_class=Qt4PkgConfigDependency,
+ configtool_class=Qt4ConfigToolDependency,
+)
+
+qt5_factory = DependencyFactory(
+ 'qt5',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
+ pkgconfig_class=Qt5PkgConfigDependency,
+ configtool_class=Qt5ConfigToolDependency,
+)
+
+qt6_factory = DependencyFactory(
+ 'qt6',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
+ pkgconfig_class=Qt6PkgConfigDependency,
+ configtool_class=Qt6ConfigToolDependency,
+)