blob: fee7932e6dfc06483aa32fedab47f1b23467c5a2 [file] [log] [blame]
#!/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:])