aboutsummaryrefslogtreecommitdiffstats
path: root/meson/mesonbuild/dependencies/dev.py
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /meson/mesonbuild/dependencies/dev.py
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'meson/mesonbuild/dependencies/dev.py')
-rw-r--r--meson/mesonbuild/dependencies/dev.py595
1 files changed, 595 insertions, 0 deletions
diff --git a/meson/mesonbuild/dependencies/dev.py b/meson/mesonbuild/dependencies/dev.py
new file mode 100644
index 000000000..7300e2fe7
--- /dev/null
+++ b/meson/mesonbuild/dependencies/dev.py
@@ -0,0 +1,595 @@
+# Copyright 2013-2019 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 useful for
+# development purposes, such as testing, debugging, etc..
+
+import glob
+import os
+import re
+import pathlib
+import shutil
+import typing as T
+
+from .. import mesonlib, mlog
+from ..compilers import AppleClangCCompiler, AppleClangCPPCompiler, detect_compiler_for
+from ..environment import get_llvm_tool_names
+from ..mesonlib import version_compare, stringlistify, extract_as_list, MachineChoice
+from .base import DependencyException, DependencyMethods, strip_system_libdirs, SystemDependency
+from .cmake import CMakeDependency
+from .configtool import ConfigToolDependency
+from .factory import DependencyFactory
+from .misc import threads_factory
+from .pkgconfig import PkgConfigDependency
+
+if T.TYPE_CHECKING:
+ from ..envconfig import MachineInfo
+ from .. environment import Environment
+
+
+def get_shared_library_suffix(environment: 'Environment', for_machine: MachineChoice) -> str:
+ """This is only guaranteed to work for languages that compile to machine
+ code, not for languages like C# that use a bytecode and always end in .dll
+ """
+ m = environment.machines[for_machine]
+ if m.is_windows():
+ return '.dll'
+ elif m.is_darwin():
+ return '.dylib'
+ return '.so'
+
+
+class GTestDependencySystem(SystemDependency):
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
+ super().__init__(name, environment, kwargs, language='cpp')
+ self.main = kwargs.get('main', False)
+ self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src']
+ if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})):
+ self.is_found = False
+ return
+ self.detect()
+
+ def detect(self) -> None:
+ gtest_detect = self.clib_compiler.find_library("gtest", self.env, [])
+ gtest_main_detect = self.clib_compiler.find_library("gtest_main", self.env, [])
+ if gtest_detect and (not self.main or gtest_main_detect):
+ self.is_found = True
+ self.compile_args = []
+ self.link_args = gtest_detect
+ if self.main:
+ self.link_args += gtest_main_detect
+ self.sources = []
+ self.prebuilt = True
+ elif self.detect_srcdir():
+ self.is_found = True
+ self.compile_args = ['-I' + d for d in self.src_include_dirs]
+ self.link_args = []
+ if self.main:
+ self.sources = [self.all_src, self.main_src]
+ else:
+ self.sources = [self.all_src]
+ self.prebuilt = False
+ else:
+ self.is_found = False
+
+ def detect_srcdir(self) -> bool:
+ for s in self.src_dirs:
+ if os.path.exists(s):
+ self.src_dir = s
+ self.all_src = mesonlib.File.from_absolute_file(
+ os.path.join(self.src_dir, 'gtest-all.cc'))
+ self.main_src = mesonlib.File.from_absolute_file(
+ os.path.join(self.src_dir, 'gtest_main.cc'))
+ self.src_include_dirs = [os.path.normpath(os.path.join(self.src_dir, '..')),
+ os.path.normpath(os.path.join(self.src_dir, '../include')),
+ ]
+ return True
+ return False
+
+ def log_info(self) -> str:
+ if self.prebuilt:
+ return 'prebuilt'
+ else:
+ return 'building self'
+
+ def log_tried(self) -> str:
+ return 'system'
+
+ @staticmethod
+ def get_methods() -> T.List[DependencyMethods]:
+ return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
+
+
+class GTestDependencyPC(PkgConfigDependency):
+
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
+ assert name == 'gtest'
+ if kwargs.get('main'):
+ name = 'gtest_main'
+ super().__init__(name, environment, kwargs)
+
+
+class GMockDependencySystem(SystemDependency):
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
+ super().__init__(name, environment, kwargs, language='cpp')
+ self.main = kwargs.get('main', False)
+ if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})):
+ self.is_found = False
+ return
+
+ # If we are getting main() from GMock, we definitely
+ # want to avoid linking in main() from GTest
+ gtest_kwargs = kwargs.copy()
+ if self.main:
+ gtest_kwargs['main'] = False
+
+ # GMock without GTest is pretty much useless
+ # this also mimics the structure given in WrapDB,
+ # where GMock always pulls in GTest
+ found = self._add_sub_dependency(gtest_factory(environment, self.for_machine, gtest_kwargs))
+ if not found:
+ self.is_found = False
+ return
+
+ # GMock may be a library or just source.
+ # Work with both.
+ gmock_detect = self.clib_compiler.find_library("gmock", self.env, [])
+ gmock_main_detect = self.clib_compiler.find_library("gmock_main", self.env, [])
+ if gmock_detect and (not self.main or gmock_main_detect):
+ self.is_found = True
+ self.link_args += gmock_detect
+ if self.main:
+ self.link_args += gmock_main_detect
+ self.prebuilt = True
+ return
+
+ for d in ['/usr/src/googletest/googlemock/src', '/usr/src/gmock/src', '/usr/src/gmock']:
+ if os.path.exists(d):
+ self.is_found = True
+ # Yes, we need both because there are multiple
+ # versions of gmock that do different things.
+ d2 = os.path.normpath(os.path.join(d, '..'))
+ self.compile_args += ['-I' + d, '-I' + d2, '-I' + os.path.join(d2, 'include')]
+ all_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock-all.cc'))
+ main_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock_main.cc'))
+ if self.main:
+ self.sources += [all_src, main_src]
+ else:
+ self.sources += [all_src]
+ self.prebuilt = False
+ return
+
+ self.is_found = False
+
+ def log_info(self) -> str:
+ if self.prebuilt:
+ return 'prebuilt'
+ else:
+ return 'building self'
+
+ def log_tried(self) -> str:
+ return 'system'
+
+ @staticmethod
+ def get_methods() -> T.List[DependencyMethods]:
+ return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
+
+
+class GMockDependencyPC(PkgConfigDependency):
+
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
+ assert name == 'gmock'
+ if kwargs.get('main'):
+ name = 'gmock_main'
+ super().__init__(name, environment, kwargs)
+
+
+class LLVMDependencyConfigTool(ConfigToolDependency):
+ """
+ LLVM uses a special tool, llvm-config, which has arguments for getting
+ c args, cxx args, and ldargs as well as version.
+ """
+ tool_name = 'llvm-config'
+ __cpp_blacklist = {'-DNDEBUG'}
+
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
+ self.tools = get_llvm_tool_names('llvm-config')
+
+ # Fedora starting with Fedora 30 adds a suffix of the number
+ # of bits in the isa that llvm targets, for example, on x86_64
+ # and aarch64 the name will be llvm-config-64, on x86 and arm
+ # it will be llvm-config-32.
+ if environment.machines[self.get_for_machine_from_kwargs(kwargs)].is_64_bit:
+ self.tools.append('llvm-config-64')
+ else:
+ self.tools.append('llvm-config-32')
+
+ # It's necessary for LLVM <= 3.8 to use the C++ linker. For 3.9 and 4.0
+ # the C linker works fine if only using the C API.
+ super().__init__(name, environment, kwargs, language='cpp')
+ self.provided_modules: T.List[str] = []
+ self.required_modules: mesonlib.OrderedSet[str] = mesonlib.OrderedSet()
+ self.module_details: T.List[str] = []
+ if not self.is_found:
+ return
+
+ self.provided_modules = self.get_config_value(['--components'], 'modules')
+ modules = stringlistify(extract_as_list(kwargs, 'modules'))
+ self.check_components(modules)
+ opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules'))
+ self.check_components(opt_modules, required=False)
+
+ cargs = mesonlib.OrderedSet(self.get_config_value(['--cppflags'], 'compile_args'))
+ self.compile_args = list(cargs.difference(self.__cpp_blacklist))
+
+ if version_compare(self.version, '>= 3.9'):
+ self._set_new_link_args(environment)
+ else:
+ self._set_old_link_args()
+ self.link_args = strip_system_libdirs(environment, self.for_machine, self.link_args)
+ self.link_args = self.__fix_bogus_link_args(self.link_args)
+ if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})):
+ self.is_found = False
+ return
+
+ def __fix_bogus_link_args(self, args: T.List[str]) -> T.List[str]:
+ """This function attempts to fix bogus link arguments that llvm-config
+ generates.
+
+ Currently it works around the following:
+ - FreeBSD: when statically linking -l/usr/lib/libexecinfo.so will
+ be generated, strip the -l in cases like this.
+ - Windows: We may get -LIBPATH:... which is later interpreted as
+ "-L IBPATH:...", if we're using an msvc like compilers convert
+ that to "/LIBPATH", otherwise to "-L ..."
+ """
+
+ new_args = []
+ for arg in args:
+ if arg.startswith('-l') and arg.endswith('.so'):
+ new_args.append(arg.lstrip('-l'))
+ elif arg.startswith('-LIBPATH:'):
+ cpp = self.env.coredata.compilers[self.for_machine]['cpp']
+ new_args.extend(cpp.get_linker_search_args(arg.lstrip('-LIBPATH:')))
+ else:
+ new_args.append(arg)
+ return new_args
+
+ def __check_libfiles(self, shared: bool) -> None:
+ """Use llvm-config's --libfiles to check if libraries exist."""
+ mode = '--link-shared' if shared else '--link-static'
+
+ # Set self.required to true to force an exception in get_config_value
+ # if the returncode != 0
+ restore = self.required
+ self.required = True
+
+ try:
+ # It doesn't matter what the stage is, the caller needs to catch
+ # the exception anyway.
+ self.link_args = self.get_config_value(['--libfiles', mode], '')
+ finally:
+ self.required = restore
+
+ def _set_new_link_args(self, environment: 'Environment') -> None:
+ """How to set linker args for LLVM versions >= 3.9"""
+ try:
+ mode = self.get_config_value(['--shared-mode'], 'link_args')[0]
+ except IndexError:
+ mlog.debug('llvm-config --shared-mode returned an error')
+ self.is_found = False
+ return
+
+ if not self.static and mode == 'static':
+ # If llvm is configured with LLVM_BUILD_LLVM_DYLIB but not with
+ # LLVM_LINK_LLVM_DYLIB and not LLVM_BUILD_SHARED_LIBS (which
+ # upstream doesn't recommend using), then llvm-config will lie to
+ # you about how to do shared-linking. It wants to link to a a bunch
+ # of individual shared libs (which don't exist because llvm wasn't
+ # built with LLVM_BUILD_SHARED_LIBS.
+ #
+ # Therefore, we'll try to get the libfiles, if the return code is 0
+ # or we get an empty list, then we'll try to build a working
+ # configuration by hand.
+ try:
+ self.__check_libfiles(True)
+ except DependencyException:
+ lib_ext = get_shared_library_suffix(environment, self.for_machine)
+ libdir = self.get_config_value(['--libdir'], 'link_args')[0]
+ # Sort for reproducibility
+ matches = sorted(glob.iglob(os.path.join(libdir, f'libLLVM*{lib_ext}')))
+ if not matches:
+ if self.required:
+ raise
+ self.is_found = False
+ return
+
+ self.link_args = self.get_config_value(['--ldflags'], 'link_args')
+ libname = os.path.basename(matches[0]).rstrip(lib_ext).lstrip('lib')
+ self.link_args.append(f'-l{libname}')
+ return
+ elif self.static and mode == 'shared':
+ # If, however LLVM_BUILD_SHARED_LIBS is true # (*cough* gentoo *cough*)
+ # then this is correct. Building with LLVM_BUILD_SHARED_LIBS has a side
+ # effect, it stops the generation of static archives. Therefore we need
+ # to check for that and error out on static if this is the case
+ try:
+ self.__check_libfiles(False)
+ except DependencyException:
+ if self.required:
+ raise
+ self.is_found = False
+ return
+
+ link_args = ['--link-static', '--system-libs'] if self.static else ['--link-shared']
+ self.link_args = self.get_config_value(
+ ['--libs', '--ldflags'] + link_args + list(self.required_modules),
+ 'link_args')
+
+ def _set_old_link_args(self) -> None:
+ """Setting linker args for older versions of llvm.
+
+ Old versions of LLVM bring an extra level of insanity with them.
+ llvm-config will provide the correct arguments for static linking, but
+ not for shared-linnking, we have to figure those out ourselves, because
+ of course we do.
+ """
+ if self.static:
+ self.link_args = self.get_config_value(
+ ['--libs', '--ldflags', '--system-libs'] + list(self.required_modules),
+ 'link_args')
+ else:
+ # llvm-config will provide arguments for static linking, so we get
+ # to figure out for ourselves what to link with. We'll do that by
+ # checking in the directory provided by --libdir for a library
+ # called libLLVM-<ver>.(so|dylib|dll)
+ libdir = self.get_config_value(['--libdir'], 'link_args')[0]
+
+ expected_name = f'libLLVM-{self.version}'
+ re_name = re.compile(fr'{expected_name}.(so|dll|dylib)$')
+
+ for file_ in os.listdir(libdir):
+ if re_name.match(file_):
+ self.link_args = [f'-L{libdir}',
+ '-l{}'.format(os.path.splitext(file_.lstrip('lib'))[0])]
+ break
+ else:
+ raise DependencyException(
+ 'Could not find a dynamically linkable library for LLVM.')
+
+ def check_components(self, modules: T.List[str], required: bool = True) -> None:
+ """Check for llvm components (modules in meson terms).
+
+ The required option is whether the module is required, not whether LLVM
+ is required.
+ """
+ for mod in sorted(set(modules)):
+ status = ''
+
+ if mod not in self.provided_modules:
+ if required:
+ self.is_found = False
+ if self.required:
+ raise DependencyException(
+ f'Could not find required LLVM Component: {mod}')
+ status = '(missing)'
+ else:
+ status = '(missing but optional)'
+ else:
+ self.required_modules.add(mod)
+
+ self.module_details.append(mod + status)
+
+ def log_details(self) -> str:
+ if self.module_details:
+ return 'modules: ' + ', '.join(self.module_details)
+ return ''
+
+class LLVMDependencyCMake(CMakeDependency):
+ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
+ self.llvm_modules = stringlistify(extract_as_list(kwargs, 'modules'))
+ self.llvm_opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules'))
+ super().__init__(name, env, kwargs, language='cpp')
+
+ # Cmake will always create a statically linked binary, so don't use
+ # cmake if dynamic is required
+ if not self.static:
+ self.is_found = False
+ mlog.warning('Ignoring LLVM CMake dependency because dynamic was requested')
+ return
+
+ if self.traceparser is None:
+ return
+
+ # Extract extra include directories and definitions
+ inc_dirs = self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS')
+ defs = self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS')
+ # LLVM explicitly uses space-separated variables rather than semicolon lists
+ if len(defs) == 1:
+ defs = defs[0].split(' ')
+ temp = ['-I' + x for x in inc_dirs] + defs
+ self.compile_args += [x for x in temp if x not in self.compile_args]
+ if not self._add_sub_dependency(threads_factory(env, self.for_machine, {})):
+ self.is_found = False
+ return
+
+ def _main_cmake_file(self) -> str:
+ # Use a custom CMakeLists.txt for LLVM
+ return 'CMakeListsLLVM.txt'
+
+ def _extra_cmake_opts(self) -> T.List[str]:
+ return ['-DLLVM_MESON_MODULES={}'.format(';'.join(self.llvm_modules + self.llvm_opt_modules))]
+
+ 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]]:
+ res = []
+ for mod, required in modules:
+ cm_targets = self.traceparser.get_cmake_var(f'MESON_LLVM_TARGETS_{mod}')
+ if not cm_targets:
+ if required:
+ raise self._gen_exception(f'LLVM module {mod} was not found')
+ else:
+ mlog.warning('Optional LLVM module', mlog.bold(mod), 'was not found')
+ continue
+ for i in cm_targets:
+ res += [(i, required)]
+ return res
+
+ def _original_module_name(self, module: str) -> str:
+ orig_name = self.traceparser.get_cmake_var(f'MESON_TARGET_TO_LLVM_{module}')
+ if orig_name:
+ return orig_name[0]
+ return module
+
+
+class ValgrindDependency(PkgConfigDependency):
+ '''
+ Consumers of Valgrind usually only need the compile args and do not want to
+ link to its (static) libraries.
+ '''
+ def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]):
+ super().__init__('valgrind', env, kwargs)
+
+ def get_link_args(self, language: T.Optional[str] = None, raw: bool = False) -> T.List[str]:
+ return []
+
+
+class ZlibSystemDependency(SystemDependency):
+
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
+ super().__init__(name, environment, kwargs)
+
+ m = self.env.machines[self.for_machine]
+
+ # I'm not sure this is entirely correct. What if we're cross compiling
+ # from something to macOS?
+ if ((m.is_darwin() and isinstance(self.clib_compiler, (AppleClangCCompiler, AppleClangCPPCompiler))) or
+ m.is_freebsd() or m.is_dragonflybsd()):
+ # No need to set includes,
+ # on macos xcode/clang will do that for us.
+ # on freebsd zlib.h is in /usr/include
+
+ self.is_found = True
+ self.link_args = ['-lz']
+ elif m.is_windows():
+ # Without a clib_compiler we can't find zlib, s just give up.
+ if self.clib_compiler is None:
+ self.is_found = False
+ return
+
+ if self.clib_compiler.get_argument_syntax() == 'msvc':
+ libs = ['zlib1' 'zlib']
+ else:
+ libs = ['z']
+ for lib in libs:
+ l = self.clib_compiler.find_library(lib, environment, [])
+ h = self.clib_compiler.has_header('zlib.h', '', environment, dependencies=[self])
+ if l and h[0]:
+ self.is_found = True
+ self.link_args = l
+ break
+ else:
+ return
+ else:
+ mlog.debug(f'Unsupported OS {m.system}')
+ return
+
+ v, _ = self.clib_compiler.get_define('ZLIB_VERSION', '#include <zlib.h>', self.env, [], [self])
+ self.version = v.strip('"')
+
+
+ @staticmethod
+ def get_methods() -> T.List[DependencyMethods]:
+ return [DependencyMethods.SYSTEM]
+
+
+class JDKSystemDependency(SystemDependency):
+ def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
+ super().__init__('jdk', environment, kwargs)
+
+ m = self.env.machines[self.for_machine]
+
+ if 'java' not in environment.coredata.compilers[self.for_machine]:
+ detect_compiler_for(environment, 'java', self.for_machine)
+ self.javac = environment.coredata.compilers[self.for_machine]['java']
+ self.version = self.javac.version
+
+ if 'version' in kwargs and not version_compare(self.version, kwargs['version']):
+ mlog.error(f'Incorrect JDK version found ({self.version}), wanted {kwargs["version"]}')
+ self.is_found = False
+ return
+
+ self.java_home = environment.properties[self.for_machine].get_java_home()
+ if not self.java_home:
+ self.java_home = pathlib.Path(shutil.which(self.javac.exelist[0])).resolve().parents[1]
+
+ platform_include_dir = self.__machine_info_to_platform_include_dir(m)
+ if platform_include_dir is None:
+ mlog.error("Could not find a JDK platform include directory for your OS, please open an issue or provide a pull request.")
+ self.is_found = False
+ return
+
+ java_home_include = self.java_home / 'include'
+ self.compile_args.append(f'-I{java_home_include}')
+ self.compile_args.append(f'-I{java_home_include / platform_include_dir}')
+ self.is_found = True
+
+ @staticmethod
+ def get_methods() -> T.List[DependencyMethods]:
+ return [DependencyMethods.SYSTEM]
+
+ @staticmethod
+ def __machine_info_to_platform_include_dir(m: 'MachineInfo') -> T.Optional[str]:
+ """Translates the machine information to the platform-dependent include directory
+
+ When inspecting a JDK release tarball or $JAVA_HOME, inside the `include/` directory is a
+ platform dependent folder that must be on the target's include path in addition to the
+ parent `include/` directory.
+ """
+ if m.is_linux():
+ return 'linux'
+ elif m.is_windows():
+ return 'win32'
+ elif m.is_darwin():
+ return 'darwin'
+
+ return None
+
+
+llvm_factory = DependencyFactory(
+ 'LLVM',
+ [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL],
+ cmake_class=LLVMDependencyCMake,
+ configtool_class=LLVMDependencyConfigTool,
+)
+
+gtest_factory = DependencyFactory(
+ 'gtest',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
+ pkgconfig_class=GTestDependencyPC,
+ system_class=GTestDependencySystem,
+)
+
+gmock_factory = DependencyFactory(
+ 'gmock',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
+ pkgconfig_class=GMockDependencyPC,
+ system_class=GMockDependencySystem,
+)
+
+zlib_factory = DependencyFactory(
+ 'zlib',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE, DependencyMethods.SYSTEM],
+ cmake_name='ZLIB',
+ system_class=ZlibSystemDependency,
+)