1# Copyright 2013-2021 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 15from .base import ExternalDependency, DependencyException, DependencyTypeName 16from .pkgconfig import PkgConfigDependency 17from ..mesonlib import Popen_safe 18from ..programs import ExternalProgram 19from ..compilers import DCompiler 20from .. import mlog 21import re 22import os 23import copy 24import json 25import platform 26import typing as T 27 28if T.TYPE_CHECKING: 29 from ..environment import Environment 30 31class DubDependency(ExternalDependency): 32 class_dubbin = None 33 34 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 35 super().__init__(DependencyTypeName('dub'), environment, kwargs, language='d') 36 self.name = name 37 self.module_path: T.Optional[str] = None 38 39 _temp_comp = super().get_compiler() 40 assert isinstance(_temp_comp, DCompiler) 41 self.compiler = _temp_comp 42 43 if 'required' in kwargs: 44 self.required = kwargs.get('required') 45 46 if DubDependency.class_dubbin is None: 47 self.dubbin = self._check_dub() 48 DubDependency.class_dubbin = self.dubbin 49 else: 50 self.dubbin = DubDependency.class_dubbin 51 52 if not self.dubbin: 53 if self.required: 54 raise DependencyException('DUB not found.') 55 self.is_found = False 56 return 57 58 assert isinstance(self.dubbin, ExternalProgram) 59 mlog.debug('Determining dependency {!r} with DUB executable ' 60 '{!r}'.format(name, self.dubbin.get_path())) 61 62 # we need to know the target architecture 63 arch = self.compiler.arch 64 65 # Ask dub for the package 66 ret, res = self._call_dubbin(['describe', name, '--arch=' + arch]) 67 68 if ret != 0: 69 self.is_found = False 70 return 71 72 comp = self.compiler.get_id().replace('llvm', 'ldc').replace('gcc', 'gdc') 73 packages = [] 74 description = json.loads(res) 75 for package in description['packages']: 76 packages.append(package['name']) 77 if package['name'] == name: 78 self.is_found = True 79 80 not_lib = True 81 if 'targetType' in package: 82 if package['targetType'] in ['library', 'sourceLibrary', 'staticLibrary', 'dynamicLibrary']: 83 not_lib = False 84 85 if not_lib: 86 mlog.error(mlog.bold(name), "found but it isn't a library") 87 self.is_found = False 88 return 89 90 self.module_path = self._find_right_lib_path(package['path'], comp, description, True, package['targetFileName']) 91 if not os.path.exists(self.module_path): 92 # check if the dependency was built for other archs 93 archs = [['x86_64'], ['x86'], ['x86', 'x86_mscoff']] 94 for a in archs: 95 description_a = copy.deepcopy(description) 96 description_a['architecture'] = a 97 arch_module_path = self._find_right_lib_path(package['path'], comp, description_a, True, package['targetFileName']) 98 if arch_module_path: 99 mlog.error(mlog.bold(name), "found but it wasn't compiled for", mlog.bold(arch)) 100 self.is_found = False 101 return 102 103 mlog.error(mlog.bold(name), "found but it wasn't compiled with", mlog.bold(comp)) 104 self.is_found = False 105 return 106 107 self.version = package['version'] 108 self.pkg = package 109 110 if self.pkg['targetFileName'].endswith('.a'): 111 self.static = True 112 113 self.compile_args = [] 114 for flag in self.pkg['dflags']: 115 self.link_args.append(flag) 116 for path in self.pkg['importPaths']: 117 self.compile_args.append('-I' + os.path.join(self.pkg['path'], path)) 118 119 self.link_args = self.raw_link_args = [] 120 for flag in self.pkg['lflags']: 121 self.link_args.append(flag) 122 123 self.link_args.append(os.path.join(self.module_path, self.pkg['targetFileName'])) 124 125 # Handle dependencies 126 libs = [] 127 128 def add_lib_args(field_name: str, target: T.Dict[str, T.Dict[str, str]]) -> None: 129 if field_name in target['buildSettings']: 130 for lib in target['buildSettings'][field_name]: 131 if lib not in libs: 132 libs.append(lib) 133 if os.name != 'nt': 134 pkgdep = PkgConfigDependency(lib, environment, {'required': 'true', 'silent': 'true'}) 135 for arg in pkgdep.get_compile_args(): 136 self.compile_args.append(arg) 137 for arg in pkgdep.get_link_args(): 138 self.link_args.append(arg) 139 for arg in pkgdep.get_link_args(raw=True): 140 self.raw_link_args.append(arg) 141 142 for target in description['targets']: 143 if target['rootPackage'] in packages: 144 add_lib_args('libs', target) 145 add_lib_args(f'libs-{platform.machine()}', target) 146 for file in target['buildSettings']['linkerFiles']: 147 lib_path = self._find_right_lib_path(file, comp, description) 148 if lib_path: 149 self.link_args.append(lib_path) 150 else: 151 self.is_found = False 152 153 def _find_right_lib_path(self, 154 default_path: str, 155 comp: str, 156 description: T.Dict[str, str], 157 folder_only: bool = False, 158 file_name: str = '') -> T.Optional[str]: 159 module_path = lib_file_name = '' 160 if folder_only: 161 module_path = default_path 162 lib_file_name = file_name 163 else: 164 module_path = os.path.dirname(default_path) 165 lib_file_name = os.path.basename(default_path) 166 module_build_path = os.path.join(module_path, '.dub', 'build') 167 168 # If default_path is a path to lib file and 169 # directory of lib don't have subdir '.dub/build' 170 if not os.path.isdir(module_build_path) and os.path.isfile(default_path): 171 if folder_only: 172 return module_path 173 else: 174 return default_path 175 176 # Get D version implemented in the compiler 177 # gdc doesn't support this 178 ret, res = self._call_dubbin(['--version']) 179 180 if ret != 0: 181 mlog.error('Failed to run {!r}', mlog.bold(comp)) 182 return None 183 184 d_ver_reg = re.search('v[0-9].[0-9][0-9][0-9].[0-9]', res) # Ex.: v2.081.2 185 if d_ver_reg is not None: 186 d_ver = d_ver_reg.group().rsplit('.', 1)[0].replace('v', '').replace('.', '') # Fix structure. Ex.: 2081 187 else: 188 d_ver = '' # gdc 189 190 if not os.path.isdir(module_build_path): 191 return '' 192 193 # Ex.: library-debug-linux.posix-x86_64-ldc_2081-EF934983A3319F8F8FF2F0E107A363BA 194 build_name = '-{}-{}-{}-{}_{}'.format(description['buildType'], '.'.join(description['platform']), '.'.join(description['architecture']), comp, d_ver) 195 for entry in os.listdir(module_build_path): 196 if build_name in entry: 197 for file in os.listdir(os.path.join(module_build_path, entry)): 198 if file == lib_file_name: 199 if folder_only: 200 return os.path.join(module_build_path, entry) 201 else: 202 return os.path.join(module_build_path, entry, lib_file_name) 203 204 return '' 205 206 def _call_dubbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str]: 207 assert isinstance(self.dubbin, ExternalProgram) 208 p, out = Popen_safe(self.dubbin.get_command() + args, env=env)[0:2] 209 return p.returncode, out.strip() 210 211 def _call_copmbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str]: 212 p, out = Popen_safe(self.compiler.get_exelist() + args, env=env)[0:2] 213 return p.returncode, out.strip() 214 215 def _check_dub(self) -> T.Union[bool, ExternalProgram]: 216 dubbin: T.Union[bool, ExternalProgram] = ExternalProgram('dub', silent=True) 217 assert isinstance(dubbin, ExternalProgram) 218 if dubbin.found(): 219 try: 220 p, out = Popen_safe(dubbin.get_command() + ['--version'])[0:2] 221 if p.returncode != 0: 222 mlog.warning('Found dub {!r} but couldn\'t run it' 223 ''.format(' '.join(dubbin.get_command()))) 224 # Set to False instead of None to signify that we've already 225 # searched for it and not found it 226 dubbin = False 227 except (FileNotFoundError, PermissionError): 228 dubbin = False 229 else: 230 dubbin = False 231 if isinstance(dubbin, ExternalProgram): 232 mlog.log('Found DUB:', mlog.bold(dubbin.get_path()), 233 '(%s)' % out.strip()) 234 else: 235 mlog.log('Found DUB:', mlog.red('NO')) 236 return dubbin 237