blob: 6f0be258df8b583ad6a3be274c113b2662d96f10 [file] [log] [blame]
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