blob: 03358f386e8242f486c43a424d987571f8519d1e [file] [log] [blame] [edit]
#!/usr/bin/env python3
#
# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Writes a .build_config.json file.
This script collects information about a target and all of its transitive
dependencies and writes it to a .build_config.json file for use by other build
steps. It also performs a few validations.
See //build/android/docs/build_config.md for more information.
"""
import argparse
import itertools
import os
import sys
import xml.dom.minidom
from util import build_utils
from util import params_json_util
import action_helpers
class OrderedSet(dict):
"""A simple implementation of an ordered set."""
def __init__(self, iterable=()):
super().__init__()
self.update(iterable)
def __add__(self, other):
ret = OrderedSet(self)
ret.update(other)
return ret
def add(self, key):
self[key] = True
def remove(self, key):
self.pop(key, None)
def update(self, iterable):
for k in iterable:
self[k] = True
def difference_update(self, iterable):
for k in iterable:
self.pop(k, None)
class AndroidManifest:
"""Helper class to inspect properties of an AndroidManifest.xml file."""
def __init__(self, path):
self.path = path
dom = xml.dom.minidom.parse(path)
manifests = dom.getElementsByTagName('manifest')
assert len(manifests) == 1
self.manifest = manifests[0]
def GetInstrumentationElements(self):
instrumentation_els = self.manifest.getElementsByTagName('instrumentation')
if len(instrumentation_els) == 0:
return None
return instrumentation_els
def CheckInstrumentationElements(self, expected_package):
instrs = self.GetInstrumentationElements()
if not instrs:
raise Exception('No <instrumentation> elements found in %s' % self.path)
for instr in instrs:
instrumented_package = instr.getAttributeNS(
'http://schemas.android.com/apk/res/android', 'targetPackage')
if instrumented_package != expected_package:
raise Exception(
'Wrong instrumented package. Expected %s, got %s'
% (expected_package, instrumented_package))
def GetPackageName(self):
return self.manifest.getAttribute('package')
class _TransitiveValues:
"""A container for the transitive dependencies of a target.
This class holds sets of paths for various types of dependencies, such as
jars, resources, assets, etc.
"""
# Some values should not be removed when subtracting subtrees.
_NEVER_REMOVE = frozenset([
'all_interface_jars',
'all_input_jars_paths',
'direct_input_jars_paths',
'direct_interface_jars',
'direct_unprocessed_jars',
'proguard_configs',
])
def __init__(self):
# Direct classpath:
self.direct_unprocessed_jars = OrderedSet()
self.direct_interface_jars = OrderedSet()
self.direct_input_jars_paths = OrderedSet()
# Jar files
self.all_unprocessed_jars = OrderedSet()
self.all_interface_jars = OrderedSet()
self.all_input_jars_paths = OrderedSet()
self.all_dex_files = OrderedSet()
self.all_processed_jars = OrderedSet()
# Assets
self.assets = OrderedSet()
self.uncompressed_assets = OrderedSet()
self.locale_paks = OrderedSet()
# Resources
self.dependency_zip_overlays = OrderedSet()
self.dependency_zips = OrderedSet()
self.extra_package_names = OrderedSet()
# Other
self.android_manifests = OrderedSet()
self.java_resources_jars = OrderedSet()
self.proguard_configs = OrderedSet()
def RemoveSubtree(self,
other,
retain_processed_jars=False,
retain_unprocessed_jars=False,
retain_resource_zips=False,
retain_extra_package_names=False,
retain_android_manifests=False):
"""Removes all values from |other| from this instance."""
for key, value in self.__dict__.items():
if not (key in _TransitiveValues._NEVER_REMOVE or
(retain_processed_jars and key == 'all_processed_jars') or
(retain_unprocessed_jars
and key in ('all_unprocessed_jars', 'all_interface_jars')) or
(retain_resource_zips
and key in ('dependency_zips', 'dependency_zip_overlays')) or
(retain_extra_package_names and key == 'extra_package_names') or
(retain_android_manifests and key == 'android_manifests')):
value.difference_update(getattr(other, key))
def _SortClasspath(dep_list):
"""Sorts a list of dependencies for the classpath.
Move low priority libraries to the end of the classpath.
"""
dep_list.sort(key=lambda p: 1 if p.get('low_classpath_priority') else 0)
return dep_list
class _TransitiveValuesBuilder:
def __init__(self, params, remove_parent_module_overlap=True):
self._params = params
self._remove_parent_module_overlap = remove_parent_module_overlap
self._ret = _TransitiveValues()
def Build(self):
"""Computes the transitive dependencies for a given target.
This is the core logic of the script, collecting all necessary paths and
metadata from the dependency graph.
"""
params = self._params
# System .jar files go into sdk_jars / sdk_interface_jars.
direct_deps = _SortClasspath(
params.deps().not_of_type('system_java_library'))
all_deps = _SortClasspath(
direct_deps.recursive().not_of_type('system_java_library'))
all_deps_without_under_test = all_deps
if apk_under_test_params := params.apk_under_test():
# Cannot use .append() since this is the cached instance.
direct_deps = direct_deps + [apk_under_test_params]
all_deps = _SortClasspath(
direct_deps.recursive().not_of_type('system_java_library'))
self._CollectClasspath(direct_deps, all_deps)
if params.merges_manifests():
self._CollectManifests(all_deps_without_under_test)
if params.collects_resources():
self._CollectResources(all_deps_without_under_test)
self._CollectExtraPackageNames(direct_deps, all_deps)
self._ret.proguard_configs.update(params.get('proguard_configs', []))
self._ret.proguard_configs.update(
all_deps.collect('proguard_configs', flatten=True))
if params.is_bundle_module() and self._remove_parent_module_overlap:
self._RemoveParentModuleOverlap()
# If there are deps common between dist_jar() and non-dist_jar(), then
# subtract them off the full classpath to avoid double-deps.
for dist_jar_params in all_deps.of_type('dist_jar'):
self._RemoveJarsOwnedByDistJars(dist_jar_params)
if apk_under_test_params := params.apk_under_test():
self._RemoveApkUnderTestOverlap(apk_under_test_params)
if params.is_bundle_module() and not params.is_base_module():
self._AddBaseModuleToClasspath()
return self._ret
def _CollectClasspath(self, direct_deps, all_deps):
params = self._params
ret = self._ret
# Bundle modules do not depend on each other because their names do not
# match the java naming scheme that identifies them as libraries.
# TODO(agrieve): Recognize bundle module names as java_library targets so
# that they contribute to classpath.
ret.direct_unprocessed_jars.update(
direct_deps.collect('unprocessed_jar_path'))
ret.direct_interface_jars.update(direct_deps.collect('interface_jar_path'))
ret.all_unprocessed_jars.update(all_deps.collect('unprocessed_jar_path'))
ret.all_interface_jars.update(all_deps.collect('interface_jar_path'))
# Deps to add to the compile-time classpath (but not the runtime
# classpath).
ret.direct_input_jars_paths.update(params.get('input_jars_paths', []))
ret.direct_input_jars_paths.update(
direct_deps.collect('input_jars_paths', flatten=True))
ret.all_input_jars_paths.update(params.get('input_jars_paths', []))
ret.all_input_jars_paths.update(
all_deps.collect('input_jars_paths', flatten=True))
# Add the target's .jar (except for dist_jar, where it's the output .jar).
if params.collects_processed_classpath():
if not params.is_dist_jar():
if path := params.get('processed_jar_path'):
ret.all_processed_jars.add(path)
ret.all_processed_jars.update(all_deps.collect('processed_jar_path'))
if params.collects_dex_paths():
if not params.is_dist_jar():
if path := params.get('dex_path'):
ret.all_dex_files.add(path)
ret.all_dex_files.update(all_deps.collect('dex_path'))
def _AddBaseModuleToClasspath(self):
base_module = self._params.base_module()
# Adding base module to classpath to compile against its R.java file
self._ret.direct_unprocessed_jars.add(base_module['unprocessed_jar_path'])
self._ret.all_unprocessed_jars.add(base_module['unprocessed_jar_path'])
self._ret.direct_interface_jars.add(base_module['interface_jar_path'])
self._ret.all_interface_jars.add(base_module['interface_jar_path'])
def _CollectManifests(self, all_deps_without_under_test):
# Manifests are listed from highest priority to lowest priority.
# Ensure direct manifests come first, then sort the rest by name.
# https://developer.android.com/build/manage-manifests#merge_priorities
params = self._params
ret = self._ret
ret.android_manifests.update(params.get('mergeable_android_manifests', []))
indirect_manifests = all_deps_without_under_test.collect(
'mergeable_android_manifests', flatten=True)
indirect_manifests.sort(key=lambda p: (os.path.basename(p), p))
ret.android_manifests.update(indirect_manifests)
# Prevent the main manifest from showing up in mergeable_android_manifests.
if path := params.get('android_manifest'):
if path in ret.android_manifests:
ret.android_manifests.remove(path)
def _CollectResources(self, all_deps_without_under_test):
params = self._params
ret = self._ret
resource_deps = params.resource_deps()
ret.dependency_zips.update(resource_deps.collect('resources_zip'))
ret.dependency_zip_overlays.update(
resource_deps.collect('resources_overlay_zip'))
assets, uncompressed_assets, locale_paks = _MergeAssets(
all_deps_without_under_test.of_type('android_assets'))
ret.assets.update(assets)
ret.uncompressed_assets.update(uncompressed_assets)
ret.locale_paks = locale_paks
if params.get('java_resources_jar_path'):
ret.java_resources_jars.add(params)
ret.java_resources_jars.update(
all_deps_without_under_test.collect('java_resources_jar_path'))
def _CollectExtraPackageNames(self, direct_deps, all_deps):
# Needed by java_library targets.
if self._params.is_library():
# TODO(agrieve): We would ideally move away from being able to use R.java
# from package_names other than the library's own.
resource_deps = direct_deps.of_type('android_resources')
else:
resource_deps = all_deps.recursive_resource_deps()
self._ret.extra_package_names.update(resource_deps.collect('package_name'))
if name := self._params.get('package_name'):
self._ret.extra_package_names.add(name)
def _RemoveJarsOwnedByDistJars(self, dist_jar_params):
"""Removes jars from a _TransitiveValues object that are owned by a
dist_jar.
"""
if dist_jar_params.get('use_interface_jars'):
return
if dist_jar_params.get('direct_deps_only'):
deps = dist_jar_params.deps()
dist_jar_tvs = _TransitiveValues()
dist_jar_tvs.all_unprocessed_jars = deps.collect('unprocessed_jar_path')
dist_jar_tvs.all_interface_jars = deps.collect('interface_jar_path')
dist_jar_tvs.all_dex_files = deps.collect('dex_path')
dist_jar_tvs.all_processed_jars = deps.collect('processed_jar_path')
else:
dist_jar_tvs = _TransitiveValuesBuilder(dist_jar_params).Build()
self._ret.RemoveSubtree(dist_jar_tvs,
retain_resource_zips=True,
retain_extra_package_names=True)
self._ret.direct_unprocessed_jars.difference_update(
dist_jar_tvs.direct_unprocessed_jars)
self._ret.direct_interface_jars.difference_update(
dist_jar_tvs.direct_interface_jars)
def _RemoveParentModuleOverlap(self):
# There are two approaches to dealing with modules dependencies:
# 1) Perform steps in android_apk_or_module(), with only the knowledge of
# ancesstor splits.
# 2) Perform steps in android_app_bundle(), with knowledge of full set of
# modules. This is required for dex because it can handle the case of
# two leaf nodes having the same dep, and promoting that dep to their
# common parent.
# _PromoteToCommonAncestor() implements this approach.
#
# We do 1) unconditionally, and 2) for dex / proguard, but we really
# should do 2) for all applicable fields.
if parent_module := self._params.parent_module():
x = _TransitiveValuesBuilder(parent_module,
remove_parent_module_overlap=False).Build()
self._ret.RemoveSubtree(x)
def _RemoveApkUnderTestOverlap(self, apk_under_test_params):
# The java code for an instrumentation test apk is assembled differently
# for R8 vs. non-R8.
#
# Without R8: Each library's jar is dexed separately and then combined
# into a single classes.dex. A test apk will include all dex files not
# present in the apk-under-test. At runtime all test code lives in the
# test apk, and the program code lives in the apk-under-test.
#
# With R8: Each library's .jar file is fed into R8, which outputs
# a single .jar, which is then dexed into a classes.dex. A test apk
# includes all jar files from the program and the tests because having
# them separate doesn't work with R8's whole-program optimizations.
# Although the apk-under-test still has all of its code in its
# classes.dex, none of it is used at runtime because the copy within the
# test apk takes precedence.
self._ret.RemoveSubtree(
_TransitiveValuesBuilder(apk_under_test_params).Build(),
retain_processed_jars=self._params.get('proguard_enabled'),
retain_unprocessed_jars=True,
retain_resource_zips=True,
retain_android_manifests=True)
def _MergeAssets(all_assets):
"""Merges all assets from the given deps.
Returns:
A tuple of: (compressed, uncompressed, locale_paks)
|compressed| and |uncompressed| are lists of "srcPath:zipPath". srcPath is
the path of the asset to add, and zipPath is the location within the zip
(excluding assets/ prefix).
|locale_paks| is a set of all zipPaths that have been marked as
treat_as_locale_paks=true.
"""
compressed = {}
uncompressed = {}
locale_paks = set()
for asset_dep in all_assets:
entry = asset_dep['assets']
disable_compression = entry.get('disable_compression')
treat_as_locale_paks = entry.get('treat_as_locale_paks')
dest_map = uncompressed if disable_compression else compressed
other_map = compressed if disable_compression else uncompressed
outputs = entry.get('outputs', [])
for src, dest in itertools.zip_longest(entry['sources'], outputs):
if not dest:
dest = os.path.basename(src)
# Merge so that each path shows up in only one of the lists, and that
# deps of the same target override previous ones.
other_map.pop(dest, 0)
dest_map[dest] = src
if treat_as_locale_paks:
locale_paks.add(dest)
def create_list(asset_map):
# Sort to ensure deterministic ordering.
items = sorted(asset_map.items())
return [f'{src}:{dest}' for dest, src in items]
return create_list(compressed), create_list(uncompressed), locale_paks
def _CreateJavaLocaleListFromAssets(assets, locale_paks):
"""Returns a java literal array from a list of locale assets.
Args:
assets: A list of all APK asset paths in the form 'src:dst'
locale_paks: A list of asset paths that correponds to the locale pak
files of interest. Each |assets| entry will have its 'dst' part matched
against it to determine if they are part of the result.
Returns:
A string that is a Java source literal array listing the locale names
of the corresponding asset files, without directory or .pak suffix.
E.g. '{"en-GB", "en-US", "es-ES", "fr", ... }'
"""
assets_paths = [a.split(':')[1] for a in assets]
locales = [os.path.basename(a)[:-4] for a in assets_paths if a in locale_paks]
return '{%s}' % ','.join('"%s"' % l for l in sorted(locales))
def _AddJarMapping(jar_to_target, config):
"""Adds mappings from jar path to GN target for a given config."""
if jar := config.get('unprocessed_jar_path'):
jar_to_target[jar] = config['gn_target']
for jar in config.get('input_jars_paths', []):
jar_to_target[jar] = config['gn_target']
def _PromoteToCommonAncestor(modules, child_to_ancestors, field_name):
"""Finds duplicates of a field across modules and moves them to the
nearest common ancestor module. This is used for app bundles to avoid
duplicating dependencies in multiple modules.
"""
module_to_fields_set = {}
for module_name, module in modules.items():
if field_name in module:
module_to_fields_set[module_name] = set(module[field_name])
# Find all items that are duplicated across at least two modules.
seen = set()
dupes = set()
for fields in module_to_fields_set.values():
new_dupes = seen & fields
if new_dupes:
dupes |= new_dupes
seen |= fields
for d in dupes:
owning_modules = []
for module_name, fields in module_to_fields_set.items():
if d in fields:
owning_modules.append(module_name)
assert len(owning_modules) >= 2
# Rely on the fact that ancestors are inserted from closest to
# farthest, where "base" should always be the last element.
# Arbitrarily using the first owning module - any would work.
for ancestor in child_to_ancestors[owning_modules[0]]:
for o in owning_modules[1:]:
if ancestor not in child_to_ancestors[o] and ancestor is not o:
break
else:
common_ancestor = ancestor
break
else:
raise Exception('Should have already removed ancestor dupes. ' +
','.join(owning_modules) + ' field=' + field_name +
' dupes: ' + ','.join(dupes))
# Move the duplicated item to the common ancestor.
for o in owning_modules:
module_to_fields_set[o].remove(d)
module_to_fields_set[common_ancestor].add(d)
# Update the original modules dictionary with the de-duplicated lists.
for module_name, module in modules.items():
if field_name in module:
module[field_name] = sorted(list(module_to_fields_set[module_name]))
def _DoPlatformChecks(params):
"""Check for platform mismatches between a target and its dependencies."""
# Robolectric is special in that it's an android target that runs on host.
# You are allowed to depend on both android |deps_require_android| and
# non-android |deps_not_support_android| targets.
if params.get('bypass_platform_checks') or params.get('requires_robolectric'):
return
deps_require_android = [d for d in params.deps() if d.requires_android()]
deps_not_support_android = [
d for d in params.deps() if not d.supports_android()
]
if deps_require_android and not params.requires_android():
raise Exception('Some deps require building for the Android platform:\n' +
'\n'.join('* ' + d['gn_target']
for d in deps_require_android))
if deps_not_support_android and params.supports_android():
raise Exception('Not all deps support the Android platform:\n' +
'\n'.join('* ' + d['gn_target']
for d in deps_not_support_android))
def _SuffixAssets(config, target_config):
"""Adds a suffix to asset paths to avoid collisions."""
def helper(suffix_names, suffix, assets):
new_assets = []
for x in assets:
src_path, apk_subpath = x.split(':', 1)
if apk_subpath in suffix_names:
apk_subpath += suffix
new_assets.append(f'{src_path}:{apk_subpath}')
return new_assets
all_assets = target_config['assets'] + target_config['uncompressed_assets']
suffix = '+' + target_config['package_name'] + '+'
suffix_names = {x.split(':', 1)[1].replace(suffix, '') for x in all_assets}
config['assets'] = helper(suffix_names, suffix, config['assets'])
config['uncompressed_assets'] = helper(suffix_names, suffix,
config['uncompressed_assets'])
config['apk_assets_suffixed_list'] = ','.join(f'"assets/{x}"'
for x in sorted(suffix_names))
config['apk_assets_suffix'] = suffix
def _ToTraceEventRewrittenPath(jar_dir, path):
"""Returns the path to the trace-event-rewritten version of a jar."""
path = path.replace('../', '')
path = path.replace('obj/', '')
path = path.replace('gen/', '')
path = path.replace('.jar', '.tracing_rewritten.jar')
return os.path.join(jar_dir, path)
def _WriteLintJson(params, lint_json, main_config):
# Collect all sources and resources at the apk/bundle_module level.
aars = set()
srcjars = set()
sources = set()
resource_sources = set()
resource_zips = set()
if path := params.get('target_sources_file'):
sources.add(path)
if paths := params.get('bundled_srcjars'):
srcjars.update(paths)
for c in params.deps().recursive():
if c.get('chromium_code', True) and c.requires_android():
if path := c.get('target_sources_file'):
sources.add(path)
if paths := c.get('bundled_srcjars'):
srcjars.update(paths)
if path := c.get('aar_path'):
aars.add(path)
for c in params.resource_deps():
if c.get('chromium_code', True):
# Prefer res_sources_path to resources_zips so that lint errors have
# real paths and to avoid needing to extract during lint.
if path := c.get('res_sources_path'):
resource_sources.add(path)
else:
resource_zips.add(
c.get('resources_zip') or c.get('resources_overlay_zip'))
if params.is_bundle():
classpath = OrderedSet()
manifests = OrderedSet(p['android_manifest'] for p in params.module_deps())
for m in params.module_deps():
module_config = m.build_config_json()
classpath.update(module_config['javac_full_interface_classpath'])
manifests.update(module_config['extra_android_manifests'])
classpath = list(classpath)
manifests = list(manifests)
else:
classpath = main_config['javac_full_interface_classpath']
manifests = [params['android_manifest']]
manifests += main_config['extra_android_manifests']
config = {}
config['aars'] = sorted(aars)
config['android_manifests'] = manifests
config['classpath'] = classpath
config['sources'] = sorted(sources)
config['srcjars'] = sorted(srcjars)
config['resource_sources'] = sorted(resource_sources)
config['resource_zips'] = sorted(resource_zips)
build_utils.WriteJson(config, lint_json, only_if_changed=True)
def main():
parser = argparse.ArgumentParser(
description='Writes a .build_config.json file.')
action_helpers.add_depfile_arg(parser)
parser.add_argument('--output', help='.build_config.json to write.')
options = parser.parse_args()
build_config_path = options.output
params = params_json_util.get_params(
build_config_path.replace('.build_config.json', '.params.json'))
if lines := params.get('fail'):
parser.error('\n'.join(lines))
target_type = params.type
is_bundle_module = params.is_bundle_module()
is_apk = params.is_apk()
is_apk_or_module = is_apk or is_bundle_module
is_bundle = params.is_bundle()
has_classpath = params.has_classpath()
proguard_enabled = params.get('proguard_enabled', False)
if has_classpath:
_DoPlatformChecks(params)
if is_bundle_module:
if parent_module_params := params.parent_module():
# Validate uses_split matches the parent split's name.
parent_split_name = params.get('uses_split', 'base')
actual = parent_module_params['module_name']
assert actual == parent_split_name, (
f'uses_split={parent_split_name} but parent was {actual}')
if is_bundle:
deps_proguard_enabled = []
deps_proguard_disabled = []
for module in params.module_deps():
if module.get('proguard_enabled'):
deps_proguard_enabled.append(module['module_name'])
else:
deps_proguard_disabled.append(module['module_name'])
if deps_proguard_enabled and deps_proguard_disabled:
raise Exception('Deps %s have proguard enabled while deps %s have '
'proguard disabled' %
(deps_proguard_enabled, deps_proguard_disabled))
apk_under_test_params = params.apk_under_test()
if is_apk and apk_under_test_params:
if apk_under_test_params['proguard_enabled']:
assert proguard_enabled, (
'proguard must be enabled for '
'instrumentation apks if it\'s enabled for the tested apk.')
if is_apk_or_module:
manifest = AndroidManifest(params['android_manifest'])
if not apk_under_test_params and manifest.GetInstrumentationElements():
# This must then have instrumentation only for itself.
manifest.CheckInstrumentationElements(manifest.GetPackageName())
config = {}
if is_apk:
config['apk_path'] = params['apk_path']
if path := params.get('incremental_install_json_path'):
config['incremental_install_json_path'] = path
config['incremental_apk_path'] = params['incremental_apk_path']
if is_bundle_module:
config['unprocessed_jar_path'] = params['unprocessed_jar_path']
config['res_size_info_path'] = params['res_size_info_path']
if has_classpath:
tv = _TransitiveValuesBuilder(params).Build()
if apk_under_test_params:
assert is_apk
config['arsc_package_name'] = (
apk_under_test_params.build_config_json()['package_name'])
config['classpath'] = list(tv.direct_unprocessed_jars) + list(
tv.direct_input_jars_paths)
config['interface_classpath'] = list(tv.direct_interface_jars) + list(
tv.direct_input_jars_paths)
# processor_configs will be of type 'java_annotation_processor', and so not
# included in deps().recursive().of_type('java_library'). Annotation
# processors run as part of the build, so need processed_jar_path.
processor_deps = params.processor_deps()
config['processor_classpath'] = _SortClasspath(
processor_deps.recursive()).collect('processed_jar_path')
config['processor_classes'] = sorted(processor_deps.collect('main_class'))
config['javac_full_classpath'] = (list(tv.all_unprocessed_jars) +
list(tv.all_input_jars_paths))
config['javac_full_interface_classpath'] = (list(tv.all_interface_jars) +
list(tv.all_input_jars_paths))
if params.collects_processed_classpath():
config['processed_classpath'] = list(tv.all_processed_jars)
if trace_events_jar_dir := params.get('trace_events_jar_dir'):
config['trace_event_rewritten_classpath'] = [
_ToTraceEventRewrittenPath(trace_events_jar_dir, p)
for p in tv.all_processed_jars
]
if params.collects_dex_paths():
config['all_dex_files'] = list(tv.all_dex_files)
if target_type in ('dist_aar', 'java_library'):
config['dependency_rtxt_files'] = (
params.resource_deps().collect('rtxt_path'))
sdk_deps = params.deps().of_type('system_java_library')
config['sdk_jars'] = sdk_deps.collect('unprocessed_jar_path')
config['sdk_interface_jars'] = sdk_deps.collect('interface_jar_path')
if proguard_enabled or target_type == 'dist_aar':
config['proguard_all_configs'] = sorted(tv.proguard_configs)
if proguard_enabled:
config['proguard_classpath_jars'] = sorted(tv.all_input_jars_paths)
if is_apk_or_module:
config['java_resources_jars'] = sorted(tv.java_resources_jars)
if params.is_dist_jar():
if params.get('direct_deps_only'):
if params.get('use_interface_jars'):
dist_jars = tv.direct_interface_jars
else:
dist_jars = params.deps().collect('processed_jar_path')
elif params.get('use_interface_jars'):
dist_jars = tv.all_interface_jars
else:
dist_jars = tv.all_processed_jars
config['dist_jar'] = {}
config['dist_jar']['jars'] = list(dist_jars)
if params.collects_resources():
# Safe to sort: Build checks that non-overlay resource have no overlap.
config['dependency_zips'] = sorted(tv.dependency_zips)
config['dependency_zip_overlays'] = list(tv.dependency_zip_overlays)
config['assets'] = sorted(tv.assets)
config['uncompressed_assets'] = sorted(tv.uncompressed_assets)
if params.get('create_locales_java_list'):
config['locales_java_list'] = _CreateJavaLocaleListFromAssets(
tv.uncompressed_assets, tv.locale_paks)
if params.compiles_resources():
config['extra_package_names'] = sorted(tv.extra_package_names)
if params.merges_manifests():
config['extra_android_manifests'] = list(tv.android_manifests)
if is_bundle:
module_deps = params.module_deps()
module_params_by_name = {m['module_name']: m for m in module_deps}
module_configs_by_name = {
m['module_name']: m.build_config_json()
for m in module_deps
}
modules = {m: {} for m in module_params_by_name}
child_to_ancestors = {n: [] for n in module_params_by_name}
for name, module in module_params_by_name.items():
while module := module.parent_module():
child_to_ancestors[name].append(module['module_name'])
per_module_fields = [
'processed_classpath',
'trace_event_rewritten_classpath',
'all_dex_files',
'assets',
'uncompressed_assets',
]
union_fields = {}
if params.get('trace_events_jar_dir'):
union_fields['processed_classpath'] = 'processed_classpath'
union_fields['trace_event_rewritten_classpath'] = (
'trace_event_rewritten_classpath')
if proguard_enabled:
union_fields['proguard_classpath_jars'] = 'proguard_classpath_jars'
union_fields['proguard_all_configs'] = 'proguard_all_configs'
union_fields['sdk_jars'] = 'sdk_jars'
unioned_values = {k: OrderedSet() for k in union_fields}
for n, c in module_configs_by_name.items():
module_params = module_params_by_name[n]
if n == 'base':
assert 'base_module_config' not in config, (
'Must have exactly 1 base module!')
config['package_name'] = c['package_name']
config['version_code'] = module_params['version_code']
config['version_name'] = module_params['version_name']
config['base_module_config'] = module_params.path
config['android_manifest'] = module_params['android_manifest']
for dst_key, src_key in union_fields.items():
unioned_values[dst_key].update(c[src_key])
module = modules[n]
module['final_dex_path'] = module_params['final_dex_path']
for f in per_module_fields:
if f in c:
module[f] = c[f]
for dst_key in union_fields:
config[dst_key] = list(unioned_values[dst_key])
# Promote duplicates from siblings/cousins.
for f in per_module_fields:
_PromoteToCommonAncestor(modules, child_to_ancestors, f)
config['modules'] = modules
if is_apk_or_module:
config['package_name'] = manifest.GetPackageName()
config['android_manifest'] = params['android_manifest']
config['merged_android_manifest'] = params['merged_android_manifest']
if is_apk:
config['version_code'] = params['version_code']
config['version_name'] = params['version_name']
# TrichromeLibrary has no dex.
if final_dex_path := params.get('final_dex_path'):
config['final_dex_path'] = final_dex_path
library_paths = params.native_libraries()
secondary_abi_libraries = params.secondary_abi_native_libraries()
paths_without_parent_dirs = [
p for p in secondary_abi_libraries if os.path.sep not in p
]
if paths_without_parent_dirs:
sys.stderr.write('Found secondary native libraries from primary '
'toolchain directory. This is a bug!\n')
sys.stderr.write('\n'.join(paths_without_parent_dirs))
sys.stderr.write('\n\nIt may be helpful to run: \n')
sys.stderr.write(' gn path out/Default //chrome/android:'
'monochrome_secondary_abi_lib //base:base\n')
sys.exit(1)
config['native'] = {}
config['native']['libraries'] = library_paths
config['native']['secondary_abi_libraries'] = secondary_abi_libraries
if is_bundle_module:
loadable_modules = params.get('loadable_modules', [])
loadable_modules.sort()
secondary_abi_loadable_modules = params.get(
'secondary_abi_loadable_modules', [])
secondary_abi_loadable_modules.sort()
placeholder_paths = params.get('native_lib_placeholders', [])
placeholder_paths.sort()
secondary_abi_placeholder_paths = params.get(
'secondary_native_lib_placeholders', [])
secondary_abi_placeholder_paths.sort()
config['native']['loadable_modules'] = loadable_modules
config['native']['placeholders'] = placeholder_paths
config['native'][
'secondary_abi_loadable_modules'] = secondary_abi_loadable_modules
config['native'][
'secondary_abi_placeholders'] = secondary_abi_placeholder_paths
config['native']['library_always_compress'] = params.get(
'library_always_compress')
config['proto_resources_path'] = params['proto_resources_path']
config['base_allowlist_rtxt_path'] = params['base_allowlist_rtxt_path']
config['rtxt_path'] = params['rtxt_path']
config['module_pathmap_path'] = params['module_pathmap_path']
if is_apk_or_module or target_type == 'robolectric_binary':
if path := params.get('suffix_apk_assets_used_by_config'):
if path == build_config_path:
target_config = config
else:
target_config = params_json_util.get_build_config(path)
_SuffixAssets(config, target_config)
if has_classpath:
jar_to_target = {}
all_params = params.deps() + [params]
if apk_under_test_params:
all_params.append(apk_under_test_params)
for d in all_params.recursive():
_AddJarMapping(jar_to_target, d)
# Used by check_for_missing_direct_deps.py to give better error message
# when missing deps are found. Both javac_full_classpath_targets and
# javac_full_classpath must be in identical orders, as they get passed as
# separate arrays and then paired up based on index.
config['javac_full_classpath_targets'] = [
jar_to_target[x] for x in config['javac_full_classpath']
]
if path := params.get('lint_json'):
_WriteLintJson(params, path, config)
build_utils.WriteJson(config, build_config_path, only_if_changed=True)
if options.depfile:
all_inputs = params_json_util.all_read_file_paths()
if path := params.get('shared_libraries_runtime_deps_file'):
# Path must be added to depfile because the GN template does not add this
# input to avoid having to depend on the generated_file() target.
all_inputs.append(path)
if path := params.get('secondary_abi_shared_libraries_runtime_deps_file'):
all_inputs.append(path)
action_helpers.write_depfile(options.depfile, build_config_path, all_inputs)
if __name__ == '__main__':
main()