// Copyright 2015 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. package com.google.devtools.build.android; import static java.util.stream.Collectors.toList; import com.android.builder.core.VariantType; import com.android.ide.common.internal.AaptCruncher; import com.android.ide.common.internal.LoggedErrorException; import com.android.ide.common.internal.PngCruncher; import com.android.ide.common.process.DefaultProcessExecutor; import com.android.ide.common.process.LoggedProcessOutputHandler; import com.android.ide.common.xml.AndroidManifestParser; import com.android.ide.common.xml.ManifestData.Instrumentation; import com.android.io.StreamException; import com.android.utils.StdLogger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException; import com.google.devtools.build.android.AndroidResourceMerger.MergingException; import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions; import com.google.devtools.build.android.AndroidResourceProcessor.FlagAaptOptions; import com.google.devtools.build.android.Converters.DependencyAndroidDataListConverter; import com.google.devtools.build.android.Converters.PathConverter; import com.google.devtools.build.android.Converters.SerializedAndroidDataListConverter; import com.google.devtools.build.android.Converters.UnvalidatedAndroidDataConverter; import com.google.devtools.build.android.Converters.VariantTypeConverter; import com.google.devtools.build.android.SplitConfigurationFilter.UnrecognizedSplitsException; import com.google.devtools.common.options.Converters; import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor; import com.google.devtools.common.options.TriState; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; /** * Provides an entry point for the resource processing using the AOSP build tools. * *
* Example Usage: * java/com/google/build/android/AndroidResourceProcessingAction\ * --sdkRoot path/to/sdk\ * --aapt path/to/sdk/aapt\ * --adb path/to/sdk/adb\ * --zipAlign path/to/sdk/zipAlign\ * --androidJar path/to/sdk/androidJar\ * --manifestOutput path/to/manifest\ * --primaryData path/to/resources:path/to/assets:path/to/manifest\ * --data p/t/res1:p/t/assets1:p/t/1/AndroidManifest.xml:p/t/1/R.txt:symbols,\ * p/t/res2:p/t/assets2:p/t/2/AndroidManifest.xml:p/t/2/R.txt:symbols\ * --packagePath path/to/write/archive.ap_\ * --srcJarOutput path/to/write/archive.srcjar **/ 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 = "primaryData", defaultValue = "null", converter = UnvalidatedAndroidDataConverter.class, category = "input", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, 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 " + UnvalidatedAndroidData.EXPECTED_FORMAT ) public UnvalidatedAndroidData primaryData; @Option( name = "data", defaultValue = "", converter = DependencyAndroidDataListConverter.class, category = "input", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = "Transitive Data dependencies. These values will be used if not defined in the " + "primary resources. The expected format is " + DependencyAndroidData.EXPECTED_FORMAT + "[,...]" ) public List
When testing Android code, the test can be run in the same or a different process as the * code being tested. If it's in the same process, we do not allow Android resources to be used by * the test, as they could overwrite the resources used by the code being tested. If this APK * won't be testing another APK, the test and code under test are in different processes, or no * resources are being used, this isn't a concern. * *
To determine whether the test and code under test are run in the same process, we check the
* package of the code under test, passed into this function, against the target packages of any
* instrumentation
tags in this APK's manifest.
*
* @param packageUnderTest the package of the code under test, or null if no code is under test
* @param processedManifest the processed manifest for this APK
* @return true if there is a conflict, false otherwise
*/
@VisibleForTesting
static boolean hasConflictWithPackageUnderTest(
@Nullable String packageUnderTest, Path processedManifest, Stopwatch timer)
throws SAXException, StreamException, ParserConfigurationException, IOException {
if (packageUnderTest == null) {
return false;
}
// We are building a test APK with resources. Validate instrumentation package is different
// from the package under test. If it isn't, fail to prevent the test resources from
// overriding the resources of the APK under test.
try (InputStream stream = Files.newInputStream(processedManifest)) {
for (Instrumentation instrumentation :
AndroidManifestParser.parse(stream).getInstrumentations()) {
if (packageUnderTest.equals(instrumentation.getTargetPackage())) {
return true;
}
}
}
logger.fine(
String.format(
"Custom package and instrumentation verification finished at %sms",
timer.elapsed(TimeUnit.MILLISECONDS)));
return false;
}
private static boolean usePngCruncher() {
// If the value was set, use that.
if (aaptConfigOptions.useAaptCruncher != TriState.AUTO) {
return aaptConfigOptions.useAaptCruncher == TriState.YES;
}
// By default png cruncher shouldn't be invoked on a library -- the work is just thrown away.
return options.packageType != VariantType.LIBRARY;
}
private static PngCruncher selectPngCruncher() {
// Use the full cruncher if asked to do so.
if (usePngCruncher()) {
return new AaptCruncher(
aaptConfigOptions.aapt.toString(),
new DefaultProcessExecutor(STD_LOGGER),
new LoggedProcessOutputHandler(STD_LOGGER));
}
// Otherwise, if this is a binary, we need to at least process nine-patch PNGs.
if (options.packageType != VariantType.LIBRARY) {
return new NinePatchOnlyCruncher(
aaptConfigOptions.aapt.toString(),
new DefaultProcessExecutor(STD_LOGGER),
new LoggedProcessOutputHandler(STD_LOGGER));
}
return null;
}
}