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