blob: 38db645e21d9ca18c3172821e43d192e4e07c992 [file]
// Copyright 2015 Google Inc. 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.
package com.google.devtools.build.android;
import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.google.devtools.build.android.Converters.DependencyAndroidDataListConverter;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
import com.google.devtools.build.android.Converters.FullRevisionConverter;
import com.google.devtools.build.android.Converters.PathConverter;
import com.google.devtools.build.android.Converters.UnvalidatedAndroidDataConverter;
import com.google.devtools.build.android.Converters.VariantConfigurationTypeConverter;
import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.TriState;
import com.android.builder.core.AndroidBuilder;
import com.android.builder.core.VariantConfiguration;
import com.android.builder.model.AaptOptions;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.ide.common.res2.MergingException;
import com.android.sdklib.repository.FullRevision;
import com.android.utils.StdLogger;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* Provides an entry point for the resource processing using the AOSP build tools.
*
* <pre>
* Example Usage:
* java/com/google/build/android/AndroidResourceProcessingAction\
* --sdkRoot path/to/sdk\
* --aapt path/to/sdk/aapt\
* --annotationJar path/to/sdk/annotationJar\
* --adb path/to/sdk/adb\
* --zipAlign path/to/sdk/zipAlign\
* --androidJar path/to/sdk/androidJar\
* --manifest path/to/manifest\
* --primaryData path/to/resources:path/to/assets:path/to/manifest:path/to/R.txt
* --data p/t/res1:p/t/assets1:p/t/1/AndroidManifest.xml:p/t/1/R.txt,\
* p/t/res2:p/t/assets2:p/t/2/AndroidManifest.xml:p/t/2/R.txt
* --generatedSourcePath path/to/write/generated/sources
* --packagePath path/to/write/archive.ap_
* --srcJarOutput path/to/write/archive.srcjar
* </pre>
*/
public class AndroidResourceProcessingAction {
private static final StdLogger STD_LOGGER =
new StdLogger(com.android.utils.StdLogger.Level.WARNING);
private static final Logger LOGGER =
Logger.getLogger(AndroidResourceProcessingAction.class.getName());
/** Flag specifications for this action. */
public static final class Options extends OptionsBase {
@Option(name = "apiVersion",
defaultValue = "21.0.0",
converter = FullRevisionConverter.class,
category = "config",
help = "ApiVersion indicates the version passed to the AndroidBuilder. ApiVersion must be"
+ " > 19.10 when defined.")
// TODO(bazel-team): Determine what the API version changes in AndroidBuilder.
public FullRevision apiVersion;
@Option(name = "aapt",
defaultValue = "null",
converter = ExistingPathConverter.class,
category = "tool",
help = "Aapt tool location for resource packaging.")
public Path aapt;
@Option(name = "annotationJar",
defaultValue = "null",
converter = ExistingPathConverter.class,
category = "tool",
help = "Annotation Jar for builder invocations.")
public Path annotationJar;
@Option(name = "adb",
defaultValue = "null",
converter = ExistingPathConverter.class,
category = "tool",
help = "Path to adb for builder functions.")
//TODO(bazel-team): Determine if this is completely necessary for running AndroidBuilder.
public Path adb;
@Option(name = "zipAlign",
defaultValue = "null",
converter = ExistingPathConverter.class,
category = "tool",
help = "Path to zipAlign for building apks.")
public Path zipAlign;
@Option(name = "androidJar",
defaultValue = "null",
converter = ExistingPathConverter.class,
category = "tool",
help = "Path to the android jar for resource packaging and building apks.")
public Path androidJar;
@Option(name = "primaryData",
defaultValue = "null",
converter = UnvalidatedAndroidDataConverter.class,
category = "input",
help = "The directory containing the primary resource directory. The contents will override"
+ " the contents of any other resource directories during merging. The expected format"
+ " is resources[|resources]:assets[|assets]:manifest")
public UnvalidatedAndroidData primaryData;
@Option(name = "data",
defaultValue = "",
converter = DependencyAndroidDataListConverter.class,
category = "input",
help = "Additional Data dependencies. These values will be used if not defined in the "
+ "primary resources. The expected format is "
+ "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.txt"
+ "[,resources[#resources]:assets[#assets]:manifest:r.txt:symbols.txt]")
public List<DependencyAndroidData> data;
@Option(name = "generatedSourcePath",
defaultValue = "null",
converter = PathConverter.class,
category = "output",
help = "Path for generated sources.")
public Path generatedSourcePath;
@Option(name = "rOutput",
defaultValue = "null",
converter = PathConverter.class,
category = "output",
help = "Path to where the R.txt should be written.")
public Path rOutput;
@Option(name = "symbolsTxtOut",
defaultValue = "null",
converter = PathConverter.class,
category = "output",
help = "Path to where the symbolsTxt should be written.")
public Path symbolsTxtOut;
@Option(name = "packagePath",
defaultValue = "null",
converter = PathConverter.class,
category = "output",
help = "Path to the write the archive.")
public Path packagePath;
@Option(name = "proguardOutput",
defaultValue = "null",
converter = PathConverter.class,
category = "output",
help = "Path for the proguard file.")
public Path proguardOutput;
@Option(name = "srcJarOutput",
defaultValue = "null",
converter = PathConverter.class,
category = "output",
help = "Path for the generated java source jar.")
public Path srcJarOutput;
@Option(name = "packageType",
defaultValue = "DEFAULT",
converter = VariantConfigurationTypeConverter.class,
category = "config",
help = "Variant configuration type for packaging the resources."
+ " Acceptible values DEFAULT, LIBRARY, TEST")
public VariantConfiguration.Type packageType;
@Option(name = "densities",
defaultValue = "",
converter = CommaSeparatedOptionListConverter.class,
category = "config",
help = "A list densities to filter the resource drawables by.")
public List<String> densities;
@Option(name = "debug",
defaultValue = "false",
category = "config",
help = "Indicates if it is a debug build.")
public boolean debug;
@Option(name = "resourceConfigs",
defaultValue = "",
converter = CommaSeparatedOptionListConverter.class,
category = "config",
help = "A list of resource config filters to pass to aapt.")
public List<String> resourceConfigs;
@Option(name = "useAaptCruncher",
defaultValue = "auto",
category = "config",
help = "Use the legacy aapt cruncher, defaults to true for non-LIBRARY packageTypes. "
+ " LIBRARY packages do not benefit from the additional processing as the resources"
+ " will need to be reprocessed during the generation of the final apk. See"
+ " https://code.google.com/p/android/issues/detail?id=67525 for a discussion of the"
+ " different png crunching methods.")
public TriState useAaptCruncher;
@Option(name = "uncompressedExtensions",
defaultValue = "",
converter = CommaSeparatedOptionListConverter.class,
category = "config",
help = "A list of file extensions not to compress.")
public List<String> uncompressedExtensions;
@Option(name = "packageForR",
defaultValue = "null",
category = "config",
help = "Custom java package to generate the R symbols files.")
public String packageForR;
@Option(name = "applicationId",
defaultValue = "null",
category = "config",
help = "Custom application id (package manifest) for the packaged manifest.")
public String applicationId;
@Option(name = "versionName",
defaultValue = "null",
category = "config",
help = "Version name to stamp into the packaged manifest.")
public String versionName;
@Option(name = "versionCode",
defaultValue = "-1",
category = "config",
help = "Version code to stamp into the packaged manifest.")
public int versionCode;
@Option(name = "assetsToIgnore",
defaultValue = "",
converter = CommaSeparatedOptionListConverter.class,
category = "config",
help = "A list of assets extensions to ignore.")
public List<String> assetsToIgnore;
}
private static Options options;
public static void main(String[] args) {
final Stopwatch timer = Stopwatch.createStarted();
OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class);
optionsParser.parseAndExitUponError(args);
options = optionsParser.getOptions(Options.class);
FileSystem fileSystem = FileSystems.getDefault();
Path working = fileSystem.getPath("").toAbsolutePath();
Path mergedAssets = working.resolve("merged_assets");
Path mergedResources = working.resolve("merged_resources");
final AndroidResourceProcessor resourceProcessor =
new AndroidResourceProcessor(STD_LOGGER);
final AndroidSdkTools sdkTools = new AndroidSdkTools(options.apiVersion,
options.aapt,
options.annotationJar,
options.adb,
options.zipAlign,
options.androidJar,
STD_LOGGER);
try {
Path expandedOut = Files.createTempDirectory("tmp-expanded");
expandedOut.toFile().deleteOnExit();
Path deduplicatedOut = Files.createTempDirectory("tmp-deduplicated");
deduplicatedOut.toFile().deleteOnExit();
LOGGER.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
final ImmutableList<DirectoryModifier> modifiers = ImmutableList.of(
new PackedResourceTarExpander(expandedOut, working),
new FileDeDuplicator(Hashing.murmur3_128(), deduplicatedOut, working));
final AndroidBuilder builder = sdkTools.createAndroidBuilder();
final MergedAndroidData mergedData = resourceProcessor.mergeData(
options.primaryData,
options.data,
mergedResources,
mergedAssets,
modifiers,
useAaptCruncher() ? builder.getAaptCruncher() : null,
true);
LOGGER.info(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
final Path filteredResources = fileSystem.getPath("resources-filtered");
final Path densityManifest = fileSystem.getPath("manifest-filtered/AndroidManifest.xml");
final DensityFilteredAndroidData filteredData = mergedData.filter(
new DensitySpecificResourceFilter(options.densities, filteredResources, working),
new DensitySpecificManifestProcessor(options.densities, densityManifest));
LOGGER.info(
String.format("Density filtering finished at %sms",
timer.elapsed(TimeUnit.MILLISECONDS)));
resourceProcessor.processResources(
builder,
options.packageType,
options.debug,
options.packageForR,
new FlagAaptOptions(),
options.resourceConfigs,
options.applicationId,
options.versionCode,
options.versionName,
filteredData,
options.data,
working.resolve("manifest"),
options.generatedSourcePath,
options.packagePath,
options.proguardOutput);
LOGGER.fine(String.format("appt finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
if (options.srcJarOutput != null) {
resourceProcessor.createSrcJar(options.generatedSourcePath, options.srcJarOutput);
}
if (options.rOutput != null) {
resourceProcessor.copyRToOutput(options.generatedSourcePath, options.rOutput);
}
if (options.symbolsTxtOut != null) {
resourceProcessor.copyRToOutput(options.generatedSourcePath, options.symbolsTxtOut);
}
LOGGER.fine(String.format("Packaging finished at %sms",
timer.elapsed(TimeUnit.MILLISECONDS)));
} catch (MergingException e) {
LOGGER.log(java.util.logging.Level.SEVERE, "Error during merging resources", e);
System.exit(1);
} catch (IOException | InterruptedException | LoggedErrorException e) {
LOGGER.log(java.util.logging.Level.SEVERE, "Error during processing resources", e);
System.exit(2);
} catch (Exception e) {
LOGGER.log(java.util.logging.Level.SEVERE, "Unexpected", e);
System.exit(3);
}
LOGGER.info(String.format("Resources processed in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
// AOSP code can leave dangling threads.
System.exit(0);
}
private static boolean useAaptCruncher() {
// If the value was set, use that.
if (options.useAaptCruncher != TriState.AUTO) {
return options.useAaptCruncher == TriState.YES;
}
// By default png cruncher shouldn't be invoked on a library -- the work is just thrown away.
return options.packageType != VariantConfiguration.Type.LIBRARY;
}
private static final class FlagAaptOptions implements AaptOptions {
@Override
public boolean getUseAaptPngCruncher() {
return options.useAaptCruncher != TriState.NO;
}
@Override
public Collection<String> getNoCompress() {
if (!options.uncompressedExtensions.isEmpty()) {
return options.uncompressedExtensions;
}
return null;
}
@Override
public String getIgnoreAssets() {
if (!options.assetsToIgnore.isEmpty()) {
return Joiner.on(":").join(options.assetsToIgnore);
}
return null;
}
@Override
public boolean getFailOnMissingConfigEntry() {
return false;
}
}
}