from enum import Enum from abc import ABC, abstractmethod import os import re import subprocess import tomllib # The file used to write output build definitions. _gOutputFile = '' # The relative directory that is currently being processed. When files are # referenced they are relative to this path. _gRelativeDir = '' # Global compiler flags _gProjectCflags = [] _gProjectCppflags = [] _gProjectVersion = 'unknown' _gProjectOptions = [] # Caches the list of dependencies found in .toml config files # Structure: # DependencyTargetType # SHARED_LIBRARY = 1 # STATIC_LIBRARY = 2 # HEADER_LIBRARY = 3 # See meson_impl.py # external_dep = { # 'zlib': { # # target_name: target_type # 'libz': 2 # }, # } external_dep = {} class IncludeDirectories: def __init__(self, name: str, dirs: []): self.name = name self.dirs = dirs def __iter__(self): return iter([self]) class File: def __init__(self, name: str): self.name = name class Machine: def __init__(self, system, cpu, cpu_family): self._system = system self._cpu = cpu self._cpu_family = cpu_family def system(self): return self._system def set_system(self, system: str): self._system = system def cpu_family(self): return self._cpu_family def cpu(self): return self._cpu class DependencyTargetType(Enum): SHARED_LIBRARY = 1 STATIC_LIBRARY = 2 HEADER_LIBRARY = 3 class DependencyTarget: def __init__(self, target_name: str, target_type: DependencyTargetType): self.target_name = target_name self.target_type = target_type class Dependency: _id_generator = 1000 def __init__( self, name: str, version='', found=False, targets=[], compile_args=[], include_directories=[], dependencies=[], sources=[], link_with=[], link_whole=[], ): self.name = name self.targets = targets self._version = version self._found = found self.compile_args = compile_args self.include_directories = include_directories self.dependencies = dependencies self.sources = sources self.link_with = link_with self.link_whole = link_whole Dependency._id_generator += 1 self.unique_id = Dependency._id_generator def version(self): return self._version def found(self): return self._found def partial_dependency(self, compile_args=''): return self def __iter__(self): return iter([self]) def __hash__(self): return hash(self.unique_id) def __eq__(self, other): return self.unique_id == other.unique_id class CommandReturn: def __init__(self, completed_process): self.completed_process = completed_process def returncode(self): return self.completed_process.returncode def stdout(self): return self.completed_process.stdout class Program: def __init__(self, command, found: bool): self.command = command self._found = found # Running commands from the ambient system may give wrong/misleading results, since # some build systems use hermetic installations of tools like python. def run_command(self, *commands, capture_output=False): command_line = [self.command] for command in commands: command_line += command completed_process = subprocess.run( command_line, check=False, capture_output=capture_output ) return CommandReturn(completed_process) def found(self): return self._found def full_path(self): return 'full_path' class PythonModule: def find_installation(self, name: str): if name == 'python3': return Program(name, found=True) exit('Unhandled python installation: ' + name) class EnableState(Enum): ENABLED = 1 DISABLED = 2 AUTO = 3 class FeatureOption: def __init__(self, name, state=EnableState.AUTO): self.name = name self.state = state def allowed(self): return self.state == EnableState.ENABLED or self.state == EnableState.AUTO def enabled(self): return self.state == EnableState.ENABLED def disabled(self): return self.state == EnableState.DISABLED def disable_auto_if(self, value: bool): if value and self.state == EnableState.AUTO: self.state = EnableState.DISABLED return self def disable_if(self, value: bool, error_message: str): if not value: return self if self.state == EnableState.ENABLED: exit(error_message) return FeatureOption(self.name, state=EnableState.DISABLED) def require(self, value: bool, error_message: str): if value: return self if self.state == EnableState.ENABLED: exit(error_message) return FeatureOption(self.name, state=EnableState.DISABLED) def set(self, value: str): value = value.lower() if value == 'auto': self.state = EnableState.AUTO elif value == 'enabled': self.state = EnableState.ENABLED elif value == 'disabled': self.state = EnableState.DISABLED else: exit('Unable to set feature to: %s' % value) class ArrayOption: def __init__(self, name: str, value: []): self.name = name self.strings = value def set(self, value: str): if value == '': self.strings = [] else: self.strings = [value] class ComboOption: def __init__(self, name: str, value: str): self.name = name self.value = value def set(self, value: str): self.value = value class BooleanOption: def __init__(self, name, value: bool): assert type(value) is bool self.name = name self.value = value def set(self, value: str): self.value = bool(value) # Value can be string or other type class SimpleOption: def __init__(self, name, value): self.name = name self.value = value def set(self, value: str): if type(self.value) is int: self.value = int(value) else: self.value = value class Environment: def set(self, var, val): return def append(self, var, val): return class StaticLibrary: def __init__(self, target_name, link_with=[], link_whole=[]): self.target_name = target_name self.link_with = link_with self.link_whole = link_whole class SharedLibrary: name = '' def __init__(self, name): self.name = name class Executable: def __init__(self, name): self.name = name class CustomTargetItem: def __init__(self, custom_target, index): self.target = custom_target self.index = index class CustomTarget: def __init__(self, name, outputs=[], generates_h=False, generates_c=False): self._name = name self._outputs = outputs self._generates_h = generates_h self._generates_c = generates_c @property def outputs(self): return self._outputs def generates_h(self): return self._generates_h def generates_c(self): return self._generates_c def target_name(self): return self._name def target_name_h(self): if self._generates_h and self._generates_c: return self._name + '.h' return self._name def target_name_c(self): if self._generates_h and self._generates_c: return self._name + '.c' return self._name def header_outputs(self): hdrs = [] for out in self._outputs: if out.endswith('.h'): hdrs.append(out) return hdrs def __iter__(self): return iter([self]) def __getitem__(self, index): return CustomTargetItem(self, index) def full_path(self): return 'fullpath' class Meson: def __init__(self, compiler): self._compiler = compiler def get_compiler(self, language_string, native=False): return self._compiler def set_compiler(self, compiler): self._compiler = compiler def project_version(self): return _gProjectVersion def project_source_root(self): return os.getcwd() def is_cross_build(self): return True def can_run_host_binaries(self): return False def current_source_dir(self): return os.getcwd() def current_build_dir(self): return '@CURRENT_BUILD_DIR@' def project_build_root(self): return '@PROJECT_BUILD_ROOT@' def add_devenv(self, env): return class Compiler(ABC): def __init__(self, cpu_family): self._id = 'clang' self._cpu_family = cpu_family @abstractmethod def has_header_symbol( self, header: str, symbol: str, args=None, dependencies=None, include_directories=None, no_builtin_args: bool = False, prefix=None, required: bool = False, ) -> bool: pass @abstractmethod def check_header(self, header: str, prefix: str = '') -> bool: pass @abstractmethod def has_function(self, function, args=None, prefix='', dependencies='') -> bool: pass @abstractmethod def links(self, snippet: str, name: str, args=None, dependencies=None) -> bool: pass def get_id(self): return self._id def is_symbol_supported(self, header: str, symbol: str): if header == 'sys/mkdev.h' or symbol == 'program_invocation_name': return False return True def is_function_supported(self, function: str): if ( function == 'qsort_s' or function == 'pthread_setaffinity_np' or function == 'secure_getenv' ): return False return True def is_link_supported(self, name: str): if name == 'GNU qsort_r' or name == 'BSD qsort_r': return False return True def is_header_supported(self, header: str): if ( header == 'xlocale.h' or header == 'pthread_np.h' or header == 'renderdoc_app.h' ): return False return True def get_define(self, define: str, prefix: str): if define == 'ETIME': return define exit('Unhandled define: ' + define) def get_supported_function_attributes(self, attributes: list[str]): # Assume all are supported return attributes def has_function_attribute(self, attribute: str): return True def has_argument(self, name: str): result = True print("has_argument '%s': %s" % (name, str(result))) return result def has_link_argument(self, name: str): result = True print("has_link_argument '%s': %s" % (name, str(result))) return result def compiles(self, snippet, name: str): # Exclude what is currently not working. result = True if name == '__uint128_t': result = False print("compiles '%s': %s" % (name, str(result))) return result def has_member(self, struct, member, prefix): # Assume it does return True def get_argument_syntax(self): return 'gcc' def get_supported_arguments(self, args): supported_args = [] for arg in args: if ( arg.startswith('-flifetime-dse') or arg.startswith('-Wno-format-truncation') or arg.startswith('-Wno-nonnull-compare') or arg.startswith('-Wno-class-memaccess') or arg.startswith('-Wno-format-truncation') ): continue supported_args.append(arg) return supported_args def get_supported_link_arguments(self, args): return args def find_library(self, name, required=False): if name == 'ws2_32' or name == 'elf' or name == 'm' or name == 'sensors': return Dependency(name, found=required) exit('Unhandled library: ' + name) def sizeof(self, string): table = _get_sizeof_table(self._cpu_family) if string not in table: exit('Unhandled compiler sizeof: ' + string) return table[string] class PkgConfigModule: def generate( self, lib, name='', description='', extra_cflags=None, filebase='', version='', libraries=None, libraries_private=None, ): pass ################################################################################################### def fprint(args): print(args, file=_gOutputFile) def set_relative_dir(dir): global _gRelativeDir _gRelativeDir = dir def open_output_file(name): global _gOutputFile _gOutputFile = open(name, 'w') def close_output_file(): global _gOutputFile _gOutputFile.close() def get_relative_dir(path_or_file=''): if isinstance(path_or_file, File): return path_or_file.name assert isinstance(path_or_file, str) if path_or_file == '': return _gRelativeDir return os.path.join(_gRelativeDir, path_or_file) def get_relative_gen_dir(path=''): return os.path.join(_gRelativeDir, path) def project(name, language_list, version, license, meson_version, default_options): if type(version) is str: _gProjectVersion = version else: assert type(version) is list version_file = version[0] assert type(version_file) is File with open(version_file.name, 'r') as file: for line in file: _gProjectVersion = line.strip() break for option in default_options: value_pair = option.split('=') _gProjectOptions.append(SimpleOption(value_pair[0], value_pair[1])) def get_project_options(): return _gProjectOptions def add_project_arguments(args, language=[], native=False): global _gProjectCflags, _gProjectCppflags if type(args) is not list: args = [args] for lang in language: for arg in args: if isinstance(arg, list): add_project_arguments(arg, language=language, native=native) continue assert isinstance(arg, str) if lang == 'c': print('cflags: ' + arg) _gProjectCflags.append(arg) elif lang == 'cpp': print('cppflags: ' + arg) _gProjectCppflags.append(arg) else: exit('Unhandle arguments language: ' + lang) def get_project_cflags(): return _gProjectCflags def get_project_cppflags(): return _gProjectCppflags def _get_sizeof_table(cpu_family): table_32 = {'void*': 4} table_64 = {'void*': 8} if cpu_family == 'arm' or cpu_family == 'x86_64': table = table_32 elif cpu_family == 'aarch64': table = table_64 else: exit('sizeof unhandled cpu family: %s' % cpu_family) return table def get_linear_list(arg_list): args = [] for arg in arg_list: if type(arg) is list: args.extend(get_linear_list(arg)) else: args.append(arg) return args def load_dependencies(config): with open(config, 'rb') as f: data = tomllib.load(f) project_configs = data.get('project_config') base_config = data.get('base_project_config') # global dependencies for dep_name, targets in base_config.get('ext_dependencies').items(): dep_targets = { t.get('target_name'): t.get('target_type') for t in targets } external_dep[dep_name] = dep_targets # project specific dependencies for project_config in project_configs: dependencies = project_config.get('ext_dependencies') for dep_name, targets in dependencies.items(): dep_targets = { t.get('target_name'): t.get('target_type') for t in targets } external_dep[dep_name] = dep_targets def dependency(*names, required=True, version=''): for name in names: print('dependency: %s' % name) if name == '': return Dependency('null', version, found=False) if name in external_dep: targets = external_dep.get(name) return Dependency( name, targets=[ DependencyTarget(t, DependencyTargetType(targets[t])) for t in targets ], version=version, found=True, ) # TODO(bpnguyen): Move these hardcoded dependencies to a global config if ( name == 'backtrace' or name == 'curses' or name == 'expat' or name == 'libconfig' or name == 'libmagma_virt' or name == 'libva' or name == 'libzstd' or name == 'libdrm' or name == 'libglvnd' or name == 'libudev' or name == 'libunwind' or name == 'llvm' or name == 'libxml-2.0' or name == 'lua54' or name == 'valgrind' or name == 'wayland-scanner' or name == 'SPIRV-Tools' ): return Dependency(name, version, found=False) if ( name == 'libarchive' or name == 'libelf' or name == 'threads' or name == 'vdpau' ): return Dependency(name, version, found=required) exit('Unhandled dependency: ' + name) def get_set_of_deps(deps, set_of_deps=set()): for dep in deps: if type(dep) is list: set_of_deps = get_set_of_deps(dep, set_of_deps) elif dep not in set_of_deps: set_of_deps.add(dep) set_of_deps = get_set_of_deps(dep.dependencies, set_of_deps) return set_of_deps def get_include_dirs(paths) -> list[str]: dir_list = [] for path in paths: if type(path) is list: dir_list.extend(get_include_dirs(p for p in path)) elif type(path) is IncludeDirectories: dir_list.extend(path.dirs) else: assert type(path) is str dir_list.append(get_relative_dir(path)) return dir_list def get_include_directories(includes) -> list[IncludeDirectories]: dirs = [] if type(includes) is list: for inc in includes: dirs.extend(get_include_directories(inc)) elif type(includes) is IncludeDirectories: dirs.extend(includes) else: assert type(includes) is str exit('get_include_directories got string: %s' % includes) return dirs def get_static_libs(arg_list): libs = [] for arg in arg_list: if type(arg) is list: libs.extend(get_static_libs(arg)) else: assert type(arg) is StaticLibrary libs.extend(get_static_libs(arg.link_with)) libs.append(arg) return libs def get_whole_static_libs(arg_list): libs = [] for arg in arg_list: if type(arg) is list: libs.extend(get_whole_static_libs(arg)) else: assert type(arg) is StaticLibrary libs.extend(get_whole_static_libs(arg._link_whole)) libs.append(arg) return libs def get_list_of_relative_inputs(list_or_string): if isinstance(list_or_string, list): ret = [] for item in list_or_string: ret.extend(get_list_of_relative_inputs(item)) return ret return [get_relative_dir(list_or_string)] def get_command_line_from_args(args: list): command_line = '' for arg in args: command_line += ' ' + arg # Escape angle brackets command_line = re.sub(r'(<|>)', '\\\\\\\\\g<1>', command_line) return command_line def replace_wrapped_input_with_target(args, python_script, python_script_target_name): outargs = [] for index, arg in enumerate(args): pattern = '(.*?)(' + python_script + ')' replace = '\g<1>' + python_script_target_name outargs.append(re.sub(pattern, replace, arg)) return outargs