# SPDX-License-Identifier: Apache-2.0 # Copyright 2013-2017 The Meson development team # This file contains the detection logic for external dependencies that # are UI-related. from __future__ import annotations import os import re import subprocess import typing as T from .. import mlog from .. import mesonlib from ..compilers.compilers import CrossNoRunException from ..mesonlib import ( Popen_safe, extract_as_list, version_compare_many ) from ..environment import detect_cpu_family from .base import DependencyException, DependencyMethods, DependencyTypeName, SystemDependency from .configtool import ConfigToolDependency from .detect import packages from .factory import DependencyFactory if T.TYPE_CHECKING: from ..environment import Environment class GLDependencySystem(SystemDependency): def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: super().__init__(name, environment, kwargs) if self.env.machines[self.for_machine].is_darwin(): self.is_found = True # FIXME: Use AppleFrameworks dependency self.link_args = ['-framework', 'OpenGL'] # FIXME: Detect version using self.clib_compiler return elif self.env.machines[self.for_machine].is_windows(): self.is_found = True # FIXME: Use self.clib_compiler.find_library() self.link_args = ['-lopengl32'] # FIXME: Detect version using self.clib_compiler return else: links = self.clib_compiler.find_library('GL', environment, []) has_header = self.clib_compiler.has_header('GL/gl.h', '', environment)[0] if links and has_header: self.is_found = True self.link_args = links elif links: raise DependencyException('Found GL runtime library but no development header files') class GnuStepDependency(ConfigToolDependency): tools = ['gnustep-config'] tool_name = 'gnustep-config' def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: super().__init__('gnustep', environment, kwargs, language='objc') if not self.is_found: return self.modules = kwargs.get('modules', []) self.compile_args = self.filter_args( self.get_config_value(['--objc-flags'], 'compile_args')) self.link_args = self.weird_filter(self.get_config_value( ['--gui-libs' if 'gui' in self.modules else '--base-libs'], 'link_args')) def find_config(self, versions: T.Optional[T.List[str]] = None, returncode: int = 0) -> T.Tuple[T.Optional[T.List[str]], T.Optional[str]]: tool = [self.tools[0]] try: p, out = Popen_safe(tool + ['--help'])[:2] except (FileNotFoundError, PermissionError): return (None, None) if p.returncode != returncode: return (None, None) self.config = tool found_version = self.detect_version() if versions and not version_compare_many(found_version, versions)[0]: return (None, found_version) return (tool, found_version) @staticmethod def weird_filter(elems: T.List[str]) -> T.List[str]: """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('-')] @staticmethod def filter_args(args: T.List[str]) -> T.List[str]: """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) -> str: gmake = self.get_config_value(['--variable=GNUMAKE'], 'variable')[0] makefile_dir = self.get_config_value(['--variable=GNUSTEP_MAKEFILES'], 'variable')[0] # 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 packages['gnustep'] = GnuStepDependency class SDL2DependencyConfigTool(ConfigToolDependency): tools = ['sdl2-config'] tool_name = 'sdl2-config' def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--libs'], 'link_args') class WxDependency(ConfigToolDependency): tools = ['wx-config-3.0', 'wx-config-3.1', 'wx-config', 'wx-config-gtk3'] tool_name = 'wx-config' def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__('WxWidgets', environment, kwargs, language='cpp') if not self.is_found: return self.requested_modules = self.get_requested(kwargs) extra_args = [] if self.static: extra_args.append('--static=yes') # Check to make sure static is going to work err = Popen_safe(self.config + extra_args)[2] if 'No config found to match' in err: mlog.debug('WxWidgets is missing static libraries.') self.is_found = False return # wx-config seems to have a cflags as well but since it requires C++, # this should be good, at least for now. self.compile_args = self.get_config_value(['--cxxflags'] + extra_args + self.requested_modules, 'compile_args') self.link_args = self.get_config_value(['--libs'] + extra_args + self.requested_modules, 'link_args') @staticmethod def get_requested(kwargs: T.Dict[str, T.Any]) -> T.List[str]: if 'modules' not in kwargs: return [] candidates = extract_as_list(kwargs, 'modules') for c in candidates: if not isinstance(c, str): raise DependencyException('wxwidgets module argument is not a string') return candidates packages['wxwidgets'] = WxDependency class VulkanDependencySystem(SystemDependency): def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: super().__init__(name, environment, kwargs, language=language) try: self.vulkan_sdk = os.environ['VULKAN_SDK'] if not os.path.isabs(self.vulkan_sdk): raise DependencyException('VULKAN_SDK must be an absolute path.') except KeyError: self.vulkan_sdk = None if self.vulkan_sdk: # TODO: this config might not work on some platforms, fix bugs as reported # we should at least detect other 64-bit platforms (e.g. armv8) lib_name = 'vulkan' lib_dir = 'lib' inc_dir = 'include' if mesonlib.is_windows(): lib_name = 'vulkan-1' lib_dir = 'Lib32' inc_dir = 'Include' if detect_cpu_family(self.env.coredata.compilers.host) == 'x86_64': lib_dir = 'Lib' # make sure header and lib are valid inc_path = os.path.join(self.vulkan_sdk, inc_dir) header = os.path.join(inc_path, 'vulkan', 'vulkan.h') lib_path = os.path.join(self.vulkan_sdk, lib_dir) find_lib = self.clib_compiler.find_library(lib_name, environment, [lib_path]) if not find_lib: raise DependencyException('VULKAN_SDK point to invalid directory (no lib)') if not os.path.isfile(header): raise DependencyException('VULKAN_SDK point to invalid directory (no include)') # XXX: this is very odd, and may deserve being removed self.type_name = DependencyTypeName('vulkan_sdk') self.is_found = True self.compile_args.append('-I' + inc_path) self.link_args.append('-L' + lib_path) self.link_args.append('-l' + lib_name) else: # simply try to guess it, usually works on linux libs = self.clib_compiler.find_library('vulkan', environment, []) if libs is not None and self.clib_compiler.has_header('vulkan/vulkan.h', '', environment, disable_cache=True)[0]: self.is_found = True for lib in libs: self.link_args.append(lib) if self.is_found: get_version = '''\ #include #include int main() { printf("%i.%i.%i", VK_VERSION_MAJOR(VK_HEADER_VERSION_COMPLETE), VK_VERSION_MINOR(VK_HEADER_VERSION_COMPLETE), VK_VERSION_PATCH(VK_HEADER_VERSION_COMPLETE)); return 0; } ''' try: run = self.clib_compiler.run(get_version, environment, extra_args=self.compile_args) except CrossNoRunException: run = None if run and run.compiled and run.returncode == 0: self.version = run.stdout elif self.vulkan_sdk: # fall back to heuristics: detect version number in path # matches the default install path on Windows match = re.search(rf'VulkanSDK{re.escape(os.path.sep)}([0-9]+(?:\.[0-9]+)+)', self.vulkan_sdk) if match: self.version = match.group(1) else: mlog.warning(f'Environment variable VULKAN_SDK={self.vulkan_sdk} is present, but Vulkan version could not be extracted.') packages['gl'] = gl_factory = DependencyFactory( 'gl', [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], system_class=GLDependencySystem, ) packages['sdl2'] = sdl2_factory = DependencyFactory( 'sdl2', [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE], configtool_class=SDL2DependencyConfigTool, cmake_name='SDL2', ) packages['vulkan'] = vulkan_factory = DependencyFactory( 'vulkan', [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], system_class=VulkanDependencySystem, )