0

Rename org.jni_zero classes when building standalone cronet

Bug: 353534209
Change-Id: I04c972cfa3f6958ef7729339384861b10ffaa68f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5735476
Reviewed-by: Mohannad Farrag <aymanm@google.com>
Reviewed-by: Andrew Grieve <agrieve@chromium.org>
Reviewed-by: Stefano Duo <stefanoduo@google.com>
Commit-Queue: Mohamed Heikal <mheikal@chromium.org>
Owners-Override: Andrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1353429}
This commit is contained in:
Mohamed Heikal
2024-09-10 17:10:50 +00:00
committed by Chromium LUCI CQ
parent 9fc1e631ec
commit dd52b4531f
14 changed files with 401 additions and 40 deletions

@ -2294,6 +2294,7 @@ _GENERIC_PYDEPS_FILES = [
'build/android/gyp/prepare_resources.pydeps',
'build/android/gyp/process_native_prebuilt.pydeps',
'build/android/gyp/proguard.pydeps',
'build/android/gyp/rename_java_classes.pydeps',
'build/android/gyp/system_image_apks.pydeps',
'build/android/gyp/trace_event_bytecode_rewriter.pydeps',
'build/android/gyp/turbine.pydeps',

@ -61,7 +61,9 @@ _IGNORE_WARNINGS = (
r'FastServiceLoader\.class:.*Could not inline ServiceLoader\.load',
# Happens on internal builds. It's a real failure, but happens in dead code.
r'(?:GeneratedExtensionRegistryLoader|ExtensionRegistryLite)\.class:.*Could not inline ServiceLoader\.load', # pylint: disable=line-too-long
# This class is referenced by kotlinx-coroutines-core-jvm but it does not
# depend on it. Not actually needed though.
r'Missing class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement',
# Ignore MethodParameter attribute count isn't matching in espresso.
# This is a banner warning and each individual file affected will have
# its own warning.
@ -70,8 +72,10 @@ _IGNORE_WARNINGS = (
# corresponding EnclosingMethod attribute. Such InnerClasses attribute
# entries are ignored."
r'Warning: InnerClasses attribute has entries missing a corresponding EnclosingMethod attribute', # pylint: disable=line-too-long
r'Warning in obj/third_party/androidx/androidx_test_espresso_espresso_core_java', # pylint: disable=line-too-long
r'Warning in obj/third_party/androidx/androidx_test_espresso_espresso_web_java', # pylint: disable=line-too-long
# Full error example: "Warning in <path to target prebuilt>:
# androidx/test/espresso/web/internal/deps/guava/collect/Maps$1.class:"
# Also happens in espresso core.
r'Warning in .*:androidx/test/espresso/.*/guava/collect/.*',
# We are following up in b/290389974
r'AppSearchDocumentClassMap\.class:.*Could not inline ServiceLoader\.load',

@ -0,0 +1,59 @@
#!/usr/bin/env python3
#
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import sys
import tempfile
from util import build_utils
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
import zip_helpers
def _RenameJars(options):
with tempfile.NamedTemporaryFile() as temp:
cmd = build_utils.JavaCmd() + [
'-cp',
options.r8_path,
'com.android.tools.r8.relocator.RelocatorCommandLine',
'--input',
options.input_jar,
'--output',
temp.name,
]
for mapping_rule in options.mapping_rules:
cmd += ['--map', mapping_rule]
build_utils.CheckOutput(cmd)
# use zip_helper.merge_zips to hermetize the zip because R8 changes the
# times and permissions inside the output jar for some reason.
zip_helpers.merge_zips(options.output_jar, [temp.name])
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--output-jar',
required=True,
help='Output file for the renamed classes jar')
parser.add_argument('--input-jar',
required=True,
help='Input jar file to rename classes in')
parser.add_argument('--r8-path', required=True, help='Path to R8 Jar')
parser.add_argument('--map',
action='append',
dest='mapping_rules',
help='List of mapping rules in the form of ' +
'"<original prefix>.**-><new prefix>"')
options = parser.parse_args()
_RenameJars(options)
if __name__ == '__main__':
main()

@ -0,0 +1,7 @@
# Generated by running:
# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/rename_java_classes.pydeps build/android/gyp/rename_java_classes.py
../../gn_helpers.py
../../zip_helpers.py
rename_java_classes.py
util/__init__.py
util/build_utils.py

@ -583,10 +583,18 @@ class OrderedSet(collections.OrderedDict):
def add(self, key):
self[key] = True
def remove(self, key):
if key in self:
del self[key]
def update(self, iterable):
for v in iterable:
self.add(v)
def difference_update(self, iterable):
for v in iterable:
self.remove(v)
def _ExtractMarkdownDocumentation(input_text):
"""Extract Markdown documentation from a list of input strings lines.
@ -1744,7 +1752,10 @@ def main(argv):
if is_java_target or options.type == 'android_app_bundle':
# The classpath to use to run this target (or as an input to ProGuard).
device_classpath = []
if is_java_target and options.device_jar_path:
# dist_jar configs should not list their device jar in their own classpath
# since the classpath is used to create the device jar itself.
if (is_java_target and options.device_jar_path
and options.type != 'dist_jar'):
device_classpath.append(options.device_jar_path)
device_classpath.extend(
c.get('device_jar_path') for c in all_library_deps
@ -1754,13 +1765,7 @@ def main(argv):
device_classpath.extend(c for c in d.get('device_classpath', [])
if c not in device_classpath)
if options.type in ('dist_jar', 'java_binary', 'robolectric_binary'):
# The classpath to use to run this target.
host_classpath = []
if options.host_jar_path:
host_classpath.append(options.host_jar_path)
host_classpath.extend(c['host_jar_path'] for c in all_library_deps)
deps_info['host_classpath'] = host_classpath
all_dist_jar_deps = deps.All('dist_jar')
# We allow lint to be run on android_apk targets, so we collect lint
# artifacts for them.
@ -1968,6 +1973,78 @@ def main(argv):
deps_info['proguard_classpath_jars'] = sorted(
set(extra_proguard_classpath_jars))
if options.type in ('dist_jar', 'java_binary', 'robolectric_binary'):
# The classpath to use to run this target.
host_classpath = []
if options.host_jar_path:
host_classpath.append(options.host_jar_path)
host_classpath.extend(c['host_jar_path'] for c in all_library_deps)
# Collect all the dist_jar host jars.
dist_jar_host_jars = [
c['host_jar_path'] for c in all_dist_jar_deps if 'host_jar_path' in c
]
# Collect all the jars that went into the dist_jar host jars.
dist_jar_host_classpath = set()
for c in all_dist_jar_deps:
dist_jar_host_classpath.update(c['host_classpath'])
# Remove the jars that went into the dist_jar host jars.
host_classpath = [
p for p in host_classpath if p not in dist_jar_host_classpath
]
# Add the dist_jar host jars themselves instead.
host_classpath += dist_jar_host_jars
deps_info['host_classpath'] = host_classpath
if is_java_target:
def _CollectListsFromDeps(deps, key_name):
combined = set()
for config in deps:
combined.update(config.get(key_name, []))
return combined
dist_jar_device_classpath = _CollectListsFromDeps(all_dist_jar_deps,
'device_classpath')
dist_jar_javac_full_classpath = _CollectListsFromDeps(
all_dist_jar_deps, 'javac_full_classpath')
dist_jar_javac_full_interface_classpath = _CollectListsFromDeps(
all_dist_jar_deps, 'javac_full_interface_classpath')
dist_jar_child_dex_files = _CollectListsFromDeps(all_dist_jar_deps,
'all_dex_files')
def _CollectSinglesFromDeps(deps, key_name):
return [config[key_name] for config in deps if key_name in config]
dist_jar_device_jars = _CollectSinglesFromDeps(all_dist_jar_deps,
'device_jar_path')
dist_jar_combined_dex_files = _CollectSinglesFromDeps(
all_dist_jar_deps, 'dex_path')
dist_jar_interface_jars = _CollectSinglesFromDeps(all_dist_jar_deps,
'interface_jar_path')
dist_jar_unprocessed_jars = _CollectSinglesFromDeps(all_dist_jar_deps,
'unprocessed_jar_path')
device_classpath = [
p for p in device_classpath if p not in dist_jar_device_classpath
]
device_classpath += dist_jar_device_jars
javac_full_classpath.difference_update(dist_jar_javac_full_classpath)
javac_full_classpath.update(dist_jar_unprocessed_jars)
javac_full_interface_classpath.difference_update(
dist_jar_javac_full_interface_classpath)
javac_full_interface_classpath.update(dist_jar_interface_jars)
javac_interface_classpath.update(dist_jar_interface_jars)
javac_classpath.update(dist_jar_unprocessed_jars)
if is_apk_or_module_target or options.type == 'dist_jar':
all_dex_files = [
p for p in all_dex_files if p not in dist_jar_child_dex_files
]
all_dex_files += dist_jar_combined_dex_files
if options.final_dex_path:
config['final_dex'] = {'path': options.final_dex_path}
if is_apk_or_module_target or options.type == 'dist_jar':

@ -3600,8 +3600,8 @@ if (enable_java_templates) {
_output_name = _main_target_name
}
_build_host_jar =
_is_java_binary || _is_annotation_processor || _type == "java_library"
_build_host_jar = _is_java_binary || _is_annotation_processor ||
_type == "java_library" || _type == "dist_jar"
_build_device_jar = _type != "system_java_library" && _supports_android
_jacoco_instrument =
@ -4601,3 +4601,35 @@ template("create_android_app_bundle_module") {
]
}
}
template("rename_jar_classes") {
action_with_pydeps(target_name) {
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
forward_variables_from(invoker,
[
"deps",
"public_deps",
])
script = "//build/android/gyp/rename_java_classes.py"
inputs = [
_r8_path,
invoker.input,
]
outputs = [ invoker.output ]
args = [
"--r8-path",
rebase_path(_r8_path, root_build_dir),
"--input-jar",
rebase_path(invoker.input, root_build_dir),
"--output-jar",
rebase_path(invoker.output, root_build_dir),
]
foreach(_rule, invoker.renaming_rules) {
args += [
"--map",
_rule,
]
}
}
}

@ -1434,6 +1434,7 @@ if (!is_robolectric && enable_java_templates) {
# direct_deps_only: Do not recurse on deps.
# jar_excluded_patterns (optional)
# List of globs for paths to exclude.
# renaming_rules: rename java classes inside according to these rules.
#
# Example
# dist_jar("lib_fatjar") {
@ -1452,30 +1453,80 @@ if (!is_robolectric && enable_java_templates) {
defined(invoker.direct_deps_only) && invoker.direct_deps_only
assert(!(_use_unprocessed_jars && _use_interface_jars),
"Cannot set both use_interface_jars and use_unprocessed_jars")
_supports_android =
!defined(invoker.supports_android) || invoker.supports_android
_requires_android =
defined(invoker.requires_android) && invoker.requires_android
_has_renaming_rules = defined(invoker.renaming_rules)
_jar_target_name = target_name
_is_java_target_name =
filter_exclude([ target_name ], java_target_patterns) == []
_target_name_without_java_or_junit =
string_replace(string_replace(target_name, "_java", "_J"),
"_junit",
"_U")
_zip_target_name = "${_target_name_without_java_or_junit}_zip"
_zip_jar_path = invoker.output
if (defined(invoker.build_config)) {
_build_config = invoker.build_config
_build_config_dep = invoker.build_config_dep
if (_has_renaming_rules) {
_zip_jar_path = "$target_gen_dir/$target_name.singlejar.jar"
# if we are java-like and have renaming rules, the main target is the
# java_library_impl otherwise its the renaming target.
if (_is_java_target_name) {
_renaming_target_name = "${_target_name_without_java_or_junit}_renamed"
_final_jar_target = _renaming_target_name
} else {
_renaming_target_name = target_name
}
} else {
_build_config = "$target_gen_dir/$target_name.build_config.json"
_build_config_target_name = "$target_name$build_config_target_suffix"
_build_config_dep = ":$_build_config_target_name"
# If we dont have renaming rules then the main target is either the zip
# target or the java_library_impl if we are java-like.
if (_is_java_target_name) {
_final_jar_target = _zip_target_name
} else {
_zip_target_name = target_name
}
}
_build_config = "$target_gen_dir/$target_name.build_config.json"
_build_config_target_name = "$target_name$build_config_target_suffix"
_build_config_dep = ":$_build_config_target_name"
if (_is_java_target_name) {
# If we have a java-like target name we need to provide the expected
# meta_targets as well as the processing (eg: ijar, bytecode rewriting)
# that is expected of java targets so that other java targets can depend
# on us.
java_library_impl(target_name) {
forward_variables_from(invoker,
[
"jar_excluded_patterns",
"deps",
])
type = "dist_jar"
if (!defined(deps)) {
deps = []
}
deps += [ ":$_final_jar_target" ]
supports_android = _supports_android
requires_android = _requires_android
jar_path = invoker.output
enable_bytecode_checks = false
}
} else {
write_build_config(_build_config_target_name) {
type = "dist_jar"
supports_android =
!defined(invoker.supports_android) || invoker.supports_android
requires_android =
defined(invoker.requires_android) && invoker.requires_android
supports_android = _supports_android
requires_android = _requires_android
possible_config_deps = invoker.deps
build_config = _build_config
}
}
_rebased_build_config = rebase_path(_build_config, root_build_dir)
action_with_pydeps(_jar_target_name) {
action_with_pydeps(_zip_target_name) {
forward_variables_from(invoker, [ "data" ])
script = "//build/android/gyp/zip.py"
depfile = "$target_gen_dir/$target_name.d"
@ -1498,13 +1549,13 @@ if (!is_robolectric && enable_java_templates) {
inputs = [ _build_config ]
outputs = [ invoker.output ]
outputs = [ _zip_jar_path ]
args = [
"--depfile",
rebase_path(depfile, root_build_dir),
"--output",
rebase_path(invoker.output, root_build_dir),
rebase_path(_zip_jar_path, root_build_dir),
"--no-compress",
]
@ -1546,6 +1597,15 @@ if (!is_robolectric && enable_java_templates) {
args += [ "--input-zips-excluded-globs=$_excludes" ]
}
}
if (_has_renaming_rules) {
rename_jar_classes(_renaming_target_name) {
input = _zip_jar_path
output = invoker.output
deps = [ ":$_zip_target_name" ]
renaming_rules = invoker.renaming_rules
}
}
}
# Combines all dependent .jar files into a single proguarded .dex file.

@ -12,6 +12,7 @@ import("//build/config/zip.gni")
import("//build/util/lastchange.gni")
import("//build/util/process_version.gni")
import("//chrome/version.gni")
import("//components/cronet/android/cronet_test_templates.gni")
import("//components/cronet/native/include/headers.gni")
import("//components/grpc_support/include/headers.gni")
import("//testing/test.gni")
@ -977,6 +978,10 @@ template("repackage_jars") {
use_unprocessed_jars = true
no_build_hooks = true
jar_excluded_patterns = _excludes
# This is to prevent clashing with the real jni_zero in apps that include
# cronet (eg: google3). See https://crbug.com/353534209
renaming_rules = [ "org.jni_zero.**->internal.org.jni_zero" ]
forward_variables_from(invoker, "*", [ "jar_excluded_patterns" ])
}
}
@ -1384,7 +1389,7 @@ android_library("cronet_javatests") {
data = [ "//components/cronet/testing/test_server/data/" ]
}
instrumentation_test_apk("cronet_test_instrumentation_apk") {
cronet_instrumentation_test_apk("cronet_test_instrumentation_apk") {
# This is the only Cronet APK with lint enabled. This one was chosen because
# it depends on basically all source files.
enable_lint = true
@ -1532,7 +1537,7 @@ instrumentation_test_apk(
# Note: The proguard rules includes the test proguard configs as
# the build system automatically pulls in all transitive proguard
# configs that can be found through the dependency chain.
instrumentation_test_apk("cronet_smoketests_apk") {
cronet_instrumentation_test_apk("cronet_smoketests_apk") {
apk_name = "CronetSmokeTestInstrumentation"
android_manifest = "test/javatests/AndroidManifest.xml"
shared_libraries = [
@ -1611,7 +1616,7 @@ android_apk("cronet_perf_test_apk") {
]
}
test("cronet_unittests_android") {
cronet_test("cronet_unittests_android") {
deps = [
":cronet_base_feature_unittest",
":cronet_impl_native_java",
@ -1639,7 +1644,7 @@ test("cronet_unittests_android") {
}
}
test("cronet_tests_android") {
cronet_test("cronet_tests_android") {
deps = [
":cronet_impl_native_java",
":cronet_static",

@ -133,9 +133,11 @@
-keep @interface org.chromium.build.annotations.DoNotInline
-keep @interface org.chromium.build.annotations.UsedByReflection
-keep @interface org.chromium.build.annotations.IdentifierNameString
-keep @interface org.jni_zero.AccessedByNative
-keep @interface org.jni_zero.CalledByNative
-keep @interface org.jni_zero.CalledByNativeUnchecked
# ** prefixed since JNI Zero classes included in cronet are jarjared to prevent
# clashes with the real JNI Zero. See https://crbug.com/353534209
-keep @interface **org.jni_zero.AccessedByNative
-keep @interface **org.jni_zero.CalledByNative
-keep @interface **org.jni_zero.CalledByNativeUnchecked
# Suppress unnecessary warnings.
-dontnote org.chromium.net.ProxyChangeListener$ProxyReceiver
@ -169,7 +171,9 @@
# See crbug.com/1440987. We must keep every native that we are manually
# registering. If Cronet bumps its min-sdk past 21, we may be able to move to
# automatic JNI registration.
-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class org.chromium.**,J.N {
# ** prefixed since JNI Zero classes included in cronet are jarjared to prevent
# clashes with the real JNI Zero. See https://crbug.com/353534209
-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class org.chromium.**,**J.N {
native <methods>;
}

@ -11,9 +11,11 @@
-keep @interface org.chromium.build.annotations.DoNotInline
-keep @interface org.chromium.build.annotations.UsedByReflection
-keep @interface org.chromium.build.annotations.IdentifierNameString
-keep @interface org.jni_zero.AccessedByNative
-keep @interface org.jni_zero.CalledByNative
-keep @interface org.jni_zero.CalledByNativeUnchecked
# ** prefixed since JNI Zero classes included in cronet are jarjared to prevent
# clashes with the real JNI Zero. See https://crbug.com/353534209
-keep @interface **org.jni_zero.AccessedByNative
-keep @interface **org.jni_zero.CalledByNative
-keep @interface **org.jni_zero.CalledByNativeUnchecked
# Suppress unnecessary warnings.
-dontnote org.chromium.net.ProxyChangeListener$ProxyReceiver
@ -47,7 +49,9 @@
# See crbug.com/1440987. We must keep every native that we are manually
# registering. If Cronet bumps its min-sdk past 21, we may be able to move to
# automatic JNI registration.
-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class org.chromium.**,J.N {
# ** prefixed since JNI Zero classes included in cronet are jarjared to prevent
# clashes with the real JNI Zero. See https://crbug.com/353534209
-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class org.chromium.**,**J.N {
native <methods>;
}

@ -0,0 +1,77 @@
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/rules.gni")
import("//testing/test.gni")
template("cronet_renamed_jar") {
dist_jar(target_name) {
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
supports_android = true
requires_android = true
if (!defined(invoker.output)) {
output = "$target_out_dir/$target_name.distjar.jar"
}
jar_excluded_patterns = [
"*/*GEN_JNI.class",
"META-INF/*",
]
renaming_rules = [ "org.jni_zero.**->internal.org.jni_zero" ]
}
}
# Drop in replacement for the instrumentation_test_apk() template that renames
# java classes in the same way the classes shipped in cronet jars are renamed.
template("cronet_instrumentation_test_apk") {
_single_jar_target_name = "${target_name}_single_jar_java"
cronet_renamed_jar(_single_jar_target_name) {
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
# Implicit java deps that are added by the test template that might need to
# be renamed.
deps =
invoker.deps + [ "//testing/android/instrumentation:test_runner_java" ]
testonly = true
}
instrumentation_test_apk(target_name) {
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
deps += [ ":$_single_jar_target_name" ]
}
}
# Drop in replacement for the test() template that renames java classes in the
# same way the classes shipped in cronet jars are renamed.
template("cronet_test") {
_single_jar_target_name = "${target_name}_single_jar_java"
cronet_renamed_jar(_single_jar_target_name) {
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
# Implicit java deps that are added by the test template that might need to
# be renamed.
deps = invoker.deps + [
"//base/test:test_support_java",
"//testing/android/native_test:native_test_java",
]
testonly = true
}
test(target_name) {
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
deps += [ ":$_single_jar_target_name" ]
}
}
# cronet_test defaults.
# Should be kept in sync with set_defaults("test") in //testing/test.gni
set_defaults("cronet_test") {
configs = default_shared_library_configs
configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
configs += [ "//build/config/android:hide_all_but_jni" ]
}

@ -20,6 +20,7 @@ import("//url/features.gni")
if (is_android) {
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
import("//components/cronet/android/cronet_test_templates.gni")
import("//third_party/jni_zero/jni_zero.gni")
} else if (is_mac) {
import("//build/config/mac/mac_sdk.gni")
@ -2555,7 +2556,13 @@ if (!is_cronet_build) {
}
}
test("net_unittests") {
if (is_cronet_build) {
_test_target_type = "cronet_test"
} else {
_test_target_type = "test"
}
target(_test_target_type, "net_unittests") {
sources = [
"base/address_family_unittest.cc",
"base/address_list_unittest.cc",

@ -1259,9 +1259,14 @@ template("isolated_script_test") {
# Test defaults.
set_defaults("test") {
if (is_android) {
# Should be kept in sync with set_defaults("cronet_test") in
# //components/cronet/android/cronet_test_templates.gni
# LINT.IfChange
configs = default_shared_library_configs
configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
configs += [ "//build/config/android:hide_all_but_jni" ]
# LINT.ThenChange(/components/cronet/android/cronet_test_templates.gni)
} else {
configs = default_executable_configs
}

@ -39,6 +39,15 @@ if (target_os == "android") {
jni_headers_dir = "$root_gen_dir/jni_headers"
}
_cronet_renaming_extra_args = [
# keep in sync with //components/cronet/android/BUILD.gn renaming_rules
"--package-prefix",
"internal",
"--package-prefix-filter",
"org.jni_zero",
]
_jni_zero_dir = "//third_party/jni_zero"
template("jni_sources_list") {
@ -261,6 +270,11 @@ template("generate_jni_registration") {
if (defined(invoker.module_name)) {
args += [ "--module-name=${invoker.module_name}" ]
}
# Cronet needs to rename jni_zero classes
if (defined(is_cronet_build) && is_cronet_build) {
args += _cronet_renaming_extra_args
}
}
}
@ -368,6 +382,11 @@ template("generate_jni_impl") {
if (defined(invoker.namespace)) {
args += [ "--namespace=${invoker.namespace}" ]
}
# Cronet needs to rename jni_zero classes
if (defined(is_cronet_build) && is_cronet_build) {
args += _cronet_renaming_extra_args
}
} else {
if (is_robolectric) {
not_needed(invoker, [ "jar_file" ])