1# Copyright 2014-2016 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 is a helper script for IDE developers. It allows you to 16extract information such as list of targets, files, compiler flags, 17tests and so on. All output is in JSON for simple parsing. 18 19Currently only works for the Ninja backend. Others use generated 20project files and don't need this info.""" 21 22import collections 23import json 24from . import build, coredata as cdata 25from . import mesonlib 26from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter 27from . import mlog 28from .backend import backends 29from .mparser import BaseNode, FunctionNode, ArrayNode, ArgumentNode, StringNode 30from .interpreter import Interpreter 31from pathlib import Path, PurePath 32import typing as T 33import os 34import argparse 35 36from .mesonlib import OptionKey 37 38def get_meson_info_file(info_dir: str) -> str: 39 return os.path.join(info_dir, 'meson-info.json') 40 41def get_meson_introspection_version() -> str: 42 return '1.0.0' 43 44def get_meson_introspection_required_version() -> T.List[str]: 45 return ['>=1.0', '<2.0'] 46 47class IntroCommand: 48 def __init__(self, 49 desc: str, 50 func: T.Optional[T.Callable[[], T.Union[dict, list]]] = None, 51 no_bd: T.Optional[T.Callable[[IntrospectionInterpreter], T.Union[dict, list]]] = None) -> None: 52 self.desc = desc + '.' 53 self.func = func 54 self.no_bd = no_bd 55 56def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None, 57 builddata: T.Optional[build.Build] = None, 58 backend: T.Optional[backends.Backend] = None, 59 sourcedir: T.Optional[str] = None) -> 'T.Mapping[str, IntroCommand]': 60 if backend and builddata: 61 benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks()) 62 testdata = backend.create_test_serialisation(builddata.get_tests()) 63 installdata = backend.create_install_data() 64 interpreter = backend.interpreter 65 else: 66 benchmarkdata = testdata = installdata = None 67 68 # Enforce key order for argparse 69 return collections.OrderedDict([ 70 ('ast', IntroCommand('Dump the AST of the meson file', no_bd=dump_ast)), 71 ('benchmarks', IntroCommand('List all benchmarks', func=lambda: list_benchmarks(benchmarkdata))), 72 ('buildoptions', IntroCommand('List all build options', func=lambda: list_buildoptions(coredata), no_bd=list_buildoptions_from_source)), 73 ('buildsystem_files', IntroCommand('List files that make up the build system', func=lambda: list_buildsystem_files(builddata, interpreter))), 74 ('dependencies', IntroCommand('List external dependencies', func=lambda: list_deps(coredata), no_bd=list_deps_from_source)), 75 ('scan_dependencies', IntroCommand('Scan for dependencies used in the meson.build file', no_bd=list_deps_from_source)), 76 ('installed', IntroCommand('List all installed files and directories', func=lambda: list_installed(installdata))), 77 ('install_plan', IntroCommand('List all installed files and directories with their details', func=lambda: list_install_plan(installdata))), 78 ('projectinfo', IntroCommand('Information about projects', func=lambda: list_projinfo(builddata), no_bd=list_projinfo_from_source)), 79 ('targets', IntroCommand('List top level targets', func=lambda: list_targets(builddata, installdata, backend), no_bd=list_targets_from_source)), 80 ('tests', IntroCommand('List all unit tests', func=lambda: list_tests(testdata))), 81 ]) 82 83def add_arguments(parser: argparse.ArgumentParser) -> None: 84 intro_types = get_meson_introspection_types() 85 for key, val in intro_types.items(): 86 flag = '--' + key.replace('_', '-') 87 parser.add_argument(flag, action='store_true', dest=key, default=False, help=val.desc) 88 89 parser.add_argument('--backend', choices=sorted(cdata.backendlist), dest='backend', default='ninja', 90 help='The backend to use for the --buildoptions introspection.') 91 parser.add_argument('-a', '--all', action='store_true', dest='all', default=False, 92 help='Print all available information.') 93 parser.add_argument('-i', '--indent', action='store_true', dest='indent', default=False, 94 help='Enable pretty printed JSON.') 95 parser.add_argument('-f', '--force-object-output', action='store_true', dest='force_dict', default=False, 96 help='Always use the new JSON format for multiple entries (even for 0 and 1 introspection commands)') 97 parser.add_argument('builddir', nargs='?', default='.', help='The build directory') 98 99def dump_ast(intr: IntrospectionInterpreter) -> T.Dict[str, T.Any]: 100 printer = AstJSONPrinter() 101 intr.ast.accept(printer) 102 return printer.result 103 104def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: 105 res = {} 106 if installdata is not None: 107 for t in installdata.targets: 108 res[os.path.join(installdata.build_dir, t.fname)] = \ 109 os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname)) 110 for alias in t.aliases.keys(): 111 res[os.path.join(installdata.build_dir, alias)] = \ 112 os.path.join(installdata.prefix, t.outdir, os.path.basename(alias)) 113 for i in installdata.data: 114 res[i.path] = os.path.join(installdata.prefix, i.install_path) 115 for i in installdata.headers: 116 res[i.path] = os.path.join(installdata.prefix, i.install_path, os.path.basename(i.path)) 117 for i in installdata.man: 118 res[i.path] = os.path.join(installdata.prefix, i.install_path) 119 for i in installdata.install_subdirs: 120 res[i.path] = os.path.join(installdata.prefix, i.install_path) 121 return res 122 123def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]: 124 plan = { 125 'targets': { 126 os.path.join(installdata.build_dir, target.fname): { 127 'destination': target.out_name, 128 'tag': target.tag or None, 129 } 130 for target in installdata.targets 131 }, 132 } # type: T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]] 133 for key, data_list in { 134 'data': installdata.data, 135 'man': installdata.man, 136 'headers': installdata.headers, 137 }.items(): 138 for data in data_list: 139 data_type = data.data_type or key 140 install_path_name = data.install_path_name 141 if key == 'headers': # in the headers, install_path_name is the directory 142 install_path_name = os.path.join(install_path_name, os.path.basename(data.path)) 143 elif data_type == 'configure': 144 install_path_name = os.path.join('{prefix}', install_path_name) 145 146 plan[data_type] = plan.get(data_type, {}) 147 plan[data_type][data.path] = { 148 'destination': install_path_name, 149 'tag': data.tag or None, 150 } 151 return plan 152 153def get_target_dir(coredata: cdata.CoreData, subdir: str) -> str: 154 if coredata.get_option(OptionKey('layout')) == 'flat': 155 return 'meson-out' 156 else: 157 return subdir 158 159def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]: 160 tlist = [] # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]] 161 root_dir = Path(intr.source_root) 162 163 def nodes_to_paths(node_list: T.List[BaseNode]) -> T.List[Path]: 164 res = [] # type: T.List[Path] 165 for n in node_list: 166 args = [] # type: T.List[BaseNode] 167 if isinstance(n, FunctionNode): 168 args = list(n.args.arguments) 169 if n.func_name in build_target_functions: 170 args.pop(0) 171 elif isinstance(n, ArrayNode): 172 args = n.args.arguments 173 elif isinstance(n, ArgumentNode): 174 args = n.arguments 175 for j in args: 176 if isinstance(j, StringNode): 177 assert isinstance(j.value, str) 178 res += [Path(j.value)] 179 elif isinstance(j, str): 180 res += [Path(j)] 181 res = [root_dir / i['subdir'] / x for x in res] 182 res = [x.resolve() for x in res] 183 return res 184 185 for i in intr.targets: 186 sources = nodes_to_paths(i['sources']) 187 extra_f = nodes_to_paths(i['extra_files']) 188 outdir = get_target_dir(intr.coredata, i['subdir']) 189 190 tlist += [{ 191 'name': i['name'], 192 'id': i['id'], 193 'type': i['type'], 194 'defined_in': i['defined_in'], 195 'filename': [os.path.join(outdir, x) for x in i['outputs']], 196 'build_by_default': i['build_by_default'], 197 'target_sources': [{ 198 'language': 'unknown', 199 'compiler': [], 200 'parameters': [], 201 'sources': [str(x) for x in sources], 202 'generated_sources': [] 203 }], 204 'extra_files': [str(x) for x in extra_f], 205 'subproject': None, # Subprojects are not supported 206 'installed': i['installed'] 207 }] 208 209 return tlist 210 211def list_targets(builddata: build.Build, installdata: backends.InstallData, backend: backends.Backend) -> T.List[T.Any]: 212 tlist = [] # type: T.List[T.Any] 213 build_dir = builddata.environment.get_build_dir() 214 src_dir = builddata.environment.get_source_dir() 215 216 # Fast lookup table for installation files 217 install_lookuptable = {} 218 for i in installdata.targets: 219 out = [os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))] 220 out += [os.path.join(installdata.prefix, i.outdir, os.path.basename(x)) for x in i.aliases] 221 install_lookuptable[os.path.basename(i.fname)] = [str(PurePath(x)) for x in out] 222 223 for (idname, target) in builddata.get_targets().items(): 224 if not isinstance(target, build.Target): 225 raise RuntimeError('The target object in `builddata.get_targets()` is not of type `build.Target`. Please file a bug with this error message.') 226 227 outdir = get_target_dir(builddata.environment.coredata, target.subdir) 228 t = { 229 'name': target.get_basename(), 230 'id': idname, 231 'type': target.get_typename(), 232 'defined_in': os.path.normpath(os.path.join(src_dir, target.subdir, 'meson.build')), 233 'filename': [os.path.join(build_dir, outdir, x) for x in target.get_outputs()], 234 'build_by_default': target.build_by_default, 235 'target_sources': backend.get_introspection_data(idname, target), 236 'extra_files': [os.path.normpath(os.path.join(src_dir, x.subdir, x.fname)) for x in target.extra_files], 237 'subproject': target.subproject or None 238 } 239 240 if installdata and target.should_install(): 241 t['installed'] = True 242 ifn = [install_lookuptable.get(x, [None]) for x in target.get_outputs()] 243 t['install_filename'] = [x for sublist in ifn for x in sublist] # flatten the list 244 else: 245 t['installed'] = False 246 tlist.append(t) 247 return tlist 248 249def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]: 250 subprojects = [i['name'] for i in intr.project_data['subprojects']] 251 return list_buildoptions(intr.coredata, subprojects) 252 253def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]: 254 optlist = [] # type: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] 255 subprojects = subprojects or [] 256 257 dir_option_names = set(cdata.BUILTIN_DIR_OPTIONS) 258 test_option_names = {OptionKey('errorlogs'), 259 OptionKey('stdsplit')} 260 261 dir_options: 'cdata.KeyedOptionDictType' = {} 262 test_options: 'cdata.KeyedOptionDictType' = {} 263 core_options: 'cdata.KeyedOptionDictType' = {} 264 for k, v in coredata.options.items(): 265 if k in dir_option_names: 266 dir_options[k] = v 267 elif k in test_option_names: 268 test_options[k] = v 269 elif k.is_builtin(): 270 core_options[k] = v 271 if not v.yielding: 272 for s in subprojects: 273 core_options[k.evolve(subproject=s)] = v 274 275 def add_keys(options: 'cdata.KeyedOptionDictType', section: str) -> None: 276 for key, opt in sorted(options.items()): 277 optdict = {'name': str(key), 'value': opt.value, 'section': section, 278 'machine': key.machine.get_lower_case_name() if coredata.is_per_machine_option(key) else 'any'} 279 if isinstance(opt, cdata.UserStringOption): 280 typestr = 'string' 281 elif isinstance(opt, cdata.UserBooleanOption): 282 typestr = 'boolean' 283 elif isinstance(opt, cdata.UserComboOption): 284 optdict['choices'] = opt.choices 285 typestr = 'combo' 286 elif isinstance(opt, cdata.UserIntegerOption): 287 typestr = 'integer' 288 elif isinstance(opt, cdata.UserArrayOption): 289 typestr = 'array' 290 if opt.choices: 291 optdict['choices'] = opt.choices 292 else: 293 raise RuntimeError("Unknown option type") 294 optdict['type'] = typestr 295 optdict['description'] = opt.description 296 optlist.append(optdict) 297 298 add_keys(core_options, 'core') 299 add_keys({k: v for k, v in coredata.options.items() if k.is_backend()}, 'backend') 300 add_keys({k: v for k, v in coredata.options.items() if k.is_base()}, 'base') 301 add_keys( 302 {k: v for k, v in sorted(coredata.options.items(), key=lambda i: i[0].machine) if k.is_compiler()}, 303 'compiler', 304 ) 305 add_keys(dir_options, 'directory') 306 add_keys({k: v for k, v in coredata.options.items() if k.is_project()}, 'user') 307 add_keys(test_options, 'test') 308 return optlist 309 310def find_buildsystem_files_list(src_dir: str) -> T.List[str]: 311 # I feel dirty about this. But only slightly. 312 filelist = [] # type: T.List[str] 313 for root, _, files in os.walk(src_dir): 314 for f in files: 315 if f == 'meson.build' or f == 'meson_options.txt': 316 filelist.append(os.path.relpath(os.path.join(root, f), src_dir)) 317 return filelist 318 319def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]: 320 src_dir = builddata.environment.get_source_dir() 321 filelist = interpreter.get_build_def_files() # type: T.List[str] 322 filelist = [PurePath(src_dir, x).as_posix() for x in filelist] 323 return filelist 324 325def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool]]]: 326 result = [] # type: T.List[T.Dict[str, T.Union[str, bool]]] 327 for i in intr.dependencies: 328 keys = [ 329 'name', 330 'required', 331 'version', 332 'has_fallback', 333 'conditional', 334 ] 335 result += [{k: v for k, v in i.items() if k in keys}] 336 return result 337 338def list_deps(coredata: cdata.CoreData) -> T.List[T.Dict[str, T.Union[str, T.List[str]]]]: 339 result = [] # type: T.List[T.Dict[str, T.Union[str, T.List[str]]]] 340 for d in coredata.deps.host.values(): 341 if d.found(): 342 result += [{'name': d.name, 343 'version': d.get_version(), 344 'compile_args': d.get_compile_args(), 345 'link_args': d.get_link_args()}] 346 return result 347 348def get_test_list(testdata: T.List[backends.TestSerialisation]) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]: 349 result = [] # type: T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]] 350 for t in testdata: 351 to = {} # type: T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]] 352 if isinstance(t.fname, str): 353 fname = [t.fname] 354 else: 355 fname = t.fname 356 to['cmd'] = fname + t.cmd_args 357 if isinstance(t.env, build.EnvironmentVariables): 358 to['env'] = t.env.get_env({}) 359 else: 360 to['env'] = t.env 361 to['name'] = t.name 362 to['workdir'] = t.workdir 363 to['timeout'] = t.timeout 364 to['suite'] = t.suite 365 to['is_parallel'] = t.is_parallel 366 to['priority'] = t.priority 367 to['protocol'] = str(t.protocol) 368 to['depends'] = t.depends 369 result.append(to) 370 return result 371 372def list_tests(testdata: T.List[backends.TestSerialisation]) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]: 373 return get_test_list(testdata) 374 375def list_benchmarks(benchdata: T.List[backends.TestSerialisation]) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]: 376 return get_test_list(benchdata) 377 378def list_projinfo(builddata: build.Build) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]: 379 result = {'version': builddata.project_version, 380 'descriptive_name': builddata.project_name, 381 'subproject_dir': builddata.subproject_dir} # type: T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]] 382 subprojects = [] 383 for k, v in builddata.subprojects.items(): 384 c = {'name': k, 385 'version': v, 386 'descriptive_name': builddata.projects.get(k)} # type: T.Dict[str, str] 387 subprojects.append(c) 388 result['subprojects'] = subprojects 389 return result 390 391def list_projinfo_from_source(intr: IntrospectionInterpreter) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]: 392 sourcedir = intr.source_root 393 files = find_buildsystem_files_list(sourcedir) 394 files = [os.path.normpath(x) for x in files] 395 396 for i in intr.project_data['subprojects']: 397 basedir = os.path.join(intr.subproject_dir, i['name']) 398 i['buildsystem_files'] = [x for x in files if x.startswith(basedir)] 399 files = [x for x in files if not x.startswith(basedir)] 400 401 intr.project_data['buildsystem_files'] = files 402 intr.project_data['subproject_dir'] = intr.subproject_dir 403 return intr.project_data 404 405def print_results(options: argparse.Namespace, results: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], indent: int) -> int: 406 if not results and not options.force_dict: 407 print('No command specified') 408 return 1 409 elif len(results) == 1 and not options.force_dict: 410 # Make to keep the existing output format for a single option 411 print(json.dumps(results[0][1], indent=indent)) 412 else: 413 out = {} 414 for i in results: 415 out[i[0]] = i[1] 416 print(json.dumps(out, indent=indent)) 417 return 0 418 419def get_infodir(builddir: T.Optional[str] = None) -> str: 420 infodir = 'meson-info' 421 if builddir is not None: 422 infodir = os.path.join(builddir, infodir) 423 return infodir 424 425def get_info_file(infodir: str, kind: T.Optional[str] = None) -> str: 426 return os.path.join(infodir, 427 'meson-info.json' if not kind else f'intro-{kind}.json') 428 429def load_info_file(infodir: str, kind: T.Optional[str] = None) -> T.Any: 430 with open(get_info_file(infodir, kind), encoding='utf-8') as fp: 431 return json.load(fp) 432 433def run(options: argparse.Namespace) -> int: 434 datadir = 'meson-private' 435 infodir = get_infodir(options.builddir) 436 if options.builddir is not None: 437 datadir = os.path.join(options.builddir, datadir) 438 indent = 4 if options.indent else None 439 results = [] # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]] 440 sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] 441 intro_types = get_meson_introspection_types(sourcedir=sourcedir) 442 443 if 'meson.build' in [os.path.basename(options.builddir), options.builddir]: 444 # Make sure that log entries in other parts of meson don't interfere with the JSON output 445 mlog.disable() 446 backend = backends.get_backend_from_name(options.backend) 447 assert backend is not None 448 intr = IntrospectionInterpreter(sourcedir, '', backend.name, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()]) 449 intr.analyze() 450 # Re-enable logging just in case 451 mlog.enable() 452 for key, val in intro_types.items(): 453 if (not options.all and not getattr(options, key, False)) or not val.no_bd: 454 continue 455 results += [(key, val.no_bd(intr))] 456 return print_results(options, results, indent) 457 458 try: 459 raw = load_info_file(infodir) 460 intro_vers = raw.get('introspection', {}).get('version', {}).get('full', '0.0.0') 461 except FileNotFoundError: 462 if not os.path.isdir(datadir) or not os.path.isdir(infodir): 463 print('Current directory is not a meson build directory.\n' 464 'Please specify a valid build dir or change the working directory to it.') 465 else: 466 print('Introspection file {} does not exist.\n' 467 'It is also possible that the build directory was generated with an old\n' 468 'meson version. Please regenerate it in this case.'.format(get_info_file(infodir))) 469 return 1 470 471 vers_to_check = get_meson_introspection_required_version() 472 for i in vers_to_check: 473 if not mesonlib.version_compare(intro_vers, i): 474 print('Introspection version {} is not supported. ' 475 'The required version is: {}' 476 .format(intro_vers, ' and '.join(vers_to_check))) 477 return 1 478 479 # Extract introspection information from JSON 480 for i in intro_types.keys(): 481 if not intro_types[i].func: 482 continue 483 if not options.all and not getattr(options, i, False): 484 continue 485 try: 486 results += [(i, load_info_file(infodir, i))] 487 except FileNotFoundError: 488 print('Introspection file {} does not exist.'.format(get_info_file(infodir, i))) 489 return 1 490 491 return print_results(options, results, indent) 492 493updated_introspection_files = [] # type: T.List[str] 494 495def write_intro_info(intro_info: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], info_dir: str) -> None: 496 global updated_introspection_files 497 for kind, data in intro_info: 498 out_file = os.path.join(info_dir, f'intro-{kind}.json') 499 tmp_file = os.path.join(info_dir, 'tmp_dump.json') 500 with open(tmp_file, 'w', encoding='utf-8') as fp: 501 json.dump(data, fp) 502 fp.flush() # Not sure if this is needed 503 os.replace(tmp_file, out_file) 504 updated_introspection_files += [kind] 505 506def generate_introspection_file(builddata: build.Build, backend: backends.Backend) -> None: 507 coredata = builddata.environment.get_coredata() 508 intro_types = get_meson_introspection_types(coredata=coredata, builddata=builddata, backend=backend) 509 intro_info = [] # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]] 510 511 for key, val in intro_types.items(): 512 if not val.func: 513 continue 514 intro_info += [(key, val.func())] 515 516 write_intro_info(intro_info, builddata.environment.info_dir) 517 518def update_build_options(coredata: cdata.CoreData, info_dir: str) -> None: 519 intro_info = [ 520 ('buildoptions', list_buildoptions(coredata)) 521 ] 522 523 write_intro_info(intro_info, info_dir) 524 525def split_version_string(version: str) -> T.Dict[str, T.Union[str, int]]: 526 vers_list = version.split('.') 527 return { 528 'full': version, 529 'major': int(vers_list[0] if len(vers_list) > 0 else 0), 530 'minor': int(vers_list[1] if len(vers_list) > 1 else 0), 531 'patch': int(vers_list[2] if len(vers_list) > 2 else 0) 532 } 533 534def write_meson_info_file(builddata: build.Build, errors: list, build_files_updated: bool = False) -> None: 535 global updated_introspection_files 536 info_dir = builddata.environment.info_dir 537 info_file = get_meson_info_file(info_dir) 538 intro_types = get_meson_introspection_types() 539 intro_info = {} 540 541 for i in intro_types.keys(): 542 if not intro_types[i].func: 543 continue 544 intro_info[i] = { 545 'file': f'intro-{i}.json', 546 'updated': i in updated_introspection_files 547 } 548 549 info_data = { 550 'meson_version': split_version_string(cdata.version), 551 'directories': { 552 'source': builddata.environment.get_source_dir(), 553 'build': builddata.environment.get_build_dir(), 554 'info': info_dir, 555 }, 556 'introspection': { 557 'version': split_version_string(get_meson_introspection_version()), 558 'information': intro_info, 559 }, 560 'build_files_updated': build_files_updated, 561 } 562 563 if errors: 564 info_data['error'] = True 565 info_data['error_list'] = [x if isinstance(x, str) else str(x) for x in errors] 566 else: 567 info_data['error'] = False 568 569 # Write the data to disc 570 tmp_file = os.path.join(info_dir, 'tmp_dump.json') 571 with open(tmp_file, 'w', encoding='utf-8') as fp: 572 json.dump(info_data, fp) 573 fp.flush() 574 os.replace(tmp_file, info_file) 575