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