""" Copyright 2024 Google LLC SPDX-License-Identifier: MIT """ import os import meson_impl as impl import tomllib import re from jinja2 import Environment, FileSystemLoader from pathlib import Path from meson_build_state import * jinja_env = Environment( loader=FileSystemLoader(Path(__file__).parent.resolve() / 'templates/') ) # A map that holds the build-system to build file # Keep the keys lower-case for non-case sensitivity OUTPUT_FILES = {'soong': r'Android_res.bp', 'bazel': r'BUILD.bazel'} def generate_build_file(translator, build_type: str): defaults_gen = jinja_env.get_template('defaults/all_defaults.txt') defaults = defaults_gen.render() # Write our manually defined defaults with open(OUTPUT_FILES[build_type], 'w') as file: if build_type == 'soong': file.write(defaults) path = build_type + '/' # Render all static libraries static_libs_template = jinja_env.get_template(path + 'static_library.txt') for static_lib in translator.meson_state.static_libraries: if static_lib.library_type is LibraryType.LibraryShared: static_libs_template = jinja_env.get_template( path + 'shared_library.txt' ) cc_lib = static_libs_template.render( name=static_lib.name, host_supported='false', # TODO(bpnguyen): Fix hardcoded host_supported srcs=static_lib.srcs, hdrs=static_lib.generated_headers, generated_headers=static_lib.generated_headers, generated_sources=static_lib.generated_sources, copts=static_lib.copts, c_std_val=static_lib.cstd, cpp_std_val=static_lib.cpp_std, cflags=static_lib.conlyflags, cppflags=static_lib.cppflags, include_directories=static_lib.local_include_dirs, system_include_directories=static_lib.system_include_dirs, static_libs=static_lib.static_libs, whole_static_libs=static_lib.whole_static_libs, shared_libs=static_lib.shared_libs, header_libs=static_lib.header_libs, target_compatible_with=static_lib.target_compatible_with, deps=static_lib.deps, ) # Set the template back to static by default if static_lib.library_type is LibraryType.LibraryShared: static_libs_template = jinja_env.get_template( path + 'static_library.txt' ) file.write(cc_lib) # Render genrules / custom targets custom_target_template = jinja_env.get_template(path + 'genrule.txt') for custom_target in translator.meson_state.custom_targets: genrule = custom_target_template.render( name=custom_target.name, srcs=custom_target.srcs, outs=custom_target.out, tools=custom_target.tools, export=len(custom_target.export_include_dirs) > 0, export_include_dirs=custom_target.export_include_dirs, cmd=custom_target.cmd, ) file.write(genrule) # python binary hosts py_binary_host_template = jinja_env.get_template(path + 'py_binary.txt') for py_binary in translator.meson_state.custom_py_targets: py_binary_render = py_binary_host_template.render( name=py_binary.name, main=py_binary.main, srcs=py_binary.srcs, imports=py_binary.imports, ) file.write(py_binary_render) include_dir_template = jinja_env.get_template(path + 'include_directories.txt') for include_dir in translator.meson_state.include_dirs: include_dir_render = include_dir_template.render( name=include_dir.name, hdrs=include_dir.dirs, ) file.write(include_dir_render) class SoongGenerator(impl.Compiler): def has_header_symbol( self, header: str, symbol: str, args=[], dependencies=[], include_directories=[], no_builtin_args=False, prefix=[], required=False, ): # Exclude what is currently not working. result = self.is_symbol_supported(header, symbol) print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result))) return result def check_header(self, header, prefix=''): result = self.is_header_supported(header) print("check_header '%s': %s" % (header, str(result))) return result def has_function(self, function, args=[], prefix='', dependencies=''): # Exclude what is currently not working. result = self.is_function_supported(function) print("has_function '%s': %s" % (function, str(result))) return result def links(self, snippet, name, args=[], dependencies=[]): # Exclude what is currently not working. result = self.is_link_supported(name) print("links '%s': %s" % (name, str(result))) return result def generate(self, translator): """ Generates a Soong valid build file :return: None """ # Render all the defaults to the file first. generate_build_file(translator, 'soong') class BazelGenerator(impl.Compiler): def is_symbol_supported(self, header: str, symbol: str): if header in meson_translator.config.headers_not_supported: return False if symbol in meson_translator.config.symbols_not_supported: return False return super().is_symbol_supported(header, symbol) def is_function_supported(self, function): if function in meson_translator.config.functions_not_supported: return False return super().is_function_supported(function) def is_link_supported(self, name): if name in meson_translator.config.links_not_supported: return False return super().is_link_supported(name) def has_header_symbol( self, header: str, symbol: str, args=None, dependencies=None, include_directories=None, no_builtin_args=False, prefix=None, required=False, ): if args is None: args = [] if dependencies is None: dependencies = [] if include_directories is None: include_directories = [] if prefix is None: prefix = [] # Exclude what is currently not working. result = self.is_symbol_supported(header, symbol) print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result))) return result def check_header(self, header, prefix=''): result = self.is_header_supported(header) print(f"check_header '{header}': {result}") return result def has_function(self, function, args=[], prefix='', dependencies='') -> bool: # Exclude what is currently not working. result = self.is_function_supported(function) print(f"has_function '{function}': {result}") return result def links(self, snippet, name, args=[], dependencies=[]): # Exclude what is currently not working. result = self.is_link_supported(name) print(f"links '{name}': {result}") return result def generate(self, translator): generate_build_file(translator, 'bazel') class BazelPkgConfigModule(impl.PkgConfigModule): def generate( self, lib, name='', description='', extra_cflags=None, filebase='', version='', libraries=None, libraries_private=None, ): assert type(lib) is impl.StaticLibrary sl = StaticLibrary() sl.name = name sl.deps = lib.target_name sl.system_include_dirs.append(".") sl.visibility.append('//visibility:public') meson_translator.meson_state.static_libraries.append(sl) class MesonTranslator: """ Abstract class that defines all common attributes between different build systems (Soong, Bazel, etc...) """ def __init__(self): self._generator = None self._config_file = None self._build = '' # configs and meson_project_states are ordered with each other. self._configs: list[ProjectConfig] = [] self._meson_project_states: list[MesonProjectState] = [] # TODO(357080225): Fix the use hardcoded state 0 self._state = 0 # index of self._configs def init_data(self, config_file: str): self._config_file: str = config_file print('CONFIG:', self._config_file) self._init_metadata() _generator = ( SoongGenerator(self.config.cpu_family) if self._build.lower() == 'soong' else BazelGenerator(self.config.cpu_family) ) self._generator: impl.Compiler = _generator return self @property def config_file(self) -> Path: return self._config_file @property def host_machine(self) -> str: return self._configs[self._state].host_machine @property def build_machine(self) -> str: return self._configs[self._state].build_machine @property def generator(self) -> impl.Compiler: return self._generator @property def build(self) -> str: return self._build @property def config(self) -> ProjectConfig: return self._configs[self._state] @property def meson_state(self): return self._meson_project_states[self._state] def is_soong(self): return self._build.lower() == 'soong' def is_bazel(self): return self._build.lower() == 'bazel' def get_output_filename(self) -> str: return OUTPUT_FILES[self._build.lower()] def _init_metadata(self): """ To be called after self._config_file has alredy been assigned. Parses the given config files for common build attributes Fills the list of all project configs :return: None """ with open(self._config_file, 'rb') as f: data = tomllib.load(f) self._build = data.get('build') base_config = data.get('base_project_config') configs = data.get('project_config') for config in configs: proj_config = ProjectConfig.create_project_config(self._build, **config) self._configs.append( proj_config ) self._meson_project_states.append(MesonProjectState()) new_configs = [] # Handle Inheritance for config in self._configs: # Parent config, that contains shared attributes base_proj_config = ProjectConfig.create_project_config(self._build, **base_config) if not config.inherits_from: new_configs.append(config) continue if config.inherits_from == 'base_project_config': new_configs.append( base_proj_config.extend(config).deepcopy() ) else: for proj_config in self._configs: if config.inherits_from == proj_config.name: new_configs.append( proj_config.extend(config).deepcopy() ) self._configs = new_configs # Declares an empty attribute data class # metadata is allocated during Generators-runtime (I.E. generate__build.py) meson_translator = MesonTranslator() _gIncludeDirectories = {} def load_meson_data(config_file: str): meson_translator.init_data(config_file) def open_output_file(): impl.open_output_file(meson_translator.get_output_filename()) def close_output_file(): impl.close_output_file() def add_subdirs_to_set(dir_, dir_set): subdirs = os.listdir(dir_) for subdir in subdirs: subdir = os.path.join(dir_, subdir) if os.path.isdir(subdir): dir_set.add(subdir) def include_directories(*paths, is_system=False): if meson_translator.host_machine == 'android': return impl.IncludeDirectories('', impl.get_include_dirs(paths)) # elif host_machine == 'fuchsia' dirs = impl.get_include_dirs(paths) name = dirs[0].replace('/', '_') if is_system: name += '_sys' global _gIncludeDirectories if name not in _gIncludeDirectories: # Mesa likes to include files at a level down from the include path, # so ensure Bazel allows this by including all of the possibilities. # Can't repeat entries so use a set. dir_set = set() for dir_ in dirs: dir_ = os.path.normpath(dir_) dir_set.add(dir_) add_subdirs_to_set(dir_, dir_set) # HACK: For special cases we go down two levels: # - Mesa vulkan runtime does #include "vulkan/wsi/..." paths_needing_two_levels = ['src/vulkan'] for dir_ in list(dir_set): if dir_ in paths_needing_two_levels: add_subdirs_to_set(dir_, dir_set) include_dir = IncludeDirectories() include_dir.name = name for dir_ in dir_set: include_dir.dirs.append(os.path.normpath(os.path.join(dir_, '*.h'))) include_dir.dirs.append(os.path.normpath(os.path.join(dir_, '*.c'))) include_dir.visibility.append('//visibility:public') _gIncludeDirectories[name] = impl.IncludeDirectories(name, dirs) meson_translator.meson_state.include_dirs.append(include_dir) return _gIncludeDirectories[name] def module_import(name: str): if name == 'python': return impl.PythonModule() if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'fuchsia': return BazelPkgConfigModule() if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'android': return impl.PkgConfigModule() if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'linux': return impl.PkgConfigModule() exit( f'Unhandled module: "{name}" for host machine: "{meson_translator.host_machine}"' ) def load_dependencies(): impl.load_dependencies(meson_translator.config_file) def dependency( *names, required=True, version='', allow_fallback=False, method='', modules=[], optional_modules=[], static=True, fallback=[], include_type='', native=False, ): return impl.dependency(*names, required=required, version=version) def static_library( target_name, *source_files, c_args=[], cpp_args=[], c_pch='', build_by_default=False, build_rpath='', d_debug=[], d_import_dirs=[], d_module_versions=[], d_unittest=False, dependencies=[], extra_files='', gnu_symbol_visibility='', gui_app=False, implicit_include_directories=False, include_directories=[], install=False, install_dir='', install_mode=[], install_rpath='', install_tag='', link_args=[], link_depends='', link_language='', link_whole=[], link_with=[], name_prefix='', name_suffix='', native=False, objects=[], override_options=[], pic=False, prelink=False, rust_abi='', rust_crate_type='', rust_dependency_map={}, sources='', vala_args=[], win_subsystem='', ): print('static_library: ' + target_name) link_with = impl.get_linear_list(link_with) link_whole = impl.get_linear_list(link_whole) # Emitting link_with/link_whole into a static library isn't useful for building, # shared libraries must specify the whole chain of dependencies. # Leaving them here for traceability though maybe it's less confusing to remove them? _emit_builtin_target( target_name, *source_files, c_args=c_args, cpp_args=cpp_args, dependencies=dependencies, include_directories=include_directories, link_with=link_with, link_whole=link_whole, builtin_type_name='cc_library_static', static=meson_translator.is_bazel(), library_type=LibraryType.LibraryStatic, ) return impl.StaticLibrary(target_name, link_with=link_with, link_whole=link_whole) def _get_sources(input_sources, sources, generated_sources, generated_headers): def generate_sources_fuchsia(source_): if type(source_) is impl.CustomTarget: generated_sources.add(source_.target_name()) for out in source_.outputs: sources.add(out) elif type(source_) is impl.CustomTargetItem: target = source_.target generated_sources.add(target.target_name()) sources.add(target.outputs[source_.index]) def generate_sources_android(source_): if type(source_) is impl.CustomTarget: if source_.generates_h(): generated_headers.add(source_.target_name_h()) if source_.generates_c(): generated_sources.add(source_.target_name_c()) elif type(source_) is impl.CustomTargetItem: source_ = source_.target if source_.generates_h(): generated_headers.add(source_.target_name_h()) if source_.generates_c(): generated_sources.add(source_.target_name_c()) for source in input_sources: if type(source) is list: _get_sources(source, sources, generated_sources, generated_headers) elif type(source) is impl.CustomTarget or type(source) is impl.CustomTargetItem: if meson_translator.is_bazel(): generate_sources_fuchsia(source) else: # is_soong generate_sources_android(source) elif type(source) is impl.File: sources.add(source.name) elif type(source) is str: sources.add(impl.get_relative_dir(source)) else: exit(f'source type not handled: {type(source)}') # TODO(bpnguyen): Implement some bazel/soong parser that would merge # logic from this function to _emit_builtin_target_android def _emit_builtin_target_fuchsia( target_name, *source, static=False, c_args=[], cpp_args=[], dependencies=[], include_directories=[], link_with=[], link_whole=[], library_type=LibraryType.Library, ): sl = StaticLibrary() target_name_so = target_name target_name = target_name if static else '_' + target_name sl.name = target_name sl.library_type = library_type srcs = set() generated_sources = set() generated_headers = set() generated_header_files = [] for source_arg in source: assert type(source_arg) is list _get_sources(source_arg, srcs, generated_sources, generated_headers) deps = impl.get_set_of_deps(dependencies) include_directories = impl.get_include_directories(include_directories) static_libs = [] whole_static_libs = [] shared_libs = [] for dep in deps: print(' dep: ' + dep.name) for src in impl.get_linear_list([dep.sources]): if type(src) is impl.CustomTargetItem: generated_headers.add(src.target.target_name_h()) generated_header_files.extend(src.target.header_outputs()) elif type(src) is impl.CustomTarget: generated_headers.add(src.target_name_h()) generated_header_files.extend(src.header_outputs()) else: exit('Unhandled source dependency: ' + str(type(src))) include_directories.extend( impl.get_include_directories(dep.include_directories) ) for target in impl.get_static_libs([dep.link_with]): assert type(target) is impl.StaticLibrary static_libs.append(target.target_name) for target in impl.get_linear_list([dep.link_whole]): assert type(target) is impl.StaticLibrary whole_static_libs.append(target.target_name) for target in dep.targets: if target.target_type == impl.DependencyTargetType.SHARED_LIBRARY: shared_libs.append(target.target_name) elif target.target_type == impl.DependencyTargetType.STATIC_LIBRARY: static_libs.append(target.target_name) elif target.target_type == impl.DependencyTargetType.HEADER_LIBRARY: exit('Header library not supported') c_args.append(dep.compile_args) cpp_args.append(dep.compile_args) for target in impl.get_static_libs(link_with): if type(target) is impl.StaticLibrary: static_libs.append(target.target_name) else: exit('Unhandled link_with type: ' + str(type(target))) for target in impl.get_whole_static_libs(link_whole): if type(target) is impl.StaticLibrary: whole_static_libs.append(target.target_name()) else: exit('Unhandled link_whole type: ' + str(type(target))) # Android turns all warnings into errors but thirdparty projects typically can't handle that cflags = ['-Wno-error'] + impl.get_linear_list(impl.get_project_cflags() + c_args) cppflags = ['-Wno-error'] + impl.get_linear_list( impl.get_project_cppflags() + cpp_args ) has_c_files = False for src in srcs: if src.endswith('.c'): has_c_files = True sl.srcs.append(src) # For Bazel to find headers in the "current area", we have to include # not just the headers in the relative dir, but also relative subdirs # that aren't themselves areas (containing meson.build). # We only look one level deep. local_include_dirs = [impl.get_relative_dir()] local_entries = ( [] if impl.get_relative_dir() == '' else os.listdir(impl.get_relative_dir()) ) for entry in local_entries: entry = os.path.join(impl.get_relative_dir(), entry) if os.path.isdir(entry): subdir_entries = os.listdir(entry) if 'meson.build' not in subdir_entries: local_include_dirs.append(entry) for hdr in set(generated_header_files): sl.generated_headers.append(hdr) for hdr in local_include_dirs: sl.local_include_dirs.append(os.path.normpath(os.path.join(hdr, '*.h'))) # Needed for subdir sources sl.copts.append(f'-I {impl.get_relative_dir()}') sl.copts.append(f'-I $(GENDIR)/{impl.get_relative_dir()}') for inc in include_directories: for dir in inc.dirs: sl.copts.append(f'-I {dir}') sl.copts.append(f'-I $(GENDIR)/{dir}') if has_c_files: for arg in cflags: # Double escape double quotations arg = re.sub(r'"', '\\\\\\"', arg) sl.copts.append(arg) else: for arg in cppflags: # Double escape double quotations arg = re.sub(r'"', '\\\\\\"', arg) sl.copts.append(arg) # Ensure bazel deps are unique bazel_deps = set() for lib in static_libs: bazel_deps.add(lib) for lib in whole_static_libs: bazel_deps.add(lib) for inc in include_directories: bazel_deps.add(inc.name) for target in generated_headers: bazel_deps.add(target) for target in generated_sources: bazel_deps.add(target) for bdep in bazel_deps: sl.deps.append(bdep) sl.target_compatible_with.append('@platforms//os:fuchsia') sl.visibility.append('//visibility:public') meson_translator.meson_state.static_libraries.append(sl) if not static: shared_sl = StaticLibrary() shared_sl.library_type = LibraryType.LibraryShared shared_sl.name = target_name_so shared_sl.deps.append(target_name) meson_translator.meson_state.static_libraries.append(shared_sl) # TODO(bpnguyen): Implement some bazel/soong parser that would merge # logic from this function to _emit_builtin_target_fuchsia def _emit_builtin_target_android( target_name, *source, c_args=[], cpp_args=[], dependencies=[], include_directories=[], link_with=[], link_whole=[], builtin_type_name='', static=False, library_type=LibraryType.Library, ): static_lib = StaticLibrary() static_lib.name = target_name static_lib.library_type = library_type srcs = set() generated_sources = set() generated_headers = set() for source_arg in source: assert type(source_arg) is list _get_sources(source_arg, srcs, generated_sources, generated_headers) deps = impl.get_set_of_deps(dependencies) include_directories = [impl.get_relative_dir()] + impl.get_include_dirs( include_directories ) static_libs = [] whole_static_libs = [] shared_libs = [] header_libs = [] for dep in deps: print(' dep: ' + dep.name) for src in impl.get_linear_list([dep.sources]): if type(src) is impl.CustomTargetItem: generated_headers.add(src.target.target_name_h()) elif type(src) is impl.CustomTarget: generated_headers.add(src.target_name_h()) else: exit('Unhandled source dependency: ' + str(type(src))) include_directories.extend(impl.get_include_dirs(dep.include_directories)) for target in impl.get_static_libs([dep.link_with]): assert type(target) is impl.StaticLibrary static_libs.append(target.target_name) for target in impl.get_linear_list([dep.link_whole]): assert type(target) is impl.StaticLibrary whole_static_libs.append(target.target_name) for target in dep.targets: if target.target_type is impl.DependencyTargetType.SHARED_LIBRARY: shared_libs.append(target.target_name) elif target.target_type is impl.DependencyTargetType.STATIC_LIBRARY: static_libs.append(target.target_name) elif target.target_type is impl.DependencyTargetType.HEADER_LIBRARY: header_libs.append(target.target_name) c_args.append(dep.compile_args) cpp_args.append(dep.compile_args) for target in impl.get_static_libs(link_with): if type(target) is impl.StaticLibrary: static_libs.append(target.target_name) else: exit(f'Unhandled link_with type: {type(target)}') for target in impl.get_whole_static_libs(link_whole): if type(target) is impl.StaticLibrary: whole_static_libs.append(target.target_name()) else: exit(f'Unhandled link_whole type: {type(target)}') # Android turns all warnings into errors but thirdparty projects typically can't handle that cflags = ['-Wno-error'] + impl.get_linear_list(impl.get_project_cflags() + c_args) cppflags = ['-Wno-error'] + impl.get_linear_list( impl.get_project_cppflags() + cpp_args ) for src in srcs: # Filter out header files if not src.endswith('.h'): static_lib.srcs.append(src) for generated in generated_headers: static_lib.generated_headers.append(generated) for generated in generated_sources: static_lib.generated_sources.append(generated) for arg in impl.get_project_options(): if arg.name == 'c_std': static_lib.cstd = arg.value elif arg.name == 'cpp_std': static_lib.cpp_std = arg.value for arg in cflags: # Escape double quotations arg = re.sub(r'"', '\\"', arg) static_lib.conlyflags.append(arg) for arg in cppflags: # Escape double quotations arg = re.sub(r'"', '\\"', arg) static_lib.cppflags.append(arg) for inc in include_directories: static_lib.local_include_dirs.append(inc) for lib in static_libs: static_lib.static_libs.append(lib) for lib in whole_static_libs: static_lib.whole_static_libs.append(lib) for lib in shared_libs: static_lib.shared_libs.append(lib) for lib in header_libs: static_lib.header_libs.append(lib) meson_translator.meson_state.static_libraries.append(static_lib) def _emit_builtin_target( target_name, *source, c_args=[], cpp_args=[], dependencies=[], include_directories=[], link_with=[], link_whole=[], builtin_type_name='', static=False, library_type=LibraryType.Library, ): if meson_translator.is_bazel(): _emit_builtin_target_fuchsia( target_name, *source, c_args=c_args, cpp_args=cpp_args, dependencies=dependencies, include_directories=include_directories, link_with=link_with, link_whole=link_whole, static=static, library_type=library_type, ) else: # meson_translator.is_soong() _emit_builtin_target_android( target_name, *source, c_args=c_args, cpp_args=cpp_args, dependencies=dependencies, include_directories=include_directories, link_with=link_with, link_whole=link_whole, builtin_type_name=builtin_type_name, library_type=library_type, ) def shared_library( target_name, *source, c_args=[], cpp_args=[], c_pch='', build_by_default=False, build_rpath='', d_debug=[], d_import_dirs=[], d_module_versions=[], d_unittest=False, darwin_versions='', dependencies=[], extra_files='', gnu_symbol_visibility='', gui_app=False, implicit_include_directories=False, include_directories=[], install=False, install_dir='', install_mode=[], install_rpath='', install_tag='', link_args=[], link_depends=[], link_language='', link_whole=[], link_with=[], name_prefix='', name_suffix='', native=False, objects=[], override_options=[], rust_abi='', rust_crate_type='', rust_dependency_map={}, sources=[], soversion='', vala_args=[], version='', vs_module_defs='', win_subsystem='', ): print('shared_library: ' + target_name) link_with = impl.get_linear_list([link_with]) link_whole = impl.get_linear_list([link_whole]) _emit_builtin_target( target_name, *source, c_args=c_args, static=False, cpp_args=cpp_args, dependencies=dependencies, include_directories=include_directories, link_with=link_with, link_whole=link_whole, builtin_type_name='cc_library_shared', library_type=LibraryType.LibraryStatic, ) return impl.SharedLibrary(target_name) def _process_target_name(name): name = re.sub(r'[\[\]]', '', name) return name def _location_wrapper(name_or_list) -> str | list[str]: if isinstance(name_or_list, list): ret = [] for i in name_or_list: ret.append(f'$(location {i})') return ret assert isinstance(name_or_list, str) return f'$(location {name_or_list})' def _is_header(name): return re.search(r'\.h[xx|pp]?$', name) is not None def _is_source(name): return re.search(r'\.c[c|xx|pp]?$', name) is not None def _get_command_args( command, input, output, deps, location_wrap=False, obfuscate_output_c=False, obfuscate_output_h=False, obfuscate_suffix='', ): args = [] gendir = 'GENDIR' if meson_translator.is_bazel() else 'genDir' for command_item in command[1:]: if isinstance(command_item, list): for item in command_item: if type(item) is impl.File: args.append( _location_wrapper(item.name) if location_wrap else item.name ) elif type(item) is str: args.append(item) continue assert type(command_item) is str match = re.match(r'@INPUT([0-9])?@', command_item) if match is not None: if match.group(1) is not None: input_index = int(match.group(1)) input_list = impl.get_list_of_relative_inputs(input[input_index]) else: input_list = impl.get_list_of_relative_inputs(input) args.extend(_location_wrapper(input_list) if location_wrap else input_list) continue match = re.match(r'(.*?)@OUTPUT([0-9])?@', command_item) if match is not None: output_list = [] if match.group(2) is not None: output_index = int(match.group(2)) selected_output = ( output[output_index] if isinstance(output, list) else output ) output_list.append(impl.get_relative_gen_dir(selected_output)) elif isinstance(output, list): for out in output: output_list.append(impl.get_relative_gen_dir(out)) else: output_list.append(impl.get_relative_gen_dir(output)) for out in output_list: if _is_header(out) and obfuscate_output_h: args.append( match.group(1) + f'$({gendir})/{out}' if location_wrap else out ) else: if _is_source(out) and obfuscate_output_c: out += obfuscate_suffix args.append( match.group(1) + _location_wrapper(out) if location_wrap else out ) continue # Assume used to locate generated outputs match = re.match(r'(.*?)@CURRENT_BUILD_DIR@', command_item) if match is not None: args.append(f'$({gendir})' + '/' + impl.get_relative_dir()) continue if meson_translator.is_bazel(): match = re.match(r'@PROJECT_BUILD_ROOT@(.*)', command_item) if match is not None: args.append(f'$({gendir}){match.group(1)}') continue # A plain arg if ' ' in command_item: args.append(f"'{command_item}'") else: args.append(command_item) return args def library( target_name, *sources, c_args=[], install=False, link_args=[], vs_module_defs='', version='', ): print('library: ' + target_name) return static_library( target_name, *sources, c_args=c_args, install=install, link_args=link_args, sources=sources, ) # Assume dependencies of custom targets are custom targets that are generating # python scripts; build a python path of their locations. def _get_python_path(deps): python_path = '' for index, dep in enumerate(deps): assert type(dep) is impl.CustomTarget if index > 0: python_path += ':' python_path += '`dirname %s`' % _location_wrapper(':%s' % dep.target_name()) return python_path def _get_export_include_dirs(): dirs = [impl.get_relative_dir()] # HACK for source files that expect that include generated files like: # include "vulkan/runtime/...h" if impl.get_relative_dir().startswith('src'): dirs.append('src') return dirs def _process_wrapped_args_for_python( wrapped_args, python_script, python_script_target_name, deps ): # The python script arg should be replaced with the python binary target name args = impl.replace_wrapped_input_with_target( wrapped_args, python_script, python_script_target_name ) if meson_translator.is_bazel(): return args # else is_soong(): python_path = 'PYTHONPATH=' # Python scripts expect to be able to import other scripts from the same directory, but this # doesn't work in the soong execution environment, so we have to explicitly add the script # dir. We can't use $(location python_binary) because that is missing the relative path; # instead we can use $(location python_script), which happens to work, and we're careful to # ensure the script is in the list of sources even when it's used as the command directly. python_path += '`dirname $(location %s)`' % python_script # Also ensure that scripts generated by dependent custom targets can be imported. if type(deps) is impl.CustomTarget: python_path += ':' + _get_python_path([deps]) if type(deps) is list: python_path += ':' + _get_python_path(deps) args.insert(0, python_path) return args # TODO(bpnguyen): merge custom_target def custom_target( target_name: str, build_always=False, build_always_stale=False, build_by_default=False, capture=False, command=[], console=False, depend_files=[], depends=[], depfile='', env=[], feed=False, input=[], install=False, install_dir='', install_mode=[], install_tag=[], output=[], ): target_name = _process_target_name(target_name) print('Custom target: ' + target_name) assert type(command) is list program = command[0] program_args = [] # The program can be an array that includes arguments if isinstance(program, list): for arg in program[1:]: assert type(arg) is str program_args.append(arg) program = program[0] assert isinstance(program, impl.Program) assert program.found() args = program_args + _get_command_args(command, input, output, depends) # Python scripts need special handling to find mako library python_script = '' python_script_target_name = '' if program.command.endswith('.py'): python_script = program.command else: for index, arg in enumerate(args): if arg.endswith('.py'): python_script = arg break if python_script != '': python_script_target_name = target_name + '_' + os.path.basename(python_script) srcs = [python_script] + impl.get_list_of_relative_inputs(depend_files) python_custom_target = PythonCustomTarget() python_custom_target.name = python_script_target_name python_custom_target.main = python_script for src in set(srcs): if src.endswith('.py'): python_custom_target.srcs.append(src) for src in set(srcs): if src.endswith('.py'): python_custom_target.imports.append(os.path.dirname(src)) meson_translator.meson_state.custom_py_targets.append(python_custom_target) relative_inputs = impl.get_list_of_relative_inputs(input) # We use python_host_binary instead of calling python scripts directly; # however there's an issue with python locating modules in the same directory # as the script; to workaround that (see _process_wrapped_args_for_python) we # ensure the script is listed in the genrule targets. if python_script != '' and python_script not in relative_inputs: relative_inputs.append(python_script) relative_inputs.extend(impl.get_list_of_relative_inputs(depend_files)) relative_inputs_set = set() if meson_translator.is_soong(): for src in relative_inputs: relative_inputs_set.add(src) relative_outputs = [] if isinstance(output, list): for file in output: relative_outputs.append(impl.get_relative_gen_dir(file)) else: assert type(output) is str relative_outputs.append(impl.get_relative_gen_dir(output)) generates_h = False generates_c = False custom_target_ = None if meson_translator.is_soong(): # Soong requires genrule to generate only headers OR non-headers for out in relative_outputs: if _is_header(out): generates_h = True if _is_source(out): generates_c = True custom_target_ = impl.CustomTarget( target_name, relative_outputs, generates_h, generates_c ) else: # is_bazel: custom_target_ = impl.CustomTarget(target_name, relative_outputs) program_command = program.command if meson_translator.is_soong(): if program_command == 'bison': program_command_arg = 'M4=$(location m4) $(location bison)' elif program_command == 'flex': program_command_arg = 'M4=$(location m4) $(location flex)' elif program_command.endswith('.py'): program_command_arg = _location_wrapper(program_command) else: program_command_arg = program_command program_args = [program_command_arg] + program_args if custom_target_.generates_h() and custom_target_.generates_c(): # Make a rule for only the headers obfuscate_suffix = '.dummy.h' wrapped_args = program_args + _get_command_args( command, input, output, depends, location_wrap=True, obfuscate_output_c=True, obfuscate_suffix=obfuscate_suffix, ) if python_script: wrapped_args = _process_wrapped_args_for_python( wrapped_args, python_script, python_script_target_name, depends ) command_line = impl.get_command_line_from_args(wrapped_args) if capture: command_line += ' > %s' % _location_wrapper( impl.get_relative_gen_dir(output) ) ct = CustomTarget() ct.name = custom_target_.target_name_h() for src in relative_inputs_set: ct.srcs.append(src) for dep in depends: assert type(dep) is impl.CustomTarget ct.srcs.append(':' + dep.target_name()) for out in relative_outputs: if _is_source(out): out += obfuscate_suffix # The scripts may still write to the assumed .c file, ensure the obfuscated # file exists command_line += ( "; echo '//nothing to see here' > " + _location_wrapper(out) ) ct.out.append(out) if python_script_target_name != '': ct.tools.append(python_script_target_name) if program_command == 'bison' or program_command == 'flex': ct.tools.append('m4') ct.tools.append(program_command) for dir in _get_export_include_dirs(): ct.export_include_dirs.append(dir) ct.cmd = command_line meson_translator.meson_state.custom_targets.append(ct) # Make a rule for only the sources obfuscate_suffix = '.dummy.c' wrapped_args = program_args + _get_command_args( command, input, output, depends, location_wrap=True, obfuscate_output_h=True, obfuscate_suffix=obfuscate_suffix, ) if python_script: wrapped_args = _process_wrapped_args_for_python( wrapped_args, python_script, python_script_target_name, depends ) command_line = impl.get_command_line_from_args(wrapped_args) if capture: command_line += ' > %s' % _location_wrapper( impl.get_relative_gen_dir(output) ) # We keep the header as an output with an obfuscated name because some scripts insist # on having --out-h (like vk_entrypoints_gen.py). When Soong depends on this genrule # it'll insist on compiling all the outputs, so we replace the content of all header # outputs. ct_ = CustomTarget() ct_.name = custom_target_.target_name_c() for src in relative_inputs_set: ct_.srcs.append(src) for dep in depends: assert type(dep) is impl.CustomTarget ct_.srcs.append(':' + dep.target_name()) for out in relative_outputs: if _is_header(out): out += obfuscate_suffix ct_.out.append(out) # Remove the content because Soong will compile this dummy source file command_line += ( "; echo '//nothing to see here' > " + _location_wrapper(out) ) ct_.out.append(out) if python_script_target_name != '': ct_.tools.append(python_script_target_name) if program_command == 'bison' or program_command == 'flex': ct.tools.append('m4') ct.tools.append(program_command) ct_.cmd = command_line meson_translator.meson_state.custom_targets.append(ct_) return custom_target_ else: wrapped_args = program_args + _get_command_args( command, input, output, depends, location_wrap=True ) if python_script: wrapped_args = _process_wrapped_args_for_python( wrapped_args, python_script, python_script_target_name, depends ) command_line = impl.get_command_line_from_args(wrapped_args) if capture: command_line += ' > %s' % _location_wrapper( impl.get_relative_gen_dir(output) ) ct = CustomTarget() ct.name = custom_target_.target_name() for src in relative_inputs_set: ct.srcs.append(src) for dep in depends: assert type(dep) is impl.CustomTarget ct.srcs.append(':' + dep.target_name()) for out in relative_outputs: ct.out.append(out) if python_script_target_name != '': ct.tools.append(python_script_target_name) if program_command == 'bison' or program_command == 'flex': ct.tools.append('m4') ct.tools.append(program_command) for dir_ in _get_export_include_dirs(): ct.export_include_dirs.append(dir_) ct.cmd = command_line meson_translator.meson_state.custom_targets.append(ct) else: # is_bazel if program_command.endswith('.py'): program_command_arg = _location_wrapper(program_command) else: program_command_arg = program_command program_args = [program_command_arg] + program_args wrapped_args = program_args + _get_command_args( command, input, output, depends, location_wrap=True ) if python_script: wrapped_args = _process_wrapped_args_for_python( wrapped_args, python_script, python_script_target_name, depends ) command_line = impl.get_command_line_from_args(wrapped_args) if capture: command_line += ' > %s' % _location_wrapper( impl.get_relative_gen_dir(output) ) ct = CustomTarget() ct.name = custom_target_.target_name() for src in set(relative_inputs): ct.srcs.append(src) for dep in depends: assert type(dep) is impl.CustomTarget ct.srcs.append(dep.target_name()) for out in set(relative_outputs): ct.out.append(out) if python_script_target_name != '': ct.tools.append(python_script_target_name) ct.cmd = command_line meson_translator.meson_state.custom_targets.append(ct) return custom_target_