diff options
Diffstat (limited to 'meson/mesonbuild/dependencies/dub.py')
-rw-r--r-- | meson/mesonbuild/dependencies/dub.py | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/meson/mesonbuild/dependencies/dub.py b/meson/mesonbuild/dependencies/dub.py new file mode 100644 index 000000000..8dfb4869d --- /dev/null +++ b/meson/mesonbuild/dependencies/dub.py @@ -0,0 +1,240 @@ +# Copyright 2013-2021 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. + +from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName +from .pkgconfig import PkgConfigDependency +from ..mesonlib import Popen_safe +from ..programs import ExternalProgram +from ..compilers import DCompiler +from .. import mlog +import re +import os +import copy +import json +import platform +import typing as T + +if T.TYPE_CHECKING: + from ..environment import Environment + +class DubDependency(ExternalDependency): + class_dubbin = None + + def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + super().__init__(DependencyTypeName('dub'), environment, kwargs, language='d') + self.name = name + self.module_path: T.Optional[str] = None + + _temp_comp = super().get_compiler() + assert isinstance(_temp_comp, DCompiler) + self.compiler = _temp_comp + + if 'required' in kwargs: + self.required = kwargs.get('required') + + if DubDependency.class_dubbin is None: + self.dubbin = self._check_dub() + DubDependency.class_dubbin = self.dubbin + else: + self.dubbin = DubDependency.class_dubbin + + if not self.dubbin: + if self.required: + raise DependencyException('DUB not found.') + self.is_found = False + return + + assert isinstance(self.dubbin, ExternalProgram) + mlog.debug('Determining dependency {!r} with DUB executable ' + '{!r}'.format(name, self.dubbin.get_path())) + + # we need to know the target architecture + arch = self.compiler.arch + + # Ask dub for the package + ret, res = self._call_dubbin(['describe', name, '--arch=' + arch]) + + if ret != 0: + self.is_found = False + return + + comp = self.compiler.get_id().replace('llvm', 'ldc').replace('gcc', 'gdc') + packages = [] + description = json.loads(res) + for package in description['packages']: + packages.append(package['name']) + if package['name'] == name: + self.is_found = True + + not_lib = True + if 'targetType' in package: + if package['targetType'] in ['library', 'sourceLibrary', 'staticLibrary', 'dynamicLibrary']: + not_lib = False + + if not_lib: + mlog.error(mlog.bold(name), "found but it isn't a library") + self.is_found = False + return + + self.module_path = self._find_right_lib_path(package['path'], comp, description, True, package['targetFileName']) + if not os.path.exists(self.module_path): + # check if the dependency was built for other archs + archs = [['x86_64'], ['x86'], ['x86', 'x86_mscoff']] + for a in archs: + description_a = copy.deepcopy(description) + description_a['architecture'] = a + arch_module_path = self._find_right_lib_path(package['path'], comp, description_a, True, package['targetFileName']) + if arch_module_path: + mlog.error(mlog.bold(name), "found but it wasn't compiled for", mlog.bold(arch)) + self.is_found = False + return + + mlog.error(mlog.bold(name), "found but it wasn't compiled with", mlog.bold(comp)) + self.is_found = False + return + + self.version = package['version'] + self.pkg = package + + if self.pkg['targetFileName'].endswith('.a'): + self.static = True + + self.compile_args = [] + for flag in self.pkg['dflags']: + self.link_args.append(flag) + for path in self.pkg['importPaths']: + self.compile_args.append('-I' + os.path.join(self.pkg['path'], path)) + + self.link_args = self.raw_link_args = [] + for flag in self.pkg['lflags']: + self.link_args.append(flag) + + self.link_args.append(os.path.join(self.module_path, self.pkg['targetFileName'])) + + # Handle dependencies + libs = [] + + def add_lib_args(field_name: str, target: T.Dict[str, T.Dict[str, str]]) -> None: + if field_name in target['buildSettings']: + for lib in target['buildSettings'][field_name]: + if lib not in libs: + libs.append(lib) + if os.name != 'nt': + pkgdep = PkgConfigDependency(lib, environment, {'required': 'true', 'silent': 'true'}) + for arg in pkgdep.get_compile_args(): + self.compile_args.append(arg) + for arg in pkgdep.get_link_args(): + self.link_args.append(arg) + for arg in pkgdep.get_link_args(raw=True): + self.raw_link_args.append(arg) + + for target in description['targets']: + if target['rootPackage'] in packages: + add_lib_args('libs', target) + add_lib_args(f'libs-{platform.machine()}', target) + for file in target['buildSettings']['linkerFiles']: + lib_path = self._find_right_lib_path(file, comp, description) + if lib_path: + self.link_args.append(lib_path) + else: + self.is_found = False + + def _find_right_lib_path(self, + default_path: str, + comp: str, + description: T.Dict[str, str], + folder_only: bool = False, + file_name: str = '') -> T.Optional[str]: + module_path = lib_file_name = '' + if folder_only: + module_path = default_path + lib_file_name = file_name + else: + module_path = os.path.dirname(default_path) + lib_file_name = os.path.basename(default_path) + module_build_path = os.path.join(module_path, '.dub', 'build') + + # If default_path is a path to lib file and + # directory of lib don't have subdir '.dub/build' + if not os.path.isdir(module_build_path) and os.path.isfile(default_path): + if folder_only: + return module_path + else: + return default_path + + # Get D version implemented in the compiler + # gdc doesn't support this + ret, res = self._call_dubbin(['--version']) + + if ret != 0: + mlog.error('Failed to run {!r}', mlog.bold(comp)) + return None + + d_ver_reg = re.search('v[0-9].[0-9][0-9][0-9].[0-9]', res) # Ex.: v2.081.2 + if d_ver_reg is not None: + d_ver = d_ver_reg.group().rsplit('.', 1)[0].replace('v', '').replace('.', '') # Fix structure. Ex.: 2081 + else: + d_ver = '' # gdc + + if not os.path.isdir(module_build_path): + return '' + + # Ex.: library-debug-linux.posix-x86_64-ldc_2081-EF934983A3319F8F8FF2F0E107A363BA + build_name = '-{}-{}-{}-{}_{}'.format(description['buildType'], '.'.join(description['platform']), '.'.join(description['architecture']), comp, d_ver) + for entry in os.listdir(module_build_path): + if build_name in entry: + for file in os.listdir(os.path.join(module_build_path, entry)): + if file == lib_file_name: + if folder_only: + return os.path.join(module_build_path, entry) + else: + return os.path.join(module_build_path, entry, lib_file_name) + + return '' + + def _call_dubbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str]: + assert isinstance(self.dubbin, ExternalProgram) + p, out = Popen_safe(self.dubbin.get_command() + args, env=env)[0:2] + return p.returncode, out.strip() + + def _call_copmbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str]: + p, out = Popen_safe(self.compiler.get_exelist() + args, env=env)[0:2] + return p.returncode, out.strip() + + def _check_dub(self) -> T.Union[bool, ExternalProgram]: + dubbin: T.Union[bool, ExternalProgram] = ExternalProgram('dub', silent=True) + assert isinstance(dubbin, ExternalProgram) + if dubbin.found(): + try: + p, out = Popen_safe(dubbin.get_command() + ['--version'])[0:2] + if p.returncode != 0: + mlog.warning('Found dub {!r} but couldn\'t run it' + ''.format(' '.join(dubbin.get_command()))) + # Set to False instead of None to signify that we've already + # searched for it and not found it + dubbin = False + except (FileNotFoundError, PermissionError): + dubbin = False + else: + dubbin = False + if isinstance(dubbin, ExternalProgram): + mlog.log('Found DUB:', mlog.bold(dubbin.get_path()), + '(%s)' % out.strip()) + else: + mlog.log('Found DUB:', mlog.red('NO')) + return dubbin + + @staticmethod + def get_methods() -> T.List[DependencyMethods]: + return [DependencyMethods.DUB] |