aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/dependencies/ui.py
diff options
context:
space:
mode:
authorElliott Sales de Andrade <quantum.analyst@gmail.com>2017-04-15 16:22:29 -0400
committerElliott Sales de Andrade <quantum.analyst@gmail.com>2017-05-09 18:13:09 -0400
commit23f9b42d3b9fd2248c405e64467bb54f85b07ec5 (patch)
tree729f7fb95e16bcf5d177d601fe04020d69e54b0b /mesonbuild/dependencies/ui.py
parent92557e1c2a97be6fb0b5c4aa7a12a43b5d43462c (diff)
downloadmeson-23f9b42d3b9fd2248c405e64467bb54f85b07ec5.zip
meson-23f9b42d3b9fd2248c405e64467bb54f85b07ec5.tar.gz
meson-23f9b42d3b9fd2248c405e64467bb54f85b07ec5.tar.bz2
Split UI-related into a separate file.
Some of these are a bit bigger than just UI libraries, but this division seems close enough.
Diffstat (limited to 'mesonbuild/dependencies/ui.py')
-rw-r--r--mesonbuild/dependencies/ui.py560
1 files changed, 560 insertions, 0 deletions
diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py
new file mode 100644
index 0000000..3174176
--- /dev/null
+++ b/mesonbuild/dependencies/ui.py
@@ -0,0 +1,560 @@
+# Copyright 2013-2017 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file contains the detection logic for external dependencies that
+# are UI-related.
+
+import os
+import re
+import shutil
+import subprocess
+from collections import OrderedDict
+
+from .. import mlog
+from .. import mesonlib
+from ..mesonlib import MesonException, Popen_safe, version_compare
+from ..environment import for_windows
+
+from .base import (Dependency, DependencyException, DependencyMethods,
+ ExternalProgram, ExtraFrameworkDependency, PkgConfigDependency)
+
+
+class GLDependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self, 'gl', kwargs)
+ self.is_found = False
+ self.cargs = []
+ self.linkargs = []
+ if DependencyMethods.PKGCONFIG in self.methods:
+ try:
+ pcdep = PkgConfigDependency('gl', environment, kwargs)
+ if pcdep.found():
+ self.type_name = 'pkgconfig'
+ self.is_found = True
+ self.cargs = pcdep.get_compile_args()
+ self.linkargs = pcdep.get_link_args()
+ self.version = pcdep.get_version()
+ return
+ except Exception:
+ pass
+ if DependencyMethods.SYSTEM in self.methods:
+ if mesonlib.is_osx():
+ self.is_found = True
+ self.linkargs = ['-framework', 'OpenGL']
+ self.version = '1' # FIXME
+ return
+ if mesonlib.is_windows():
+ self.is_found = True
+ self.linkargs = ['-lopengl32']
+ self.version = '1' # FIXME: unfixable?
+ return
+
+ def get_link_args(self):
+ return self.linkargs
+
+ def get_version(self):
+ return self.version
+
+ def get_methods(self):
+ if mesonlib.is_osx() or mesonlib.is_windows():
+ return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
+ else:
+ return [DependencyMethods.PKGCONFIG]
+
+
+class GnuStepDependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self, 'gnustep', kwargs)
+ self.required = kwargs.get('required', True)
+ self.modules = kwargs.get('modules', [])
+ self.detect()
+
+ def detect(self):
+ self.confprog = 'gnustep-config'
+ try:
+ gp = Popen_safe([self.confprog, '--help'])[0]
+ except (FileNotFoundError, PermissionError):
+ self.args = None
+ mlog.log('Dependency GnuStep found:', mlog.red('NO'), '(no gnustep-config)')
+ return
+ if gp.returncode != 0:
+ self.args = None
+ mlog.log('Dependency GnuStep found:', mlog.red('NO'))
+ return
+ if 'gui' in self.modules:
+ arg = '--gui-libs'
+ else:
+ arg = '--base-libs'
+ fp, flagtxt, flagerr = Popen_safe([self.confprog, '--objc-flags'])
+ if fp.returncode != 0:
+ raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr))
+ args = flagtxt.split()
+ self.args = self.filter_arsg(args)
+ fp, libtxt, liberr = Popen_safe([self.confprog, arg])
+ if fp.returncode != 0:
+ raise DependencyException('Error getting objc-lib args: %s %s' % (libtxt, liberr))
+ self.libs = self.weird_filter(libtxt.split())
+ self.version = self.detect_version()
+ mlog.log('Dependency', mlog.bold('GnuStep'), 'found:',
+ mlog.green('YES'), self.version)
+
+ def weird_filter(self, elems):
+ """When building packages, the output of the enclosing Make
+is sometimes mixed among the subprocess output. I have no idea
+why. As a hack filter out everything that is not a flag."""
+ return [e for e in elems if e.startswith('-')]
+
+ def filter_arsg(self, args):
+ """gnustep-config returns a bunch of garbage args such
+ as -O2 and so on. Drop everything that is not needed."""
+ result = []
+ for f in args:
+ if f.startswith('-D') \
+ or f.startswith('-f') \
+ or f.startswith('-I') \
+ or f == '-pthread' \
+ or (f.startswith('-W') and not f == '-Wall'):
+ result.append(f)
+ return result
+
+ def detect_version(self):
+ gmake = self.get_variable('GNUMAKE')
+ makefile_dir = self.get_variable('GNUSTEP_MAKEFILES')
+ # This Makefile has the GNUStep version set
+ base_make = os.path.join(makefile_dir, 'Additional', 'base.make')
+ # Print the Makefile variable passed as the argument. For instance, if
+ # you run the make target `print-SOME_VARIABLE`, this will print the
+ # value of the variable `SOME_VARIABLE`.
+ printver = "print-%:\n\t@echo '$($*)'"
+ env = os.environ.copy()
+ # See base.make to understand why this is set
+ env['FOUNDATION_LIB'] = 'gnu'
+ p, o, e = Popen_safe([gmake, '-f', '-', '-f', base_make,
+ 'print-GNUSTEP_BASE_VERSION'],
+ env=env, write=printver, stdin=subprocess.PIPE)
+ version = o.strip()
+ if not version:
+ mlog.debug("Couldn't detect GNUStep version, falling back to '1'")
+ # Fallback to setting some 1.x version
+ version = '1'
+ return version
+
+ def get_variable(self, var):
+ p, o, e = Popen_safe([self.confprog, '--variable=' + var])
+ if p.returncode != 0 and self.required:
+ raise DependencyException('{!r} for variable {!r} failed to run'
+ ''.format(self.confprog, var))
+ return o.strip()
+
+ def found(self):
+ return self.args is not None
+
+ def get_version(self):
+ return self.version
+
+ def get_compile_args(self):
+ if self.args is None:
+ return []
+ return self.args
+
+ def get_link_args(self):
+ return self.libs
+
+
+class QtBaseDependency(Dependency):
+ def __init__(self, name, env, kwargs):
+ Dependency.__init__(self, name, kwargs)
+ self.name = name
+ 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 = None
+ self.silent = kwargs.get('silent', False)
+ # We store the value of required here instead of passing it on to
+ # PkgConfigDependency etc because we want to try the qmake-based
+ # fallback as well.
+ self.required = kwargs.pop('required', True)
+ kwargs['required'] = False
+ mods = kwargs.get('modules', [])
+ self.cargs = []
+ self.largs = []
+ self.is_found = False
+ if isinstance(mods, str):
+ mods = [mods]
+ if not mods:
+ raise DependencyException('No ' + self.qtname + ' modules specified.')
+ type_text = 'cross' if env.is_cross_build() else 'native'
+ found_msg = '{} {} {{}} dependency (modules: {}) found:' \
+ ''.format(self.qtname, type_text, ', '.join(mods))
+ from_text = 'pkg-config'
+
+ # Keep track of the detection methods used, for logging purposes.
+ methods = []
+ # Prefer pkg-config, then fallback to `qmake -query`
+ if DependencyMethods.PKGCONFIG in self.methods:
+ self._pkgconfig_detect(mods, env, kwargs)
+ methods.append('pkgconfig')
+ if not self.is_found and DependencyMethods.QMAKE in self.methods:
+ from_text = self._qmake_detect(mods, env, kwargs)
+ methods.append('qmake-' + self.name)
+ methods.append('qmake')
+ if not self.is_found:
+ # Reset compile args and link args
+ self.cargs = []
+ self.largs = []
+ from_text = '(checked {})'.format(mlog.format_list(methods))
+ self.version = 'none'
+ if self.required:
+ err_msg = '{} {} dependency not found {}' \
+ ''.format(self.qtname, type_text, from_text)
+ raise DependencyException(err_msg)
+ if not self.silent:
+ mlog.log(found_msg.format(from_text), mlog.red('NO'))
+ return
+ from_text = '`{}`'.format(from_text)
+ if not self.silent:
+ mlog.log(found_msg.format(from_text), mlog.green('YES'))
+
+ def compilers_detect(self):
+ "Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH"
+ if self.bindir:
+ moc = ExternalProgram(os.path.join(self.bindir, 'moc'), silent=True)
+ uic = ExternalProgram(os.path.join(self.bindir, 'uic'), silent=True)
+ rcc = ExternalProgram(os.path.join(self.bindir, 'rcc'), silent=True)
+ else:
+ # We don't accept unsuffixed 'moc', 'uic', and 'rcc' because they
+ # are sometimes older, or newer versions.
+ moc = ExternalProgram('moc-' + self.name, silent=True)
+ uic = ExternalProgram('uic-' + self.name, silent=True)
+ rcc = ExternalProgram('rcc-' + self.name, silent=True)
+ return moc, uic, rcc
+
+ def _pkgconfig_detect(self, mods, env, kwargs):
+ modules = OrderedDict()
+ for module in mods:
+ modules[module] = PkgConfigDependency(self.qtpkgname + module, env, kwargs)
+ self.is_found = True
+ for m in modules.values():
+ if not m.found():
+ self.is_found = False
+ return
+ self.cargs += m.get_compile_args()
+ self.largs += m.get_link_args()
+ self.version = m.modversion
+ # Try to detect moc, uic, rcc
+ if 'Core' in modules:
+ core = modules['Core']
+ else:
+ corekwargs = {'required': 'false', 'silent': 'true'}
+ core = PkgConfigDependency(self.qtpkgname + 'Core', env, corekwargs)
+ # Used by self.compilers_detect()
+ self.bindir = self.get_pkgconfig_host_bins(core)
+ if not self.bindir:
+ # If exec_prefix is not defined, the pkg-config file is broken
+ prefix = core.get_pkgconfig_variable('exec_prefix')
+ if prefix:
+ self.bindir = os.path.join(prefix, 'bin')
+
+ def _find_qmake(self, qmake, env):
+ # Even when cross-compiling, if we don't get a cross-info qmake, we
+ # fallback to using the qmake in PATH because that's what we used to do
+ if env.is_cross_build():
+ qmake = env.cross_info.config['binaries'].get('qmake', qmake)
+ return ExternalProgram(qmake, silent=True)
+
+ def _qmake_detect(self, mods, env, kwargs):
+ for qmake in ('qmake-' + self.name, 'qmake'):
+ self.qmake = self._find_qmake(qmake, env)
+ if not self.qmake.found():
+ continue
+ # Check that the qmake is for qt5
+ pc, stdo = Popen_safe(self.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!
+ break
+ else:
+ # Didn't find qmake :(
+ return
+ self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0)
+ # Query library path, header path, and binary path
+ mlog.log("Found qmake:", mlog.bold(self.qmake.get_name()), '(%s)' % self.version)
+ stdo = Popen_safe(self.qmake.get_command() + ['-query'])[1]
+ qvars = {}
+ for line in stdo.split('\n'):
+ line = line.strip()
+ if line == '':
+ continue
+ (k, v) = tuple(line.split(':', 1))
+ qvars[k] = v
+ if mesonlib.is_osx():
+ return self._framework_detect(qvars, mods, kwargs)
+ incdir = qvars['QT_INSTALL_HEADERS']
+ self.cargs.append('-I' + incdir)
+ libdir = qvars['QT_INSTALL_LIBS']
+ # Used by self.compilers_detect()
+ self.bindir = self.get_qmake_host_bins(qvars)
+ self.is_found = True
+ for module in mods:
+ mincdir = os.path.join(incdir, 'Qt' + module)
+ self.cargs.append('-I' + mincdir)
+ if for_windows(env.is_cross_build(), env):
+ libfile = os.path.join(libdir, self.qtpkgname + module + '.lib')
+ if not os.path.isfile(libfile):
+ # MinGW can link directly to .dll
+ libfile = os.path.join(self.bindir, self.qtpkgname + module + '.dll')
+ if not os.path.isfile(libfile):
+ self.is_found = False
+ break
+ else:
+ libfile = os.path.join(libdir, 'lib{}{}.so'.format(self.qtpkgname, module))
+ if not os.path.isfile(libfile):
+ self.is_found = False
+ break
+ self.largs.append(libfile)
+ return qmake
+
+ def _framework_detect(self, qvars, modules, kwargs):
+ libdir = qvars['QT_INSTALL_LIBS']
+ for m in modules:
+ fname = 'Qt' + m
+ fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir, kwargs)
+ self.cargs.append('-F' + libdir)
+ if fwdep.found():
+ self.is_found = True
+ self.cargs += fwdep.get_compile_args()
+ self.largs += fwdep.get_link_args()
+ # Used by self.compilers_detect()
+ self.bindir = self.get_qmake_host_bins(qvars)
+
+ def get_qmake_host_bins(self, qvars):
+ # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling)
+ # but fall back to QT_INSTALL_BINS (qt4)
+ if 'QT_HOST_BINS' in qvars:
+ return qvars['QT_HOST_BINS']
+ else:
+ return qvars['QT_INSTALL_BINS']
+
+ def get_version(self):
+ return self.version
+
+ def get_compile_args(self):
+ return self.cargs
+
+ def get_sources(self):
+ return []
+
+ def get_link_args(self):
+ return self.largs
+
+ def get_methods(self):
+ return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE]
+
+ def found(self):
+ return self.is_found
+
+ def get_exe_args(self, compiler):
+ # 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 Qt4Dependency(QtBaseDependency):
+ def __init__(self, env, kwargs):
+ QtBaseDependency.__init__(self, 'qt4', env, kwargs)
+
+ def get_pkgconfig_host_bins(self, core):
+ # Only return one bins dir, because the tools are generally all in one
+ # directory for Qt4, in Qt5, they must all be in one directory. Return
+ # the first one found among the bin variables, in case one tool is not
+ # configured to be built.
+ applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease']
+ for application in applications:
+ try:
+ return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application))
+ except MesonException:
+ pass
+
+
+class Qt5Dependency(QtBaseDependency):
+ def __init__(self, env, kwargs):
+ QtBaseDependency.__init__(self, 'qt5', env, kwargs)
+
+ def get_pkgconfig_host_bins(self, core):
+ return core.get_pkgconfig_variable('host_bins')
+
+
+# There are three different ways of depending on SDL2:
+# sdl2-config, pkg-config and OSX framework
+class SDL2Dependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self, 'sdl2', kwargs)
+ self.is_found = False
+ self.cargs = []
+ self.linkargs = []
+ if DependencyMethods.PKGCONFIG in self.methods:
+ try:
+ pcdep = PkgConfigDependency('sdl2', environment, kwargs)
+ if pcdep.found():
+ self.type_name = 'pkgconfig'
+ self.is_found = True
+ self.cargs = pcdep.get_compile_args()
+ self.linkargs = pcdep.get_link_args()
+ self.version = pcdep.get_version()
+ return
+ except Exception as e:
+ mlog.debug('SDL 2 not found via pkgconfig. Trying next, error was:', str(e))
+ pass
+ if DependencyMethods.SDLCONFIG in self.methods:
+ sdlconf = shutil.which('sdl2-config')
+ if sdlconf:
+ stdo = Popen_safe(['sdl2-config', '--cflags'])[1]
+ self.cargs = stdo.strip().split()
+ stdo = Popen_safe(['sdl2-config', '--libs'])[1]
+ self.linkargs = stdo.strip().split()
+ stdo = Popen_safe(['sdl2-config', '--version'])[1]
+ self.version = stdo.strip()
+ self.is_found = True
+ mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'),
+ self.version, '(%s)' % sdlconf)
+ return
+ mlog.debug('Could not find sdl2-config binary, trying next.')
+ if DependencyMethods.EXTRAFRAMEWORK in self.methods:
+ if mesonlib.is_osx():
+ fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True), None, kwargs)
+ if fwdep.found():
+ self.is_found = True
+ self.cargs = fwdep.get_compile_args()
+ self.linkargs = fwdep.get_link_args()
+ self.version = '2' # FIXME
+ return
+ mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO'))
+
+ def get_compile_args(self):
+ return self.cargs
+
+ def get_link_args(self):
+ return self.linkargs
+
+ def found(self):
+ return self.is_found
+
+ def get_version(self):
+ return self.version
+
+ def get_methods(self):
+ if mesonlib.is_osx():
+ return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG, DependencyMethods.EXTRAFRAMEWORK]
+ else:
+ return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG]
+
+
+class WxDependency(Dependency):
+ wx_found = None
+
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self, 'wx', kwargs)
+ self.is_found = False
+ # FIXME: use version instead of modversion
+ self.modversion = 'none'
+ if WxDependency.wx_found is None:
+ self.check_wxconfig()
+ if not WxDependency.wx_found:
+ # FIXME: this message could be printed after Dependncy found
+ mlog.log("Neither wx-config-3.0 nor wx-config found; can't detect dependency")
+ return
+
+ # FIXME: This should print stdout and stderr using mlog.debug
+ p, out = Popen_safe([self.wxc, '--version'])[0:2]
+ if p.returncode != 0:
+ mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
+ self.cargs = []
+ self.libs = []
+ else:
+ self.modversion = out.strip()
+ version_req = kwargs.get('version', None)
+ if version_req is not None:
+ if not version_compare(self.modversion, version_req, strict=True):
+ mlog.log('Wxwidgets version %s does not fullfill requirement %s' %
+ (self.modversion, version_req))
+ return
+ mlog.log('Dependency wxwidgets found:', mlog.green('YES'))
+ self.is_found = True
+ self.requested_modules = self.get_requested(kwargs)
+ # wx-config seems to have a cflags as well but since it requires C++,
+ # this should be good, at least for now.
+ p, out = Popen_safe([self.wxc, '--cxxflags'])[0:2]
+ # FIXME: this error should only be raised if required is true
+ if p.returncode != 0:
+ raise DependencyException('Could not generate cargs for wxwidgets.')
+ self.cargs = out.split()
+
+ # FIXME: this error should only be raised if required is true
+ p, out = Popen_safe([self.wxc, '--libs'] + self.requested_modules)[0:2]
+ if p.returncode != 0:
+ raise DependencyException('Could not generate libs for wxwidgets.')
+ self.libs = out.split()
+
+ def get_requested(self, kwargs):
+ modules = 'modules'
+ if modules not in kwargs:
+ return []
+ candidates = kwargs[modules]
+ if isinstance(candidates, str):
+ return [candidates]
+ for c in candidates:
+ if not isinstance(c, str):
+ raise DependencyException('wxwidgets module argument is not a string.')
+ return candidates
+
+ def get_modversion(self):
+ return self.modversion
+
+ def get_version(self):
+ return self.modversion
+
+ def get_compile_args(self):
+ return self.cargs
+
+ def get_link_args(self):
+ return self.libs
+
+ def check_wxconfig(self):
+ for wxc in ['wx-config-3.0', 'wx-config']:
+ try:
+ p, out = Popen_safe([wxc, '--version'])[0:2]
+ if p.returncode == 0:
+ mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)),
+ '(%s)' % out.strip())
+ self.wxc = wxc
+ WxDependency.wx_found = True
+ return
+ except (FileNotFoundError, PermissionError):
+ pass
+ WxDependency.wxconfig_found = False
+ mlog.log('Found wx-config:', mlog.red('NO'))
+
+ def found(self):
+ return self.is_found