blob: 69a7cfe5fe1003617840d18a0c99373234b82818 [file]
# Copyright 2018 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.
"""Creates the apk(s)."""
load("//rules:utils.bzl", "get_android_sdk")
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
load(":utils.bzl", "utils")
visibility(PROJECT_VISIBILITY)
def _compile_android_manifest(ctx, manifest, resources_zip, out_manifest):
"""Compile AndroidManifest.xml."""
android_jar = get_android_sdk(ctx).android_jar
args = ctx.actions.args()
args.use_param_file(param_file_arg = "-flagfile=%s", use_always = True)
args.set_param_file_format("multiline")
args.add("-aapt2", ctx.executable._aapt2)
args.add("-manifest", manifest)
args.add("-out", out_manifest)
args.add("-sdk_jar", android_jar)
args.add("-res", resources_zip)
args.add("-force_debuggable=true")
ctx.actions.run(
executable = ctx.executable._android_kit,
arguments = ["manifest", args],
tools = [ctx.executable._aapt2],
inputs = [manifest, resources_zip, android_jar],
outputs = [out_manifest],
mnemonic = "CompileAndroidManifest",
progress_message = "MI Compiling AndroidManifest.xml from " + manifest.path,
)
def _patch_split_manifests(ctx, orig_manifest, split_manifests, out_manifest_package_name):
args = ctx.actions.args()
args.add("-in", orig_manifest)
args.add("-split", ",".join(["%s:%s" % (k, v.path) for k, v in split_manifests.items()]))
# prefer setting hasCode to always false. Otherwise dex2oat runs on installation
args.add("-attr", "application:hasCode:false")
args.add("-pkg", out_manifest_package_name)
ctx.actions.run(
executable = ctx.executable._android_kit,
arguments = ["patch", args],
inputs = [orig_manifest],
outputs = [out_manifest_package_name] + split_manifests.values(),
mnemonic = "PatchAndroidManifest",
progress_message = "MI Patch split manifests",
)
def _make_split_apk(ctx, dirs, artifacts, debug_signing_keys, debug_signing_lineage_file, key_rotation_min_sdk, out):
unsigned = utils.isolated_declare_file(ctx, out.basename + "_unsigned", sibling = out)
args = ctx.actions.args()
args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True)
args.set_param_file_format("multiline")
args.add("-out", unsigned)
args.add_joined("-in", artifacts, join_with = ",")
inputs = artifacts
dir_paths = {}
for d in dirs:
inputs.append(d)
dir_paths[d.dirname] = True
args.add_joined("-dir", dir_paths.keys(), join_with = ",")
ctx.actions.run(
executable = ctx.executable._android_kit,
arguments = ["repack", args],
inputs = inputs,
outputs = [unsigned],
mnemonic = "MakeSplitApk",
progress_message = "MI Making split app %s" % out.path,
)
_zipalign_sign(ctx, unsigned, out, debug_signing_keys, debug_signing_lineage_file, key_rotation_min_sdk)
def make_split_apks(
ctx,
manifest,
r_dex,
dexes,
resource_apk,
jar_resources,
native_zips,
swigdeps_file,
debug_signing_keys,
debug_signing_lineage_file,
key_rotation_min_sdk,
sibling):
"""Create a split for each dex and for resources"""
manifest_package_name = utils.isolated_declare_file(ctx, "manifest_package_name.txt", sibling = sibling)
manifests = {}
artifacts = {}
dirs = {}
splits = []
to_pack = dexes + [r_dex]
if native_zips:
to_pack.extend(native_zips)
for i, artifact in enumerate(to_pack):
# the split attr in the manifest will be used to name the file on the device like
# split_${SPLIT_ID}.apk. We need to follow the same pattern so that we can compare
# files during sync time and only do the incremental install.
# The split names need to be valid java package names
name = "mi_" + artifact.basename.split(".")[0].replace("-", "_")
manifests[name] = utils.isolated_declare_file(
ctx,
"split_manifests/AndroidManifest_%s.xml" % name,
sibling = sibling,
)
artifacts[name] = [artifact]
# If we have a swigdeps file push it in the jar resources zip to avoid creating an extra one.
if jar_resources or swigdeps_file:
name = "jresources"
manifests[name] = utils.isolated_declare_file(
ctx,
"split_manifests/AndroidManifest_%s.xml" % name,
sibling = sibling,
)
if jar_resources:
artifacts[name] = jar_resources
if swigdeps_file:
dirs[name] = [swigdeps_file]
_patch_split_manifests(ctx, manifest, manifests, manifest_package_name)
for k, v in manifests.items():
compiled = utils.isolated_declare_file(
ctx,
"split_manifests/%s/AndroidManifest.xml" % k,
sibling = sibling,
)
_compile_android_manifest(ctx, v, resource_apk, compiled)
split = utils.isolated_declare_file(
ctx,
"splits/split_%s.apk" % k,
sibling = sibling,
)
_make_split_apk(
ctx,
[compiled] + dirs.get(k, []),
artifacts.get(k, []),
debug_signing_keys,
debug_signing_lineage_file,
key_rotation_min_sdk,
split,
)
splits.append(split)
# make the base split
compiled = utils.isolated_declare_file(ctx, "split_manifests/base/AndroidManifest.xml", sibling = sibling)
_compile_android_manifest(ctx, manifest, resource_apk, compiled)
# base needs to have code and declare it. Otherwise classpath will be empty :(
# Use legacy dex here
java8_legacy = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/dex_java8_legacy/java8_legacy.zip")
ctx.actions.run_shell(
command = "cp $1 $2",
arguments = [
ctx.file._mi_java8_legacy_dex.path,
java8_legacy.path,
],
inputs = [ctx.file._mi_java8_legacy_dex],
outputs = [java8_legacy],
mnemonic = "CopyJava8Legacy",
progress_message = "MI Copy %s to %s" % (ctx.file._mi_java8_legacy_dex.path, java8_legacy.path),
)
# Resources are now in the base apk to support RRO. Previously they were a separate split, but
# base reinstalls no longer require a full reinstall.
base = utils.isolated_declare_file(ctx, "splits/base.apk", sibling = sibling)
_make_split_apk(ctx, [compiled], [resource_apk, java8_legacy], debug_signing_keys, debug_signing_lineage_file, key_rotation_min_sdk, base)
splits.append(base)
return manifest_package_name, splits
def _zipalign_sign(ctx, unsigned_apk, signed_apk, debug_signing_keys, debug_signing_lineage_file, key_rotation_min_sdk):
"""Zipalign and signs the given apk."""
signing_params = ((("--lineage %s " % debug_signing_lineage_file.path) if debug_signing_lineage_file else "") +
(("--rotation-min-sdk-version %s " % key_rotation_min_sdk) if key_rotation_min_sdk else "") +
" --next-signer ".join([
"--ks %s --ks-pass pass:android" % debug_signing_key.path
for debug_signing_key in debug_signing_keys
]) +
" --v1-signing-enabled true" +
" --v1-signer-name CERT" +
" --v2-signing-enabled true" +
" --v3-signing-enabled true" +
" --deterministic-dsa-signing true" +
" --provider-class org.bouncycastle.jce.provider.BouncyCastleProvider")
# note zipalign usage:
# https://cs.android.com/android/platform/superproject/main/+/main:build/make/tools/zipalign/ZipAlignMain.cpp
cmd = """
zipalign=$1
unsigned_apk=$2
jvm=$3
apk_signer=$4
signing_params=$5
signed_apk=$6
tmp_dir=$(mktemp -d)
tmp_apk="${tmp_dir}/zipaligned.apk"
${zipalign} -P 16 4 ${unsigned_apk} ${tmp_apk}
${jvm} -jar ${apk_signer} sign ${signing_params} --out ${signed_apk} ${tmp_apk}
"""
ctx.actions.run_shell(
command = cmd,
arguments = [
ctx.executable._zipalign.path,
unsigned_apk.path,
utils.host_jvm_path(ctx),
utils.first(ctx.attr._apk_signer[DefaultInfo].files.to_list()).path,
signing_params,
signed_apk.path,
],
tools = [ctx.executable._zipalign],
inputs = (debug_signing_keys +
([debug_signing_lineage_file] if debug_signing_lineage_file else []) +
[unsigned_apk] +
ctx.attr._apk_signer[DefaultInfo].files.to_list() +
ctx.attr._java_jdk[DefaultInfo].files.to_list()),
outputs = [signed_apk],
mnemonic = "SignShellApp",
progress_message = "MI Signing shell app %s" % unsigned_apk.path,
)