1from enum import Enum 2from abc import ABC, abstractmethod 3import os 4import re 5import subprocess 6import tomllib 7 8# The file used to write output build definitions. 9_gOutputFile = '' 10 11# The relative directory that is currently being processed. When files are 12# referenced they are relative to this path. 13_gRelativeDir = '' 14 15# Global compiler flags 16_gProjectCflags = [] 17_gProjectCppflags = [] 18 19_gProjectVersion = 'unknown' 20_gProjectOptions = [] 21 22# Caches the list of dependencies found in .toml config files 23# Structure: 24# DependencyTargetType 25# SHARED_LIBRARY = 1 26# STATIC_LIBRARY = 2 27# HEADER_LIBRARY = 3 28# See meson_impl.py 29# external_dep = { 30# 'zlib': { 31# # target_name: target_type 32# 'libz': 2 33# }, 34# } 35external_dep = {} 36 37 38class IncludeDirectories: 39 def __init__(self, name: str, dirs: []): 40 self.name = name 41 self.dirs = dirs 42 43 def __iter__(self): 44 return iter([self]) 45 46 47class File: 48 def __init__(self, name: str): 49 self.name = name 50 51 52class Machine: 53 def __init__(self, system, cpu, cpu_family): 54 self._system = system 55 self._cpu = cpu 56 self._cpu_family = cpu_family 57 58 def system(self): 59 return self._system 60 61 def set_system(self, system: str): 62 self._system = system 63 64 def cpu_family(self): 65 return self._cpu_family 66 67 def cpu(self): 68 return self._cpu 69 70 71class DependencyTargetType(Enum): 72 SHARED_LIBRARY = 1 73 STATIC_LIBRARY = 2 74 HEADER_LIBRARY = 3 75 76 77class DependencyTarget: 78 def __init__(self, target_name: str, target_type: DependencyTargetType): 79 self.target_name = target_name 80 self.target_type = target_type 81 82 83class Dependency: 84 _id_generator = 1000 85 86 def __init__( 87 self, 88 name: str, 89 version='', 90 found=False, 91 targets=[], 92 compile_args=[], 93 include_directories=[], 94 dependencies=[], 95 sources=[], 96 link_with=[], 97 link_whole=[], 98 ): 99 self.name = name 100 self.targets = targets 101 self._version = version 102 self._found = found 103 self.compile_args = compile_args 104 self.include_directories = include_directories 105 self.dependencies = dependencies 106 self.sources = sources 107 self.link_with = link_with 108 self.link_whole = link_whole 109 Dependency._id_generator += 1 110 self.unique_id = Dependency._id_generator 111 112 def version(self): 113 return self._version 114 115 def found(self): 116 return self._found 117 118 def partial_dependency(self, compile_args=''): 119 return self 120 121 def __iter__(self): 122 return iter([self]) 123 124 def __hash__(self): 125 return hash(self.unique_id) 126 127 def __eq__(self, other): 128 return self.unique_id == other.unique_id 129 130 131class CommandReturn: 132 def __init__(self, completed_process): 133 self.completed_process = completed_process 134 135 def returncode(self): 136 return self.completed_process.returncode 137 138 def stdout(self): 139 return self.completed_process.stdout 140 141 142class Program: 143 def __init__(self, command, found: bool): 144 self.command = command 145 self._found = found 146 147 # Running commands from the ambient system may give wrong/misleading results, since 148 # some build systems use hermetic installations of tools like python. 149 def run_command(self, *commands, capture_output=False): 150 command_line = [self.command] 151 for command in commands: 152 command_line += command 153 154 completed_process = subprocess.run( 155 command_line, check=False, capture_output=capture_output 156 ) 157 return CommandReturn(completed_process) 158 159 def found(self): 160 return self._found 161 162 def full_path(self): 163 return 'full_path' 164 165 166class PythonModule: 167 def find_installation(self, name: str): 168 if name == 'python3': 169 return Program(name, found=True) 170 exit('Unhandled python installation: ' + name) 171 172 173class EnableState(Enum): 174 ENABLED = 1 175 DISABLED = 2 176 AUTO = 3 177 178 179class FeatureOption: 180 def __init__(self, name, state=EnableState.AUTO): 181 self.name = name 182 self.state = state 183 184 def allowed(self): 185 return self.state == EnableState.ENABLED or self.state == EnableState.AUTO 186 187 def enabled(self): 188 return self.state == EnableState.ENABLED 189 190 def disabled(self): 191 return self.state == EnableState.DISABLED 192 193 def disable_auto_if(self, value: bool): 194 if value and self.state == EnableState.AUTO: 195 self.state = EnableState.DISABLED 196 return self 197 198 def disable_if(self, value: bool, error_message: str): 199 if not value: 200 return self 201 if self.state == EnableState.ENABLED: 202 exit(error_message) 203 return FeatureOption(self.name, state=EnableState.DISABLED) 204 205 def require(self, value: bool, error_message: str): 206 if value: 207 return self 208 if self.state == EnableState.ENABLED: 209 exit(error_message) 210 return FeatureOption(self.name, state=EnableState.DISABLED) 211 212 def set(self, value: str): 213 value = value.lower() 214 if value == 'auto': 215 self.state = EnableState.AUTO 216 elif value == 'enabled': 217 self.state = EnableState.ENABLED 218 elif value == 'disabled': 219 self.state = EnableState.DISABLED 220 else: 221 exit('Unable to set feature to: %s' % value) 222 223 224class ArrayOption: 225 def __init__(self, name: str, value: []): 226 self.name = name 227 self.strings = value 228 229 def set(self, value: str): 230 if value == '': 231 self.strings = [] 232 else: 233 self.strings = [value] 234 235 236class ComboOption: 237 def __init__(self, name: str, value: str): 238 self.name = name 239 self.value = value 240 241 def set(self, value: str): 242 self.value = value 243 244 245class BooleanOption: 246 def __init__(self, name, value: bool): 247 assert type(value) is bool 248 self.name = name 249 self.value = value 250 251 def set(self, value: str): 252 self.value = bool(value) 253 254 255# Value can be string or other type 256class SimpleOption: 257 def __init__(self, name, value): 258 self.name = name 259 self.value = value 260 261 def set(self, value: str): 262 if type(self.value) is int: 263 self.value = int(value) 264 else: 265 self.value = value 266 267 268class Environment: 269 def set(self, var, val): 270 return 271 272 def append(self, var, val): 273 return 274 275 276class StaticLibrary: 277 def __init__(self, target_name, link_with=[], link_whole=[]): 278 self.target_name = target_name 279 self.link_with = link_with 280 self.link_whole = link_whole 281 282 283class SharedLibrary: 284 name = '' 285 286 def __init__(self, name): 287 self.name = name 288 289 290class Executable: 291 def __init__(self, name): 292 self.name = name 293 294 295class CustomTargetItem: 296 def __init__(self, custom_target, index): 297 self.target = custom_target 298 self.index = index 299 300 301class CustomTarget: 302 def __init__(self, name, outputs=[], generates_h=False, generates_c=False): 303 self._name = name 304 self._outputs = outputs 305 self._generates_h = generates_h 306 self._generates_c = generates_c 307 308 @property 309 def outputs(self): 310 return self._outputs 311 312 def generates_h(self): 313 return self._generates_h 314 315 def generates_c(self): 316 return self._generates_c 317 318 def target_name(self): 319 return self._name 320 321 def target_name_h(self): 322 if self._generates_h and self._generates_c: 323 return self._name + '.h' 324 return self._name 325 326 def target_name_c(self): 327 if self._generates_h and self._generates_c: 328 return self._name + '.c' 329 return self._name 330 331 def header_outputs(self): 332 hdrs = [] 333 for out in self._outputs: 334 if out.endswith('.h'): 335 hdrs.append(out) 336 return hdrs 337 338 def __iter__(self): 339 return iter([self]) 340 341 def __getitem__(self, index): 342 return CustomTargetItem(self, index) 343 344 def full_path(self): 345 return 'fullpath' 346 347 348class Meson: 349 def __init__(self, compiler): 350 self._compiler = compiler 351 352 def get_compiler(self, language_string, native=False): 353 return self._compiler 354 355 def set_compiler(self, compiler): 356 self._compiler = compiler 357 358 def project_version(self): 359 return _gProjectVersion 360 361 def project_source_root(self): 362 return os.getcwd() 363 364 def is_cross_build(self): 365 return True 366 367 def can_run_host_binaries(self): 368 return False 369 370 def current_source_dir(self): 371 return os.getcwd() 372 373 def current_build_dir(self): 374 return '@CURRENT_BUILD_DIR@' 375 376 def project_build_root(self): 377 return '@PROJECT_BUILD_ROOT@' 378 379 def add_devenv(self, env): 380 return 381 382 383class Compiler(ABC): 384 def __init__(self, cpu_family): 385 self._id = 'clang' 386 self._cpu_family = cpu_family 387 388 @abstractmethod 389 def has_header_symbol( 390 self, 391 header: str, 392 symbol: str, 393 args=None, 394 dependencies=None, 395 include_directories=None, 396 no_builtin_args: bool = False, 397 prefix=None, 398 required: bool = False, 399 ) -> bool: 400 pass 401 402 @abstractmethod 403 def check_header(self, header: str, prefix: str = '') -> bool: 404 pass 405 406 @abstractmethod 407 def has_function(self, function, args=None, prefix='', dependencies='') -> bool: 408 pass 409 410 @abstractmethod 411 def links(self, snippet: str, name: str, args=None, dependencies=None) -> bool: 412 pass 413 414 def get_id(self): 415 return self._id 416 417 def is_symbol_supported(self, header: str, symbol: str): 418 if header == 'sys/mkdev.h' or symbol == 'program_invocation_name': 419 return False 420 return True 421 422 def is_function_supported(self, function: str): 423 if ( 424 function == 'qsort_s' 425 or function == 'pthread_setaffinity_np' 426 or function == 'secure_getenv' 427 ): 428 return False 429 return True 430 431 def is_link_supported(self, name: str): 432 if name == 'GNU qsort_r' or name == 'BSD qsort_r': 433 return False 434 return True 435 436 def is_header_supported(self, header: str): 437 if ( 438 header == 'xlocale.h' 439 or header == 'pthread_np.h' 440 or header == 'renderdoc_app.h' 441 ): 442 return False 443 return True 444 445 def get_define(self, define: str, prefix: str): 446 if define == 'ETIME': 447 return define 448 exit('Unhandled define: ' + define) 449 450 def get_supported_function_attributes(self, attributes: list[str]): 451 # Assume all are supported 452 return attributes 453 454 def has_function_attribute(self, attribute: str): 455 return True 456 457 def has_argument(self, name: str): 458 result = True 459 print("has_argument '%s': %s" % (name, str(result))) 460 return result 461 462 def has_link_argument(self, name: str): 463 result = True 464 print("has_link_argument '%s': %s" % (name, str(result))) 465 return result 466 467 def compiles(self, snippet, name: str): 468 # Exclude what is currently not working. 469 result = True 470 if name == '__uint128_t': 471 result = False 472 print("compiles '%s': %s" % (name, str(result))) 473 return result 474 475 def has_member(self, struct, member, prefix): 476 # Assume it does 477 return True 478 479 def get_argument_syntax(self): 480 return 'gcc' 481 482 def get_supported_arguments(self, args): 483 supported_args = [] 484 for arg in args: 485 if ( 486 arg.startswith('-flifetime-dse') 487 or arg.startswith('-Wno-format-truncation') 488 or arg.startswith('-Wno-nonnull-compare') 489 or arg.startswith('-Wno-class-memaccess') 490 or arg.startswith('-Wno-format-truncation') 491 ): 492 continue 493 supported_args.append(arg) 494 return supported_args 495 496 def get_supported_link_arguments(self, args): 497 return args 498 499 def find_library(self, name, required=False): 500 if name == 'ws2_32' or name == 'elf' or name == 'm' or name == 'sensors': 501 return Dependency(name, found=required) 502 exit('Unhandled library: ' + name) 503 504 def sizeof(self, string): 505 table = _get_sizeof_table(self._cpu_family) 506 507 if string not in table: 508 exit('Unhandled compiler sizeof: ' + string) 509 return table[string] 510 511 512class PkgConfigModule: 513 def generate( 514 self, 515 lib, 516 name='', 517 description='', 518 extra_cflags=None, 519 filebase='', 520 version='', 521 libraries=None, 522 libraries_private=None, 523 ): 524 pass 525 526 527################################################################################################### 528 529 530def fprint(args): 531 print(args, file=_gOutputFile) 532 533 534def set_relative_dir(dir): 535 global _gRelativeDir 536 _gRelativeDir = dir 537 538 539def open_output_file(name): 540 global _gOutputFile 541 _gOutputFile = open(name, 'w') 542 543 544def close_output_file(): 545 global _gOutputFile 546 _gOutputFile.close() 547 548 549def get_relative_dir(path_or_file=''): 550 if isinstance(path_or_file, File): 551 return path_or_file.name 552 553 assert isinstance(path_or_file, str) 554 if path_or_file == '': 555 return _gRelativeDir 556 return os.path.join(_gRelativeDir, path_or_file) 557 558 559def get_relative_gen_dir(path=''): 560 return os.path.join(_gRelativeDir, path) 561 562 563def project(name, language_list, version, license, meson_version, default_options): 564 if type(version) is str: 565 _gProjectVersion = version 566 else: 567 assert type(version) is list 568 version_file = version[0] 569 assert type(version_file) is File 570 with open(version_file.name, 'r') as file: 571 for line in file: 572 _gProjectVersion = line.strip() 573 break 574 575 for option in default_options: 576 value_pair = option.split('=') 577 _gProjectOptions.append(SimpleOption(value_pair[0], value_pair[1])) 578 579 580def get_project_options(): 581 return _gProjectOptions 582 583 584def add_project_arguments(args, language=[], native=False): 585 global _gProjectCflags, _gProjectCppflags 586 if type(args) is not list: 587 args = [args] 588 for lang in language: 589 for arg in args: 590 if isinstance(arg, list): 591 add_project_arguments(arg, language=language, native=native) 592 continue 593 assert isinstance(arg, str) 594 if lang == 'c': 595 print('cflags: ' + arg) 596 _gProjectCflags.append(arg) 597 elif lang == 'cpp': 598 print('cppflags: ' + arg) 599 _gProjectCppflags.append(arg) 600 else: 601 exit('Unhandle arguments language: ' + lang) 602 603 604def get_project_cflags(): 605 return _gProjectCflags 606 607 608def get_project_cppflags(): 609 return _gProjectCppflags 610 611 612def _get_sizeof_table(cpu_family): 613 table_32 = {'void*': 4} 614 table_64 = {'void*': 8} 615 if cpu_family == 'arm' or cpu_family == 'x86_64': 616 table = table_32 617 elif cpu_family == 'aarch64': 618 table = table_64 619 else: 620 exit('sizeof unhandled cpu family: %s' % cpu_family) 621 return table 622 623 624def get_linear_list(arg_list): 625 args = [] 626 for arg in arg_list: 627 if type(arg) is list: 628 args.extend(get_linear_list(arg)) 629 else: 630 args.append(arg) 631 return args 632 633 634def load_dependencies(config): 635 with open(config, 'rb') as f: 636 data = tomllib.load(f) 637 project_configs = data.get('project_config') 638 base_config = data.get('base_project_config') 639 # global dependencies 640 for dep_name, targets in base_config.get('ext_dependencies').items(): 641 dep_targets = { 642 t.get('target_name'): t.get('target_type') for t in targets 643 } 644 external_dep[dep_name] = dep_targets 645 # project specific dependencies 646 for project_config in project_configs: 647 dependencies = project_config.get('ext_dependencies') 648 for dep_name, targets in dependencies.items(): 649 dep_targets = { 650 t.get('target_name'): t.get('target_type') for t in targets 651 } 652 external_dep[dep_name] = dep_targets 653 654 655def dependency(*names, required=True, version=''): 656 for name in names: 657 print('dependency: %s' % name) 658 if name == '': 659 return Dependency('null', version, found=False) 660 661 if name in external_dep: 662 targets = external_dep.get(name) 663 return Dependency( 664 name, 665 targets=[ 666 DependencyTarget(t, DependencyTargetType(targets[t])) 667 for t in targets 668 ], 669 version=version, 670 found=True, 671 ) 672 # TODO(bpnguyen): Move these hardcoded dependencies to a global config 673 if ( 674 name == 'backtrace' 675 or name == 'curses' 676 or name == 'expat' 677 or name == 'libconfig' 678 or name == 'libmagma_virt' 679 or name == 'libva' 680 or name == 'libzstd' 681 or name == 'libdrm' 682 or name == 'libglvnd' 683 or name == 'libudev' 684 or name == 'libunwind' 685 or name == 'llvm' 686 or name == 'libxml-2.0' 687 or name == 'lua54' 688 or name == 'valgrind' 689 or name == 'wayland-scanner' 690 or name == 'SPIRV-Tools' 691 ): 692 return Dependency(name, version, found=False) 693 694 if ( 695 name == 'libarchive' 696 or name == 'libelf' 697 or name == 'threads' 698 or name == 'vdpau' 699 ): 700 return Dependency(name, version, found=required) 701 702 exit('Unhandled dependency: ' + name) 703 704 705def get_set_of_deps(deps, set_of_deps=set()): 706 for dep in deps: 707 if type(dep) is list: 708 set_of_deps = get_set_of_deps(dep, set_of_deps) 709 elif dep not in set_of_deps: 710 set_of_deps.add(dep) 711 set_of_deps = get_set_of_deps(dep.dependencies, set_of_deps) 712 return set_of_deps 713 714 715def get_include_dirs(paths) -> list[str]: 716 dir_list = [] 717 for path in paths: 718 if type(path) is list: 719 dir_list.extend(get_include_dirs(p for p in path)) 720 elif type(path) is IncludeDirectories: 721 dir_list.extend(path.dirs) 722 else: 723 assert type(path) is str 724 dir_list.append(get_relative_dir(path)) 725 return dir_list 726 727 728def get_include_directories(includes) -> list[IncludeDirectories]: 729 dirs = [] 730 if type(includes) is list: 731 for inc in includes: 732 dirs.extend(get_include_directories(inc)) 733 elif type(includes) is IncludeDirectories: 734 dirs.extend(includes) 735 else: 736 assert type(includes) is str 737 exit('get_include_directories got string: %s' % includes) 738 return dirs 739 740 741def get_static_libs(arg_list): 742 libs = [] 743 for arg in arg_list: 744 if type(arg) is list: 745 libs.extend(get_static_libs(arg)) 746 else: 747 assert type(arg) is StaticLibrary 748 libs.extend(get_static_libs(arg.link_with)) 749 libs.append(arg) 750 return libs 751 752 753def get_whole_static_libs(arg_list): 754 libs = [] 755 for arg in arg_list: 756 if type(arg) is list: 757 libs.extend(get_whole_static_libs(arg)) 758 else: 759 assert type(arg) is StaticLibrary 760 libs.extend(get_whole_static_libs(arg._link_whole)) 761 libs.append(arg) 762 return libs 763 764 765def get_list_of_relative_inputs(list_or_string): 766 if isinstance(list_or_string, list): 767 ret = [] 768 for item in list_or_string: 769 ret.extend(get_list_of_relative_inputs(item)) 770 return ret 771 772 return [get_relative_dir(list_or_string)] 773 774 775def get_command_line_from_args(args: list): 776 command_line = '' 777 for arg in args: 778 command_line += ' ' + arg 779 # Escape angle brackets 780 command_line = re.sub(r'(<|>)', '\\\\\\\\\g<1>', command_line) 781 return command_line 782 783 784def replace_wrapped_input_with_target(args, python_script, python_script_target_name): 785 outargs = [] 786 for index, arg in enumerate(args): 787 pattern = '(.*?)(' + python_script + ')' 788 replace = '\g<1>' + python_script_target_name 789 outargs.append(re.sub(pattern, replace, arg)) 790 return outargs 791