blob: 88b2b5a3b7558091e177eb4e73ef74b6bcf9a643 [file] [log] [blame]
#!/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 collections
import json
import logging as log
import os
import re
import sys
import gn_utils
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Defines a custom init_rc argument to be applied to the corresponding output
# blueprint target.
target_initrc = {
# TODO: this can probably be removed.
}
target_host_supported = [
# TODO: remove if this is not useful for the cronet build.
]
# Proto target groups which will be made public.
proto_groups = {
# TODO: remove if this is not used for the cronet build.
}
# 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',
'android.hardware.atrace@1.0',
'android.hardware.health@2.0',
'android.hardware.health-V1-ndk',
'android.hardware.power.stats@1.0',
"android.hardware.power.stats-V1-cpp",
'base',
'binder',
'binder_ndk',
'cutils',
'hidlbase',
'hidltransport',
'hwbinder',
'incident',
'log',
'services',
'statssocket',
"tracingproxy",
'utils',
]
# Static libraries which are directly translated to Android system equivalents.
static_library_allowlist = [
'statslog_perfetto',
]
# 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/perfetto'
# 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'
# Compiler flags which are passed through to the blueprint.
cflag_allowlist = r'^-DPERFETTO.*$'
# Additional arguments to apply to Android.bp rules.
additional_args = {
# TODO: remove if this is not useful for the cronet build.
# Consider using additional_args for overriding the genrule cmd property for gn actions.
}
def enable_gtest_and_gmock(module):
module.static_libs.add('libgmock')
module.static_libs.add('libgtest')
if module.name != 'perfetto_gtest_logcat_printer':
module.whole_static_libs.add('perfetto_gtest_logcat_printer')
def enable_protobuf_full(module):
if module.type == 'cc_binary_host':
module.static_libs.add('libprotobuf-cpp-full')
elif module.host_supported:
module.host.static_libs.add('libprotobuf-cpp-full')
module.android.shared_libs.add('libprotobuf-cpp-full')
else:
module.shared_libs.add('libprotobuf-cpp-full')
def enable_protobuf_lite(module):
module.shared_libs.add('libprotobuf-cpp-lite')
def enable_protoc_lib(module):
if module.type == 'cc_binary_host':
module.static_libs.add('libprotoc')
else:
module.shared_libs.add('libprotoc')
def enable_libunwindstack(module):
if module.name != 'heapprofd_standalone_client':
module.shared_libs.add('libunwindstack')
module.shared_libs.add('libprocinfo')
module.shared_libs.add('libbase')
else:
module.static_libs.add('libunwindstack')
module.static_libs.add('libprocinfo')
module.static_libs.add('libbase')
module.static_libs.add('liblzma')
module.static_libs.add('libdexfile_support')
module.runtime_libs.add('libdexfile') # libdexfile_support dependency
def enable_libunwind(module):
# libunwind is disabled on Darwin so we cannot depend on it.
pass
def enable_sqlite(module):
if module.type == 'cc_binary_host':
module.static_libs.add('libsqlite')
module.static_libs.add('sqlite_ext_percentile')
elif module.host_supported:
# Copy what the sqlite3 command line tool does.
module.android.shared_libs.add('libsqlite')
module.android.shared_libs.add('libicu')
module.android.shared_libs.add('liblog')
module.android.shared_libs.add('libutils')
module.android.static_libs.add('sqlite_ext_percentile')
module.host.static_libs.add('libsqlite')
module.host.static_libs.add('sqlite_ext_percentile')
else:
module.shared_libs.add('libsqlite')
module.shared_libs.add('libicu')
module.shared_libs.add('liblog')
module.shared_libs.add('libutils')
module.static_libs.add('sqlite_ext_percentile')
def enable_zlib(module):
if module.type == 'cc_binary_host':
module.static_libs.add('libz')
elif module.host_supported:
module.android.shared_libs.add('libz')
module.host.static_libs.add('libz')
else:
module.shared_libs.add('libz')
def enable_uapi_headers(module):
module.include_dirs.add('bionic/libc/kernel')
def enable_bionic_libc_platform_headers_on_android(module):
module.header_libs.add('bionic_libc_platform_headers')
# Android equivalents for third-party libraries that the upstream project
# depends on.
builtin_deps = {
'//gn:default_deps':
lambda x: None,
'//gn:gtest_main':
lambda x: None,
'//gn:protoc':
lambda x: None,
'//gn:gtest_and_gmock':
enable_gtest_and_gmock,
'//gn:libunwind':
enable_libunwind,
'//gn:protobuf_full':
enable_protobuf_full,
'//gn:protobuf_lite':
enable_protobuf_lite,
'//gn:protoc_lib':
enable_protoc_lib,
'//gn:libunwindstack':
enable_libunwindstack,
'//gn:sqlite':
enable_sqlite,
'//gn:zlib':
enable_zlib,
'//gn:bionic_kernel_uapi_headers':
enable_uapi_headers,
'//src/profiling/memory:bionic_libc_platform_headers_on_android':
enable_bionic_libc_platform_headers_on_android,
}
# ----------------------------------------------------------------------------
# End of configuration.
# ----------------------------------------------------------------------------
class Error(Exception):
pass
class ThrowingArgumentParser(argparse.ArgumentParser):
def __init__(self, context):
super(ThrowingArgumentParser, self).__init__()
self.context = context
def error(self, message):
raise Error('%s: %s' % (self.context, message))
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, 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 Target(object):
"""A target-scoped part of a module"""
def __init__(self, name):
self.name = name
self.shared_libs = set()
self.static_libs = set()
self.whole_static_libs = set()
self.cflags = set()
self.dist = dict()
self.strip = dict()
self.stl = None
def to_string(self, output):
nested_out = []
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, 'cflags')
self._output_field(nested_out, 'stl')
self._output_field(nested_out, 'dist')
self._output_field(nested_out, 'strip')
if nested_out:
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)
class Module(object):
"""A single module (e.g., cc_binary, cc_test) in a blueprint."""
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_utils.label_without_toolchain(gn_target)
self.shared_libs = set()
self.static_libs = set()
self.whole_static_libs = set()
self.runtime_libs = set()
self.tools = set()
self.cmd = None
self.host_supported = False
self.vendor_available = False
self.init_rc = set()
self.out = set()
self.export_include_dirs = set()
self.generated_headers = set()
self.export_generated_headers = set()
self.defaults = set()
self.cflags = set()
self.include_dirs = set()
self.local_include_dirs = set()
self.header_libs = set()
self.required = set()
self.tool_files = set()
self.android = Target('android')
self.host = Target('host')
self.stl = None
self.cpp_std = None
self.dist = dict()
self.strip = dict()
self.data = set()
self.apex_available = set()
self.min_sdk_version = None
self.proto = dict()
# 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.version_script = None
self.test_suites = set()
self.test_config = None
self.stubs = {}
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, 'runtime_libs')
self._output_field(output, 'tools')
self._output_field(output, 'cmd', sort=False)
if self.host_supported:
self._output_field(output, 'host_supported')
if self.vendor_available:
self._output_field(output, 'vendor_available')
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, '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, 'required')
self._output_field(output, 'dist')
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, 'stubs')
self._output_field(output, 'proto')
target_out = []
self._output_field(target_out, 'android')
self._output_field(target_out, 'host')
if target_out:
output.append(' target: {')
for line in target_out:
output.append(' %s' % line)
output.append(' },')
output.append('}')
output.append('')
def add_android_static_lib(self, lib):
if self.type == 'cc_binary_host':
raise Exception('Adding Android static lib for host tool is unsupported')
elif self.host_supported:
self.android.static_libs.add(lib)
else:
self.static_libs.add(lib)
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.android.shared_libs.add(lib)
else:
self.shared_libs.add(lib)
def _output_field(self, output, name, sort=True):
value = getattr(self, name)
return write_blueprint_key_value(output, name, value, sort)
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):
m.to_string(output)
def label_to_module_name(label):
"""Turn a GN label (e.g., //:perfetto_tests) into a module name."""
# If the label is explicibly listed in the default target list, don't prefix
# its name and return just the target name. This is so tools like
# "traceconv" stay as such in the Android tree.
label_without_toolchain = gn_utils.label_without_toolchain(label)
module = re.sub(r'^//:?', '', label_without_toolchain)
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', '.java', '.proto']
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')
tools = {'aprotoc'}
cpp_out_dir = '$(genDir)/%s/' % tree_path
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 = ['mkdir -p %s &&' % cpp_out_dir, '$(location aprotoc)']
cmd += ['--proto_path=%s' % tree_path]
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('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('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('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'}
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]
elif target.proto_plugin == 'protozero':
suffixes = ['pbzero']
plugin = create_modules_from_target(blueprint, gn, protozero_plugin)
tools.add(plugin.name)
cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
cmd += ['--plugin_out=wrapper_namespace=pbzero:' + cpp_out_dir]
elif target.proto_plugin == 'cppgen':
suffixes = ['gen']
plugin = create_modules_from_target(blueprint, gn, cppgen_plugin)
tools.add(plugin.name)
cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
cmd += ['--plugin_out=wrapper_namespace=gen:' + cpp_out_dir]
elif target.proto_plugin == 'ipc':
suffixes = ['ipc']
plugin = create_modules_from_target(blueprint, gn, ipc_plugin)
tools.add(plugin.name)
cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
cmd += ['--plugin_out=wrapper_namespace=gen:' + cpp_out_dir]
else:
raise Error('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_amalgamated_sql_metrics_module(blueprint, target):
bp_module_name = label_to_module_name(target.name)
module = Module('genrule', bp_module_name, target.name)
module.tool_files.add('tools/gen_amalgamated_sql_metrics.py')
module.cmd = ' '.join([
'$(location tools/gen_amalgamated_sql_metrics.py)',
'--cpp_out=$(out)',
'$(in)',
])
module.genrule_headers.add(module.name)
module.out.update(target.outputs)
module.srcs.update(gn_utils.label_to_path(src) for src in target.inputs)
blueprint.add_module(module)
return module
def create_cc_proto_descriptor_module(blueprint, target):
bp_module_name = label_to_module_name(target.name)
module = Module('genrule', bp_module_name, target.name)
module.tool_files.add('tools/gen_cc_proto_descriptor.py')
module.cmd = ' '.join([
'$(location tools/gen_cc_proto_descriptor.py)', '--gen_dir=$(genDir)',
'--cpp_out=$(out)', '$(in)'
])
module.genrule_headers.add(module.name)
module.srcs.update(
':' + label_to_module_name(dep) for dep in target.proto_deps)
module.srcs.update(
gn_utils.label_to_path(src)
for src in target.inputs
if "tmp.gn_utils" not in src)
module.out.update(target.outputs)
blueprint.add_module(module)
return module
def create_gen_version_module(blueprint, target, bp_module_name):
module = Module('genrule', bp_module_name, gn_utils.GEN_VERSION_TARGET)
script_path = gn_utils.label_to_path(target.script)
module.genrule_headers.add(bp_module_name)
module.tool_files.add(script_path)
module.out.update(target.outputs)
module.srcs.update(gn_utils.label_to_path(src) for src in target.inputs)
module.cmd = ' '.join([
'python3 $(location %s)' % script_path, '--no_git',
'--changelog=$(location CHANGELOG)', '--cpp_out=$(out)'
])
blueprint.add_module(module)
return module
def create_proto_group_modules(blueprint, gn, module_name, target_names):
# TODO(lalitm): today, we're only adding a Java lite module because that's
# the only one used in practice. In the future, if we need other target types
# (e.g. C++, Java full etc.) add them here.
bp_module_name = label_to_module_name(module_name) + '_java_protos'
module = Module('java_library', bp_module_name, bp_module_name)
module.comment = f'''GN: [{', '.join(target_names)}]'''
module.proto = {'type': 'lite', 'canonical_path_from_root': False}
for name in target_names:
target = gn.get_target(name)
module.srcs.update(gn_utils.label_to_path(src) for src in target.sources)
for dep_label in target.transitive_proto_deps:
dep = gn.get_target(dep_label)
module.srcs.update(gn_utils.label_to_path(src) for src in dep.sources)
blueprint.add_module(module)
# There may be a better way to do target specific modification
# Target specific modification before create_action_module
def pre_create_action_module(target):
# Convert ['--param=value'] to ['--param', 'value'] for consistency.
# TODO: we may want to only do this for python scripts arguments. If argparse
# is used, this transformation is safe.
target.args = [str for it in target.args for str in it.split('=')]
if target.script == "//build/write_buildflag_header.py":
# write_buildflag_header.py writes result to args.genDir/args.output
# So, override args.genDir by '.' so that args.output=$(out) works
for i, val in enumerate(target.args):
if val == '--gen-dir':
target.args[i + 1] = '.'
break
elif target.script == '//base/android/jni_generator/jni_generator.py':
for i, val in enumerate(target.args):
if val == '--output_dir':
# replace --output_dir gen/... with --output_dir $(genDir)/...
target.args[i + 1] = re.sub('^gen', '$(genDir)', target.args[i + 1])
if val == '--input_file':
# --input_file supports both .class specifiers or source files as arguments.
# Only source files need to be wrapped inside a $(location <label>) tag.
if re.match('.*\.class$', target.args[i + 1]):
continue
# replace --input_file ../../... with --input_file $(location ...)
# TODO: put inside function
filename = re.sub('^\.\./\.\./', '', target.args[i + 1])
target.args[i + 1] = '$(location %s)' % filename
def create_action_module(blueprint, target):
bp_module_name = label_to_module_name(target.name)
module = Module('genrule', bp_module_name, target.name)
script = gn_utils.label_to_path(target.script)
module.tool_files.add(script)
# Replace arg by {$out} if possible
if len(target.outputs) == 1:
out = list(target.outputs)[0]
target.args = ['$(out)' if arg == out or arg == 'gen/' + out else arg for arg in target.args]
# Handle passing parameters via response file by piping them into the script
# and reading them from /dev/stdin.
response_file = '{{response_file_name}}'
use_response_file = response_file in target.args
if use_response_file:
# Replace {{response_file_contents}} with /dev/stdin
target.args = ['/dev/stdin' if it == response_file else it for it in target.args]
# put all args on a new line for better diffs.
NEWLINE = ' " +\n "'
arg_string = NEWLINE.join(target.args)
module.cmd = '$(location %s) %s' % (script, arg_string)
if use_response_file:
# Pipe response file contents into script
module.cmd = 'echo \'%s\' |%s%s' % (target.response_file_contents, NEWLINE, module.cmd)
if all(os.path.splitext(it)[1] == '.h' for it in target.outputs):
module.genrule_headers.add(bp_module_name)
# gn treats inputs and sources for actions equally.
# soong only supports source files inside srcs, non-source files are added as
# tool_files dependency.
for it in target.sources or target.inputs:
if is_supported_source_file(it):
module.srcs.add(gn_utils.label_to_path(it))
else:
module.tool_files.add(gn_utils.label_to_path(it))
# Actions using template "action_with_pydeps" also put script inside inputs.
# TODO: it might make sense to filter inputs inside GnParser.
if script in module.srcs:
module.srcs.remove(script)
module.out.update(target.outputs)
blueprint.add_module(module)
return module
def _get_cflags(target):
cflags = {flag for flag in target.cflags if re.match(cflag_allowlist, flag)}
# Consider proper allowlist or denylist if needed
cflags |= set("-D%s" % define.replace("\"", "\\\"") for define in target.defines)
return cflags
def create_modules_from_target(blueprint, gn, gn_target_name):
"""Generate module(s) for a given GN target.
Given a GN target name, generate one or more corresponding modules into a
blueprint. The only case when this generates >1 module is proto libraries.
Args:
blueprint: Blueprint instance which is being generated.
gn: gn_utils.GnParser object.
gn_target_name: GN target for module generation.
"""
bp_module_name = label_to_module_name(gn_target_name)
if bp_module_name in blueprint.modules:
return blueprint.modules[bp_module_name]
target = gn.get_target(gn_target_name)
log.info('create modules for %s (%s)', target.name, target.type)
name_without_toolchain = gn_utils.label_without_toolchain(target.name)
if target.type == 'executable':
if target.toolchain == gn_utils.HOST_TOOLCHAIN:
module_type = 'cc_binary_host'
elif target.testonly:
module_type = 'cc_test'
else:
module_type = 'cc_binary'
module = Module(module_type, bp_module_name, gn_target_name)
elif target.type == 'static_library':
module = Module('cc_library_static', bp_module_name, gn_target_name)
elif target.type == 'shared_library':
module = Module('cc_library_shared', bp_module_name, gn_target_name)
elif target.type == 'source_set':
module = Module('filegroup', bp_module_name, gn_target_name)
elif target.type == 'group':
# "group" targets are resolved recursively by gn_utils.get_target().
# There's nothing we need to do at this level for them.
return None
elif target.type == 'proto_library':
module = create_proto_modules(blueprint, gn, target)
if module is None:
return None
elif target.type == 'action':
if 'gen_amalgamated_sql_metrics' in target.name:
module = create_amalgamated_sql_metrics_module(blueprint, target)
elif re.match('.*gen_cc_.*_descriptor$', name_without_toolchain):
module = create_cc_proto_descriptor_module(blueprint, target)
elif target.type == 'action' and \
name_without_toolchain == gn_utils.GEN_VERSION_TARGET:
module = create_gen_version_module(blueprint, target, bp_module_name)
else:
pre_create_action_module(target)
module = create_action_module(blueprint, target)
elif target.type == 'copy':
# TODO: careful now! copy targets are not supported yet, but this will stop
# traversing the dependency tree. For //base:base, this is not a big
# problem as libicu contains the only copy target which happens to be a
# leaf node.
return None
else:
raise Error('Unknown target %s (%s)' % (target.name, target.type))
blueprint.add_module(module)
module.host_supported = (name_without_toolchain in target_host_supported)
module.init_rc = target_initrc.get(target.name, [])
module.srcs.update(
gn_utils.label_to_path(src)
for src in target.sources
if is_supported_source_file(src))
if target.type in gn_utils.LINKER_UNIT_TYPES:
module.cflags.update(_get_cflags(target))
# HACK! We may have to link against chromium's sysroot instead, but this
# seems to work for //base:base.
# TODO: implement proper cflag parsing.
for flag in target.cflags:
if '--sysroot=' in flag:
module.header_libs.add('jni_headers')
if '-std=' in flag:
module.cpp_std = flag[len('-std='):]
module.local_include_dirs.update(gn_utils.label_to_path(it) for it in target.include_dirs)
module_is_compiled = module.type not in ('genrule', 'filegroup')
if module_is_compiled:
# Don't try to inject library/source dependencies into genrules or
# filegroups because they are not compiled in the traditional sense.
module.defaults = [defaults_module]
for lib in target.libs:
# Generally library names should be mangled as 'libXXX', unless they
# are HAL libraries (e.g., android.hardware.health@2.0) or AIDL c++ / NDK
# libraries (e.g. "android.hardware.power.stats-V1-cpp")
android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \
else 'lib' + lib
if lib in shared_library_allowlist:
module.add_android_shared_lib(android_lib)
if lib in static_library_allowlist:
module.add_android_static_lib(android_lib)
# If the module is a static library, export all the generated headers.
if module.type == 'cc_library_static':
module.export_generated_headers = module.generated_headers
# Merge in additional hardcoded arguments.
for key, add_val in additional_args.get(module.name, []):
curr = getattr(module, key)
if add_val and isinstance(add_val, set) and isinstance(curr, set):
curr.update(add_val)
elif isinstance(add_val, str) and (not curr or isinstance(curr, str)):
setattr(module, key, add_val)
elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)):
setattr(module, key, add_val)
elif isinstance(add_val, dict) and isinstance(curr, dict):
curr.update(add_val)
elif isinstance(add_val, dict) and isinstance(curr, Target):
curr.__dict__.update(add_val)
else:
raise Error('Unimplemented type %r of additional_args: %r' %
(type(add_val), key))
# dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)).
all_deps = target.deps | target.source_set_deps | target.transitive_proto_deps
for dep_name in all_deps:
# |builtin_deps| override GN deps with Android-specific ones. See the
# config in the top of this file.
if gn_utils.label_without_toolchain(dep_name) in builtin_deps:
builtin_deps[gn_utils.label_without_toolchain(dep_name)](module)
continue
dep_module = create_modules_from_target(blueprint, gn, dep_name)
# For filegroups and genrule, recurse but don't apply the deps.
if not module_is_compiled:
continue
if dep_module is None:
continue
if dep_module.type == 'cc_library_shared':
module.shared_libs.add(dep_module.name)
elif dep_module.type == 'cc_library_static':
module.static_libs.add(dep_module.name)
elif dep_module.type == 'filegroup':
module.srcs.add(':' + dep_module.name)
elif dep_module.type == 'genrule':
module.generated_headers.update(dep_module.genrule_headers)
module.srcs.update(dep_module.genrule_srcs)
module.shared_libs.update(dep_module.genrule_shared_libs)
elif dep_module.type == 'cc_binary':
continue # Ignore executables deps (used by cmdline integration tests).
else:
raise Error('Unknown dep %s (%s) for target %s' %
(dep_module.name, dep_module.type, module.name))
return module
def create_blueprint_for_targets(gn, desc, targets):
"""Generate a blueprint for a list of GN targets."""
blueprint = Blueprint()
# Default settings used by all modules.
defaults = Module('cc_defaults', defaults_module, '//gn:default_deps')
defaults.cflags = [
'-Wno-error=return-type',
'-Wno-sign-compare',
'-Wno-sign-promo',
'-Wno-unused-parameter',
'-fvisibility=hidden',
'-O2',
]
blueprint.add_module(defaults)
for target in targets:
create_modules_from_target(blueprint, gn, target)
return blueprint
def main():
parser = argparse.ArgumentParser(
description='Generate Android.bp from a GN description.')
parser.add_argument(
'--desc',
help='GN description (e.g., gn desc out --format=json --all-toolchains "//*"',
required=True
)
parser.add_argument(
'--extras',
help='Extra targets to include at the end of the Blueprint file',
default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'),
)
parser.add_argument(
'--output',
help='Blueprint file to create',
default=os.path.join(gn_utils.repo_root(), 'Android.bp'),
)
parser.add_argument(
'-v',
'--verbose',
help='Print debug logs.',
action='store_true',
)
parser.add_argument(
'targets',
nargs=argparse.REMAINDER,
help='Targets to include in the blueprint (e.g., "//:perfetto_tests")'
)
args = parser.parse_args()
if args.verbose:
log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=log.DEBUG)
with open(args.desc) as f:
desc = json.load(f)
gn = gn_utils.GnParser(desc)
blueprint = create_blueprint_for_targets(gn, desc, args.targets)
project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
# Add any proto groups to the blueprint.
for l_name, t_names in proto_groups.items():
create_proto_group_modules(blueprint, gn, l_name, t_names)
output = [
"""// 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 file is automatically generated by %s. Do not edit.
""" % (tool_name)
]
blueprint.to_string(output)
if os.path.exists(args.extras):
with open(args.extras, 'r') as r:
for line in r:
output.append(line.rstrip("\n\r"))
out_files = []
# Generate the Android.bp file.
out_files.append(args.output + '.swp')
with open(out_files[-1], 'w') as f:
f.write('\n'.join(output))
# Text files should have a trailing EOL.
f.write('\n')
return 0
if __name__ == '__main__':
sys.exit(main())