1# Copyright 2018 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 class contains the basic functionality needed to run any interpreter 16# or an interpreter-based tool 17 18from .interpreter import AstInterpreter 19from .visitor import AstVisitor 20from .. import compilers, environment, mesonlib, optinterpreter 21from .. import coredata as cdata 22from ..mesonlib import MachineChoice, OptionKey 23from ..interpreterbase import InvalidArguments, TYPE_nvar 24from ..build import BuildTarget, Executable, Jar, SharedLibrary, SharedModule, StaticLibrary 25from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode 26from ..compilers import detect_compiler_for 27import typing as T 28import os 29import argparse 30 31build_target_functions = ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries'] 32 33class IntrospectionHelper(argparse.Namespace): 34 # mimic an argparse namespace 35 def __init__(self, cross_file: str): 36 super().__init__() 37 self.cross_file = cross_file # type: str 38 self.native_file = None # type: str 39 self.cmd_line_options = {} # type: T.Dict[str, str] 40 41 def __eq__(self, other: object) -> bool: 42 return NotImplemented 43 44class IntrospectionInterpreter(AstInterpreter): 45 # Interpreter to detect the options without a build directory 46 # Most of the code is stolen from interpreter.Interpreter 47 def __init__(self, 48 source_root: str, 49 subdir: str, 50 backend: str, 51 visitors: T.Optional[T.List[AstVisitor]] = None, 52 cross_file: T.Optional[str] = None, 53 subproject: str = '', 54 subproject_dir: str = 'subprojects', 55 env: T.Optional[environment.Environment] = None): 56 visitors = visitors if visitors is not None else [] 57 super().__init__(source_root, subdir, subproject, visitors=visitors) 58 59 options = IntrospectionHelper(cross_file) 60 self.cross_file = cross_file 61 if env is None: 62 self.environment = environment.Environment(source_root, None, options) 63 else: 64 self.environment = env 65 self.subproject_dir = subproject_dir 66 self.coredata = self.environment.get_coredata() 67 self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') 68 self.backend = backend 69 self.default_options = {OptionKey('backend'): self.backend} 70 self.project_data = {} # type: T.Dict[str, T.Any] 71 self.targets = [] # type: T.List[T.Dict[str, T.Any]] 72 self.dependencies = [] # type: T.List[T.Dict[str, T.Any]] 73 self.project_node = None # type: BaseNode 74 75 self.funcs.update({ 76 'add_languages': self.func_add_languages, 77 'dependency': self.func_dependency, 78 'executable': self.func_executable, 79 'jar': self.func_jar, 80 'library': self.func_library, 81 'project': self.func_project, 82 'shared_library': self.func_shared_lib, 83 'shared_module': self.func_shared_module, 84 'static_library': self.func_static_lib, 85 'both_libraries': self.func_both_lib, 86 }) 87 88 def func_project(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None: 89 if self.project_node: 90 raise InvalidArguments('Second call to project()') 91 self.project_node = node 92 if len(args) < 1: 93 raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') 94 95 proj_name = args[0] 96 proj_vers = kwargs.get('version', 'undefined') 97 proj_langs = self.flatten_args(args[1:]) 98 if isinstance(proj_vers, ElementaryNode): 99 proj_vers = proj_vers.value 100 if not isinstance(proj_vers, str): 101 proj_vers = 'undefined' 102 self.project_data = {'descriptive_name': proj_name, 'version': proj_vers} 103 104 if os.path.exists(self.option_file): 105 oi = optinterpreter.OptionInterpreter(self.subproject) 106 oi.process(self.option_file) 107 self.coredata.update_project_options(oi.options) 108 109 def_opts = self.flatten_args(kwargs.get('default_options', [])) 110 _project_default_options = mesonlib.stringlistify(def_opts) 111 self.project_default_options = cdata.create_options_dict(_project_default_options, self.subproject) 112 self.default_options.update(self.project_default_options) 113 self.coredata.set_default_options(self.default_options, self.subproject, self.environment) 114 115 if not self.is_subproject() and 'subproject_dir' in kwargs: 116 spdirname = kwargs['subproject_dir'] 117 if isinstance(spdirname, StringNode): 118 assert isinstance(spdirname.value, str) 119 self.subproject_dir = spdirname.value 120 if not self.is_subproject(): 121 self.project_data['subprojects'] = [] 122 subprojects_dir = os.path.join(self.source_root, self.subproject_dir) 123 if os.path.isdir(subprojects_dir): 124 for i in os.listdir(subprojects_dir): 125 if os.path.isdir(os.path.join(subprojects_dir, i)): 126 self.do_subproject(i) 127 128 self.coredata.init_backend_options(self.backend) 129 options = {k: v for k, v in self.environment.options.items() if k.is_backend()} 130 131 self.coredata.set_options(options) 132 self._add_languages(proj_langs, MachineChoice.HOST) 133 self._add_languages(proj_langs, MachineChoice.BUILD) 134 135 def do_subproject(self, dirname: str) -> None: 136 subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) 137 subpr = os.path.join(subproject_dir_abs, dirname) 138 try: 139 subi = IntrospectionInterpreter(subpr, '', self.backend, cross_file=self.cross_file, subproject=dirname, subproject_dir=self.subproject_dir, env=self.environment, visitors=self.visitors) 140 subi.analyze() 141 subi.project_data['name'] = dirname 142 self.project_data['subprojects'] += [subi.project_data] 143 except (mesonlib.MesonException, RuntimeError): 144 return 145 146 def func_add_languages(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None: 147 kwargs = self.flatten_kwargs(kwargs) 148 if 'native' in kwargs: 149 native = kwargs.get('native', False) 150 self._add_languages(args, MachineChoice.BUILD if native else MachineChoice.HOST) 151 else: 152 for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]: 153 self._add_languages(args, for_machine) 154 155 def _add_languages(self, raw_langs: T.List[TYPE_nvar], for_machine: MachineChoice) -> None: 156 langs = [] # type: T.List[str] 157 for l in self.flatten_args(raw_langs): 158 if isinstance(l, str): 159 langs.append(l) 160 elif isinstance(l, StringNode): 161 langs.append(l.value) 162 163 for lang in sorted(langs, key=compilers.sort_clink): 164 lang = lang.lower() 165 if lang not in self.coredata.compilers[for_machine]: 166 detect_compiler_for(self.environment, lang, for_machine) 167 168 def func_dependency(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None: 169 args = self.flatten_args(args) 170 kwargs = self.flatten_kwargs(kwargs) 171 if not args: 172 return 173 name = args[0] 174 has_fallback = 'fallback' in kwargs 175 required = kwargs.get('required', True) 176 version = kwargs.get('version', []) 177 if not isinstance(version, list): 178 version = [version] 179 if isinstance(required, ElementaryNode): 180 required = required.value 181 if not isinstance(required, bool): 182 required = False 183 self.dependencies += [{ 184 'name': name, 185 'required': required, 186 'version': version, 187 'has_fallback': has_fallback, 188 'conditional': node.condition_level > 0, 189 'node': node 190 }] 191 192 def build_target(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs_raw: T.Dict[str, TYPE_nvar], targetclass: T.Type[BuildTarget]) -> T.Optional[T.Dict[str, T.Any]]: 193 args = self.flatten_args(args) 194 if not args or not isinstance(args[0], str): 195 return None 196 name = args[0] 197 srcqueue = [node] 198 extra_queue = [] 199 200 # Process the sources BEFORE flattening the kwargs, to preserve the original nodes 201 if 'sources' in kwargs_raw: 202 srcqueue += mesonlib.listify(kwargs_raw['sources']) 203 204 if 'extra_files' in kwargs_raw: 205 extra_queue += mesonlib.listify(kwargs_raw['extra_files']) 206 207 kwargs = self.flatten_kwargs(kwargs_raw, True) 208 209 def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]: 210 res = [] # type: T.List[BaseNode] 211 while inqueue: 212 curr = inqueue.pop(0) 213 arg_node = None 214 assert(isinstance(curr, BaseNode)) 215 if isinstance(curr, FunctionNode): 216 arg_node = curr.args 217 elif isinstance(curr, ArrayNode): 218 arg_node = curr.args 219 elif isinstance(curr, IdNode): 220 # Try to resolve the ID and append the node to the queue 221 assert isinstance(curr.value, str) 222 var_name = curr.value 223 if var_name in self.assignments: 224 tmp_node = self.assignments[var_name] 225 if isinstance(tmp_node, (ArrayNode, IdNode, FunctionNode)): 226 inqueue += [tmp_node] 227 elif isinstance(curr, ArithmeticNode): 228 inqueue += [curr.left, curr.right] 229 if arg_node is None: 230 continue 231 arg_nodes = arg_node.arguments.copy() 232 # Pop the first element if the function is a build target function 233 if isinstance(curr, FunctionNode) and curr.func_name in build_target_functions: 234 arg_nodes.pop(0) 235 elemetary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))] 236 inqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))] 237 if elemetary_nodes: 238 res += [curr] 239 return res 240 241 source_nodes = traverse_nodes(srcqueue) 242 extraf_nodes = traverse_nodes(extra_queue) 243 244 # Make sure nothing can crash when creating the build class 245 kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in ['install', 'build_by_default', 'build_always']} 246 kwargs_reduced = {k: v.value if isinstance(v, ElementaryNode) else v for k, v in kwargs_reduced.items()} 247 kwargs_reduced = {k: v for k, v in kwargs_reduced.items() if not isinstance(v, BaseNode)} 248 for_machine = MachineChoice.HOST 249 objects = [] # type: T.List[T.Any] 250 empty_sources = [] # type: T.List[T.Any] 251 # Passing the unresolved sources list causes errors 252 target = targetclass(name, self.subdir, self.subproject, for_machine, empty_sources, objects, self.environment, kwargs_reduced) 253 254 new_target = { 255 'name': target.get_basename(), 256 'id': target.get_id(), 257 'type': target.get_typename(), 258 'defined_in': os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)), 259 'subdir': self.subdir, 260 'build_by_default': target.build_by_default, 261 'installed': target.should_install(), 262 'outputs': target.get_outputs(), 263 'sources': source_nodes, 264 'extra_files': extraf_nodes, 265 'kwargs': kwargs, 266 'node': node, 267 } 268 269 self.targets += [new_target] 270 return new_target 271 272 def build_library(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 273 default_library = self.coredata.get_option(OptionKey('default_library')) 274 if default_library == 'shared': 275 return self.build_target(node, args, kwargs, SharedLibrary) 276 elif default_library == 'static': 277 return self.build_target(node, args, kwargs, StaticLibrary) 278 elif default_library == 'both': 279 return self.build_target(node, args, kwargs, SharedLibrary) 280 return None 281 282 def func_executable(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 283 return self.build_target(node, args, kwargs, Executable) 284 285 def func_static_lib(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 286 return self.build_target(node, args, kwargs, StaticLibrary) 287 288 def func_shared_lib(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 289 return self.build_target(node, args, kwargs, SharedLibrary) 290 291 def func_both_lib(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 292 return self.build_target(node, args, kwargs, SharedLibrary) 293 294 def func_shared_module(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 295 return self.build_target(node, args, kwargs, SharedModule) 296 297 def func_library(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 298 return self.build_library(node, args, kwargs) 299 300 def func_jar(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 301 return self.build_target(node, args, kwargs, Jar) 302 303 def func_build_target(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: 304 if 'target_type' not in kwargs: 305 return None 306 target_type = kwargs.pop('target_type') 307 if isinstance(target_type, ElementaryNode): 308 target_type = target_type.value 309 if target_type == 'executable': 310 return self.build_target(node, args, kwargs, Executable) 311 elif target_type == 'shared_library': 312 return self.build_target(node, args, kwargs, SharedLibrary) 313 elif target_type == 'static_library': 314 return self.build_target(node, args, kwargs, StaticLibrary) 315 elif target_type == 'both_libraries': 316 return self.build_target(node, args, kwargs, SharedLibrary) 317 elif target_type == 'library': 318 return self.build_library(node, args, kwargs) 319 elif target_type == 'jar': 320 return self.build_target(node, args, kwargs, Jar) 321 return None 322 323 def is_subproject(self) -> bool: 324 return self.subproject != '' 325 326 def analyze(self) -> None: 327 self.load_root_meson_file() 328 self.sanity_check_ast() 329 self.parse_project() 330 self.run() 331