1""" 2Copyright 2024 Google LLC 3SPDX-License-Identifier: MIT 4""" 5 6import os 7import meson_impl as impl 8import tomllib 9import re 10 11from jinja2 import Environment, FileSystemLoader 12from pathlib import Path 13 14from meson_build_state import * 15 16jinja_env = Environment( 17 loader=FileSystemLoader(Path(__file__).parent.resolve() / 'templates/') 18) 19 20# A map that holds the build-system to build file 21# Keep the keys lower-case for non-case sensitivity 22OUTPUT_FILES = {'soong': r'Android_res.bp', 'bazel': r'BUILD.bazel'} 23 24 25def generate_build_file(translator, build_type: str): 26 defaults_gen = jinja_env.get_template('defaults/all_defaults.txt') 27 defaults = defaults_gen.render() 28 # Write our manually defined defaults 29 with open(OUTPUT_FILES[build_type], 'w') as file: 30 if build_type == 'soong': 31 file.write(defaults) 32 path = build_type + '/' 33 # Render all static libraries 34 static_libs_template = jinja_env.get_template(path + 'static_library.txt') 35 for static_lib in translator.meson_state.static_libraries: 36 if static_lib.library_type is LibraryType.LibraryShared: 37 static_libs_template = jinja_env.get_template( 38 path + 'shared_library.txt' 39 ) 40 cc_lib = static_libs_template.render( 41 name=static_lib.name, 42 host_supported='false', # TODO(bpnguyen): Fix hardcoded host_supported 43 srcs=static_lib.srcs, 44 hdrs=static_lib.generated_headers, 45 generated_headers=static_lib.generated_headers, 46 generated_sources=static_lib.generated_sources, 47 copts=static_lib.copts, 48 c_std_val=static_lib.cstd, 49 cpp_std_val=static_lib.cpp_std, 50 cflags=static_lib.conlyflags, 51 cppflags=static_lib.cppflags, 52 include_directories=static_lib.local_include_dirs, 53 static_libs=static_lib.static_libs, 54 whole_static_libs=static_lib.whole_static_libs, 55 shared_libs=static_lib.shared_libs, 56 header_libs=static_lib.header_libs, 57 target_compatible_with=static_lib.target_compatible_with, 58 deps=static_lib.deps, 59 ) 60 # Set the template back to static by default 61 if static_lib.library_type is LibraryType.LibraryShared: 62 static_libs_template = jinja_env.get_template( 63 path + 'static_library.txt' 64 ) 65 file.write(cc_lib) 66 67 # Render genrules / custom targets 68 custom_target_template = jinja_env.get_template(path + 'genrule.txt') 69 for custom_target in translator.meson_state.custom_targets: 70 genrule = custom_target_template.render( 71 name=custom_target.name, 72 srcs=custom_target.srcs, 73 outs=custom_target.out, 74 tools=custom_target.tools, 75 export=len(custom_target.export_include_dirs) > 0, 76 export_include_dirs=custom_target.export_include_dirs, 77 cmd=custom_target.cmd, 78 ) 79 file.write(genrule) 80 81 # python binary hosts 82 py_binary_host_template = jinja_env.get_template(path + 'py_binary.txt') 83 for py_binary in translator.meson_state.custom_py_targets: 84 py_binary_render = py_binary_host_template.render( 85 name=py_binary.name, 86 main=py_binary.main, 87 srcs=py_binary.srcs, 88 imports=py_binary.imports, 89 ) 90 file.write(py_binary_render) 91 92 include_dir_template = jinja_env.get_template(path + 'include_directories.txt') 93 for include_dir in translator.meson_state.include_dirs: 94 include_dir_render = include_dir_template.render( 95 name=include_dir.name, 96 hdrs=include_dir.dirs, 97 ) 98 file.write(include_dir_render) 99 100 101class SoongGenerator(impl.Compiler): 102 def has_header_symbol( 103 self, 104 header: str, 105 symbol: str, 106 args=[], 107 dependencies=[], 108 include_directories=[], 109 no_builtin_args=False, 110 prefix=[], 111 required=False, 112 ): 113 # Exclude what is currently not working. 114 result = self.is_symbol_supported(header, symbol) 115 print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result))) 116 return result 117 118 def check_header(self, header, prefix=''): 119 result = self.is_header_supported(header) 120 print("check_header '%s': %s" % (header, str(result))) 121 return result 122 123 def has_function(self, function, args=[], prefix='', dependencies=''): 124 # Exclude what is currently not working. 125 result = self.is_function_supported(function) 126 print("has_function '%s': %s" % (function, str(result))) 127 return result 128 129 def links(self, snippet, name, args=[], dependencies=[]): 130 # Exclude what is currently not working. 131 result = self.is_link_supported(name) 132 print("links '%s': %s" % (name, str(result))) 133 return result 134 135 def generate(self, translator): 136 """ 137 Generates a Soong valid build file 138 :return: None 139 """ 140 # Render all the defaults to the file first. 141 generate_build_file(translator, 'soong') 142 143 144class BazelGenerator(impl.Compiler): 145 def is_symbol_supported(self, header: str, symbol: str): 146 if header in meson_translator.config.headers_not_supported: 147 return False 148 if symbol in meson_translator.config.symbols_not_supported: 149 return False 150 return super().is_symbol_supported(header, symbol) 151 152 def is_function_supported(self, function): 153 if function in meson_translator.config.functions_not_supported: 154 return False 155 return super().is_function_supported(function) 156 157 def is_link_supported(self, name): 158 if name in meson_translator.config.links_not_supported: 159 return False 160 return super().is_link_supported(name) 161 162 def has_header_symbol( 163 self, 164 header: str, 165 symbol: str, 166 args=None, 167 dependencies=None, 168 include_directories=None, 169 no_builtin_args=False, 170 prefix=None, 171 required=False, 172 ): 173 if args is None: 174 args = [] 175 if dependencies is None: 176 dependencies = [] 177 if include_directories is None: 178 include_directories = [] 179 if prefix is None: 180 prefix = [] 181 # Exclude what is currently not working. 182 result = self.is_symbol_supported(header, symbol) 183 print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result))) 184 return result 185 186 def check_header(self, header, prefix=''): 187 result = self.is_header_supported(header) 188 print(f"check_header '{header}': {result}") 189 return result 190 191 def has_function(self, function, args=[], prefix='', dependencies='') -> bool: 192 # Exclude what is currently not working. 193 result = self.is_function_supported(function) 194 print(f"has_function '{function}': {result}") 195 return result 196 197 def links(self, snippet, name, args=[], dependencies=[]): 198 # Exclude what is currently not working. 199 result = self.is_link_supported(name) 200 print(f"links '{name}': {result}") 201 return result 202 203 def generate(self, translator): 204 generate_build_file(translator, 'bazel') 205 206 207class BazelPkgConfigModule(impl.PkgConfigModule): 208 def generate( 209 self, 210 lib, 211 name='', 212 description='', 213 extra_cflags=None, 214 filebase='', 215 version='', 216 libraries=None, 217 libraries_private=None, 218 ): 219 if extra_cflags is None: 220 extra_cflags = [] 221 impl.fprint('# package library') 222 impl.fprint('cc_library(') 223 impl.fprint(' name = "%s",' % name) 224 assert type(lib) is impl.StaticLibrary 225 impl.fprint(' deps = [ "%s" ],' % lib.target_name) 226 # This line tells Bazel to use -isystem for targets that depend on this one, 227 # which is needed for clients that include package headers with angle brackets. 228 impl.fprint(' includes = [ "." ],') 229 impl.fprint(' visibility = [ "//visibility:public" ],') 230 impl.fprint(')') 231 232 233class MesonTranslator: 234 """ 235 Abstract class that defines all common attributes 236 between different build systems (Soong, Bazel, etc...) 237 """ 238 239 def __init__(self): 240 self._generator = None 241 self._config_file = None 242 self._build = '' 243 # configs and meson_project_states are ordered with each other. 244 self._configs: list[ProjectConfig] = [] 245 self._meson_project_states: list[MesonProjectState] = [] 246 # TODO(357080225): Fix the use hardcoded state 0 247 self._state = 0 # index of self._configs 248 249 def init_data(self, config_file: str): 250 self._config_file: str = config_file 251 print('CONFIG:', self._config_file) 252 self._init_metadata() 253 _generator = ( 254 SoongGenerator(self.config.cpu_family) 255 if self._build.lower() == 'soong' 256 else BazelGenerator(self.config.cpu_family) 257 ) 258 self._generator: impl.Compiler = _generator 259 return self 260 261 @property 262 def config_file(self) -> Path: 263 return self._config_file 264 265 @property 266 def host_machine(self) -> str: 267 return self._configs[self._state].host_machine 268 269 @property 270 def build_machine(self) -> str: 271 return self._configs[self._state].build_machine 272 273 @property 274 def generator(self) -> impl.Compiler: 275 return self._generator 276 277 @property 278 def build(self) -> str: 279 return self._build 280 281 @property 282 def config(self) -> ProjectConfig: 283 return self._configs[self._state] 284 285 @property 286 def meson_state(self): 287 return self._meson_project_states[self._state] 288 289 def is_soong(self): 290 return self._build.lower() == 'soong' 291 292 def is_bazel(self): 293 return self._build.lower() == 'bazel' 294 295 def get_output_filename(self) -> str: 296 return OUTPUT_FILES[self._build.lower()] 297 298 def _init_metadata(self): 299 """ 300 To be called after self._config_file has alredy been assigned. 301 Parses the given config files for common build attributes 302 Fills the list of all project configs 303 :return: None 304 """ 305 with open(self._config_file, 'rb') as f: 306 data = tomllib.load(f) 307 self._build = data.get('build') 308 base_config = data.get('base_project_config') 309 310 configs = data.get('project_config') 311 for config in configs: 312 proj_config = ProjectConfig.create_project_config(self._build, **config) 313 self._configs.append( 314 proj_config 315 ) 316 self._meson_project_states.append(MesonProjectState()) 317 318 new_configs = [] 319 # Handle Inheritance 320 for config in self._configs: 321 # Parent config, that contains shared attributes 322 base_proj_config = ProjectConfig.create_project_config(self._build, **base_config) 323 if not config.inherits_from: 324 new_configs.append(config) 325 continue 326 if config.inherits_from == 'base_project_config': 327 new_configs.append( 328 base_proj_config.extend(config).deepcopy() 329 ) 330 else: 331 for proj_config in self._configs: 332 if config.inherits_from == proj_config.name: 333 new_configs.append( 334 proj_config.extend(config).deepcopy() 335 ) 336 self._configs = new_configs 337 338 339# Declares an empty attribute data class 340# metadata is allocated during Generators-runtime (I.E. generate_<host>_build.py) 341meson_translator = MesonTranslator() 342 343_gIncludeDirectories = {} 344 345 346def load_meson_data(config_file: str): 347 meson_translator.init_data(config_file) 348 349 350def open_output_file(): 351 impl.open_output_file(meson_translator.get_output_filename()) 352 353 354def close_output_file(): 355 impl.close_output_file() 356 357 358def add_subdirs_to_set(dir_, dir_set): 359 subdirs = os.listdir(dir_) 360 for subdir in subdirs: 361 subdir = os.path.join(dir_, subdir) 362 if os.path.isdir(subdir): 363 dir_set.add(subdir) 364 365 366def include_directories(*paths, is_system=False): 367 if meson_translator.host_machine == 'android': 368 return impl.IncludeDirectories('', impl.get_include_dirs(paths)) 369 # elif host_machine == 'fuchsia' 370 dirs = impl.get_include_dirs(paths) 371 name = dirs[0].replace('/', '_') 372 if is_system: 373 name += '_sys' 374 375 global _gIncludeDirectories 376 if name not in _gIncludeDirectories: 377 # Mesa likes to include files at a level down from the include path, 378 # so ensure Bazel allows this by including all of the possibilities. 379 # Can't repeat entries so use a set. 380 dir_set = set() 381 for dir_ in dirs: 382 dir_ = os.path.normpath(dir_) 383 dir_set.add(dir_) 384 add_subdirs_to_set(dir_, dir_set) 385 386 # HACK: For special cases we go down two levels: 387 # - Mesa vulkan runtime does #include "vulkan/wsi/..." 388 paths_needing_two_levels = ['src/vulkan'] 389 for dir_ in list(dir_set): 390 if dir_ in paths_needing_two_levels: 391 add_subdirs_to_set(dir_, dir_set) 392 393 include_dir = IncludeDirectories() 394 include_dir.name = name 395 396 for dir_ in dir_set: 397 include_dir.dirs.append(os.path.normpath(os.path.join(dir_, '*.h'))) 398 include_dir.dirs.append(os.path.normpath(os.path.join(dir_, '*.c'))) 399 include_dir.visibility.append('//visibility:public') 400 _gIncludeDirectories[name] = impl.IncludeDirectories(name, dirs) 401 meson_translator.meson_state.include_dirs.append(include_dir) 402 return _gIncludeDirectories[name] 403 404 405def module_import(name: str): 406 if name == 'python': 407 return impl.PythonModule() 408 if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'fuchsia': 409 return BazelPkgConfigModule() 410 if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'android': 411 return impl.PkgConfigModule() 412 if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'linux': 413 return impl.PkgConfigModule() 414 exit( 415 f'Unhandled module: "{name}" for host machine: "{meson_translator.host_machine}"' 416 ) 417 418 419def load_dependencies(): 420 impl.load_dependencies(meson_translator.config_file) 421 422 423def dependency( 424 *names, 425 required=True, 426 version='', 427 allow_fallback=False, 428 method='', 429 modules=[], 430 optional_modules=[], 431 static=True, 432 fallback=[], 433 include_type='', 434 native=False, 435): 436 return impl.dependency(*names, required=required, version=version) 437 438 439def static_library( 440 target_name, 441 *source_files, 442 c_args=[], 443 cpp_args=[], 444 c_pch='', 445 build_by_default=False, 446 build_rpath='', 447 d_debug=[], 448 d_import_dirs=[], 449 d_module_versions=[], 450 d_unittest=False, 451 dependencies=[], 452 extra_files='', 453 gnu_symbol_visibility='', 454 gui_app=False, 455 implicit_include_directories=False, 456 include_directories=[], 457 install=False, 458 install_dir='', 459 install_mode=[], 460 install_rpath='', 461 install_tag='', 462 link_args=[], 463 link_depends='', 464 link_language='', 465 link_whole=[], 466 link_with=[], 467 name_prefix='', 468 name_suffix='', 469 native=False, 470 objects=[], 471 override_options=[], 472 pic=False, 473 prelink=False, 474 rust_abi='', 475 rust_crate_type='', 476 rust_dependency_map={}, 477 sources='', 478 vala_args=[], 479 win_subsystem='', 480): 481 print('static_library: ' + target_name) 482 483 link_with = impl.get_linear_list(link_with) 484 link_whole = impl.get_linear_list(link_whole) 485 486 # Emitting link_with/link_whole into a static library isn't useful for building, 487 # shared libraries must specify the whole chain of dependencies. 488 # Leaving them here for traceability though maybe it's less confusing to remove them? 489 _emit_builtin_target( 490 target_name, 491 *source_files, 492 c_args=c_args, 493 cpp_args=cpp_args, 494 dependencies=dependencies, 495 include_directories=include_directories, 496 link_with=link_with, 497 link_whole=link_whole, 498 builtin_type_name='cc_library_static', 499 static=meson_translator.is_bazel(), 500 library_type=LibraryType.LibraryStatic, 501 ) 502 503 return impl.StaticLibrary(target_name, link_with=link_with, link_whole=link_whole) 504 505 506def _get_sources(input_sources, sources, generated_sources, generated_headers): 507 def generate_sources_fuchsia(source_): 508 if type(source_) is impl.CustomTarget: 509 generated_sources.add(source_.target_name()) 510 for out in source_.outputs: 511 sources.add(out) 512 elif type(source_) is impl.CustomTargetItem: 513 target = source_.target 514 generated_sources.add(target.target_name()) 515 sources.add(target.outputs[source_.index]) 516 517 def generate_sources_android(source_): 518 if type(source_) is impl.CustomTarget: 519 if source_.generates_h(): 520 generated_headers.add(source_.target_name_h()) 521 if source_.generates_c(): 522 generated_sources.add(source_.target_name_c()) 523 elif type(source_) is impl.CustomTargetItem: 524 source_ = source_.target 525 if source_.generates_h(): 526 generated_headers.add(source_.target_name_h()) 527 if source_.generates_c(): 528 generated_sources.add(source_.target_name_c()) 529 530 for source in input_sources: 531 if type(source) is list: 532 _get_sources(source, sources, generated_sources, generated_headers) 533 elif type(source) is impl.CustomTarget or type(source) is impl.CustomTargetItem: 534 if meson_translator.is_bazel(): 535 generate_sources_fuchsia(source) 536 else: # is_soong 537 generate_sources_android(source) 538 elif type(source) is impl.File: 539 sources.add(source.name) 540 elif type(source) is str: 541 sources.add(impl.get_relative_dir(source)) 542 else: 543 exit(f'source type not handled: {type(source)}') 544 545 546# TODO(bpnguyen): Implement some bazel/soong parser that would merge 547# logic from this function to _emit_builtin_target_android 548def _emit_builtin_target_fuchsia( 549 target_name, 550 *source, 551 static=False, 552 c_args=[], 553 cpp_args=[], 554 dependencies=[], 555 include_directories=[], 556 link_with=[], 557 link_whole=[], 558 library_type=LibraryType.Library, 559): 560 sl = StaticLibrary() 561 target_name_so = target_name 562 target_name = target_name if static else '_' + target_name 563 sl.name = target_name 564 sl.library_type = library_type 565 566 srcs = set() 567 generated_sources = set() 568 generated_headers = set() 569 generated_header_files = [] 570 for source_arg in source: 571 assert type(source_arg) is list 572 _get_sources(source_arg, srcs, generated_sources, generated_headers) 573 574 deps = impl.get_set_of_deps(dependencies) 575 include_directories = impl.get_include_directories(include_directories) 576 static_libs = [] 577 whole_static_libs = [] 578 shared_libs = [] 579 for dep in deps: 580 print(' dep: ' + dep.name) 581 for src in impl.get_linear_list([dep.sources]): 582 if type(src) is impl.CustomTargetItem: 583 generated_headers.add(src.target.target_name_h()) 584 generated_header_files.extend(src.target.header_outputs()) 585 elif type(src) is impl.CustomTarget: 586 generated_headers.add(src.target_name_h()) 587 generated_header_files.extend(src.header_outputs()) 588 else: 589 exit('Unhandled source dependency: ' + str(type(src))) 590 include_directories.extend( 591 impl.get_include_directories(dep.include_directories) 592 ) 593 for target in impl.get_static_libs([dep.link_with]): 594 assert type(target) is impl.StaticLibrary 595 static_libs.append(target.target_name) 596 for target in impl.get_linear_list([dep.link_whole]): 597 assert type(target) is impl.StaticLibrary 598 whole_static_libs.append(target.target_name) 599 for target in dep.targets: 600 if target.target_type == impl.DependencyTargetType.SHARED_LIBRARY: 601 shared_libs.append(target.target_name) 602 elif target.target_type == impl.DependencyTargetType.STATIC_LIBRARY: 603 static_libs.append(target.target_name) 604 elif target.target_type == impl.DependencyTargetType.HEADER_LIBRARY: 605 exit('Header library not supported') 606 c_args.append(dep.compile_args) 607 cpp_args.append(dep.compile_args) 608 609 for target in impl.get_static_libs(link_with): 610 if type(target) is impl.StaticLibrary: 611 static_libs.append(target.target_name) 612 else: 613 exit('Unhandled link_with type: ' + str(type(target))) 614 615 for target in impl.get_whole_static_libs(link_whole): 616 if type(target) is impl.StaticLibrary: 617 whole_static_libs.append(target.target_name()) 618 else: 619 exit('Unhandled link_whole type: ' + str(type(target))) 620 621 # Android turns all warnings into errors but thirdparty projects typically can't handle that 622 cflags = ['-Wno-error'] + impl.get_linear_list(impl.get_project_cflags() + c_args) 623 cppflags = ['-Wno-error'] + impl.get_linear_list( 624 impl.get_project_cppflags() + cpp_args 625 ) 626 627 has_c_files = False 628 for src in srcs: 629 if src.endswith('.c'): 630 has_c_files = True 631 sl.srcs.append(src) 632 633 # For Bazel to find headers in the "current area", we have to include 634 # not just the headers in the relative dir, but also relative subdirs 635 # that aren't themselves areas (containing meson.build). 636 # We only look one level deep. 637 local_include_dirs = [impl.get_relative_dir()] 638 local_entries = ( 639 [] if impl.get_relative_dir() == '' else os.listdir(impl.get_relative_dir()) 640 ) 641 for entry in local_entries: 642 entry = os.path.join(impl.get_relative_dir(), entry) 643 if os.path.isdir(entry): 644 subdir_entries = os.listdir(entry) 645 if 'meson.build' not in subdir_entries: 646 local_include_dirs.append(entry) 647 648 for hdr in set(generated_header_files): 649 sl.generated_headers.append(hdr) 650 for hdr in local_include_dirs: 651 sl.local_include_dirs.append(os.path.normpath(os.path.join(hdr, '*.h'))) 652 # Needed for subdir sources 653 sl.copts.append(f'-I {impl.get_relative_dir()}') 654 sl.copts.append(f'-I $(GENDIR)/{impl.get_relative_dir()}') 655 for inc in include_directories: 656 for dir in inc.dirs: 657 sl.copts.append(f'-I {dir}') 658 sl.copts.append(f'-I $(GENDIR)/{dir}') 659 660 if has_c_files: 661 for arg in cflags: 662 # Double escape double quotations 663 arg = re.sub(r'"', '\\\\\\"', arg) 664 sl.copts.append(arg) 665 else: 666 for arg in cppflags: 667 # Double escape double quotations 668 arg = re.sub(r'"', '\\\\\\"', arg) 669 sl.copts.append(arg) 670 671 # Ensure bazel deps are unique 672 bazel_deps = set() 673 for lib in static_libs: 674 bazel_deps.add(lib) 675 for lib in whole_static_libs: 676 bazel_deps.add(lib) 677 for inc in include_directories: 678 bazel_deps.add(inc.name) 679 for target in generated_headers: 680 bazel_deps.add(target) 681 for target in generated_sources: 682 bazel_deps.add(target) 683 684 for bdep in bazel_deps: 685 sl.deps.append(bdep) 686 687 sl.target_compatible_with.append('@platforms//os:fuchsia') 688 sl.visibility.append('//visibility:public') 689 690 meson_translator.meson_state.static_libraries.append(sl) 691 692 if not static: 693 shared_sl = StaticLibrary() 694 shared_sl.library_type = LibraryType.LibraryShared 695 shared_sl.name = target_name_so 696 shared_sl.deps.append(target_name) 697 meson_translator.meson_state.static_libraries.append(shared_sl) 698 699 700# TODO(bpnguyen): Implement some bazel/soong parser that would merge 701# logic from this function to _emit_builtin_target_fuchsia 702def _emit_builtin_target_android( 703 target_name, 704 *source, 705 c_args=[], 706 cpp_args=[], 707 dependencies=[], 708 include_directories=[], 709 link_with=[], 710 link_whole=[], 711 builtin_type_name='', 712 static=False, 713 library_type=LibraryType.Library, 714): 715 static_lib = StaticLibrary() 716 static_lib.name = target_name 717 static_lib.library_type = library_type 718 719 srcs = set() 720 generated_sources = set() 721 generated_headers = set() 722 for source_arg in source: 723 assert type(source_arg) is list 724 _get_sources(source_arg, srcs, generated_sources, generated_headers) 725 726 deps = impl.get_set_of_deps(dependencies) 727 include_directories = [impl.get_relative_dir()] + impl.get_include_dirs( 728 include_directories 729 ) 730 static_libs = [] 731 whole_static_libs = [] 732 shared_libs = [] 733 header_libs = [] 734 for dep in deps: 735 print(' dep: ' + dep.name) 736 for src in impl.get_linear_list([dep.sources]): 737 if type(src) is impl.CustomTargetItem: 738 generated_headers.add(src.target.target_name_h()) 739 elif type(src) is impl.CustomTarget: 740 generated_headers.add(src.target_name_h()) 741 else: 742 exit('Unhandled source dependency: ' + str(type(src))) 743 include_directories.extend(impl.get_include_dirs(dep.include_directories)) 744 for target in impl.get_static_libs([dep.link_with]): 745 assert type(target) is impl.StaticLibrary 746 static_libs.append(target.target_name) 747 for target in impl.get_linear_list([dep.link_whole]): 748 assert type(target) is impl.StaticLibrary 749 whole_static_libs.append(target.target_name) 750 for target in dep.targets: 751 if target.target_type is impl.DependencyTargetType.SHARED_LIBRARY: 752 shared_libs.append(target.target_name) 753 elif target.target_type is impl.DependencyTargetType.STATIC_LIBRARY: 754 static_libs.append(target.target_name) 755 elif target.target_type is impl.DependencyTargetType.HEADER_LIBRARY: 756 header_libs.append(target.target_name) 757 c_args.append(dep.compile_args) 758 cpp_args.append(dep.compile_args) 759 760 for target in impl.get_static_libs(link_with): 761 if type(target) is impl.StaticLibrary: 762 static_libs.append(target.target_name) 763 else: 764 exit(f'Unhandled link_with type: {type(target)}') 765 766 for target in impl.get_whole_static_libs(link_whole): 767 if type(target) is impl.StaticLibrary: 768 whole_static_libs.append(target.target_name()) 769 else: 770 exit(f'Unhandled link_whole type: {type(target)}') 771 772 # Android turns all warnings into errors but thirdparty projects typically can't handle that 773 cflags = ['-Wno-error'] + impl.get_linear_list(impl.get_project_cflags() + c_args) 774 cppflags = ['-Wno-error'] + impl.get_linear_list( 775 impl.get_project_cppflags() + cpp_args 776 ) 777 778 for src in srcs: 779 # Filter out header files 780 if not src.endswith('.h'): 781 static_lib.srcs.append(src) 782 for generated in generated_headers: 783 static_lib.generated_headers.append(generated) 784 for generated in generated_sources: 785 static_lib.generated_sources.append(generated) 786 787 for arg in impl.get_project_options(): 788 if arg.name == 'c_std': 789 static_lib.cstd = arg.value 790 elif arg.name == 'cpp_std': 791 static_lib.cpp_std = arg.value 792 793 for arg in cflags: 794 # Escape double quotations 795 arg = re.sub(r'"', '\\"', arg) 796 static_lib.conlyflags.append(arg) 797 for arg in cppflags: 798 # Escape double quotations 799 arg = re.sub(r'"', '\\"', arg) 800 static_lib.cppflags.append(arg) 801 802 for inc in include_directories: 803 static_lib.local_include_dirs.append(inc) 804 805 for lib in static_libs: 806 static_lib.static_libs.append(lib) 807 808 for lib in whole_static_libs: 809 static_lib.whole_static_libs.append(lib) 810 811 for lib in shared_libs: 812 static_lib.shared_libs.append(lib) 813 814 for lib in header_libs: 815 static_lib.header_libs.append(lib) 816 817 meson_translator.meson_state.static_libraries.append(static_lib) 818 819 820def _emit_builtin_target( 821 target_name, 822 *source, 823 c_args=[], 824 cpp_args=[], 825 dependencies=[], 826 include_directories=[], 827 link_with=[], 828 link_whole=[], 829 builtin_type_name='', 830 static=False, 831 library_type=LibraryType.Library, 832): 833 if meson_translator.is_bazel(): 834 _emit_builtin_target_fuchsia( 835 target_name, 836 *source, 837 c_args=c_args, 838 cpp_args=cpp_args, 839 dependencies=dependencies, 840 include_directories=include_directories, 841 link_with=link_with, 842 link_whole=link_whole, 843 static=static, 844 library_type=library_type, 845 ) 846 else: # meson_translator.is_soong() 847 _emit_builtin_target_android( 848 target_name, 849 *source, 850 c_args=c_args, 851 cpp_args=cpp_args, 852 dependencies=dependencies, 853 include_directories=include_directories, 854 link_with=link_with, 855 link_whole=link_whole, 856 builtin_type_name=builtin_type_name, 857 library_type=library_type, 858 ) 859 860 861def shared_library( 862 target_name, 863 *source, 864 c_args=[], 865 cpp_args=[], 866 c_pch='', 867 build_by_default=False, 868 build_rpath='', 869 d_debug=[], 870 d_import_dirs=[], 871 d_module_versions=[], 872 d_unittest=False, 873 darwin_versions='', 874 dependencies=[], 875 extra_files='', 876 gnu_symbol_visibility='', 877 gui_app=False, 878 implicit_include_directories=False, 879 include_directories=[], 880 install=False, 881 install_dir='', 882 install_mode=[], 883 install_rpath='', 884 install_tag='', 885 link_args=[], 886 link_depends=[], 887 link_language='', 888 link_whole=[], 889 link_with=[], 890 name_prefix='', 891 name_suffix='', 892 native=False, 893 objects=[], 894 override_options=[], 895 rust_abi='', 896 rust_crate_type='', 897 rust_dependency_map={}, 898 sources=[], 899 soversion='', 900 vala_args=[], 901 version='', 902 vs_module_defs='', 903 win_subsystem='', 904): 905 print('shared_library: ' + target_name) 906 907 link_with = impl.get_linear_list([link_with]) 908 link_whole = impl.get_linear_list([link_whole]) 909 910 _emit_builtin_target( 911 target_name, 912 *source, 913 c_args=c_args, 914 static=False, 915 cpp_args=cpp_args, 916 dependencies=dependencies, 917 include_directories=include_directories, 918 link_with=link_with, 919 link_whole=link_whole, 920 builtin_type_name='cc_library_shared', 921 library_type=LibraryType.LibraryStatic, 922 ) 923 return impl.SharedLibrary(target_name) 924 925 926def _process_target_name(name): 927 name = re.sub(r'[\[\]]', '', name) 928 return name 929 930 931def _location_wrapper(name_or_list) -> str | list[str]: 932 if isinstance(name_or_list, list): 933 ret = [] 934 for i in name_or_list: 935 ret.append(f'$(location {i})') 936 return ret 937 938 assert isinstance(name_or_list, str) 939 return f'$(location {name_or_list})' 940 941 942def _is_header(name): 943 return re.search(r'\.h[xx|pp]?$', name) is not None 944 945 946def _is_source(name): 947 return re.search(r'\.c[c|xx|pp]?$', name) is not None 948 949 950def _get_command_args( 951 command, 952 input, 953 output, 954 deps, 955 location_wrap=False, 956 obfuscate_output_c=False, 957 obfuscate_output_h=False, 958 obfuscate_suffix='', 959): 960 args = [] 961 gendir = 'GENDIR' if meson_translator.is_bazel() else 'genDir' 962 for command_item in command[1:]: 963 if isinstance(command_item, list): 964 for item in command_item: 965 if type(item) is impl.File: 966 args.append( 967 _location_wrapper(item.name) if location_wrap else item.name 968 ) 969 elif type(item) is str: 970 args.append(item) 971 continue 972 973 assert type(command_item) is str 974 match = re.match(r'@INPUT([0-9])?@', command_item) 975 if match is not None: 976 if match.group(1) is not None: 977 input_index = int(match.group(1)) 978 input_list = impl.get_list_of_relative_inputs(input[input_index]) 979 else: 980 input_list = impl.get_list_of_relative_inputs(input) 981 args.extend(_location_wrapper(input_list) if location_wrap else input_list) 982 continue 983 984 match = re.match(r'(.*?)@OUTPUT([0-9])?@', command_item) 985 if match is not None: 986 output_list = [] 987 if match.group(2) is not None: 988 output_index = int(match.group(2)) 989 selected_output = ( 990 output[output_index] if isinstance(output, list) else output 991 ) 992 output_list.append(impl.get_relative_gen_dir(selected_output)) 993 elif isinstance(output, list): 994 for out in output: 995 output_list.append(impl.get_relative_gen_dir(out)) 996 else: 997 output_list.append(impl.get_relative_gen_dir(output)) 998 for out in output_list: 999 if _is_header(out) and obfuscate_output_h: 1000 args.append( 1001 match.group(1) + f'$({gendir})/{out}' if location_wrap else out 1002 ) 1003 else: 1004 if _is_source(out) and obfuscate_output_c: 1005 out += obfuscate_suffix 1006 args.append( 1007 match.group(1) + _location_wrapper(out) 1008 if location_wrap 1009 else out 1010 ) 1011 continue 1012 1013 # Assume used to locate generated outputs 1014 match = re.match(r'(.*?)@CURRENT_BUILD_DIR@', command_item) 1015 if match is not None: 1016 args.append(f'$({gendir})' + '/' + impl.get_relative_dir()) 1017 continue 1018 1019 if meson_translator.is_bazel(): 1020 match = re.match(r'@PROJECT_BUILD_ROOT@(.*)', command_item) 1021 if match is not None: 1022 args.append(f'$({gendir}){match.group(1)}') 1023 continue 1024 1025 # A plain arg 1026 if ' ' in command_item: 1027 args.append(f"'{command_item}'") 1028 else: 1029 args.append(command_item) 1030 1031 return args 1032 1033 1034def library( 1035 target_name, 1036 *sources, 1037 c_args=[], 1038 install=False, 1039 link_args=[], 1040 vs_module_defs='', 1041 version='', 1042): 1043 print('library: ' + target_name) 1044 1045 return static_library( 1046 target_name, 1047 *sources, 1048 c_args=c_args, 1049 install=install, 1050 link_args=link_args, 1051 sources=sources, 1052 ) 1053 1054 1055# Assume dependencies of custom targets are custom targets that are generating 1056# python scripts; build a python path of their locations. 1057def _get_python_path(deps): 1058 python_path = '' 1059 for index, dep in enumerate(deps): 1060 assert type(dep) is impl.CustomTarget 1061 if index > 0: 1062 python_path += ':' 1063 python_path += '`dirname %s`' % _location_wrapper(':%s' % dep.target_name()) 1064 return python_path 1065 1066 1067def _get_export_include_dirs(): 1068 dirs = [impl.get_relative_dir()] 1069 # HACK for source files that expect that include generated files like: 1070 # include "vulkan/runtime/...h" 1071 if impl.get_relative_dir().startswith('src'): 1072 dirs.append('src') 1073 return dirs 1074 1075 1076def _process_wrapped_args_for_python( 1077 wrapped_args, python_script, python_script_target_name, deps 1078): 1079 # The python script arg should be replaced with the python binary target name 1080 args = impl.replace_wrapped_input_with_target( 1081 wrapped_args, python_script, python_script_target_name 1082 ) 1083 if meson_translator.is_bazel(): 1084 return args 1085 # else is_soong(): 1086 python_path = 'PYTHONPATH=' 1087 # Python scripts expect to be able to import other scripts from the same directory, but this 1088 # doesn't work in the soong execution environment, so we have to explicitly add the script 1089 # dir. We can't use $(location python_binary) because that is missing the relative path; 1090 # instead we can use $(location python_script), which happens to work, and we're careful to 1091 # ensure the script is in the list of sources even when it's used as the command directly. 1092 python_path += '`dirname $(location %s)`' % python_script 1093 # Also ensure that scripts generated by dependent custom targets can be imported. 1094 if type(deps) is impl.CustomTarget: 1095 python_path += ':' + _get_python_path([deps]) 1096 if type(deps) is list: 1097 python_path += ':' + _get_python_path(deps) 1098 args.insert(0, python_path) 1099 return args 1100 1101 1102# TODO(bpnguyen): merge custom_target 1103def custom_target( 1104 target_name: str, 1105 build_always=False, 1106 build_always_stale=False, 1107 build_by_default=False, 1108 capture=False, 1109 command=[], 1110 console=False, 1111 depend_files=[], 1112 depends=[], 1113 depfile='', 1114 env=[], 1115 feed=False, 1116 input=[], 1117 install=False, 1118 install_dir='', 1119 install_mode=[], 1120 install_tag=[], 1121 output=[], 1122): 1123 target_name = _process_target_name(target_name) 1124 print('Custom target: ' + target_name) 1125 assert type(command) is list 1126 program = command[0] 1127 program_args = [] 1128 1129 # The program can be an array that includes arguments 1130 if isinstance(program, list): 1131 for arg in program[1:]: 1132 assert type(arg) is str 1133 program_args.append(arg) 1134 program = program[0] 1135 1136 assert isinstance(program, impl.Program) 1137 assert program.found() 1138 1139 args = program_args + _get_command_args(command, input, output, depends) 1140 1141 # Python scripts need special handling to find mako library 1142 python_script = '' 1143 python_script_target_name = '' 1144 if program.command.endswith('.py'): 1145 python_script = program.command 1146 else: 1147 for index, arg in enumerate(args): 1148 if arg.endswith('.py'): 1149 python_script = arg 1150 break 1151 if python_script != '': 1152 python_script_target_name = target_name + '_' + os.path.basename(python_script) 1153 srcs = [python_script] + impl.get_list_of_relative_inputs(depend_files) 1154 python_custom_target = PythonCustomTarget() 1155 python_custom_target.name = python_script_target_name 1156 python_custom_target.main = python_script 1157 for src in srcs: 1158 if src.endswith('.py'): 1159 python_custom_target.srcs.append(src) 1160 for src in set(srcs): 1161 if src.endswith('.py'): 1162 python_custom_target.imports.append(os.path.dirname(src)) 1163 1164 meson_translator.meson_state.custom_py_targets.append(python_custom_target) 1165 1166 relative_inputs = impl.get_list_of_relative_inputs(input) 1167 # We use python_host_binary instead of calling python scripts directly; 1168 # however there's an issue with python locating modules in the same directory 1169 # as the script; to workaround that (see _process_wrapped_args_for_python) we 1170 # ensure the script is listed in the genrule targets. 1171 if python_script != '' and python_script not in relative_inputs: 1172 relative_inputs.append(python_script) 1173 relative_inputs.extend(impl.get_list_of_relative_inputs(depend_files)) 1174 1175 relative_inputs_set = set() 1176 if meson_translator.is_soong(): 1177 for src in relative_inputs: 1178 relative_inputs_set.add(src) 1179 1180 relative_outputs = [] 1181 if isinstance(output, list): 1182 for file in output: 1183 relative_outputs.append(impl.get_relative_gen_dir(file)) 1184 else: 1185 assert type(output) is str 1186 relative_outputs.append(impl.get_relative_gen_dir(output)) 1187 1188 generates_h = False 1189 generates_c = False 1190 custom_target_ = None 1191 if meson_translator.is_soong(): 1192 # Soong requires genrule to generate only headers OR non-headers 1193 for out in relative_outputs: 1194 if _is_header(out): 1195 generates_h = True 1196 if _is_source(out): 1197 generates_c = True 1198 custom_target_ = impl.CustomTarget( 1199 target_name, relative_outputs, generates_h, generates_c 1200 ) 1201 else: # is_bazel: 1202 custom_target_ = impl.CustomTarget(target_name, relative_outputs) 1203 1204 program_command = program.command 1205 if meson_translator.is_soong(): 1206 if program_command == 'bison': 1207 program_command_arg = 'M4=$(location m4) $(location bison)' 1208 elif program_command == 'flex': 1209 program_command_arg = 'M4=$(location m4) $(location flex)' 1210 elif program_command.endswith('.py'): 1211 program_command_arg = _location_wrapper(program_command) 1212 else: 1213 program_command_arg = program_command 1214 1215 program_args = [program_command_arg] + program_args 1216 1217 if custom_target_.generates_h() and custom_target_.generates_c(): 1218 # Make a rule for only the headers 1219 obfuscate_suffix = '.dummy.h' 1220 wrapped_args = program_args + _get_command_args( 1221 command, 1222 input, 1223 output, 1224 depends, 1225 location_wrap=True, 1226 obfuscate_output_c=True, 1227 obfuscate_suffix=obfuscate_suffix, 1228 ) 1229 if python_script: 1230 wrapped_args = _process_wrapped_args_for_python( 1231 wrapped_args, python_script, python_script_target_name, depends 1232 ) 1233 1234 command_line = impl.get_command_line_from_args(wrapped_args) 1235 if capture: 1236 command_line += ' > %s' % _location_wrapper( 1237 impl.get_relative_gen_dir(output) 1238 ) 1239 ct = CustomTarget() 1240 ct.name = custom_target_.target_name_h() 1241 for src in relative_inputs_set: 1242 ct.srcs.append(src) 1243 for dep in depends: 1244 assert type(dep) is impl.CustomTarget 1245 ct.srcs.append(':' + dep.target_name()) 1246 for out in relative_outputs: 1247 if _is_source(out): 1248 out += obfuscate_suffix 1249 # The scripts may still write to the assumed .c file, ensure the obfuscated 1250 # file exists 1251 command_line += ( 1252 "; echo '//nothing to see here' > " + _location_wrapper(out) 1253 ) 1254 ct.out.append(out) 1255 if python_script_target_name != '': 1256 ct.tools.append(python_script_target_name) 1257 1258 if program_command == 'bison' or program_command == 'flex': 1259 ct.tools.append('m4') 1260 ct.tools.append(program_command) 1261 1262 for dir in _get_export_include_dirs(): 1263 ct.export_include_dirs.append(dir) 1264 1265 ct.cmd = command_line 1266 meson_translator.meson_state.custom_targets.append(ct) 1267 # Make a rule for only the sources 1268 obfuscate_suffix = '.dummy.c' 1269 wrapped_args = program_args + _get_command_args( 1270 command, 1271 input, 1272 output, 1273 depends, 1274 location_wrap=True, 1275 obfuscate_output_h=True, 1276 obfuscate_suffix=obfuscate_suffix, 1277 ) 1278 if python_script: 1279 wrapped_args = _process_wrapped_args_for_python( 1280 wrapped_args, python_script, python_script_target_name, depends 1281 ) 1282 1283 command_line = impl.get_command_line_from_args(wrapped_args) 1284 if capture: 1285 command_line += ' > %s' % _location_wrapper( 1286 impl.get_relative_gen_dir(output) 1287 ) 1288 1289 # We keep the header as an output with an obfuscated name because some scripts insist 1290 # on having --out-h (like vk_entrypoints_gen.py). When Soong depends on this genrule 1291 # it'll insist on compiling all the outputs, so we replace the content of all header 1292 # outputs. 1293 ct_ = CustomTarget() 1294 ct_.name = custom_target_.target_name_c() 1295 1296 for src in relative_inputs_set: 1297 ct_.srcs.append(src) 1298 for dep in depends: 1299 assert type(dep) is impl.CustomTarget 1300 ct_.srcs.append(':' + dep.target_name()) 1301 for out in relative_outputs: 1302 if _is_header(out): 1303 out += obfuscate_suffix 1304 ct_.out.append(out) 1305 # Remove the content because Soong will compile this dummy source file 1306 command_line += ( 1307 "; echo '//nothing to see here' > " + _location_wrapper(out) 1308 ) 1309 ct_.out.append(out) 1310 if python_script_target_name != '': 1311 ct_.tools.append(python_script_target_name) 1312 if program_command == 'bison' or program_command == 'flex': 1313 ct.tools.append('m4') 1314 ct.tools.append(program_command) 1315 ct_.cmd = command_line 1316 meson_translator.meson_state.custom_targets.append(ct_) 1317 return custom_target_ 1318 else: 1319 wrapped_args = program_args + _get_command_args( 1320 command, input, output, depends, location_wrap=True 1321 ) 1322 if python_script: 1323 wrapped_args = _process_wrapped_args_for_python( 1324 wrapped_args, python_script, python_script_target_name, depends 1325 ) 1326 1327 command_line = impl.get_command_line_from_args(wrapped_args) 1328 if capture: 1329 command_line += ' > %s' % _location_wrapper( 1330 impl.get_relative_gen_dir(output) 1331 ) 1332 ct = CustomTarget() 1333 ct.name = custom_target_.target_name() 1334 for src in relative_inputs_set: 1335 ct.srcs.append(src) 1336 for dep in depends: 1337 assert type(dep) is impl.CustomTarget 1338 ct.srcs.append(':' + dep.target_name()) 1339 for out in relative_outputs: 1340 ct.out.append(out) 1341 if python_script_target_name != '': 1342 ct.tools.append(python_script_target_name) 1343 if program_command == 'bison' or program_command == 'flex': 1344 ct.tools.append('m4') 1345 ct.tools.append(program_command) 1346 for dir_ in _get_export_include_dirs(): 1347 ct.export_include_dirs.append(dir_) 1348 ct.cmd = command_line 1349 meson_translator.meson_state.custom_targets.append(ct) 1350 else: # is_bazel 1351 if program_command.endswith('.py'): 1352 program_command_arg = _location_wrapper(program_command) 1353 else: 1354 program_command_arg = program_command 1355 1356 program_args = [program_command_arg] + program_args 1357 1358 wrapped_args = program_args + _get_command_args( 1359 command, input, output, depends, location_wrap=True 1360 ) 1361 if python_script: 1362 wrapped_args = _process_wrapped_args_for_python( 1363 wrapped_args, python_script, python_script_target_name, depends 1364 ) 1365 1366 command_line = impl.get_command_line_from_args(wrapped_args) 1367 if capture: 1368 command_line += ' > %s' % _location_wrapper( 1369 impl.get_relative_gen_dir(output) 1370 ) 1371 1372 ct = CustomTarget() 1373 ct.name = custom_target_.target_name() 1374 for src in set(relative_inputs): 1375 ct.srcs.append(src) 1376 for dep in depends: 1377 assert type(dep) is impl.CustomTarget 1378 ct.srcs.append(dep.target_name()) 1379 for out in set(relative_outputs): 1380 ct.out.append(out) 1381 if python_script_target_name != '': 1382 ct.tools.append(python_script_target_name) 1383 ct.cmd = command_line 1384 meson_translator.meson_state.custom_targets.append(ct) 1385 1386 return custom_target_ 1387