1# Copyright 2013-2019 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 useful for 16# development purposes, such as testing, debugging, etc.. 17 18import glob 19import os 20import re 21import typing as T 22 23from .. import mesonlib, mlog 24from ..mesonlib import version_compare, stringlistify, extract_as_list, MachineChoice 25from ..environment import get_llvm_tool_names 26from .base import ( 27 DependencyException, DependencyMethods, ExternalDependency, PkgConfigDependency, 28 strip_system_libdirs, ConfigToolDependency, CMakeDependency, DependencyFactory, 29) 30from .misc import threads_factory 31from ..compilers.c import AppleClangCCompiler 32from ..compilers.cpp import AppleClangCPPCompiler 33 34if T.TYPE_CHECKING: 35 from .. environment import Environment 36 37 38def get_shared_library_suffix(environment, for_machine: MachineChoice): 39 """This is only guaranteed to work for languages that compile to machine 40 code, not for languages like C# that use a bytecode and always end in .dll 41 """ 42 m = environment.machines[for_machine] 43 if m.is_windows(): 44 return '.dll' 45 elif m.is_darwin(): 46 return '.dylib' 47 return '.so' 48 49 50class GTestDependencySystem(ExternalDependency): 51 def __init__(self, name: str, environment, kwargs): 52 super().__init__(name, environment, kwargs, language='cpp') 53 self.main = kwargs.get('main', False) 54 self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src'] 55 if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})): 56 self.is_found = False 57 return 58 self.detect() 59 60 def detect(self): 61 gtest_detect = self.clib_compiler.find_library("gtest", self.env, []) 62 gtest_main_detect = self.clib_compiler.find_library("gtest_main", self.env, []) 63 if gtest_detect and (not self.main or gtest_main_detect): 64 self.is_found = True 65 self.compile_args = [] 66 self.link_args = gtest_detect 67 if self.main: 68 self.link_args += gtest_main_detect 69 self.sources = [] 70 self.prebuilt = True 71 elif self.detect_srcdir(): 72 self.is_found = True 73 self.compile_args = ['-I' + d for d in self.src_include_dirs] 74 self.link_args = [] 75 if self.main: 76 self.sources = [self.all_src, self.main_src] 77 else: 78 self.sources = [self.all_src] 79 self.prebuilt = False 80 else: 81 self.is_found = False 82 83 def detect_srcdir(self): 84 for s in self.src_dirs: 85 if os.path.exists(s): 86 self.src_dir = s 87 self.all_src = mesonlib.File.from_absolute_file( 88 os.path.join(self.src_dir, 'gtest-all.cc')) 89 self.main_src = mesonlib.File.from_absolute_file( 90 os.path.join(self.src_dir, 'gtest_main.cc')) 91 self.src_include_dirs = [os.path.normpath(os.path.join(self.src_dir, '..')), 92 os.path.normpath(os.path.join(self.src_dir, '../include')), 93 ] 94 return True 95 return False 96 97 def log_info(self): 98 if self.prebuilt: 99 return 'prebuilt' 100 else: 101 return 'building self' 102 103 def log_tried(self): 104 return 'system' 105 106 @staticmethod 107 def get_methods(): 108 return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] 109 110 111class GTestDependencyPC(PkgConfigDependency): 112 113 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 114 assert name == 'gtest' 115 if kwargs.get('main'): 116 name = 'gtest_main' 117 super().__init__(name, environment, kwargs) 118 119 120class GMockDependencySystem(ExternalDependency): 121 def __init__(self, name: str, environment, kwargs): 122 super().__init__(name, environment, kwargs, language='cpp') 123 self.main = kwargs.get('main', False) 124 if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})): 125 self.is_found = False 126 return 127 128 # If we are getting main() from GMock, we definitely 129 # want to avoid linking in main() from GTest 130 gtest_kwargs = kwargs.copy() 131 if self.main: 132 gtest_kwargs['main'] = False 133 134 # GMock without GTest is pretty much useless 135 # this also mimics the structure given in WrapDB, 136 # where GMock always pulls in GTest 137 found = self._add_sub_dependency(gtest_factory(environment, self.for_machine, gtest_kwargs)) 138 if not found: 139 self.is_found = False 140 return 141 142 # GMock may be a library or just source. 143 # Work with both. 144 gmock_detect = self.clib_compiler.find_library("gmock", self.env, []) 145 gmock_main_detect = self.clib_compiler.find_library("gmock_main", self.env, []) 146 if gmock_detect and (not self.main or gmock_main_detect): 147 self.is_found = True 148 self.link_args += gmock_detect 149 if self.main: 150 self.link_args += gmock_main_detect 151 self.prebuilt = True 152 return 153 154 for d in ['/usr/src/googletest/googlemock/src', '/usr/src/gmock/src', '/usr/src/gmock']: 155 if os.path.exists(d): 156 self.is_found = True 157 # Yes, we need both because there are multiple 158 # versions of gmock that do different things. 159 d2 = os.path.normpath(os.path.join(d, '..')) 160 self.compile_args += ['-I' + d, '-I' + d2, '-I' + os.path.join(d2, 'include')] 161 all_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock-all.cc')) 162 main_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock_main.cc')) 163 if self.main: 164 self.sources += [all_src, main_src] 165 else: 166 self.sources += [all_src] 167 self.prebuilt = False 168 return 169 170 self.is_found = False 171 172 def log_info(self): 173 if self.prebuilt: 174 return 'prebuilt' 175 else: 176 return 'building self' 177 178 def log_tried(self): 179 return 'system' 180 181 @staticmethod 182 def get_methods(): 183 return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] 184 185 186class GMockDependencyPC(PkgConfigDependency): 187 188 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 189 assert name == 'gmock' 190 if kwargs.get('main'): 191 name = 'gmock_main' 192 super().__init__(name, environment, kwargs) 193 194 195class LLVMDependencyConfigTool(ConfigToolDependency): 196 """ 197 LLVM uses a special tool, llvm-config, which has arguments for getting 198 c args, cxx args, and ldargs as well as version. 199 """ 200 tool_name = 'llvm-config' 201 __cpp_blacklist = {'-DNDEBUG'} 202 203 def __init__(self, name: str, environment, kwargs): 204 self.tools = get_llvm_tool_names('llvm-config') 205 206 # Fedora starting with Fedora 30 adds a suffix of the number 207 # of bits in the isa that llvm targets, for example, on x86_64 208 # and aarch64 the name will be llvm-config-64, on x86 and arm 209 # it will be llvm-config-32. 210 if environment.machines[self.get_for_machine_from_kwargs(kwargs)].is_64_bit: 211 self.tools.append('llvm-config-64') 212 else: 213 self.tools.append('llvm-config-32') 214 215 # It's necessary for LLVM <= 3.8 to use the C++ linker. For 3.9 and 4.0 216 # the C linker works fine if only using the C API. 217 super().__init__(name, environment, kwargs, language='cpp') 218 self.provided_modules = [] 219 self.required_modules = set() 220 self.module_details = [] 221 if not self.is_found: 222 return 223 224 self.provided_modules = self.get_config_value(['--components'], 'modules') 225 modules = stringlistify(extract_as_list(kwargs, 'modules')) 226 self.check_components(modules) 227 opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules')) 228 self.check_components(opt_modules, required=False) 229 230 cargs = set(self.get_config_value(['--cppflags'], 'compile_args')) 231 self.compile_args = list(cargs.difference(self.__cpp_blacklist)) 232 233 if version_compare(self.version, '>= 3.9'): 234 self._set_new_link_args(environment) 235 else: 236 self._set_old_link_args() 237 self.link_args = strip_system_libdirs(environment, self.for_machine, self.link_args) 238 self.link_args = self.__fix_bogus_link_args(self.link_args) 239 if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})): 240 self.is_found = False 241 return 242 243 def __fix_bogus_link_args(self, args): 244 """This function attempts to fix bogus link arguments that llvm-config 245 generates. 246 247 Currently it works around the following: 248 - FreeBSD: when statically linking -l/usr/lib/libexecinfo.so will 249 be generated, strip the -l in cases like this. 250 - Windows: We may get -LIBPATH:... which is later interpreted as 251 "-L IBPATH:...", if we're using an msvc like compilers convert 252 that to "/LIBPATH", otherwise to "-L ..." 253 """ 254 cpp = self.env.coredata.compilers[self.for_machine]['cpp'] 255 256 new_args = [] 257 for arg in args: 258 if arg.startswith('-l') and arg.endswith('.so'): 259 new_args.append(arg.lstrip('-l')) 260 elif arg.startswith('-LIBPATH:'): 261 new_args.extend(cpp.get_linker_search_args(arg.lstrip('-LIBPATH:'))) 262 else: 263 new_args.append(arg) 264 return new_args 265 266 def __check_libfiles(self, shared): 267 """Use llvm-config's --libfiles to check if libraries exist.""" 268 mode = '--link-shared' if shared else '--link-static' 269 270 # Set self.required to true to force an exception in get_config_value 271 # if the returncode != 0 272 restore = self.required 273 self.required = True 274 275 try: 276 # It doesn't matter what the stage is, the caller needs to catch 277 # the exception anyway. 278 self.link_args = self.get_config_value(['--libfiles', mode], '') 279 finally: 280 self.required = restore 281 282 def _set_new_link_args(self, environment): 283 """How to set linker args for LLVM versions >= 3.9""" 284 mode = self.get_config_value(['--shared-mode'], 'link_args')[0] 285 if not self.static and mode == 'static': 286 # If llvm is configured with LLVM_BUILD_LLVM_DYLIB but not with 287 # LLVM_LINK_LLVM_DYLIB and not LLVM_BUILD_SHARED_LIBS (which 288 # upstream doesn't recommend using), then llvm-config will lie to 289 # you about how to do shared-linking. It wants to link to a a bunch 290 # of individual shared libs (which don't exist because llvm wasn't 291 # built with LLVM_BUILD_SHARED_LIBS. 292 # 293 # Therefore, we'll try to get the libfiles, if the return code is 0 294 # or we get an empty list, then we'll try to build a working 295 # configuration by hand. 296 try: 297 self.__check_libfiles(True) 298 except DependencyException: 299 lib_ext = get_shared_library_suffix(environment, self.for_machine) 300 libdir = self.get_config_value(['--libdir'], 'link_args')[0] 301 # Sort for reproducibility 302 matches = sorted(glob.iglob(os.path.join(libdir, 'libLLVM*{}'.format(lib_ext)))) 303 if not matches: 304 if self.required: 305 raise 306 self.is_found = False 307 return 308 309 self.link_args = self.get_config_value(['--ldflags'], 'link_args') 310 libname = os.path.basename(matches[0]).rstrip(lib_ext).lstrip('lib') 311 self.link_args.append('-l{}'.format(libname)) 312 return 313 elif self.static and mode == 'shared': 314 # If, however LLVM_BUILD_SHARED_LIBS is true # (*cough* gentoo *cough*) 315 # then this is correct. Building with LLVM_BUILD_SHARED_LIBS has a side 316 # effect, it stops the generation of static archives. Therefore we need 317 # to check for that and error out on static if this is the case 318 try: 319 self.__check_libfiles(False) 320 except DependencyException: 321 if self.required: 322 raise 323 self.is_found = False 324 return 325 326 link_args = ['--link-static', '--system-libs'] if self.static else ['--link-shared'] 327 self.link_args = self.get_config_value( 328 ['--libs', '--ldflags'] + link_args + list(self.required_modules), 329 'link_args') 330 331 def _set_old_link_args(self): 332 """Setting linker args for older versions of llvm. 333 334 Old versions of LLVM bring an extra level of insanity with them. 335 llvm-config will provide the correct arguments for static linking, but 336 not for shared-linnking, we have to figure those out ourselves, because 337 of course we do. 338 """ 339 if self.static: 340 self.link_args = self.get_config_value( 341 ['--libs', '--ldflags', '--system-libs'] + list(self.required_modules), 342 'link_args') 343 else: 344 # llvm-config will provide arguments for static linking, so we get 345 # to figure out for ourselves what to link with. We'll do that by 346 # checking in the directory provided by --libdir for a library 347 # called libLLVM-<ver>.(so|dylib|dll) 348 libdir = self.get_config_value(['--libdir'], 'link_args')[0] 349 350 expected_name = 'libLLVM-{}'.format(self.version) 351 re_name = re.compile(r'{}.(so|dll|dylib)$'.format(expected_name)) 352 353 for file_ in os.listdir(libdir): 354 if re_name.match(file_): 355 self.link_args = ['-L{}'.format(libdir), 356 '-l{}'.format(os.path.splitext(file_.lstrip('lib'))[0])] 357 break 358 else: 359 raise DependencyException( 360 'Could not find a dynamically linkable library for LLVM.') 361 362 def check_components(self, modules, required=True): 363 """Check for llvm components (modules in meson terms). 364 365 The required option is whether the module is required, not whether LLVM 366 is required. 367 """ 368 for mod in sorted(set(modules)): 369 status = '' 370 371 if mod not in self.provided_modules: 372 if required: 373 self.is_found = False 374 if self.required: 375 raise DependencyException( 376 'Could not find required LLVM Component: {}'.format(mod)) 377 status = '(missing)' 378 else: 379 status = '(missing but optional)' 380 else: 381 self.required_modules.add(mod) 382 383 self.module_details.append(mod + status) 384 385 def log_details(self): 386 if self.module_details: 387 return 'modules: ' + ', '.join(self.module_details) 388 return '' 389 390class LLVMDependencyCMake(CMakeDependency): 391 def __init__(self, name: str, env, kwargs): 392 self.llvm_modules = stringlistify(extract_as_list(kwargs, 'modules')) 393 self.llvm_opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules')) 394 super().__init__(name, env, kwargs, language='cpp') 395 396 # Cmake will always create a statically linked binary, so don't use 397 # cmake if dynamic is required 398 if not self.static: 399 self.is_found = False 400 mlog.warning('Ignoring LLVM CMake dependency because dynamic was requested') 401 return 402 403 if self.traceparser is None: 404 return 405 406 # Extract extra include directories and definitions 407 inc_dirs = self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS') 408 defs = self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS') 409 # LLVM explicitly uses space-separated variables rather than semicolon lists 410 if len(defs) == 1: 411 defs = defs[0].split(' ') 412 temp = ['-I' + x for x in inc_dirs] + defs 413 self.compile_args += [x for x in temp if x not in self.compile_args] 414 if not self._add_sub_dependency(threads_factory(env, self.for_machine, {})): 415 self.is_found = False 416 return 417 418 def _main_cmake_file(self) -> str: 419 # Use a custom CMakeLists.txt for LLVM 420 return 'CMakeListsLLVM.txt' 421 422 def _extra_cmake_opts(self) -> T.List[str]: 423 return ['-DLLVM_MESON_MODULES={}'.format(';'.join(self.llvm_modules + self.llvm_opt_modules))] 424 425 def _map_module_list(self, modules: T.List[T.Tuple[str, bool]], components: T.List[T.Tuple[str, bool]]) -> T.List[T.Tuple[str, bool]]: 426 res = [] 427 for mod, required in modules: 428 cm_targets = self.traceparser.get_cmake_var('MESON_LLVM_TARGETS_{}'.format(mod)) 429 if not cm_targets: 430 if required: 431 raise self._gen_exception('LLVM module {} was not found'.format(mod)) 432 else: 433 mlog.warning('Optional LLVM module', mlog.bold(mod), 'was not found') 434 continue 435 for i in cm_targets: 436 res += [(i, required)] 437 return res 438 439 def _original_module_name(self, module: str) -> str: 440 orig_name = self.traceparser.get_cmake_var('MESON_TARGET_TO_LLVM_{}'.format(module)) 441 if orig_name: 442 return orig_name[0] 443 return module 444 445 446class ValgrindDependency(PkgConfigDependency): 447 ''' 448 Consumers of Valgrind usually only need the compile args and do not want to 449 link to its (static) libraries. 450 ''' 451 def __init__(self, env, kwargs): 452 super().__init__('valgrind', env, kwargs) 453 454 def get_link_args(self, **kwargs): 455 return [] 456 457 458class ZlibSystemDependency(ExternalDependency): 459 460 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 461 super().__init__(name, environment, kwargs) 462 463 m = self.env.machines[self.for_machine] 464 465 # I'm not sure this is entirely correct. What if we're cross compiling 466 # from something to macOS? 467 if ((m.is_darwin() and isinstance(self.clib_compiler, (AppleClangCCompiler, AppleClangCPPCompiler))) or 468 m.is_freebsd() or m.is_dragonflybsd()): 469 self.is_found = True 470 self.link_args = ['-lz'] 471 472 # No need to set includes, 473 # on macos xcode/clang will do that for us. 474 # on freebsd zlib.h is in /usr/include 475 elif m.is_windows(): 476 if self.clib_compiler.get_argument_syntax() == 'msvc': 477 libs = ['zlib1' 'zlib'] 478 else: 479 libs = ['z'] 480 for lib in libs: 481 l = self.clib_compiler.find_library(lib, environment, []) 482 h = self.clib_compiler.has_header('zlib.h', '', environment, dependencies=[self]) 483 if l and h: 484 self.is_found = True 485 self.link_args = l 486 break 487 else: 488 return 489 else: 490 mlog.debug('Unsupported OS {}'.format(m.system)) 491 return 492 493 v, _ = self.clib_compiler.get_define('ZLIB_VERSION', '#include <zlib.h>', self.env, [], [self]) 494 self.version = v.strip('"') 495 496 497 @staticmethod 498 def get_methods(): 499 return [DependencyMethods.SYSTEM] 500 501 502llvm_factory = DependencyFactory( 503 'LLVM', 504 [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL], 505 cmake_class=LLVMDependencyCMake, 506 configtool_class=LLVMDependencyConfigTool, 507) 508 509gtest_factory = DependencyFactory( 510 'gtest', 511 [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], 512 pkgconfig_class=GTestDependencyPC, 513 system_class=GTestDependencySystem, 514) 515 516gmock_factory = DependencyFactory( 517 'gmock', 518 [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], 519 pkgconfig_class=GMockDependencyPC, 520 system_class=GMockDependencySystem, 521) 522 523zlib_factory = DependencyFactory( 524 'zlib', 525 [DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE, DependencyMethods.SYSTEM], 526 cmake_name='ZLIB', 527 system_class=ZlibSystemDependency, 528) 529