#!/usr/bin/env python3 # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This tool translates a collection of BUILD.gn files into a mostly equivalent # Android.bp file for the Android Soong build system. The input to the tool is a # JSON description of the GN build definition generated with the following # command: # # gn desc out --format=json --all-toolchains "//*" > desc.json # # The tool is then given a list of GN labels for which to generate Android.bp # build rules. The dependencies for the GN labels are squashed to the generated # Android.bp target, except for actions which get their own genrule. Some # libraries are also mapped to their Android equivalents -- see |builtin_deps|. import argparse import json import logging as log import operator import os import re import sys import copy from pathlib import Path import gn_utils ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) CRONET_LICENSE_NAME = "external_cronet_license" # Default targets to translate to the blueprint file. DEFAULT_TARGETS = [ '//components/cronet/android:cronet', '//components/cronet/android:cronet_android_mainline', ] DEFAULT_TESTS = [ '//components/cronet/android:cronet_unittests_android__library', '//net:net_unittests__library', '//components/cronet/android:cronet_tests', ] EXTRAS_ANDROID_BP_FILE = "Android.extras.bp" CRONET_API_MODULE_NAME = "cronet_aml_api_java" # All module names are prefixed with this string to avoid collisions. module_prefix = 'cronet_aml_' # Shared libraries which are directly translated to Android system equivalents. shared_library_allowlist = [ 'android', 'log', ] # Include directories that will be removed from all targets. local_include_dirs_denylist = [ 'third_party/zlib/', ] # Name of the module which settings such as compiler flags for all other # modules. defaults_module = module_prefix + 'defaults' # Location of the project in the Android source tree. tree_path = 'external/cronet' # Path for the protobuf sources in the standalone build. buildtools_protobuf_src = '//buildtools/protobuf/src' # Location of the protobuf src dir in the Android source tree. android_protobuf_src = 'external/protobuf/src' # put all args on a new line for better diffs. NEWLINE = ' " +\n "' # Compiler flags which are passed through to the blueprint. cflag_allowlist = [ # needed for zlib:zlib "-mpclmul", # needed for zlib:zlib "-mssse3", # needed for zlib:zlib "-msse3", # needed for zlib:zlib "-msse4.2", # flags to reduce binary size "-O1", "-O2", "-O3", "-Oz", "-g1", "-g2", "-fdata-sections", "-ffunction-sections", "-fvisibility=hidden", "-fvisibility-inlines-hidden", "-fstack-protector", "-mno-outline", "-mno-outline-atomics", "-fno-asynchronous-unwind-tables", "-fno-unwind-tables", ] # Linker flags which are passed through to the blueprint. ldflag_allowlist = [ # flags to reduce binary size "-Wl,--as-needed", "-Wl,--gc-sections", "-Wl,--icf=all", ] def get_linker_script_ldflag(script_path): return f'-Wl,--script,{tree_path}/{script_path}' # Additional arguments to apply to Android.bp rules. additional_args = { 'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers': [ ('export_include_dirs', { "net/third_party/quiche/src", }) ], 'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto__testing_gen_headers': [ ('export_include_dirs', { "net/third_party/quiche/src", }) ], 'cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers': [ ('export_include_dirs', { "third_party/quic_trace/src", }) ], 'cronet_aml_net_net': [ ('export_static_lib_headers', { 'cronet_aml_net_third_party_quiche_quiche', 'cronet_aml_crypto_crypto', }), ], # TODO: fix upstream. Both //base:base and # //base/allocator/partition_allocator:partition_alloc do not create a # dependency on gtest despite using gtest_prod.h. 'cronet_aml_base_base': [ ('header_libs', { 'libgtest_prod_headers', }), ('export_header_lib_headers', { 'libgtest_prod_headers', }), ], 'cronet_aml_base_allocator_partition_allocator_partition_alloc': [ ('header_libs', { 'libgtest_prod_headers', }), ], } def always_disable(module, arch): return None def enable_zlib(module, arch): # Requires crrev/c/4109079 if arch == 'common': module.shared_libs.add('libz') else: module.target[arch].shared_libs.add('libz') def enable_boringssl(module, arch): # Do not add boringssl targets to cc_genrules. This happens, because protobuf targets are # originally static_libraries, but later get converted to a cc_genrule. if module.is_genrule(): return if arch == 'common': shared_libs = module.shared_libs else: shared_libs = module.target[arch].shared_libs shared_libs.add('//external/cronet/third_party/boringssl:libcrypto') shared_libs.add('//external/cronet/third_party/boringssl:libssl') # Android equivalents for third-party libraries that the upstream project # depends on. builtin_deps = { '//buildtools/third_party/libunwind:libunwind': always_disable, '//buildtools/third_party/libunwind:libunwind__testing': always_disable, '//net/data/ssl/chrome_root_store:gen_root_store_inc': always_disable, '//net/data/ssl/chrome_root_store:gen_root_store_inc__testing': always_disable, '//net/tools/root_store_tool:root_store_tool': always_disable, '//net/tools/root_store_tool:root_store_tool__testing': always_disable, '//third_party/zlib:zlib': enable_zlib, '//third_party/zlib:zlib__testing': enable_zlib, '//third_party/boringssl:boringssl': enable_boringssl, '//third_party/boringssl:boringssl_asm': # Due to FIPS requirements, downstream BoringSSL has a different "shape" than upstream's. # We're guaranteed that if X depends on :boringssl it will also depend on :boringssl_asm. # Hence, always drop :boringssl_asm and handle the translation entirely in :boringssl. always_disable, } # Name of tethering apex module tethering_apex = "com.android.tethering" # Name of cronet api target java_api_target_name = "//components/cronet/android:cronet_api_java" # Visibility set for package default package_default_visibility = ":__subpackages__" # Visibility set for modules used from Connectivity connectivity_visibility = "//packages/modules/Connectivity:__subpackages__" # ---------------------------------------------------------------------------- # End of configuration. # ---------------------------------------------------------------------------- def write_blueprint_key_value(output, name, value, sort=True): """Writes a Blueprint key-value pair to the output""" if isinstance(value, bool): if value: output.append(' %s: true,' % name) else: output.append(' %s: false,' % name) return if not value: return if isinstance(value, set): value = sorted(value) if isinstance(value, list): output.append(' %s: [' % name) for item in sorted(value) if sort else value: output.append(' "%s",' % item) output.append(' ],') return if isinstance(value, Module.Target): value.to_string(output) return if isinstance(value, dict): kv_output = [] for k, v in value.items(): write_blueprint_key_value(kv_output, k, v) output.append(' %s: {' % name) for line in kv_output: output.append(' %s' % line) output.append(' },') return output.append(' %s: "%s",' % (name, value)) class Module(object): """A single module (e.g., cc_binary, cc_test) in a blueprint.""" class Target(object): """A target-scoped part of a module""" def __init__(self, name): self.name = name self.srcs = set() self.shared_libs = set() self.static_libs = set() self.whole_static_libs = set() self.header_libs = set() self.cflags = set() self.stl = None self.cppflags = set() self.local_include_dirs = set() self.generated_headers = set() self.export_generated_headers = set() self.ldflags = set() self.compile_multilib = None if name == 'host': self.compile_multilib = '64' def to_string(self, output): nested_out = [] self._output_field(nested_out, 'srcs') self._output_field(nested_out, 'shared_libs') self._output_field(nested_out, 'static_libs') self._output_field(nested_out, 'whole_static_libs') self._output_field(nested_out, 'header_libs') self._output_field(nested_out, 'cflags') self._output_field(nested_out, 'stl') self._output_field(nested_out, 'cppflags') self._output_field(nested_out, 'local_include_dirs') self._output_field(nested_out, 'generated_headers') self._output_field(nested_out, 'export_generated_headers') self._output_field(nested_out, 'ldflags') if nested_out: # This is added here to make sure it doesn't add a `host` arch-specific module just for # `compile_multilib` flag. self._output_field(nested_out, 'compile_multilib') output.append(' %s: {' % self.name) for line in nested_out: output.append(' %s' % line) output.append(' },') def _output_field(self, output, name, sort=True): value = getattr(self, name) return write_blueprint_key_value(output, name, value, sort) def __init__(self, mod_type, name, gn_target): self.type = mod_type self.gn_target = gn_target self.name = name self.srcs = set() self.comment = 'GN: ' + gn_target self.shared_libs = set() self.static_libs = set() self.whole_static_libs = set() self.tools = set() self.cmd = None self.host_supported = False self.device_supported = True self.init_rc = set() self.out = set() self.export_include_dirs = set() self.generated_headers = set() self.export_generated_headers = set() self.export_static_lib_headers = set() self.export_header_lib_headers = set() self.defaults = set() self.cflags = set() self.include_dirs = set() self.local_include_dirs = set() self.header_libs = set() self.tool_files = set() # target contains a dict of Targets indexed by os_arch. # example: { 'android_x86': Target('android_x86') self.target = dict() self.target['android'] = self.Target('android') self.target['android_x86'] = self.Target('android_x86') self.target['android_x86_64'] = self.Target('android_x86_64') self.target['android_arm'] = self.Target('android_arm') self.target['android_arm64'] = self.Target('android_arm64') self.target['host'] = self.Target('host') self.target['glibc'] = self.Target('glibc') self.stl = None self.cpp_std = None self.strip = dict() self.data = set() self.apex_available = set() self.min_sdk_version = None self.proto = dict() self.linker_scripts = set() self.ldflags = set() # The genrule_XXX below are properties that must to be propagated back # on the module(s) that depend on the genrule. self.genrule_headers = set() self.genrule_srcs = set() self.genrule_shared_libs = set() self.genrule_header_libs = set() self.version_script = None self.test_suites = set() self.test_config = None self.cppflags = set() self.rtti = False # Name of the output. Used for setting .so file name for libcronet self.libs = set() self.stem = None self.compile_multilib = None self.aidl = dict() self.plugins = set() self.processor_class = None self.sdk_version = None self.javacflags = set() self.c_std = None self.default_applicable_licenses = set() self.default_visibility = [] self.visibility = [] self.gn_type = None def to_string(self, output): if self.comment: output.append('// %s' % self.comment) output.append('%s {' % self.type) self._output_field(output, 'name') self._output_field(output, 'srcs') self._output_field(output, 'shared_libs') self._output_field(output, 'static_libs') self._output_field(output, 'whole_static_libs') self._output_field(output, 'tools') self._output_field(output, 'cmd', sort=False) if self.host_supported: self._output_field(output, 'host_supported') if not self.device_supported: self._output_field(output, 'device_supported') self._output_field(output, 'init_rc') self._output_field(output, 'out') self._output_field(output, 'export_include_dirs') self._output_field(output, 'generated_headers') self._output_field(output, 'export_generated_headers') self._output_field(output, 'export_static_lib_headers') self._output_field(output, 'export_header_lib_headers') self._output_field(output, 'defaults') self._output_field(output, 'cflags') self._output_field(output, 'include_dirs') self._output_field(output, 'local_include_dirs') self._output_field(output, 'header_libs') self._output_field(output, 'strip') self._output_field(output, 'tool_files') self._output_field(output, 'data') self._output_field(output, 'stl') self._output_field(output, 'cpp_std') self._output_field(output, 'apex_available') self._output_field(output, 'min_sdk_version') self._output_field(output, 'version_script') self._output_field(output, 'test_suites') self._output_field(output, 'test_config') self._output_field(output, 'proto') self._output_field(output, 'linker_scripts') self._output_field(output, 'ldflags') self._output_field(output, 'cppflags') self._output_field(output, 'libs') self._output_field(output, 'stem') self._output_field(output, 'compile_multilib') self._output_field(output, 'aidl') self._output_field(output, 'plugins') self._output_field(output, 'processor_class') self._output_field(output, 'sdk_version') self._output_field(output, 'javacflags') self._output_field(output, 'c_std') self._output_field(output, 'default_applicable_licenses') self._output_field(output, 'default_visibility') self._output_field(output, 'visibility') if self.rtti: self._output_field(output, 'rtti') target_out = [] for arch, target in sorted(self.target.items()): # _output_field calls getattr(self, arch). setattr(self, arch, target) self._output_field(target_out, arch) if target_out: output.append(' target: {') for line in target_out: output.append(' %s' % line) output.append(' },') output.append('}') output.append('') def add_android_shared_lib(self, lib): if self.type == 'cc_binary_host': raise Exception('Adding Android shared lib for host tool is unsupported') elif self.host_supported: self.target['android'].shared_libs.add(lib) else: self.shared_libs.add(lib) def is_test(self): if gn_utils.TESTING_SUFFIX in self.name: name_without_prefix = self.name[:self.name.find(gn_utils.TESTING_SUFFIX)] return any([name_without_prefix == label_to_module_name(target) for target in DEFAULT_TESTS]) return False def _output_field(self, output, name, sort=True): value = getattr(self, name) return write_blueprint_key_value(output, name, value, sort) def is_compiled(self): return self.type not in ('cc_genrule', 'filegroup', 'java_genrule') def is_genrule(self): return self.type == "cc_genrule" def has_input_files(self): if len(self.srcs) > 0: return True if any([len(target.srcs) > 0 for target in self.target.values()]): return True # Allow cc_static_library with export_generated_headers as those are crucial for # the depending modules return len(self.export_generated_headers) > 0 class Blueprint(object): """In-memory representation of an Android.bp file.""" def __init__(self): self.modules = {} def add_module(self, module): """Adds a new module to the blueprint, replacing any existing module with the same name. Args: module: Module instance. """ self.modules[module.name] = module def to_string(self, output): for m in sorted(self.modules.values(), key=lambda m: m.name): if m.type != "cc_library_static" or m.has_input_files(): # Don't print cc_library_static with empty srcs. These attributes are already # propagated up the tree. Printing them messes the presubmits because # every module is compiled while those targets are not reachable in # a normal compilation path. m.to_string(output) def label_to_module_name(label): """Turn a GN label (e.g., //:perfetto_tests) into a module name.""" module = re.sub(r'^//:?', '', label) module = re.sub(r'[^a-zA-Z0-9_]', '_', module) if not module.startswith(module_prefix): return module_prefix + module return module def is_supported_source_file(name): """Returns True if |name| can appear in a 'srcs' list.""" return os.path.splitext(name)[1] in ['.c', '.cc', '.cpp', '.java', '.proto', '.S'] def get_protoc_module_name(gn): protoc_gn_target_name = gn.get_target('//third_party/protobuf:protoc').name return label_to_module_name(protoc_gn_target_name) def create_proto_modules(blueprint, gn, target): """Generate genrules for a proto GN target. GN actions are used to dynamically generate files during the build. The Soong equivalent is a genrule. This function turns a specific kind of genrule which turns .proto files into source and header files into a pair equivalent genrules. Args: blueprint: Blueprint instance which is being generated. target: gn_utils.Target object. Returns: The source_genrule module. """ assert (target.type == 'proto_library') protoc_module_name = get_protoc_module_name(gn) tools = {protoc_module_name} cpp_out_dir = '$(genDir)/%s/%s/' % (tree_path, target.proto_in_dir) target_module_name = label_to_module_name(target.name) # In GN builds the proto path is always relative to the output directory # (out/tmp.xxx). cmd = ['$(location %s)' % protoc_module_name] cmd += ['--proto_path=%s/%s' % (tree_path, target.proto_in_dir)] if buildtools_protobuf_src in target.proto_paths: cmd += ['--proto_path=%s' % android_protobuf_src] # We don't generate any targets for source_set proto modules because # they will be inlined into other modules if required. if target.proto_plugin == 'source_set': return None # Descriptor targets only generate a single target. if target.proto_plugin == 'descriptor': out = '{}.bin'.format(target_module_name) cmd += ['--descriptor_set_out=$(out)'] cmd += ['$(in)'] descriptor_module = Module('cc_genrule', target_module_name, target.name) descriptor_module.cmd = ' '.join(cmd) descriptor_module.out = [out] descriptor_module.tools = tools blueprint.add_module(descriptor_module) # Recursively extract the .proto files of all the dependencies and # add them to srcs. descriptor_module.srcs.update( gn_utils.label_to_path(src) for src in target.sources) for dep in target.transitive_proto_deps: current_target = gn.get_target(dep) descriptor_module.srcs.update( gn_utils.label_to_path(src) for src in current_target.sources) return descriptor_module # We create two genrules for each proto target: one for the headers and # another for the sources. This is because the module that depends on the # generated files needs to declare two different types of dependencies -- # source files in 'srcs' and headers in 'generated_headers' -- and it's not # valid to generate .h files from a source dependency and vice versa. source_module_name = target_module_name + '_gen' source_module = Module('cc_genrule', source_module_name, target.name) blueprint.add_module(source_module) source_module.srcs.update( gn_utils.label_to_path(src) for src in target.sources) header_module = Module('cc_genrule', source_module_name + '_headers', target.name) blueprint.add_module(header_module) header_module.srcs = set(source_module.srcs) # TODO(primiano): at some point we should remove this. This was introduced # by aosp/1108421 when adding "protos/" to .proto include paths, in order to # avoid doing multi-repo changes and allow old clients in the android tree # to still do the old #include "perfetto/..." rather than # #include "protos/perfetto/...". header_module.export_include_dirs = {'.', 'protos'} # Since the .cc file and .h get created by a different gerule target, they # are not put in the same intermediate path, so local includes do not work # without explictily exporting the include dir. header_module.export_include_dirs.add(target.proto_in_dir) # This function does not return header_module so setting apex_available attribute here. header_module.apex_available.add(tethering_apex) source_module.genrule_srcs.add(':' + source_module.name) source_module.genrule_headers.add(header_module.name) if target.proto_plugin == 'proto': suffixes = ['pb'] source_module.genrule_shared_libs.add('libprotobuf-cpp-lite') cmd += ['--cpp_out=lite=true:' + cpp_out_dir] else: raise Exception('Unsupported proto plugin: %s' % target.proto_plugin) cmd += ['$(in)'] source_module.cmd = ' '.join(cmd) header_module.cmd = source_module.cmd source_module.tools = tools header_module.tools = tools for sfx in suffixes: source_module.out.update('%s/%s' % (tree_path, src.replace('.proto', '.%s.cc' % sfx)) for src in source_module.srcs) header_module.out.update('%s/%s' % (tree_path, src.replace('.proto', '.%s.h' % sfx)) for src in header_module.srcs) return source_module def create_gcc_preprocess_modules(blueprint, target): # gcc_preprocess.py internally execute host gcc which is not allowed in genrule. # So, this function create multiple modules and realize equivalent processing # TODO: Consider to support gcc_preprocess.py in different way # It's not great to have genrule and cc_object in the dependency from java_library assert (len(target.sources) == 1) source = list(target.sources)[0] assert (Path(source).suffix == '.template') stem = Path(source).stem bp_module_name = label_to_module_name(target.name) # Rename .template to .cc since cc_object does not accept .template file as srcs rename_module = Module('genrule', bp_module_name + '_rename', target.name) rename_module.srcs.add(gn_utils.label_to_path(source)) rename_module.out.add(stem + '.cc') rename_module.cmd = 'cp $(in) $(out)' blueprint.add_module(rename_module) # Preprocess template file and generates java file preprocess_module = Module('cc_object', bp_module_name + '_preprocess', target.name) # -E: stop after preprocessing. # -P: disable line markers, i.e. '#line 309' preprocess_module.cflags.update(['-E', '-P', '-DANDROID']) preprocess_module.srcs.add(':' + rename_module.name) defines = ['-D' + target.args[i+1] for i, arg in enumerate(target.args) if arg == '--define'] preprocess_module.cflags.update(defines) # HACK: Specifying compile_multilib to build cc_object only once. # Without this, soong complain to genrule that depends on cc_object when built for 64bit target. # It seems this is because cc object is a module with per-architecture variants and genrule is a # module with default variant. For 64bit target, cc_object is built multiple times for 32/64bit # modes and genrule doesn't know which one to depend on. preprocess_module.compile_multilib = 'first' blueprint.add_module(preprocess_module) # Generates srcjar using soong_zip module = Module('genrule', bp_module_name, target.name) module.srcs.add(':' + preprocess_module.name) module.out.add(stem + '.srcjar') module.cmd = NEWLINE.join([ f'cp $(in) $(genDir)/{stem}.java &&', f'$(location soong_zip) -o $(out) -srcjar -C $(genDir) -f $(genDir)/{stem}.java' ]) module.tools.add('soong_zip') blueprint.add_module(module) return module class BaseActionSanitizer(): def __init__(self, target, arch): # Just to be on the safe side, create a deep-copy. self.target = copy.deepcopy(target) if arch: # Merge arch specific attributes self.target.sources |= arch.sources self.target.inputs |= arch.inputs self.target.outputs |= arch.outputs self.target.script = self.target.script or arch.script self.target.args = self.target.args or arch.args self.target.response_file_contents = \ self.target.response_file_contents or arch.response_file_contents self.target.args = self._normalize_args() def get_name(self): return label_to_module_name(self.target.name) def _normalize_args(self): # Convert ['--param=value'] to ['--param', 'value'] for consistency. # Escape quotations. normalized_args = [] for arg in self.target.args: arg = arg.replace('"', r'\"') if arg.startswith('-'): normalized_args.extend(arg.split('=')) else: normalized_args.append(arg) return normalized_args # There are three types of args: # - flags (--flag) # - value args (--arg value) # - list args (--arg value1 --arg value2) # value args have exactly one arg value pair and list args have one or more arg value pairs. # Note that the set of list args contains the set of value args. # This is because list and value args are identical when the list args has only one arg value pair # Some functions provide special implementations for each type, while others # work on all of them. def _has_arg(self, arg): return arg in self.target.args def _get_arg_indices(self, target_arg): return [i for i, arg in enumerate(self.target.args) if arg == target_arg] # Whether an arg value pair appears once or more times def _is_list_arg(self, arg): indices = self._get_arg_indices(arg) return len(indices) > 0 and all([not self.target.args[i + 1].startswith('--') for i in indices]) def _update_list_arg(self, arg, func, throw_if_absent = True): if self._should_fail_silently(arg, throw_if_absent): return assert(self._is_list_arg(arg)) indices = self._get_arg_indices(arg) for i in indices: self._set_arg_at(i + 1, func(self.target.args[i + 1])) # Whether an arg value pair appears exactly once def _is_value_arg(self, arg): return operator.countOf(self.target.args, arg) == 1 and self._is_list_arg(arg) def _get_value_arg(self, arg): assert(self._is_value_arg(arg)) i = self.target.args.index(arg) return self.target.args[i + 1] # used to check whether a function call should cause an error when an arg is # missing. def _should_fail_silently(self, arg, throw_if_absent): return not throw_if_absent and not self._has_arg(arg) def _set_value_arg(self, arg, value, throw_if_absent = True): if self._should_fail_silently(arg, throw_if_absent): return assert(self._is_value_arg(arg)) i = self.target.args.index(arg) self.target.args[i + 1] = value def _update_value_arg(self, arg, func, throw_if_absent = True): if self._should_fail_silently(arg, throw_if_absent): return self._set_value_arg(arg, func(self._get_value_arg(arg))) def _set_arg_at(self, position, value): self.target.args[position] = value def _update_arg_at(self, position, func): self.target.args[position] = func(self.target.args[position]) def _delete_value_arg(self, arg, throw_if_absent = True): if self._should_fail_silently(arg, throw_if_absent): return assert(self._is_value_arg(arg)) i = self.target.args.index(arg) self.target.args.pop(i) self.target.args.pop(i) def _append_arg(self, arg, value): self.target.args.append(arg) self.target.args.append(value) def _sanitize_filepath_with_location_tag(self, arg): if arg.startswith('../../'): arg = self._sanitize_filepath(arg) arg = self._add_location_tag(arg) return arg # wrap filename in location tag. def _add_location_tag(self, filename): return '$(location %s)' % filename # applies common directory transformation that *should* be universally applicable. # TODO: verify if it actually *is* universally applicable. def _sanitize_filepath(self, filepath): # Careful, order matters! # delete all leading ../ filepath = re.sub('^(\.\./)+', '', filepath) filepath = re.sub('^gen/jni_headers', '$(genDir)', filepath) filepath = re.sub('^gen', '$(genDir)', filepath) return filepath # Iterate through all the args and apply function def _update_all_args(self, func): self.target.args = [func(arg) for arg in self.target.args] def get_cmd(self): arg_string = NEWLINE.join(self.target.args) cmd = '$(location %s) %s' % ( gn_utils.label_to_path(self.target.script), arg_string) if self.use_response_file: # Pipe response file contents into script cmd = 'echo \'%s\' |%s%s' % (self.target.response_file_contents, NEWLINE, cmd) return cmd def get_outputs(self): return self.target.outputs def get_srcs(self): # gn treats inputs and sources for actions equally. # soong only supports source files inside srcs, non-source files are added as # tool_files dependency. files = self.target.sources.union(self.target.inputs) return {gn_utils.label_to_path(file) for file in files if is_supported_source_file(file)} def get_tools(self): return set() def get_tool_files(self): # gn treats inputs and sources for actions equally. # soong only supports source files inside srcs, non-source files are added as # tool_files dependency. files = self.target.sources.union(self.target.inputs) tool_files = {gn_utils.label_to_path(file) for file in files if not is_supported_source_file(file)} tool_files.add(gn_utils.label_to_path(self.target.script)) return tool_files def _sanitize_args(self): # Handle passing parameters via response file by piping them into the script # and reading them from /dev/stdin. self.use_response_file = gn_utils.RESPONSE_FILE in self.target.args if self.use_response_file: # Replace {{response_file_contents}} with /dev/stdin self.target.args = ['/dev/stdin' if it == gn_utils.RESPONSE_FILE else it for it in self.target.args] def _sanitize_outputs(self): pass def _sanitize_inputs(self): pass def sanitize(self): self._sanitize_args() self._sanitize_outputs() self._sanitize_inputs() # Whether this target generates header files def is_header_generated(self): return any(os.path.splitext(it)[1] == '.h' for it in self.target.outputs) class WriteBuildDateHeaderSanitizer(BaseActionSanitizer): def _sanitize_args(self): self._set_arg_at(0, '$(out)') super()._sanitize_args() class WriteBuildFlagHeaderSanitizer(BaseActionSanitizer): def _sanitize_args(self): self._set_value_arg('--gen-dir', '.') self._set_value_arg('--output', '$(out)') super()._sanitize_args() class GnRunBinarySanitizer(BaseActionSanitizer): def __init__(self, target, arch): super().__init__(target, arch) self.binary_to_target = { "clang_x64/transport_security_state_generator": "cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing", } self.binary = self.binary_to_target[self.target.args[0]] def _replace_gen_with_location_tag(self, arg): if arg.startswith("gen/"): return "$(location %s)" % arg.replace("gen/", "") return arg def _replace_binary(self, arg): if arg in self.binary_to_target: return '$(location %s)' % self.binary return arg def _remove_python_args(self): self.target.args = [arg for arg in self.target.args if "python3" not in arg] def _sanitize_args(self): self._update_all_args(self._sanitize_filepath_with_location_tag) self._update_all_args(self._replace_gen_with_location_tag) self._update_all_args(self._replace_binary) self._remove_python_args() super()._sanitize_args() def get_tools(self): tools = super().get_tools() tools.add(self.binary) return tools def get_cmd(self): # Remove the script and use the binary right away return NEWLINE.join(self.target.args) class JniGeneratorSanitizer(BaseActionSanitizer): def __init__(self, target, arch, is_test_target): self.is_test_target = is_test_target super().__init__(target, arch) def _add_location_tag_to_filepath(self, arg): if not arg.endswith('.class'): # --input_file supports both .class specifiers or source files as arguments. # Only source files need to be wrapped inside a $(location