| # 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. |
| """Utilities for by the Mobile-Install aspect.""" |
| |
| load("//rules:min_sdk_version.bzl", _min_sdk_version = "min_sdk_version") |
| load("//rules:visibility.bzl", "PROJECT_VISIBILITY") |
| load("//rules/flags:flags.bzl", "flags") |
| load("@rules_java//java/common:java_common.bzl", "java_common") |
| # Copybara: placeholder for GeneratedExtensionRegistryInfo load |
| load("//tools/jdk:jvmopts.bzl", "BASE_JVMOPTS") |
| load(":constants.bzl", "constants") |
| |
| visibility(PROJECT_VISIBILITY) |
| |
| _PACKAGE_NAME_EXTRACTION_SCRIPT = """ |
| #!/bin/bash |
| set -e # exit on failure |
| umask 022 # set default file/dir creation mode to 755 |
| |
| base=$(pwd) |
| $base/{aapt_tool} dump badging {apk_path} | awk -v FS="'" '/package: name=/{{print $2}}' > {output_file} |
| """ |
| |
| def create_flag_file(ctx, flag_file, **flags): |
| """Creates flag file artifact from keyword args (named flags). |
| |
| The flags variable is a map where the key/value pairs are used as flag/value |
| pairs. Each flag/value pair is outputted onto its own line. List and set |
| values will have their contents joined into comma separated string. |
| |
| Example: |
| |
| flags = {"a": 1, "b": [2, 3, 4]} |
| |
| is converted to a flag file with the following contents: |
| |
| --a=1 |
| --b=2,3,4 |
| |
| Args: |
| ctx: Context. |
| flag_file: File artifact to output contents. |
| **flags: Is the kwargs where the key/value pairs are treated as flag/value |
| pairs. Values can be primitives, list or sets. dicts are not supported. |
| """ |
| content = [] |
| for flag, value in flags.items(): |
| if not value: |
| continue |
| |
| # join set or list values into a comma separted string. |
| value_type = type(value) |
| if constants.TYPE_LIST == value_type: |
| value = ",".join(value) |
| elif constants.TYPE_DEPSET == value_type: |
| value = ",".join(value.to_list()) |
| elif constants.TYPE_DICT == value_type: |
| fail("Error, dict is an unsupported value type: " + str(value_type)) |
| content.append("--%s=%s\n" % (flag, value)) |
| ctx.actions.write(output = flag_file, content = "".join(content)) |
| |
| def isolated_declare_file(ctx, name, sibling = None): |
| """A helper method to ensure creating different outputs for each version of the aspect. |
| |
| Wraps ctx.actions.declare_file to add a "mi_test/" prefix to each output file if the |
| _mi_is_test attr is True. |
| |
| Args: |
| ctx: The context. |
| name: Name of the file. |
| sibling: Provides a location to create the new file. |
| |
| Returns: |
| A new file. |
| """ |
| |
| return ctx.actions.declare_file( |
| "mi_test/" + name if hasattr(ctx.attr, "_mi_is_test") and ctx.attr._mi_is_test else name, |
| sibling = sibling, |
| ) |
| |
| def declare_file(ctx, name, sibling = None): |
| """A helper method for creating files for the transforms uniquely and safely. |
| |
| Wraps ctx.actions.declare_file method, but provides additional checks around |
| the optional sibling parameter. Since transform functions run on a varied set |
| of inputs, it is highly probable that some proposed siblings may be invalid to |
| be used as such. Hence, this method provides a fallback mechanism keep files |
| unique when an invalid sibling is given. |
| |
| Args: |
| ctx: The context. |
| name: Name of the file. |
| sibling: Provides a location to create the new file. |
| |
| Returns: |
| A new file. |
| """ |
| |
| # The sibling file must be owned by the same label as the file that is being |
| # created (ctx.label). |
| # |
| # For example: |
| # |
| # //foo/bar:myapp - android_binary |
| # deps = [//foo/bar:quux_lite_proto] |
| # |
| # //com/common:quux_lite_proto - java_lite_proto_library |
| # deps = [//com/common:quuz_proto, //com/common:corge_proto] |
| # exports jar-> [quuz_lite.jar, corge_lite.jar] |
| # |
| # //com/common:quuz_proto - proto_library |
| # //com/common:corge_proto - proto_library |
| # |
| # Here, quux_lite_proto rule in its files provider, provides both the |
| # quuz_lite.jar and corge_lite.jar neither of which were produced by the rule |
| # itself, which then causes the sibling (jar) not to be valid, because the |
| # owner (or owners in this case) are the proto_library rules. |
| # |
| # When the sibling is invalid, we create the new file under the context of the |
| # current label.name and prepend the label of the proposed sibling's owners. |
| # This is done to guarantee the uniqueness of the new file graph-wide. |
| if sibling and sibling.owner and sibling.owner != ctx.label: |
| name = (ctx.label.name + "_mi/" + sibling.owner.package.replace("/", "_") + "/" + name) |
| sibling = None # Remove the invalid sibling. |
| return isolated_declare_file(ctx, name, sibling = sibling) |
| |
| def host_jvm_path(ctx): |
| """Returns the path to the host JVM. |
| |
| Args: |
| ctx: The context. |
| |
| Returns: |
| The execpath to the "java" binary. |
| """ |
| return str(ctx.attr._host_java_runtime[java_common.JavaRuntimeInfo].java_executable_exec_path) |
| |
| def dex(ctx, jar, out_dex_shards, deps = None): |
| """Desugar, dex and shard a Jar. |
| |
| Args: |
| ctx: The context. |
| jar: The Jar to Dex. |
| out_dex_shards: A list of files to output. When more than on file |
| is given, will shard the Jar to Dex across all given files in a |
| deterministic manner. |
| deps: The list of dependencies for the Jar being desugared. |
| """ |
| min_sdk = _min_sdk_version.get(ctx) |
| args = ctx.actions.args() |
| args.use_param_file(param_file_arg = "-flagfile=%s", use_always = True) |
| |
| args.add("-android_jar", first(ctx.files._android_sdk)) |
| if deps: |
| args.add_joined("-classpath", deps, join_with = ",") |
| args.add("-desugar_core_libs", "True") |
| |
| # Unconditionally add -desugared_lib_config. This matches the behavior of tools/android/d8_desugar.sh. |
| args.add("-desugared_lib_config", ctx.file._desugared_lib_config) |
| |
| args.add("-min_sdk_version", min_sdk) |
| args.add("-in", jar) |
| args.add_joined("-out", out_dex_shards, join_with = ",") |
| |
| java_runtime = ctx.attr._mi_host_javabase[java_common.JavaRuntimeInfo] |
| java = java_runtime.java_executable_exec_path |
| |
| # Performance-related JVM flags for the desugar tool. |
| jvm_flags = BASE_JVMOPTS + [ |
| # b/71513487 |
| "-Xms8g", |
| "-Xmx8g", |
| ] |
| |
| ctx.actions.run( |
| executable = java, |
| tools = [ctx.executable._desugar_dex_sharding], |
| arguments = jvm_flags + ["-jar", ctx.executable._desugar_dex_sharding.path, args], |
| inputs = depset( |
| ctx.files._android_sdk + ctx.files._mi_host_javabase + [jar, ctx.file._desugared_lib_config], |
| transitive = [deps] if deps else [], |
| ), |
| outputs = out_dex_shards, |
| mnemonic = "DesugarDexSharding", |
| progress_message = "MI Desugar, dex and sharding " + jar.path, |
| execution_requirements = { |
| "worker-key-mnemonic": "DesugarDexSharding", |
| "supports-workers": "1", |
| "supports-multiplex-workers": "1", |
| }, |
| toolchain = None, |
| ) |
| |
| def extract_jar_resources(ctx, jar, out_resources): |
| """Extracts the non-class files from the Jars. |
| |
| Args: |
| ctx: The context. |
| jar: The Jar to extract resources from. |
| out_resources: The file to output the resources from the Jar. |
| """ |
| |
| # TODO(djwhang): Make another action that strips the resources from the Jar. |
| # This makes the Jar itself a cacheable, even though resources changed. |
| # Filters .class and directories from Jar files |
| ctx.actions.run_shell( |
| command = ( |
| 'cp $2 $1; chmod 644 $1; zip -qd $1 "*.class" "*/";' + "err=$?; if" + |
| " [ 0 -ne $err ] && [ 12 -ne $err ]; then exit ${err}; fi" |
| ), |
| arguments = [out_resources.path, jar.path], |
| inputs = [jar], |
| outputs = [out_resources], |
| mnemonic = "ExtractJarResources", |
| progress_message = "MI Extracting resources from " + jar.path, |
| ) |
| |
| def first(collection, allow_empty = False): |
| """Returns the first item in the collection. |
| |
| Args: |
| collection: The container object to extract data from. |
| allow_empty: Whether to allow empty containers. |
| |
| Returns: |
| The first object in the collection. |
| """ |
| for i in collection: |
| return i |
| if not allow_empty: |
| fail("Error, the collection is empty.") |
| return None |
| |
| def only(collection, allow_empty = False): |
| """Returns the only item in the collection. |
| |
| Args: |
| collection: The container object to extract data from. |
| allow_empty: Whether to allow empty containers. |
| |
| Returns: |
| The _only_ object in the container (size must be 1 or 0 if allow_empty == True). |
| """ |
| if len(collection) > 1: |
| fail("Error, the collection has more than 1 item.") |
| return first(collection, allow_empty) |
| |
| def make_substitutions(**kwargs): |
| return {"%%%s%%" % key: val for key, val in kwargs.items()} |
| |
| def merge_dex_shards( |
| ctx, |
| dex_archives, |
| out_dex_zip): |
| """Merge dex archive zips into a single archive with dex files merged up to dex file limits. |
| |
| Args: |
| ctx: The context. |
| dex_archives: A list or depset of Dex zip archives. |
| out_dex_zip: The file to output. |
| """ |
| args = ctx.actions.args() |
| |
| |
| args.add("--multidex", "best_effort") |
| args.add("--output", out_dex_zip.path) |
| args.add_all(dex_archives, before_each = "--input") |
| args.use_param_file(param_file_arg = "@%s", use_always = True) |
| args.set_param_file_format("multiline") |
| |
| ctx.actions.run( |
| executable = ctx.executable._dexmerger, |
| arguments = [args], |
| tools = [], |
| inputs = dex_archives, |
| outputs = [out_dex_zip], |
| mnemonic = "DexMerge", |
| progress_message = "MI Merging dexes", |
| toolchain = None, |
| ) |
| |
| def strip_r(ctx, jar, out_jar): |
| """Strips the R from the Jar. |
| |
| Args: |
| ctx: The context. |
| jar: The Jar to strip. |
| out_jar: The file to output the stripped 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("-filter_r") |
| args.add("-in", jar) |
| args.add("-out", out_jar) |
| |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = ["repack", args], |
| inputs = [jar], |
| outputs = [out_jar], |
| mnemonic = "StripR", |
| progress_message = "MI Stripping R from " + jar.path, |
| toolchain = None, |
| ) |
| |
| def _extract_package_name(ctx, apk, package_name_output_file): |
| """Extracts the package name from an apk using aapt and writes into output file |
| |
| Args: |
| ctx: The context. |
| apk: The apk. |
| package_name_output_file: The file to output the package_name. |
| """ |
| |
| cmd = _PACKAGE_NAME_EXTRACTION_SCRIPT.format( |
| apk_path = apk.path, |
| aapt_tool = ctx.executable._aapt2.path, |
| output_file = package_name_output_file.path, |
| ) |
| |
| ctx.actions.run_shell( |
| command = cmd, |
| tools = [ctx.executable._aapt2], |
| inputs = [apk], |
| outputs = [package_name_output_file], |
| mnemonic = "ExtractPackageName", |
| progress_message = "MI Extracts the package name from %s" % apk.path, |
| ) |
| |
| def _get_extension_registry_class_jar(target): |
| class_jar = None |
| |
| return class_jar |
| |
| utils = struct( |
| create_flag_file = create_flag_file, |
| declare_file = declare_file, |
| isolated_declare_file = isolated_declare_file, |
| dex = dex, |
| extract_jar_resources = extract_jar_resources, |
| extract_package_name = _extract_package_name, |
| first = first, |
| get_extension_registry_class_jar = _get_extension_registry_class_jar, |
| host_jvm_path = host_jvm_path, |
| make_substitutions = make_substitutions, |
| merge_dex_shards = merge_dex_shards, |
| only = only, |
| strip_r = strip_r, |
| ) |