# 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. """Bazel Android Resources.""" load(":attrs.bzl", _attrs = "attrs") load(":busybox.bzl", _busybox = "busybox") load(":common.bzl", _common = "common") load(":java.bzl", _java = "java") load(":path.bzl", _path = "path") load( ":providers.bzl", "ResourcesNodeInfo", "StarlarkAndroidResourcesInfo", ) load( ":utils.bzl", "utils", _compilation_mode = "compilation_mode", _log = "log", ) _RESOURCE_FOLDER_TYPES = [ "anim", "animator", "color", "drawable", "font", "interpolator", "layout", "menu", "mipmap", "navigation", "values", "xml", "raw", "transition", ] _RESOURCE_QUALIFIER_SEP = "-" _MANIFEST_MISSING_ERROR = ( "In target %s, manifest attribute is required when resource_files or " + "assets are defined." ) _ASSET_DEFINITION_ERROR = ( "In target %s, the assets and assets_dir attributes should be either " + "both empty or non-empty." ) _JAVA_PACKAGE_MISSING_ERROR = ( "In target %s, a java package is required when stamping " + "the manifest." ) _INCORRECT_RESOURCE_LAYOUT_ERROR = ( "'%s' is not in the expected resource directory structure of " + "/{%s}/" % (",").join(_RESOURCE_FOLDER_TYPES) ) # Keys for manifest_values _VERSION_NAME = "versionName" _VERSION_CODE = "versionCode" # Resources context attributes. _ASSETS_PROVIDER = "assets_provider" _DEFINES_RESOURCES = "defines_resources" _DIRECT_ANDROID_RESOURCES = "direct_android_resources" _MERGED_MANIFEST = "merged_manifest" _PROVIDERS = "providers" _R_JAVA = "r_java" _RESOURCES_APK = "resources_apk" _VALIDATION_RESULTS = "validation_results" _VALIDATION_OUTPUTS = "validation_outputs" _RESOURCES_PROVIDER = "resources_provider" _STARLARK_PROCESSED_MANIFEST = "starlark_processed_manifest" _STARLARK_R_TXT = "starlark_r_txt" _STARLARK_PROCESSED_RESOURCES = "starlark_processed_resources" _ResourcesProcessContextInfo = provider( "Resources context object", fields = { _DEFINES_RESOURCES: "If local resources were defined.", _DIRECT_ANDROID_RESOURCES: "Direct android resources.", _MERGED_MANIFEST: "Merged manifest.", _PROVIDERS: "The list of all providers to propagate.", _R_JAVA: "JavaInfo for R.jar.", _RESOURCES_APK: "ResourcesApk.", _VALIDATION_RESULTS: "List of validation results.", _VALIDATION_OUTPUTS: "List of outputs given to OutputGroupInfo _validation group", # TODO(djwhang): The android_library aar generation requires direct # access to providers. Remove once aar is its own rule. _ASSETS_PROVIDER: "AndroidAssetsInfo provider.", _RESOURCES_PROVIDER: "AndroidResourcesInfo provider.", _STARLARK_PROCESSED_MANIFEST: "The processed manifest from the starlark resource processing pipeline.", _STARLARK_R_TXT: "The R.txt from the starlark resource processing pipeline.", _STARLARK_PROCESSED_RESOURCES: "The processed resources from the starlark processing pipeline.", }, ) # Packaged resources context attributes. _PACKAGED_FINAL_MANIFEST = "processed_manifest" _PACKAGED_RESOURCE_APK = "resources_apk" _PACKAGED_CLASS_JAR = "class_jar" _PACKAGED_VALIDATION_RESULT = "validation_result" _ResourcesPackageContextInfo = provider( "Packaged resources context object", fields = { _PACKAGED_FINAL_MANIFEST: "Final processed manifest.", _PACKAGED_RESOURCE_APK: "ResourceApk.", _PACKAGED_CLASS_JAR: "R class jar.", _PACKAGED_VALIDATION_RESULT: "Validation result.", _R_JAVA: "JavaInfo for R.jar", _PROVIDERS: "The list of all providers to propagate.", }, ) def _generate_dummy_manifest( ctx, out_manifest = None, java_package = None, min_sdk_version = None): content = """ """ % java_package if min_sdk_version: content = content + """ """ % min_sdk_version content = content + """ """ ctx.actions.write( output = out_manifest, content = content, ) def _add_g3itr( ctx, manifest = None, out_manifest = None, xsltproc = None, instrument_xslt = None): """Adds Google3InstrumentationTestRunner instrumentation element to the manifest. Element is only added if the manifest contains an instrumentation element with name "android.test.InstrumentationTestRunner". The added element's name attr is "com.google.android.apps.common.testing.testrunner.Google3InstrumentationTestRunner". Args: ctx: The context. manifest: File. The AndroidManifest.xml file. out_manifest: File. The transformed AndroidManifest.xml. xsltproc: FilesToRunProvider. The xsltproc executable or FilesToRunProvider. instrument_xslt: File. The add_g3itr.xslt file describing the xslt transformation to apply. """ args = ctx.actions.args() args.add("--nonet") args.add("--novalid") args.add("-o", out_manifest) args.add(instrument_xslt) args.add(manifest) ctx.actions.run( executable = xsltproc, arguments = [args], inputs = [manifest, instrument_xslt], outputs = [out_manifest], mnemonic = "AddG3ITRStarlark", progress_message = "Adding G3ITR to test manifest for %s" % ctx.label, ) def _get_legacy_mergee_manifests(resources_infos): all_dependencies = depset( transitive = [ ri.direct_android_resources for ri in resources_infos ] + [ ri.transitive_android_resources for ri in resources_infos ], ) mergee_manifests = [] for dep in all_dependencies.to_list(): if dep.to_provider.manifest.exports_manifest: mergee_manifests.append(dep.to_provider.manifest.manifest) return depset(mergee_manifests) def _legacy_mergee_manifest(manifest): sort_key = manifest.short_path + "#" return sort_key + "--mergee=" + manifest.path def _legacy_merge_manifests( ctx, out_merged_manifest = None, manifest = None, mergee_manifests = None, legacy_merger = None): """Merges manifests with the legacy manifest merger." This should not be called with empty mergee_manifests. Args: ctx: The context. out_merged_manifest: File. The merged AndroidManifest.xml. manifest: File. The AndroidManifest.xml. mergee_manifests: A sequence of Files. All transitive manifests to be merged. legacy_merger: A FilesToRunProvider. The legacy manifest merger executable. """ args = ctx.actions.args() args.use_param_file("%s", use_always = True) args.set_param_file_format("multiline") args.add("--merger=%s" % manifest.path) args.add("--exclude_permission=all") args.add("--output=%s" % out_merged_manifest.path) manifest_params = ctx.actions.declare_file(ctx.label.name + "/legacy_merger.params") manifest_args = ctx.actions.args() manifest_args.use_param_file("%s", use_always = True) manifest_args.set_param_file_format("multiline") manifest_args.add_joined(mergee_manifests, map_each = _legacy_mergee_manifest, join_with = "\n") ctx.actions.run_shell( command = """ # Sorts the mergee manifests by path and combines with other busybox args. set -e SORTED=`sort $1 | sed 's/^.*#//'` cat $2 > $3 echo "$SORTED" >> $3 """, arguments = [manifest_args, args, manifest_params.path], outputs = [manifest_params], ) args = ctx.actions.args() args.add(manifest_params, format = "--flagfile=%s") ctx.actions.run( executable = legacy_merger, arguments = [args], inputs = depset([manifest, manifest_params], transitive = [mergee_manifests]), outputs = [out_merged_manifest], mnemonic = "StarlarkLegacyAndroidManifestMerger", progress_message = "Merging Android Manifests", ) def _make_databinding_outputs( ctx, resource_files): """Helper method to create arguments for the process_databinding busybox tool. Declares databinding-processed resource files that are generated by the PROCESS_DATABINDING busybox tool, which must be declared underneath an output resources directory and namespaced by their paths. The busybox takes the output directory exec path and generates the underlying resource files. Args: ctx: The context. resource_files: List of Files. The android resource files to be processed by _process_databinding. Returns: A tuple containing the list of declared databinding processed resource files and the output resource directory path expected by the busybox. The path is a full path. """ # TODO(b/160907203): Clean up databinding_rel_path. We capitalize "Databinding" here to avoid # conflicting with native artifact file names. This is changed back to "databinding" during # process_starlark so that compiled resources exactly match those of the native resource # processing pipeline. Even a single character mismatch in the file names causes selected # resources to differ in the final APK. databinding_rel_path = _path.join(["Databinding-processed-resources", ctx.label.name]) databinding_processed_resources = [ ctx.actions.declare_file(_path.join([databinding_rel_path, f.path])) for f in resource_files ] databinding_resources_dirname = _path.join([ ctx.bin_dir.path, ctx.label.package, databinding_rel_path, ]) return databinding_processed_resources, databinding_resources_dirname def _fix_databinding_compiled_resources( ctx, out_compiled_resources = None, compiled_resources = None, zip_tool = None): """Fix compiled resources to match those produced by the native pipeline. Changes "Databinding" to "databinding" in each compiled resource .flat file name and header. Args: ctx: The context. out_compiled_resources: File. The modified compiled_resources output. compiled_resources: File. The compiled_resources zip. """ ctx.actions.run_shell( outputs = [out_compiled_resources], inputs = [compiled_resources], tools = [zip_tool], arguments = [compiled_resources.path, out_compiled_resources.path, zip_tool.executable.path], command = """#!/bin/bash set -e IN_DIR=$(mktemp -d) OUT_DIR=$(mktemp -d) CUR_PWD=$(pwd) if zipinfo -t "$1"; then ORDERED_LIST=`(unzip -l "$1" | sed -e '1,3d' | head -n -2 | tr -s " " | cut -d " " -f5)` unzip -q "$1" -d "$IN_DIR" # Iterate through the ordered list, change "Databinding" to "databinding" in the file header # and file name and zip the files with the right comment for FILE in $ORDERED_LIST; do cd "$IN_DIR" if [ -f "$FILE" ]; then sed -i 's/Databinding\\-processed\\-resources/databinding\\-processed\\-resources/g' "$FILE" NEW_NAME=`echo "$FILE" | sed 's/Databinding\\-processed\\-resources/databinding\\-processed\\-resources/g' | sed 's#'"$IN_DIR"'/##g'` mkdir -p `dirname "$OUT_DIR/$NEW_NAME"` && touch "$OUT_DIR/$NEW_NAME" cp -p "$FILE" "$OUT_DIR/$NEW_NAME" PATH_SEGMENTS=(`echo ${FILE} | tr '/' ' '`) BASE_PATH_SEGMENT="${PATH_SEGMENTS[0]}" COMMENT= if [ "${BASE_PATH_SEGMENT}" == "generated" ]; then COMMENT="generated" elif [ "${BASE_PATH_SEGMENT}" == "default" ]; then COMMENT="default" fi cd "$OUT_DIR" "$CUR_PWD/$3" -jt -X -0 -q -r -c "$CUR_PWD/$2" $NEW_NAME <