1import os 2import re 3import meson_impl as impl 4 5 6class Compiler(impl.Compiler): 7 8 def is_symbol_supported(self, header: str, symbol: str): 9 if header == 'sys/sysmacros.h': 10 return False 11 return super().is_symbol_supported(header, symbol) 12 13 def is_function_supported(self, function): 14 if function == 'getrandom' or function == 'memfd_create': 15 return False 16 return super().is_function_supported(function) 17 18 def is_link_supported(self, name): 19 if name == 'strtod has locale support': 20 return False 21 return super().is_link_supported(name) 22 23 def has_header_symbol( 24 self, 25 header: str, 26 symbol: str, 27 args=[], 28 dependencies=[], 29 include_directories=[], 30 no_builtin_args=False, 31 prefix=[], 32 required=False, 33 ): 34 # Exclude what is currently not working. 35 result = self.is_symbol_supported(header, symbol) 36 print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result))) 37 return result 38 39 def check_header(self, header, prefix=''): 40 result = self.is_header_supported(header) 41 print("check_header '%s': %s" % (header, str(result))) 42 return result 43 44 def has_function(self, function, args=[], prefix='', dependencies=''): 45 # Exclude what is currently not working. 46 result = self.is_function_supported(function) 47 print("has_function '%s': %s" % (function, str(result))) 48 return result 49 50 def links(self, snippet, name, args=[], dependencies=[]): 51 # Exclude what is currently not working. 52 result = self.is_link_supported(name) 53 print("links '%s': %s" % (name, str(result))) 54 return result 55 56 57class PkgconfigModule: 58 59 def generate(self, lib, name='', description='', extra_cflags=[]): 60 impl.fprint('# package library') 61 impl.fprint('cc_library(') 62 impl.fprint(' name = "%s",' % name) 63 assert type(lib) == impl.StaticLibrary 64 impl.fprint(' deps = [ "%s" ],' % lib.target_name) 65 # This line tells Bazel to use -isystem for targets that depend on this one, 66 # which is needed for clients that include package headers with angle brackets. 67 impl.fprint(' includes = [ "." ],') 68 impl.fprint(' visibility = [ "//visibility:public" ],') 69 impl.fprint(')') 70 71 72# These globals exposed by the meson API 73meson = impl.Meson(Compiler()) 74host_machine = impl.Machine('fuchsia') 75build_machine = impl.Machine('linux') 76 77_gIncludeDirectories = dict() 78 79 80def open_output_file(): 81 impl.open_output_file(r'BUILD.bazel') 82 83 84def close_output_file(): 85 impl.close_output_file() 86 87 88def load_config_file(): 89 impl.load_config_file('python-build/generate_fuchsia_build.config') 90 91 92def include_directories(*paths, is_system=False): 93 dirs = impl.get_include_dirs(paths) 94 name = dirs[0].replace('/', '_') 95 if is_system: 96 name += '_sys' 97 98 global _gIncludeDirectories 99 if name not in _gIncludeDirectories: 100 # Mesa likes to include files at a level down from the include path, 101 # so ensure Bazel allows this by including all of the possibilities. 102 # Hopefully we don't need to go two or more levels down. 103 # Can't repeat entries so use a set. 104 dir_set = set() 105 for dir in dirs: 106 dir = os.path.normpath(dir) 107 dir_set.add(dir) 108 subdirs = os.listdir(dir) 109 for subdir in subdirs: 110 subdir = os.path.join(dir, subdir) 111 if os.path.isdir(subdir): 112 dir_set.add(subdir) 113 114 impl.fprint('# header library') 115 impl.fprint('cc_library(') 116 impl.fprint(' name = "%s",' % name) 117 impl.fprint(' hdrs = []') 118 for dir in dir_set: 119 impl.fprint( 120 ' + glob(["%s"])' % os.path.normpath(os.path.join(dir, '*.h')) 121 ) 122 # C files included because... 123 impl.fprint( 124 ' + glob(["%s"])' % os.path.normpath(os.path.join(dir, '*.c')) 125 ) 126 impl.fprint(' ,') 127 impl.fprint(' visibility = [ "//visibility:public" ],') 128 impl.fprint(')') 129 _gIncludeDirectories[name] = impl.IncludeDirectories(name, dirs) 130 131 return _gIncludeDirectories[name] 132 133 134def module_import(name: str): 135 if name == 'python': 136 return impl.PythonModule() 137 if name == 'pkgconfig': 138 return PkgconfigModule() 139 exit('Unhandled module: ' + name) 140 141 142def dependency( 143 *names, 144 required=True, 145 version='', 146 allow_fallback=False, 147 method='', 148 modules=[], 149 optional_modules=[], 150 static=True, 151 fallback=[], 152 include_type='', 153 native=False 154): 155 for name in names: 156 if name == 'zlib': 157 return impl.Dependency( 158 name, 159 version, 160 targets=[ 161 impl.DependencyTarget( 162 '@zlib//:zlib', 163 impl.DependencyTargetType.STATIC_LIBRARY, 164 ), 165 ], 166 found=True, 167 ) 168 169 if name == 'libmagma': 170 return impl.Dependency( 171 name, 172 targets=[ 173 impl.DependencyTarget( 174 '@fuchsia_sdk//pkg/magma_client', 175 impl.DependencyTargetType.STATIC_LIBRARY, 176 ) 177 ], 178 found=True, 179 ) 180 181 if name == 'libmagma_virt': 182 return impl.Dependency(name, version, found=True) 183 184 # common deps 185 return impl.dependency(*names) 186 187 188def static_library( 189 target_name, 190 *source_files, 191 c_args=[], 192 cpp_args=[], 193 c_pch='', 194 build_by_default=False, 195 build_rpath='', 196 d_debug=[], 197 d_import_dirs=[], 198 d_module_versions=[], 199 d_unittest=False, 200 dependencies=[], 201 extra_files='', 202 gnu_symbol_visibility='', 203 gui_app=False, 204 implicit_include_directories=False, 205 include_directories=[], 206 install=False, 207 install_dir='', 208 install_mode=[], 209 install_rpath='', 210 install_tag='', 211 link_args=[], 212 link_depends='', 213 link_language='', 214 link_whole=[], 215 link_with=[], 216 name_prefix='', 217 name_suffix='', 218 native=False, 219 objects=[], 220 override_options=[], 221 pic=False, 222 prelink=False, 223 rust_abi='', 224 rust_crate_type='', 225 rust_dependency_map={}, 226 sources='', 227 vala_args=[], 228 win_subsystem='' 229): 230 print('static_library: ' + target_name) 231 232 link_with = impl.get_linear_list(link_with) 233 link_whole = impl.get_linear_list(link_whole) 234 235 # Emitting link_with/link_whole into a static library isn't useful for building, 236 # shared libraries must specify the whole chain of dependencies. 237 # Leaving them here for traceability though maybe it's less confusing to remove them? 238 _emit_builtin_target( 239 target_name, 240 *source_files, 241 static=True, 242 c_args=c_args, 243 cpp_args=cpp_args, 244 dependencies=dependencies, 245 include_directories=include_directories, 246 link_with=link_with, 247 link_whole=link_whole, 248 ) 249 250 return impl.StaticLibrary( 251 target_name, link_with=link_with, link_whole=link_whole 252 ) 253 254 255def shared_library( 256 target_name, 257 *source, 258 c_args=[], 259 cpp_args=[], 260 c_pch='', 261 build_by_default=False, 262 build_rpath='', 263 d_debug=[], 264 d_import_dirs=[], 265 d_module_versions=[], 266 d_unittest=False, 267 darwin_versions='', 268 dependencies=[], 269 extra_files='', 270 gnu_symbol_visibility='', 271 gui_app=False, 272 implicit_include_directories=False, 273 include_directories=[], 274 install=False, 275 install_dir='', 276 install_mode=[], 277 install_rpath='', 278 install_tag='', 279 link_args=[], 280 link_depends=[], 281 link_language='', 282 link_whole=[], 283 link_with=[], 284 name_prefix='', 285 name_suffix='', 286 native=False, 287 objects=[], 288 override_options=[], 289 rust_abi='', 290 rust_crate_type='', 291 rust_dependency_map={}, 292 sources=[], 293 soversion='', 294 vala_args=[], 295 version='', 296 vs_module_defs='', 297 win_subsystem='' 298): 299 print('shared_library: ' + target_name) 300 301 link_with = impl.get_linear_list([link_with]) 302 link_whole = impl.get_linear_list([link_whole]) 303 304 _emit_builtin_target( 305 target_name, 306 *source, 307 static=False, 308 c_args=c_args, 309 cpp_args=cpp_args, 310 dependencies=dependencies, 311 include_directories=include_directories, 312 link_with=link_with, 313 link_whole=link_whole, 314 ) 315 return impl.SharedLibrary(target_name) 316 317 318def library( 319 target_name, 320 *sources, 321 c_args=[], 322 install=False, 323 link_args=[], 324 vs_module_defs='', 325 version='' 326): 327 print('library: ' + target_name) 328 329 return static_library( 330 target_name, 331 *sources, 332 c_args=c_args, 333 install=install, 334 link_args=link_args, 335 sources=sources, 336 ) 337 338 339def _get_sources(input_sources, sources, generated_sources, generated_headers): 340 for source in input_sources: 341 if type(source) is list: 342 _get_sources(source, sources, generated_sources, generated_headers) 343 elif type(source) is impl.CustomTarget: 344 generated_sources.add(source.target_name()) 345 for out in source._outputs: 346 sources.add(out) 347 elif type(source) is impl.CustomTargetItem: 348 target = source.target 349 generated_sources.add(target.target_name()) 350 sources.add(target._outputs[source.index]) 351 elif type(source) is impl.File: 352 sources.add(source.name) 353 elif type(source) is str: 354 sources.add(impl.get_relative_dir(source)) 355 else: 356 exit('source type not handled: ' + str(type(source))) 357 358 359def _emit_builtin_target( 360 target_name, 361 *source, 362 static=False, 363 c_args=[], 364 cpp_args=[], 365 dependencies=[], 366 include_directories=[], 367 link_with=[], 368 link_whole=[] 369): 370 impl.fprint('cc_library(') 371 target_name_so = target_name 372 target_name = target_name if static else '_' + target_name 373 impl.fprint(' name = "%s",' % target_name) 374 375 srcs = set() 376 generated_sources = set() 377 generated_headers = set() 378 for source_arg in source: 379 assert type(source_arg) is list 380 _get_sources(source_arg, srcs, generated_sources, generated_headers) 381 382 deps = impl.get_set_of_deps(dependencies) 383 include_directories = impl.get_include_directories(include_directories) 384 static_libs = [] 385 whole_static_libs = [] 386 shared_libs = [] 387 for dep in deps: 388 print(' dep: ' + dep.name) 389 for src in impl.get_linear_list([dep.sources]): 390 if type(src) == impl.CustomTargetItem: 391 generated_headers.add(src.target.target_name_h()) 392 elif type(src) == impl.CustomTarget: 393 generated_headers.add(src.target_name_h()) 394 else: 395 exit('Unhandled source dependency: ' + str(type(src))) 396 include_directories.extend( 397 impl.get_include_directories(dep.include_directories) 398 ) 399 for target in impl.get_static_libs([dep.link_with]): 400 assert type(target) == impl.StaticLibrary 401 static_libs.append(target.target_name) 402 for target in impl.get_linear_list([dep.link_whole]): 403 assert type(target) == impl.StaticLibrary 404 whole_static_libs.append(target.target_name) 405 for target in dep.targets: 406 if target.target_type == impl.DependencyTargetType.SHARED_LIBRARY: 407 shared_libs.append(target.target_name) 408 elif target.target_type == impl.DependencyTargetType.STATIC_LIBRARY: 409 static_libs.append(target.target_name) 410 elif target.target_type == impl.DependencyTargetType.HEADER_LIBRARY: 411 exit('Header library not supported') 412 413 for target in impl.get_static_libs(link_with): 414 if type(target) == impl.StaticLibrary: 415 static_libs.append(target.target_name) 416 else: 417 exit('Unhandled link_with type: ' + str(type(target))) 418 419 for target in impl.get_whole_static_libs(link_whole): 420 if type(target) == impl.StaticLibrary: 421 whole_static_libs.append(target.target_name()) 422 else: 423 exit('Unhandled link_whole type: ' + str(type(target))) 424 425 # Android turns all warnings into errors but thirdparty projects typically can't handle that 426 cflags = ['-Wno-error'] + impl.get_linear_list( 427 impl.get_project_cflags() + c_args 428 ) 429 cppflags = ['-Wno-error'] + impl.get_linear_list( 430 impl.get_project_cppflags() + cpp_args 431 ) 432 433 has_c_files = False 434 impl.fprint(' srcs = [') 435 for src in srcs: 436 if src.endswith('.c'): 437 has_c_files = True 438 impl.fprint(' "%s",' % src) 439 impl.fprint(' ],') 440 441 # For Bazel to find headers in the "current area", we have to include 442 # not just the headers in the relative dir, but also relative subdirs 443 # that aren't themselves areas (containing meson.build). 444 # We only look one level deep. 445 local_include_dirs = [impl.get_relative_dir()] 446 local_entries = ( 447 [] 448 if impl.get_relative_dir() == '' 449 else os.listdir(impl.get_relative_dir()) 450 ) 451 for entry in local_entries: 452 entry = os.path.join(impl.get_relative_dir(), entry) 453 if os.path.isdir(entry): 454 subdir_entries = os.listdir(entry) 455 if not 'meson.build' in subdir_entries: 456 local_include_dirs.append(entry) 457 458 impl.fprint( 459 ' # hdrs are our files that might be included; listed here so Bazel will' 460 ' allow them to be included' 461 ) 462 impl.fprint(' hdrs = []') 463 for hdr in local_include_dirs: 464 impl.fprint( 465 ' + glob(["%s"])' % os.path.normpath(os.path.join(hdr, '*.h')) 466 ) 467 468 impl.fprint(' ,') 469 470 impl.fprint(' copts = [') 471 # Needed for subdir sources 472 impl.fprint(' "-I %s",' % impl.get_relative_dir()) 473 impl.fprint(' "-I $(GENDIR)/%s",' % impl.get_relative_dir()) 474 for inc in include_directories: 475 for dir in inc.dirs: 476 impl.fprint(' "-I %s",' % dir) 477 impl.fprint(' "-I $(GENDIR)/%s",' % dir) 478 479 if has_c_files: 480 for arg in cflags: 481 # Double escape double quotations 482 arg = re.sub(r'"', '\\\\\\"', arg) 483 impl.fprint(" '%s'," % arg) 484 else: 485 for arg in cppflags: 486 # Double escape double quotations 487 arg = re.sub(r'"', '\\\\\\"', arg) 488 impl.fprint(" '%s'," % arg) 489 490 impl.fprint(' ],') 491 492 # Ensure bazel deps are unique 493 bazel_deps = set() 494 for lib in static_libs: 495 bazel_deps.add(lib) 496 for lib in whole_static_libs: 497 bazel_deps.add(lib) 498 for inc in include_directories: 499 bazel_deps.add(inc.name) 500 for target in generated_headers: 501 bazel_deps.add(target) 502 for target in generated_sources: 503 bazel_deps.add(target) 504 505 impl.fprint(' deps = [') 506 for bdep in bazel_deps: 507 impl.fprint(' "%s",' % bdep) 508 impl.fprint(' ],') 509 510 impl.fprint(' target_compatible_with = [ "@platforms//os:fuchsia" ],') 511 impl.fprint(' visibility = [ "//visibility:public" ],') 512 impl.fprint(')') 513 514 if not static: 515 impl.fprint('cc_shared_library(') 516 impl.fprint(' name = "%s",' % target_name_so) 517 impl.fprint(' deps = [') 518 impl.fprint(' "%s",' % target_name) 519 impl.fprint(' ],') 520 impl.fprint(')') 521 522 523def _process_target_name(name): 524 name = re.sub(r'[\[\]]', '', name) 525 return name 526 527 528# Returns string or list of string 529def _location_wrapper(name_or_list): 530 if isinstance(name_or_list, list): 531 ret = [] 532 for i in name_or_list: 533 ret.append('$(location %s)' % i) 534 return ret 535 536 assert isinstance(name_or_list, str) 537 return '$(location %s)' % name_or_list 538 539 540def _is_header(name): 541 return re.search(r'\.h[xx|pp]?$', name) != None 542 543 544def _is_source(name): 545 return re.search(r'\.c[c|xx|pp]?$', name) != None 546 547 548def _get_command_args( 549 command, 550 input, 551 output, 552 deps, 553 location_wrap=False, 554 obfuscate_output_c=False, 555 obfuscate_output_h=False, 556 obfuscate_suffix='', 557): 558 args = [] 559 for command_item in command[1:]: 560 if isinstance(command_item, list): 561 for item in command_item: 562 assert type(item) == impl.File 563 args.append( 564 _location_wrapper(item.name) if location_wrap else item.name 565 ) 566 continue 567 568 assert type(command_item) is str 569 match = re.match(r'@INPUT([0-9])?@', command_item) 570 if match != None: 571 if match.group(1) != None: 572 input_index = int(match.group(1)) 573 input_list = impl.get_list_of_relative_inputs(input[input_index]) 574 else: 575 input_list = impl.get_list_of_relative_inputs(input) 576 args.extend( 577 _location_wrapper(input_list) if location_wrap else input_list 578 ) 579 continue 580 581 match = re.match(r'(.*?)@OUTPUT([0-9])?@', command_item) 582 if match != None: 583 output_list = [] 584 if match.group(2) != None: 585 output_index = int(match.group(2)) 586 selected_output = ( 587 output[output_index] if isinstance(output, list) else output 588 ) 589 output_list.append(impl.get_relative_gen_dir(selected_output)) 590 elif isinstance(output, list): 591 for out in output: 592 output_list.append(impl.get_relative_gen_dir(out)) 593 else: 594 output_list.append(impl.get_relative_gen_dir(output)) 595 for out in output_list: 596 if _is_header(out) and obfuscate_output_h: 597 args.append( 598 match.group(1) + '$(GENDIR)/%s' % out if location_wrap else out 599 ) 600 else: 601 if _is_source(out) and obfuscate_output_c: 602 out += obfuscate_suffix 603 args.append( 604 match.group(1) + _location_wrapper(out) if location_wrap else out 605 ) 606 continue 607 608 # Assume used to locate generated outputs 609 match = re.match(r'(.*?)@CURRENT_BUILD_DIR@', command_item) 610 if match != None: 611 args.append('$(GENDIR)' + '/' + impl.get_relative_dir()) 612 continue 613 614 match = re.match(r'@PROJECT_BUILD_ROOT@(.*)', command_item) 615 if match != None: 616 args.append('$(GENDIR)%s' % match.group(1)) 617 continue 618 619 # A plain arg 620 if ' ' in command_item: 621 args.append("'%s'" % command_item) 622 else: 623 args.append(command_item) 624 625 return args 626 627 628def _process_wrapped_args_for_python( 629 wrapped_args, python_script, python_script_target_name, deps 630): 631 # The python script arg should be replaced with the python binary target name 632 args = impl.replace_wrapped_input_with_target( 633 wrapped_args, python_script, python_script_target_name 634 ) 635 return args 636 637 638def custom_target( 639 target_name: str, 640 build_always=False, 641 build_always_stale=False, 642 build_by_default=False, 643 capture=False, 644 command=[], 645 console=False, 646 depend_files=[], 647 depends=[], 648 depfile='', 649 env=[], 650 feed=False, 651 input=[], 652 install=False, 653 install_dir='', 654 install_mode=[], 655 install_tag=[], 656 output=[], 657): 658 target_name = _process_target_name(target_name) 659 print('Custom target: ' + target_name) 660 assert type(command) is list 661 program = command[0] 662 program_args = [] 663 664 # The program can be an array that includes arguments 665 if isinstance(program, list): 666 for arg in program[1:]: 667 assert type(arg) is str 668 program_args.append(arg) 669 program = program[0] 670 671 assert isinstance(program, impl.Program) 672 assert program.found() 673 674 args = program_args + _get_command_args(command, input, output, depends) 675 676 # Python scripts need special handling to find mako library 677 python_script = '' 678 python_script_target_name = '' 679 if program.command.endswith('.py'): 680 python_script = program.command 681 else: 682 for index, arg in enumerate(args): 683 if arg.endswith('.py'): 684 python_script = arg 685 break 686 687 if python_script != '': 688 python_script_target_name = ( 689 target_name + '_' + os.path.basename(python_script) 690 ) 691 srcs = [python_script] + impl.get_list_of_relative_inputs(depend_files) 692 impl.fprint('py_binary(') 693 impl.fprint(' name = "%s",' % python_script_target_name) 694 impl.fprint(' main = "%s",' % python_script) 695 impl.fprint(' srcs = [') 696 for src in srcs: 697 if src.endswith('.py'): 698 impl.fprint(' "%s",' % src) 699 impl.fprint(' ],') 700 # So scripts can find other scripts 701 impl.fprint(' imports = [') 702 for src in srcs: 703 if src.endswith('.py'): 704 impl.fprint(' "%s",' % os.path.dirname(src)) 705 impl.fprint(' ],') 706 impl.fprint(')') 707 708 relative_inputs = impl.get_list_of_relative_inputs(input) 709 # We use python_host_binary instead of calling python scripts directly; 710 # however there's an issue with python locating modules in the same directory 711 # as the script; to workaround that (see process_wrapped_args_for_python) we 712 # ensure the script is listed in the genrule targets. 713 if python_script != '' and not python_script in relative_inputs: 714 relative_inputs.append(python_script) 715 relative_inputs.extend(impl.get_list_of_relative_inputs(depend_files)) 716 717 relative_outputs = [] 718 if isinstance(output, list): 719 for file in output: 720 relative_outputs.append(impl.get_relative_gen_dir(file)) 721 else: 722 assert type(output) == str 723 relative_outputs.append(impl.get_relative_gen_dir(output)) 724 725 custom_target = impl.CustomTarget(target_name, relative_outputs) 726 727 program_command = program.command 728 if program_command.endswith('.py'): 729 program_command_arg = _location_wrapper(program_command) 730 else: 731 program_command_arg = program_command 732 733 program_args = [program_command_arg] + program_args 734 735 wrapped_args = program_args + _get_command_args( 736 command, input, output, depends, location_wrap=True 737 ) 738 if python_script: 739 wrapped_args = _process_wrapped_args_for_python( 740 wrapped_args, python_script, python_script_target_name, depends 741 ) 742 743 command_line = impl.get_command_line_from_args(wrapped_args) 744 if capture: 745 command_line += ' > %s' % _location_wrapper( 746 impl.get_relative_gen_dir(output) 747 ) 748 749 impl.fprint('genrule(') 750 impl.fprint(' name = "%s",' % custom_target.target_name()) 751 impl.fprint(' srcs = [') 752 for src in relative_inputs: 753 impl.fprint(' "%s",' % src) 754 for dep in depends: 755 assert type(dep) is impl.CustomTarget 756 impl.fprint(' ":%s",' % dep.target_name()) 757 impl.fprint(' ],') 758 impl.fprint(' outs = [') 759 for out in relative_outputs: 760 impl.fprint(' "%s",' % out) 761 impl.fprint(' ],') 762 if python_script_target_name != '': 763 impl.fprint(' tools = [ "%s" ],' % python_script_target_name) 764 impl.fprint(' cmd = "%s"' % command_line) 765 impl.fprint(')') 766 767 return custom_target 768