Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,3 @@ common:windows --host_per_file_copt=external/.*@/w
build:windows --define=protobuf_allow_msvc=true

common --enable_platform_specific_config

2 changes: 2 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package(
default_visibility = ["//visibility:public"],
)

exports_files(["MODULE.bazel"])

license(
name = "license",
package_name = "bazelbuild/rules_android",
Expand Down
1 change: 1 addition & 0 deletions rules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ bzl_library(
visibility = [
"//mobile_install:__pkg__",
"//stardoc:__pkg__",
"//test/rules/android_binary/r8_integration:__pkg__",
"//test/rules/android_sdk_repository:__pkg__",
],
deps = [
Expand Down
32 changes: 29 additions & 3 deletions rules/android_binary/r8.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ load("//providers:providers.bzl", "AndroidDexInfo", "AndroidPreDexJarInfo")
load("//rules:acls.bzl", "acls")
load("//rules:android_neverlink_aspect.bzl", "StarlarkAndroidNeverlinkInfo")
load("//rules:common.bzl", "common")
load("//rules:dex.bzl", _dex = "dex")
load("//rules:java.bzl", "java")
load("//rules:min_sdk_version.bzl", "min_sdk_version")
load(
Expand Down Expand Up @@ -80,6 +81,7 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_

android_jar = get_android_sdk(ctx).android_jar
proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_config)
desugared_lib_config = ctx.file._desugared_lib_config

# Get min SDK version from attribute, manifest_values, or depot floor
effective_min_sdk = min_sdk_version.DEPOT_FLOOR
Expand Down Expand Up @@ -107,21 +109,45 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_
args.add(deploy_jar) # jar to optimize + desugar + dex
args.add("--pg-map-output", proguard_mappings_output_file)

r8_inputs = [android_jar, deploy_jar] + proguard_specs
if ctx.fragments.android.desugar_java8_libs and desugared_lib_config:
args.add("--desugared-lib", desugared_lib_config)
r8_inputs.append(desugared_lib_config)

java.run(
ctx = ctx,
host_javabase = common.get_host_javabase(ctx),
executable = get_android_toolchain(ctx).r8.files_to_run,
arguments = [args],
inputs = depset([android_jar, deploy_jar] + proguard_specs, transitive = [neverlink_jars]),
inputs = depset(r8_inputs, transitive = [neverlink_jars]),
outputs = [dexes_zip, proguard_mappings_output_file],
mnemonic = "AndroidR8",
jvm_flags = ["-Xmx8G"],
progress_message = "R8 Optimizing, Desugaring, and Dexing %{label}",
)

# When R8 runs with --desugared-lib, it rewrites java.* API calls to j$.*
# backport references, but does NOT include the j$.* implementation classes
# in its output. Append the prebuilt desugared library DEX so the j$.*
# classes are available at runtime.
if ctx.fragments.android.desugar_java8_libs and desugared_lib_config:
final_classes_dex_zip = ctx.actions.declare_file(ctx.label.name + "_final_dexes.zip")
java8_legacy_dex = utils.only(
get_android_toolchain(ctx).java8_legacy_dex.files.to_list(),
)
_dex.append_desugar_dexes(
ctx,
output = final_classes_dex_zip,
input = dexes_zip,
dexes = [java8_legacy_dex],
dex_zips_merger = get_android_toolchain(ctx).dex_zips_merger.files_to_run,
)
else:
final_classes_dex_zip = dexes_zip

android_dex_info = AndroidDexInfo(
deploy_jar = deploy_jar,
final_classes_dex_zip = dexes_zip,
final_classes_dex_zip = final_classes_dex_zip,
# R8 preserves the Java resources (i.e. non-Java-class files) in its output zip, so no need
# to provide a Java resources zip.
java_resource_jar = None,
Expand All @@ -130,7 +156,7 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_
return ProviderInfo(
name = "r8_ctx",
value = struct(
final_classes_dex_zip = dexes_zip,
final_classes_dex_zip = final_classes_dex_zip,
dex_info = android_dex_info,
providers = [
android_dex_info,
Expand Down
36 changes: 36 additions & 0 deletions test/rules/android_binary/r8_integration/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
load("@bazel_binaries//:defs.bzl", "bazel_binaries")
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load(
"@rules_bazel_integration_test//bazel_integration_test:defs.bzl",
"script_test",
)
load("@rules_python//python:py_test.bzl", "py_test")
load("@rules_shell//shell:sh_library.bzl", "sh_library")
load(":test.bzl", "r8_neverlink_deps_test")

py_test(
Expand All @@ -24,3 +30,33 @@ build_test(
name = "android_binary_with_neverlink_deps_build_test",
targets = ["//test/rules/android_binary/r8_integration/java/com/neverlink:android_binary_with_neverlink_deps"],
)

sh_library(
name = "r8_desugaring_helper",
testonly = True,
srcs = ["r8_desugaring_helper.sh"],
data = [
"//:MODULE.bazel",
"//rules:bzl",
],
visibility = ["//visibility:private"],
deps = [
"//test/bashunit",
"@rules_shell//shell/runfiles",
],
)

script_test(
name = "r8_desugaring_integration_test",
size = "enormous",
srcs = ["r8_desugaring_integration_test.sh"],
bazel_binaries = bazel_binaries,
bazel_version = bazel_binaries.versions.current,
tags = ["manual"],
timeout = "eternal",
deps = [
":r8_desugaring_helper",
"//test/bashunit",
"@rules_shell//shell/runfiles",
],
)
236 changes: 236 additions & 0 deletions test/rules/android_binary/r8_integration/r8_desugaring_helper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#!/bin/bash
#
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# 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.

# Helper functions for R8 desugaring integration tests.

# --- begin runfiles.bash initialization v2 ---
# Copy-pasted from the Bazel Bash runfiles library v2.
set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v2 ---

source "$(rlocation rules_android/test/bashunit/unittest.bash)" || \
(echo >&2 "Failed to locate bashunit.sh" && exit 1)

_WORKSPACE_INITIALIZED=false

# Resolve the real filesystem path of the rules_android source tree.
function get_rules_android_path() {
local module_bazel="$(rlocation rules_android/MODULE.bazel)"
if [[ -z "${module_bazel}" || ! -f "${module_bazel}" ]]; then
fail "Failed to locate rules_android MODULE.bazel"
fi
local real_path
real_path="$(python3 -c "import os; print(os.path.realpath('${module_bazel}'))")"
dirname "${real_path}"
}

# set_up is called before each test by bashunit. We only initialize the
# workspace once since the inner Bazel build is expensive. Subsequent tests
# reuse the same workspace and inner Bazel server.
function set_up() {
if [[ "${_WORKSPACE_INITIALIZED}" == "true" ]]; then
return
fi
_WORKSPACE_INITIALIZED=true

# Clean out the workspace.
rm -rf *

set_up_workspace
create_desugaring_app
}

function set_up_workspace() {
local rules_dir="$(get_rules_android_path)"

# Find the Android SDK path from environment.
local sdk_path="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "${sdk_path}" ]]; then
fail "ANDROID_HOME or ANDROID_SDK_ROOT must be set"
fi

cat > MODULE.bazel <<EOF
module(name = "desugaring_test")

bazel_dep(name = "rules_java", version = "9.3.0")
bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "rules_android", version = "0.7.1")

local_path_override(
module_name = "rules_android",
path = "${rules_dir}",
)

remote_android_extensions = use_extension(
"@rules_android//bzlmod_extensions:android_extensions.bzl",
"remote_android_tools_extensions")
use_repo(remote_android_extensions, "android_tools")

android_sdk_repository_extension = use_extension(
"@rules_android//rules/android_sdk_repository:rule.bzl",
"android_sdk_repository_extension")
android_sdk_repository_extension.configure(path = "${sdk_path}")
use_repo(android_sdk_repository_extension, "androidsdk")

register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all")
EOF

cat > .bazelrc <<EOF
common --action_env=ANDROID_HOME=${sdk_path}
common --noenable_workspace
common --enable_bzlmod
common --java_language_version=17
common --java_runtime_version=17
common --tool_java_language_version=17
common --tool_java_runtime_version=17
common --enable_platform_specific_config
common:linux --cxxopt=-std=c++17
common:linux --host_cxxopt=-std=c++17
common:macos --cxxopt=-std=c++17
common:macos --host_cxxopt=-std=c++17
common:windows --cxxopt=/std:c++17
common:windows --host_cxxopt=/std:c++17
common:linux --per_file_copt=external/.*@-w
common:linux --host_per_file_copt=external/.*@-w
common:macos --per_file_copt=external/.*@-w
common:macos --host_per_file_copt=external/.*@-w
common:windows --per_file_copt=external/.*@/w
common:windows --host_per_file_copt=external/.*@/w
common --repositories_without_autoloads=bazel_features_version,bazel_features_globals,cc_compatibility_proxy
EOF
}

function create_desugaring_app() {
mkdir -p app/res/layout app/res/values

cat > app/BUILD <<'EOF'
load("@rules_android//rules:rules.bzl", "android_binary")

android_binary(
name = "desugaring_app",
srcs = [
"DesugaringActivity.java",
"DurationUser.java",
],
manifest = "AndroidManifest.xml",
proguard_specs = ["proguard.cfg"],
resource_files = glob(["res/**"]),
)
EOF

cat > app/AndroidManifest.xml <<'EOF'
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.desugaring.test">
<application>
<activity android:name=".DesugaringActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
EOF

cat > app/DesugaringActivity.java <<'EOF'
package com.desugaring.test;

import android.app.Activity;
import android.os.Bundle;
import java.time.Duration;

public class DesugaringActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long seconds = DurationUser.getSeconds(Duration.ofMinutes(5));
setTitle("Seconds: " + seconds);
}
}
EOF

cat > app/DurationUser.java <<'EOF'
package com.desugaring.test;

import java.time.Duration;

public class DurationUser {
public static long getSeconds(Duration duration) {
return duration.toSeconds();
}
}
EOF

cat > app/proguard.cfg <<'EOF'
-dontobfuscate
-keep class com.desugaring.test.DurationUser { *; }
-keep class com.desugaring.test.DesugaringActivity { *; }
EOF

cat > app/res/layout/activity_main.xml <<'EOF'
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
EOF

cat > app/res/values/strings.xml <<'EOF'
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">DesugarTest</string>
</resources>
EOF
}

# Build the desugaring app with extra Bazel flags.
# Usage: build_desugaring_app [--desugar_java8_libs | --nodesugar_java8_libs]
function build_desugaring_app() {
"${BIT_BAZEL_BINARY}" build "$@" -- //app:desugaring_app >& $TEST_log || \
fail "Failed to build desugaring app"
}

# Returns 0 if any dex in the APK contains a string matching the given pattern.
# Usage: apk_dex_contains <grep_pattern>
function apk_dex_contains() {
local pattern="$1"
local apk_path="bazel-bin/app/desugaring_app.apk"
if [[ ! -f "${apk_path}" ]]; then
echo "APK not found at ${apk_path}" >&2
return 1
fi

local tmpdir=$(mktemp -d)
unzip -o "${apk_path}" '*.dex' -d "${tmpdir}" > /dev/null 2>&1

local found=false
for dex in "${tmpdir}"/classes*.dex; do
if [[ -f "${dex}" ]] && strings "${dex}" | grep -q "${pattern}"; then
found=true
break
fi
done

rm -rf "${tmpdir}"
[[ "${found}" == "true" ]]
}
Loading