android build: Split process_resources GN rule and script.
This is a reland of [1] which itself was a reland of [2].
Note that [1] was reverted in [3] due to broken package id
generation in monochrome_public_apk and its cousins.
The process_resources() GN template and the associated
process_resources.py script are huge and difficult to understand
because they can be used to perform two slightly related
different things.
In preparation for a future CL that will introduce a new resource
compilation mode, this CL tries to clarify the situation by splitting
them into two sets of GN template + script:
- prepare_resources() + prepare_resources.py, which are
used to create .resources.zip archives of raw/uncompiled
resources for resource related targets, as well as
generating a corresponding R.txt and .srcjar(R.java)
file.
This corresponds to what process_resources() did when
it was called from android_resources() and junit_binary().
Note that this always generates non-final resource IDs,
as well as a dummy onResourcesLoaded() method, necessary
to compile, but not link, the ResourceRewriter class
used to implement the system webview feature.
- compile_resources() + compile_resources.py, which are used
to compile all resource dependencies of a given binary
(e.g. android_apk) into an intermediate .ap_ file.
This corresponds to the behaviour of process_resources()
when called from android_apk(). This generates final
resource IDs by default, unless |shared_resources| or
|shared_resources_whitelist| is used.
Also, as a simplification, |shared_resources_whitelist|
now implies |shared_resources|, except that it restrict
the list of non-final resource IDs.
- Removed generate_constant_ids, since compile_resources()
will always generate constant ids unless shared resources
are being used. And prepare_resources() always generates
non-constant IDs, as before the CL.
+ Add documentation for the prepare_resources() and
compile_resources() internal GN rules, to make them
a little less intimidating.
+ Add documentation for shared_resources, app_as_shared_lib,
shared_resources_whitelist_target for android_apk()
template.
+ Add sanity checking for the resources table package
ID of each generated APK. This is done by adding
(and using) --check-resources-pkg-id=ID option to
compile_resources.py.
+ Improve --help output for prepare_resources.py and
compile_resources.py by using option groups for inputs
and outputs.
[1] https://chromium-review.googlesource.com/c/chromium/src/+/968870
[2] https://chromium-review.googlesource.com/c/chromium/src/+/957095
[3] https://chromium-review.googlesource.com/c/chromium/src/+/972632
[email protected],[email protected],[email protected],[email protected]
Bug: 820459
Change-Id: I34a5018f54c06c110bbe996da88cddb9e3b9a21b
Reviewed-on: https://chromium-review.googlesource.com/973963
Commit-Queue: David Turner <[email protected]>
Reviewed-by: agrieve <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#545392}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 81bb55bc0b81aa61561e93c928eeb2de69980e68
diff --git a/android/gyp/prepare_resources.py b/android/gyp/prepare_resources.py
new file mode 100755
index 0000000..fee7932
--- /dev/null
+++ b/android/gyp/prepare_resources.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Process Android resource directories to generate .resources.zip, R.txt and
+.srcjar files."""
+
+import argparse
+import collections
+import os
+import re
+import shutil
+import sys
+
+import generate_v14_compatible_resources
+
+from util import build_utils
+from util import resource_utils
+
+
+def _ParseArgs(args):
+ """Parses command line options.
+
+ Returns:
+ An options object as from argparse.ArgumentParser.parse_args()
+ """
+ parser, input_opts, output_opts = resource_utils.ResourceArgsParser()
+
+ input_opts.add_argument('--resource-dirs',
+ default='[]',
+ help='A list of input directories containing resources '
+ 'for this target.')
+
+ input_opts.add_argument(
+ '--shared-resources',
+ action='store_true',
+ help='Make resources shareable by generating an onResourcesLoaded() '
+ 'method in the R.java source file.')
+
+ input_opts.add_argument('--custom-package',
+ help='Optional Java package for main R.java.')
+
+ input_opts.add_argument(
+ '--android-manifest',
+ help='Optional AndroidManifest.xml path. Only used to extract a package '
+ 'name for R.java if a --custom-package is not provided.')
+
+ output_opts.add_argument(
+ '--resource-zip-out',
+ help='Path to a zip archive containing all resources from '
+ '--resource-dirs, merged into a single directory tree. This will '
+ 'also include auto-generated v14-compatible resources unless '
+ '--v14-skip is used.')
+
+ output_opts.add_argument('--srcjar-out',
+ help='Path to .srcjar to contain the generated R.java.')
+
+ output_opts.add_argument('--r-text-out',
+ help='Path to store the generated R.txt file.')
+
+ input_opts.add_argument(
+ '--v14-skip',
+ action="store_true",
+ help='Do not generate nor verify v14 resources.')
+
+ options = parser.parse_args(args)
+
+ resource_utils.HandleCommonOptions(options)
+
+ options.resource_dirs = build_utils.ParseGnList(options.resource_dirs)
+
+ return options
+
+
+def _GenerateGlobs(pattern):
+ # This function processes the aapt ignore assets pattern into a list of globs
+ # to be used to exclude files on the python side. It removes the '!', which is
+ # used by aapt to mean 'not chatty' so it does not output if the file is
+ # ignored (we dont output anyways, so it is not required). This function does
+ # not handle the <dir> and <file> prefixes used by aapt and are assumed not to
+ # be included in the pattern string.
+ return pattern.replace('!', '').split(':')
+
+
+def _ZipResources(resource_dirs, zip_path, ignore_pattern):
+ # Python zipfile does not provide a way to replace a file (it just writes
+ # another file with the same name). So, first collect all the files to put
+ # in the zip (with proper overriding), and then zip them.
+ # ignore_pattern is a string of ':' delimited list of globs used to ignore
+ # files that should not be part of the final resource zip.
+ files_to_zip = dict()
+ globs = _GenerateGlobs(ignore_pattern)
+ for d in resource_dirs:
+ for root, _, files in os.walk(d):
+ for f in files:
+ archive_path = f
+ parent_dir = os.path.relpath(root, d)
+ if parent_dir != '.':
+ archive_path = os.path.join(parent_dir, f)
+ path = os.path.join(root, f)
+ if build_utils.MatchesGlob(archive_path, globs):
+ continue
+ files_to_zip[archive_path] = path
+ build_utils.DoZip(files_to_zip.iteritems(), zip_path)
+
+
+def _GenerateRTxt(options, dep_subdirs, gen_dir):
+ """Generate R.txt file.
+
+ Args:
+ options: The command-line options tuple.
+ dep_subdirs: List of directories containing extracted dependency resources.
+ gen_dir: Locates where the aapt-generated files will go. In particular
+ the output file is always generated as |{gen_dir}/R.txt|.
+ """
+ # NOTE: This uses aapt rather than aapt2 because 'aapt2 compile' does not
+ # support the --output-text-symbols option yet (https://crbug.com/820460).
+ package_command = [options.aapt_path,
+ 'package',
+ '-m',
+ '-M', resource_utils.EMPTY_ANDROID_MANIFEST_PATH,
+ '--no-crunch',
+ '--auto-add-overlay',
+ '--no-version-vectors',
+ '-I', options.android_sdk_jar,
+ '--output-text-symbols', gen_dir,
+ '-J', gen_dir, # Required for R.txt generation.
+ '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
+
+ # Adding all dependencies as sources is necessary for @type/foo references
+ # to symbols within dependencies to resolve. However, it has the side-effect
+ # that all Java symbols from dependencies are copied into the new R.java.
+ # E.g.: It enables an arguably incorrect usage of
+ # "mypackage.R.id.lib_symbol" where "libpackage.R.id.lib_symbol" would be
+ # more correct. This is just how Android works.
+ for d in dep_subdirs:
+ package_command += ['-S', d]
+
+ for d in options.resource_dirs:
+ package_command += ['-S', d]
+
+ # Only creates an R.txt
+ build_utils.CheckOutput(
+ package_command, print_stdout=False, print_stderr=False)
+
+
+def _GenerateResourcesZip(output_resource_zip, input_resource_dirs,
+ v14_skip, temp_dir):
+ """Generate a .resources.zip file fron a list of input resource dirs.
+
+ Args:
+ output_resource_zip: Path to the output .resources.zip file.
+ input_resource_dirs: A list of input resource directories.
+ v14_skip: If False, then v14-compatible resource will also be
+ generated in |{temp_dir}/v14| and added to the final zip.
+ temp_dir: Path to temporary directory.
+ """
+ if not v14_skip:
+ # Generate v14-compatible resources in temp_dir.
+ v14_dir = os.path.join(temp_dir, 'v14')
+ build_utils.MakeDirectory(v14_dir)
+
+ for resource_dir in input_resource_dirs:
+ generate_v14_compatible_resources.GenerateV14Resources(
+ resource_dir,
+ v14_dir)
+
+ input_resource_dirs.append(v14_dir)
+
+ _ZipResources(input_resource_dirs, output_resource_zip,
+ build_utils.AAPT_IGNORE_PATTERN)
+
+
+def _OnStaleMd5(options):
+ with resource_utils.BuildContext() as build:
+ if options.r_text_in:
+ r_txt_path = options.r_text_in
+ else:
+ # Extract dependencies to resolve @foo/type references into
+ # dependent packages.
+ dep_subdirs = resource_utils.ExtractDeps(options.dependencies_res_zips,
+ build.deps_dir)
+
+ _GenerateRTxt(options, dep_subdirs, build.gen_dir)
+ r_txt_path = build.r_txt_path
+
+ # 'aapt' doesn't generate any R.txt file if res/ was empty.
+ if not os.path.exists(r_txt_path):
+ build_utils.Touch(r_txt_path)
+
+ if options.r_text_out:
+ shutil.copyfile(r_txt_path, options.r_text_out)
+
+ if options.srcjar_out:
+ package = options.custom_package
+ if not package and options.android_manifest:
+ package = resource_utils.ExtractPackageFromManifest(
+ options.android_manifest)
+
+ # Don't create a .java file for the current resource target when no
+ # package name was provided (either by manifest or build rules).
+ if package:
+ # All resource IDs should be non-final here, but the
+ # onResourcesLoaded() method should only be generated if
+ # --shared-resources is used.
+ rjava_build_options = resource_utils.RJavaBuildOptions()
+ rjava_build_options.ExportAllResources()
+ rjava_build_options.ExportAllStyleables()
+ if options.shared_resources:
+ rjava_build_options.GenerateOnResourcesLoaded()
+
+ resource_utils.CreateRJavaFiles(
+ build.srcjar_dir, package, r_txt_path,
+ options.extra_res_packages,
+ options.extra_r_text_files,
+ rjava_build_options)
+
+ build_utils.ZipDir(options.srcjar_out, build.srcjar_dir)
+
+ if options.resource_zip_out:
+ _GenerateResourcesZip(options.resource_zip_out, options.resource_dirs,
+ options.v14_skip, build.temp_dir)
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ options = _ParseArgs(args)
+
+ # Order of these must match order specified in GN so that the correct one
+ # appears first in the depfile.
+ possible_output_paths = [
+ options.resource_zip_out,
+ options.r_text_out,
+ options.srcjar_out,
+ ]
+ output_paths = [x for x in possible_output_paths if x]
+
+ # List python deps in input_strings rather than input_paths since the contents
+ # of them does not change what gets written to the depsfile.
+ input_strings = options.extra_res_packages + [
+ options.custom_package,
+ options.shared_resources,
+ options.v14_skip,
+ ]
+
+ possible_input_paths = [
+ options.aapt_path,
+ options.android_manifest,
+ options.android_sdk_jar,
+ ]
+ input_paths = [x for x in possible_input_paths if x]
+ input_paths.extend(options.dependencies_res_zips)
+ input_paths.extend(options.extra_r_text_files)
+
+ # Resource files aren't explicitly listed in GN. Listing them in the depfile
+ # ensures the target will be marked stale when resource files are removed.
+ depfile_deps = []
+ resource_names = []
+ for resource_dir in options.resource_dirs:
+ for resource_file in build_utils.FindInDirectory(resource_dir, '*'):
+ # Don't list the empty .keep file in depfile. Since it doesn't end up
+ # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors
+ # if ever moved.
+ if not resource_file.endswith(os.path.join('empty', '.keep')):
+ input_paths.append(resource_file)
+ depfile_deps.append(resource_file)
+ resource_names.append(os.path.relpath(resource_file, resource_dir))
+
+ # Resource filenames matter to the output, so add them to strings as well.
+ # This matters if a file is renamed but not changed (http://crbug.com/597126).
+ input_strings.extend(sorted(resource_names))
+
+ build_utils.CallAndWriteDepfileIfStale(
+ lambda: _OnStaleMd5(options),
+ options,
+ input_paths=input_paths,
+ input_strings=input_strings,
+ output_paths=output_paths,
+ depfile_deps=depfile_deps)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])