1# Copyright 2013-2017 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# This file contains the detection logic for external dependencies that 16# are UI-related. 17import os 18import subprocess 19import typing as T 20 21from .. import mlog 22from .. import mesonlib 23from ..mesonlib import ( 24 Popen_safe, extract_as_list, version_compare_many 25) 26from ..environment import detect_cpu_family 27 28from .base import DependencyException, DependencyMethods, DependencyTypeName, SystemDependency 29from .configtool import ConfigToolDependency 30from .factory import DependencyFactory 31 32if T.TYPE_CHECKING: 33 from ..environment import Environment 34 35 36class GLDependencySystem(SystemDependency): 37 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: 38 super().__init__(name, environment, kwargs) 39 40 if self.env.machines[self.for_machine].is_darwin(): 41 self.is_found = True 42 # FIXME: Use AppleFrameworks dependency 43 self.link_args = ['-framework', 'OpenGL'] 44 # FIXME: Detect version using self.clib_compiler 45 return 46 if self.env.machines[self.for_machine].is_windows(): 47 self.is_found = True 48 # FIXME: Use self.clib_compiler.find_library() 49 self.link_args = ['-lopengl32'] 50 # FIXME: Detect version using self.clib_compiler 51 return 52 53 @staticmethod 54 def get_methods() -> T.List[DependencyMethods]: 55 if mesonlib.is_osx() or mesonlib.is_windows(): 56 return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] 57 else: 58 return [DependencyMethods.PKGCONFIG] 59 60 def log_tried(self) -> str: 61 return 'system' 62 63class GnuStepDependency(ConfigToolDependency): 64 65 tools = ['gnustep-config'] 66 tool_name = 'gnustep-config' 67 68 def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: 69 super().__init__('gnustep', environment, kwargs, language='objc') 70 if not self.is_found: 71 return 72 self.modules = kwargs.get('modules', []) 73 self.compile_args = self.filter_args( 74 self.get_config_value(['--objc-flags'], 'compile_args')) 75 self.link_args = self.weird_filter(self.get_config_value( 76 ['--gui-libs' if 'gui' in self.modules else '--base-libs'], 77 'link_args')) 78 79 def find_config(self, versions: T.Optional[T.List[str]] = None, returncode: int = 0) -> T.Tuple[T.Optional[T.List[str]], T.Optional[str]]: 80 tool = [self.tools[0]] 81 try: 82 p, out = Popen_safe(tool + ['--help'])[:2] 83 except (FileNotFoundError, PermissionError): 84 return (None, None) 85 if p.returncode != returncode: 86 return (None, None) 87 self.config = tool 88 found_version = self.detect_version() 89 if versions and not version_compare_many(found_version, versions)[0]: 90 return (None, found_version) 91 92 return (tool, found_version) 93 94 @staticmethod 95 def weird_filter(elems: T.List[str]) -> T.List[str]: 96 """When building packages, the output of the enclosing Make is 97 sometimes mixed among the subprocess output. I have no idea why. As a 98 hack filter out everything that is not a flag. 99 """ 100 return [e for e in elems if e.startswith('-')] 101 102 @staticmethod 103 def filter_args(args: T.List[str]) -> T.List[str]: 104 """gnustep-config returns a bunch of garbage args such as -O2 and so 105 on. Drop everything that is not needed. 106 """ 107 result = [] 108 for f in args: 109 if f.startswith('-D') \ 110 or f.startswith('-f') \ 111 or f.startswith('-I') \ 112 or f == '-pthread' \ 113 or (f.startswith('-W') and not f == '-Wall'): 114 result.append(f) 115 return result 116 117 def detect_version(self) -> str: 118 gmake = self.get_config_value(['--variable=GNUMAKE'], 'variable')[0] 119 makefile_dir = self.get_config_value(['--variable=GNUSTEP_MAKEFILES'], 'variable')[0] 120 # This Makefile has the GNUStep version set 121 base_make = os.path.join(makefile_dir, 'Additional', 'base.make') 122 # Print the Makefile variable passed as the argument. For instance, if 123 # you run the make target `print-SOME_VARIABLE`, this will print the 124 # value of the variable `SOME_VARIABLE`. 125 printver = "print-%:\n\t@echo '$($*)'" 126 env = os.environ.copy() 127 # See base.make to understand why this is set 128 env['FOUNDATION_LIB'] = 'gnu' 129 p, o, e = Popen_safe([gmake, '-f', '-', '-f', base_make, 130 'print-GNUSTEP_BASE_VERSION'], 131 env=env, write=printver, stdin=subprocess.PIPE) 132 version = o.strip() 133 if not version: 134 mlog.debug("Couldn't detect GNUStep version, falling back to '1'") 135 # Fallback to setting some 1.x version 136 version = '1' 137 return version 138 139 140class SDL2DependencyConfigTool(ConfigToolDependency): 141 142 tools = ['sdl2-config'] 143 tool_name = 'sdl2-config' 144 145 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 146 super().__init__(name, environment, kwargs) 147 if not self.is_found: 148 return 149 self.compile_args = self.get_config_value(['--cflags'], 'compile_args') 150 self.link_args = self.get_config_value(['--libs'], 'link_args') 151 152 @staticmethod 153 def get_methods() -> T.List[DependencyMethods]: 154 if mesonlib.is_osx(): 155 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK] 156 else: 157 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL] 158 159 160class WxDependency(ConfigToolDependency): 161 162 tools = ['wx-config-3.0', 'wx-config', 'wx-config-gtk3'] 163 tool_name = 'wx-config' 164 165 def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 166 super().__init__('WxWidgets', environment, kwargs, language='cpp') 167 if not self.is_found: 168 return 169 self.requested_modules = self.get_requested(kwargs) 170 171 extra_args = [] 172 if self.static: 173 extra_args.append('--static=yes') 174 175 # Check to make sure static is going to work 176 err = Popen_safe(self.config + extra_args)[2] 177 if 'No config found to match' in err: 178 mlog.debug('WxWidgets is missing static libraries.') 179 self.is_found = False 180 return 181 182 # wx-config seems to have a cflags as well but since it requires C++, 183 # this should be good, at least for now. 184 self.compile_args = self.get_config_value(['--cxxflags'] + extra_args + self.requested_modules, 'compile_args') 185 self.link_args = self.get_config_value(['--libs'] + extra_args + self.requested_modules, 'link_args') 186 187 @staticmethod 188 def get_requested(kwargs: T.Dict[str, T.Any]) -> T.List[str]: 189 if 'modules' not in kwargs: 190 return [] 191 candidates = extract_as_list(kwargs, 'modules') 192 for c in candidates: 193 if not isinstance(c, str): 194 raise DependencyException('wxwidgets module argument is not a string') 195 return candidates 196 197 198class VulkanDependencySystem(SystemDependency): 199 200 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: 201 super().__init__(name, environment, kwargs, language=language) 202 203 try: 204 self.vulkan_sdk = os.environ['VULKAN_SDK'] 205 if not os.path.isabs(self.vulkan_sdk): 206 raise DependencyException('VULKAN_SDK must be an absolute path.') 207 except KeyError: 208 self.vulkan_sdk = None 209 210 if self.vulkan_sdk: 211 # TODO: this config might not work on some platforms, fix bugs as reported 212 # we should at least detect other 64-bit platforms (e.g. armv8) 213 lib_name = 'vulkan' 214 lib_dir = 'lib' 215 inc_dir = 'include' 216 if mesonlib.is_windows(): 217 lib_name = 'vulkan-1' 218 lib_dir = 'Lib32' 219 inc_dir = 'Include' 220 if detect_cpu_family(self.env.coredata.compilers.host) == 'x86_64': 221 lib_dir = 'Lib' 222 223 # make sure header and lib are valid 224 inc_path = os.path.join(self.vulkan_sdk, inc_dir) 225 header = os.path.join(inc_path, 'vulkan', 'vulkan.h') 226 lib_path = os.path.join(self.vulkan_sdk, lib_dir) 227 find_lib = self.clib_compiler.find_library(lib_name, environment, [lib_path]) 228 229 if not find_lib: 230 raise DependencyException('VULKAN_SDK point to invalid directory (no lib)') 231 232 if not os.path.isfile(header): 233 raise DependencyException('VULKAN_SDK point to invalid directory (no include)') 234 235 # XXX: this is very odd, and may deserve being removed 236 self.type_name = DependencyTypeName('vulkan_sdk') 237 self.is_found = True 238 self.compile_args.append('-I' + inc_path) 239 self.link_args.append('-L' + lib_path) 240 self.link_args.append('-l' + lib_name) 241 242 # TODO: find a way to retrieve the version from the sdk? 243 # Usually it is a part of the path to it (but does not have to be) 244 return 245 else: 246 # simply try to guess it, usually works on linux 247 libs = self.clib_compiler.find_library('vulkan', environment, []) 248 if libs is not None and self.clib_compiler.has_header('vulkan/vulkan.h', '', environment, disable_cache=True)[0]: 249 self.is_found = True 250 for lib in libs: 251 self.link_args.append(lib) 252 return 253 254 @staticmethod 255 def get_methods() -> T.List[DependencyMethods]: 256 return [DependencyMethods.SYSTEM] 257 258 def log_tried(self) -> str: 259 return 'system' 260 261gl_factory = DependencyFactory( 262 'gl', 263 [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], 264 system_class=GLDependencySystem, 265) 266 267sdl2_factory = DependencyFactory( 268 'sdl2', 269 [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK], 270 configtool_class=SDL2DependencyConfigTool, 271) 272 273vulkan_factory = DependencyFactory( 274 'vulkan', 275 [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], 276 system_class=VulkanDependencySystem, 277) 278