com.android.builder.core.AndroidBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications.
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.builder.core;
import static com.android.SdkConstants.DOT_DEX;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.FD_RES_XML;
import static com.android.builder.core.BuilderConstants.ANDROID_WEAR;
import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK;
import static com.android.manifmerger.ManifestMerger2.Invoker;
import static com.android.manifmerger.ManifestMerger2.SystemProperty;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.builder.compiling.DependencyFileProcessor;
import com.android.builder.dependency.ManifestDependency;
import com.android.builder.dependency.SymbolFileProvider;
import com.android.builder.internal.ClassFieldImpl;
import com.android.builder.internal.SymbolLoader;
import com.android.builder.internal.SymbolWriter;
import com.android.builder.internal.TestManifestGenerator;
import com.android.builder.internal.compiler.AidlProcessor;
import com.android.builder.internal.compiler.JackConversionCache;
import com.android.builder.internal.compiler.LeafFolderGatherer;
import com.android.builder.internal.compiler.PreDexCache;
import com.android.builder.internal.compiler.RenderScriptProcessor;
import com.android.builder.internal.compiler.SourceSearcher;
import com.android.builder.internal.incremental.DependencyData;
import com.android.builder.internal.packaging.JavaResourceProcessor;
import com.android.builder.internal.packaging.Packager;
import com.android.builder.model.ClassField;
import com.android.builder.model.PackagingOptions;
import com.android.builder.model.SigningConfig;
import com.android.builder.packaging.DuplicateFileException;
import com.android.builder.packaging.PackagerException;
import com.android.builder.packaging.SealedPackageException;
import com.android.builder.packaging.SigningException;
import com.android.builder.sdk.SdkInfo;
import com.android.builder.sdk.TargetInfo;
import com.android.builder.signing.SignedJarBuilder;
import com.android.ide.common.internal.AaptCruncher;
import com.android.ide.common.internal.CommandLineRunner;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.ide.common.internal.PngCruncher;
import com.android.ide.common.process.CachedProcessOutputHandler;
import com.android.ide.common.process.JavaProcessExecutor;
import com.android.ide.common.process.JavaProcessInfo;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.ide.common.process.ProcessInfo;
import com.android.ide.common.process.ProcessInfoBuilder;
import com.android.ide.common.process.ProcessOutputHandler;
import com.android.ide.common.process.ProcessResult;
import com.android.ide.common.signing.CertificateInfo;
import com.android.ide.common.signing.KeystoreHelper;
import com.android.ide.common.signing.KeytoolException;
import com.android.manifmerger.ManifestMerger2;
import com.android.manifmerger.MergingReport;
import com.android.manifmerger.PlaceholderEncoder;
import com.android.manifmerger.PlaceholderHandler;
import com.android.manifmerger.XmlDocument;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repository.FullRevision;
import com.android.utils.ILogger;
import com.android.utils.Pair;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This is the main builder class. It is given all the data to process the build (such as
* {@link DefaultProductFlavor}s, {@link DefaultBuildType} and dependencies) and use them when doing specific
* build steps.
*
* To use:
* create a builder with {@link #AndroidBuilder(String, CommandLineRunner, ProcessExecutor, JavaProcessExecutor, ProcessOutputHandler, ILogger, boolean)}
*
* then build steps can be done with
* {@link #mergeManifests(File, List, List, String, int, String, String, String, Integer, String, String, ManifestMerger2.MergeType, Map, File)}
* {@link #processTestManifest(String, String, String, String, String, Boolean, Boolean, java.io.File, java.util.List, java.io.File, java.io.File)}
* {@link #processResources(AaptPackageProcessBuilder, boolean)}
* {@link #compileAllAidlFiles(java.util.List, java.io.File, java.io.File, java.util.List, com.android.builder.compiling.DependencyFileProcessor)}
* {@link #convertByteCode(Collection, Collection, File, boolean, boolean, File, DexOptions, List, File, boolean, boolean)}
* {@link #packageApk(String, java.io.File, java.util.Collection, java.util.Collection, String, java.util.Collection, java.util.Set, boolean, com.android.builder.model.SigningConfig, com.android.builder.model.PackagingOptions, String)}
*
* Java compilation is not handled but the builder provides the bootclasspath with
* {@link #getBootClasspath()}.
*/
public class AndroidBuilder {
private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(19, 1, 0);
private static final DependencyFileProcessor sNoOpDependencyFileProcessor = new DependencyFileProcessor() {
@Override
public DependencyData processFile(@NonNull File dependencyFile) {
return null;
}
};
@NonNull private final String mProjectId;
@NonNull private final ILogger mLogger;
@NonNull private final ProcessExecutor mProcessExecutor;
@NonNull private final JavaProcessExecutor mJavaProcessExecutor;
@NonNull private final ProcessOutputHandler mProcessOutputHandler;
private final boolean mVerboseExec;
@Nullable private String mCreatedBy;
private SdkInfo mSdkInfo;
private TargetInfo mTargetInfo;
/**
* Creates an AndroidBuilder.
*
* verboseExec is needed on top of the ILogger due to remote exec tools not being
* able to output info and verbose messages separately.
*
* @param createdBy the createdBy String for the apk manifest.
* @param logger the Logger
* @param verboseExec whether external tools are launched in verbose mode
*/
public AndroidBuilder(
@NonNull String projectId,
@Nullable String createdBy,
@NonNull ProcessExecutor processExecutor,
@NonNull JavaProcessExecutor javaProcessExecutor,
@NonNull ProcessOutputHandler processOutputHandler,
@NonNull ILogger logger,
boolean verboseExec) {
mProjectId = projectId;
mCreatedBy = createdBy;
mProcessExecutor = processExecutor;
mJavaProcessExecutor = javaProcessExecutor;
mProcessOutputHandler = processOutputHandler;
mLogger = checkNotNull(logger);
mVerboseExec = verboseExec;
}
@VisibleForTesting
AndroidBuilder(
@NonNull String projectId,
@NonNull CommandLineRunner cmdLineRunner,
@NonNull ProcessExecutor processExecutor,
@NonNull JavaProcessExecutor javaProcessExecutor,
@NonNull ProcessOutputHandler processOutputHandler,
@NonNull ILogger logger,
boolean verboseExec) {
mProjectId = projectId;
mProcessExecutor = checkNotNull(processExecutor);
mJavaProcessExecutor = checkNotNull(javaProcessExecutor);
mProcessOutputHandler = processOutputHandler;
mLogger = checkNotNull(logger);
mVerboseExec = verboseExec;
}
/**
* Sets the SdkInfo and the targetInfo on the builder. This is required to actually
* build (some of the steps).
*
* @param sdkInfo the SdkInfo
* @param targetInfo the TargetInfo
*
* @see com.android.builder.sdk.SdkLoader
*/
public void setTargetInfo(@NonNull SdkInfo sdkInfo, @NonNull TargetInfo targetInfo) {
mSdkInfo = sdkInfo;
mTargetInfo = targetInfo;
if (mTargetInfo.getBuildTools().getRevision().compareTo(MIN_BUILD_TOOLS_REV) < 0) {
throw new IllegalArgumentException(String.format(
"The SDK Build Tools revision (%1$s) is too low for project '%2$s'. Minimum required is %3$s",
mTargetInfo.getBuildTools().getRevision(), mProjectId, MIN_BUILD_TOOLS_REV));
}
}
/**
* Returns the SdkInfo, if set.
*/
@Nullable
public SdkInfo getSdkInfo() {
return mSdkInfo;
}
/**
* Returns the TargetInfo, if set.
*/
@Nullable
public TargetInfo getTargetInfo() {
return mTargetInfo;
}
@NonNull
public ILogger getLogger() {
return mLogger;
}
/**
* Returns the compilation target, if set.
*/
@Nullable
public IAndroidTarget getTarget() {
checkState(mTargetInfo != null,
"Cannot call getTarget() before setTargetInfo() is called.");
return mTargetInfo.getTarget();
}
/**
* Returns whether the compilation target is a preview.
*/
public boolean isPreviewTarget() {
checkState(mTargetInfo != null,
"Cannot call isTargetAPreview() before setTargetInfo() is called.");
return mTargetInfo.getTarget().getVersion().isPreview();
}
public String getTargetCodename() {
checkState(mTargetInfo != null,
"Cannot call getTargetCodename() before setTargetInfo() is called.");
return mTargetInfo.getTarget().getVersion().getCodename();
}
@NonNull
public File getDxJar() {
checkState(mTargetInfo != null,
"Cannot call getDxJar() before setTargetInfo() is called.");
return new File(mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.DX_JAR));
}
/**
* Helper method to get the boot classpath to be used during compilation.
*/
@NonNull
public List getBootClasspath() {
checkState(mTargetInfo != null,
"Cannot call getBootClasspath() before setTargetInfo() is called.");
List classpath = Lists.newArrayList();
IAndroidTarget target = mTargetInfo.getTarget();
for (String p : target.getBootClasspath()) {
classpath.add(new File(p));
}
// add optional libraries if any
IAndroidTarget.IOptionalLibrary[] libs = target.getOptionalLibraries();
if (libs != null) {
for (IAndroidTarget.IOptionalLibrary lib : libs) {
classpath.add(new File(lib.getJarPath()));
}
}
// add annotations.jar if needed.
if (target.getVersion().getApiLevel() <= 15) {
classpath.add(mSdkInfo.getAnnotationsJar());
}
return classpath;
}
/**
* Helper method to get the boot classpath to be used during compilation.
*/
@NonNull
public List getBootClasspathAsStrings() {
checkState(mTargetInfo != null,
"Cannot call getBootClasspath() before setTargetInfo() is called.");
List classpath = Lists.newArrayList();
IAndroidTarget target = mTargetInfo.getTarget();
classpath.addAll(target.getBootClasspath());
// add optional libraries if any
IAndroidTarget.IOptionalLibrary[] libs = target.getOptionalLibraries();
if (libs != null) {
for (IAndroidTarget.IOptionalLibrary lib : libs) {
classpath.add(lib.getJarPath());
}
}
// add annotations.jar if needed.
if (target.getVersion().getApiLevel() <= 15) {
classpath.add(mSdkInfo.getAnnotationsJar().getPath());
}
return classpath;
}
/**
* Returns the jar file for the renderscript mode.
*
* This may return null if the SDK has not been loaded yet.
*
* @return the jar file, or null.
*
* @see #setTargetInfo(com.android.builder.sdk.SdkInfo, com.android.builder.sdk.TargetInfo)
*/
@Nullable
public File getRenderScriptSupportJar() {
if (mTargetInfo != null) {
return RenderScriptProcessor.getSupportJar(
mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
}
return null;
}
/**
* Returns the compile classpath for this config. If the config tests a library, this
* will include the classpath of the tested config.
*
* If the SDK was loaded, this may include the renderscript support jar.
*
* @return a non null, but possibly empty set.
*/
@NonNull
public Set getCompileClasspath(@NonNull VariantConfiguration,?,?> variantConfiguration) {
Set compileClasspath = variantConfiguration.getCompileClasspath();
if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
File renderScriptSupportJar = getRenderScriptSupportJar();
Set fullJars = Sets.newHashSetWithExpectedSize(compileClasspath.size() + 1);
fullJars.addAll(compileClasspath);
if (renderScriptSupportJar != null) {
fullJars.add(renderScriptSupportJar);
}
compileClasspath = fullJars;
}
return compileClasspath;
}
/**
* Returns the list of packaged jars for this config. If the config tests a library, this
* will include the jars of the tested config
*
* If the SDK was loaded, this may include the renderscript support jar.
*
* @return a non null, but possibly empty list.
*/
@NonNull
public Set getPackagedJars(@NonNull VariantConfiguration,?,?> variantConfiguration) {
Set packagedJars = Sets.newHashSet(variantConfiguration.getPackagedJars());
if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
File renderScriptSupportJar = getRenderScriptSupportJar();
if (renderScriptSupportJar != null) {
packagedJars.add(renderScriptSupportJar);
}
}
return packagedJars;
}
/**
* Returns the native lib folder for the renderscript mode.
*
* This may return null if the SDK has not been loaded yet.
*
* @return the folder, or null.
*
* @see #setTargetInfo(com.android.builder.sdk.SdkInfo, com.android.builder.sdk.TargetInfo)
*/
@Nullable
public File getSupportNativeLibFolder() {
if (mTargetInfo != null) {
return RenderScriptProcessor.getSupportNativeLibFolder(
mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
}
return null;
}
/**
* Returns an {@link PngCruncher} using aapt underneath
* @return an PngCruncher object
*/
@NonNull
public PngCruncher getAaptCruncher() {
checkState(mTargetInfo != null,
"Cannot call getAaptCruncher() before setTargetInfo() is called.");
return new AaptCruncher(
mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.AAPT),
mProcessExecutor,
mProcessOutputHandler);
}
@NonNull
public ProcessExecutor getProcessExecutor() {
return mProcessExecutor;
}
@NonNull
public ProcessResult executeProcess(@NonNull ProcessInfo processInfo) {
return executeProcess(processInfo, mProcessOutputHandler);
}
@NonNull
public ProcessResult executeProcess(@NonNull ProcessInfo processInfo,
@NonNull ProcessOutputHandler handler) {
return mProcessExecutor.execute(processInfo, handler);
}
@NonNull
public static ClassField createClassField(@NonNull String type, @NonNull String name, @NonNull String value) {
return new ClassFieldImpl(type, name, value);
}
/**
* Invoke the Manifest Merger version 2.
*/
public void mergeManifests(
@NonNull File mainManifest,
@NonNull List manifestOverlays,
@NonNull List extends ManifestDependency> libraries,
String packageOverride,
int versionCode,
String versionName,
@Nullable String minSdkVersion,
@Nullable String targetSdkVersion,
@Nullable Integer maxSdkVersion,
@NonNull String outManifestLocation,
@Nullable String outAaptSafeManifestLocation,
ManifestMerger2.MergeType mergeType,
Map placeHolders,
@Nullable File reportFile) {
try {
Invoker manifestMergerInvoker =
ManifestMerger2.newMerger(mainManifest, mLogger, mergeType)
.setPlaceHolderValues(placeHolders)
.addFlavorAndBuildTypeManifests(
manifestOverlays.toArray(new File[manifestOverlays.size()]))
.addLibraryManifests(collectLibraries(libraries))
.setMergeReportFile(reportFile);
if (mergeType == ManifestMerger2.MergeType.APPLICATION) {
manifestMergerInvoker.withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
}
setInjectableValues(manifestMergerInvoker,
packageOverride, versionCode, versionName,
minSdkVersion, targetSdkVersion, maxSdkVersion);
MergingReport mergingReport = manifestMergerInvoker.merge();
mLogger.info("Merging result:" + mergingReport.getResult());
switch (mergingReport.getResult()) {
case WARNING:
mergingReport.log(mLogger);
// fall through since these are just warnings.
case SUCCESS:
XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
try {
String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
mLogger.verbose(annotatedDocument);
} catch (Exception e) {
mLogger.error(e, "cannot print resulting xml");
}
save(xmlDocument, new File(outManifestLocation));
if (outAaptSafeManifestLocation != null) {
new PlaceholderEncoder().visit(xmlDocument);
save(xmlDocument, new File(outAaptSafeManifestLocation));
}
mLogger.info("Merged manifest saved to " + outManifestLocation);
break;
case ERROR:
mergingReport.log(mLogger);
throw new RuntimeException(mergingReport.getReportString());
default:
throw new RuntimeException("Unhandled result type : "
+ mergingReport.getResult());
}
} catch (ManifestMerger2.MergeFailureException e) {
// TODO: unacceptable.
throw new RuntimeException(e);
}
}
/**
* Sets the {@link com.android.manifmerger.ManifestMerger2.SystemProperty} that can be injected
* in the manifest file.
*/
private static void setInjectableValues(
ManifestMerger2.Invoker> invoker,
String packageOverride,
int versionCode,
String versionName,
@Nullable String minSdkVersion,
@Nullable String targetSdkVersion,
@Nullable Integer maxSdkVersion) {
if (!Strings.isNullOrEmpty(packageOverride)) {
invoker.setOverride(SystemProperty.PACKAGE, packageOverride);
}
if (versionCode > 0) {
invoker.setOverride(SystemProperty.VERSION_CODE,
String.valueOf(versionCode));
}
if (!Strings.isNullOrEmpty(versionName)) {
invoker.setOverride(SystemProperty.VERSION_NAME, versionName);
}
if (!Strings.isNullOrEmpty(minSdkVersion)) {
invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
}
if (!Strings.isNullOrEmpty(targetSdkVersion)) {
invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
}
if (maxSdkVersion != null) {
invoker.setOverride(SystemProperty.MAX_SDK_VERSION, maxSdkVersion.toString());
}
}
/**
* Saves the {@link com.android.manifmerger.XmlDocument} to a file in UTF-8 encoding.
* @param xmlDocument xml document to save.
* @param out file to save to.
*/
private void save(XmlDocument xmlDocument, File out) {
try {
Files.write(xmlDocument.prettyPrint(), out, Charsets.UTF_8);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
/**
* Collect the list of libraries' manifest files.
* @param libraries declared dependencies
* @return a list of files and names for the libraries' manifest files.
*/
private static ImmutableList> collectLibraries(
List extends ManifestDependency> libraries) {
ImmutableList.Builder> manifestFiles = ImmutableList.builder();
if (libraries != null) {
collectLibraries(libraries, manifestFiles);
}
return manifestFiles.build();
}
/**
* recursively calculate the list of libraries to merge the manifests files from.
* @param libraries the dependencies
* @param manifestFiles list of files and names identifiers for the libraries' manifest files.
*/
private static void collectLibraries(List extends ManifestDependency> libraries,
ImmutableList.Builder> manifestFiles) {
for (ManifestDependency library : libraries) {
manifestFiles.add(Pair.of(library.getName(), library.getManifest()));
List extends ManifestDependency> manifestDependencies = library
.getManifestDependencies();
if (!manifestDependencies.isEmpty()) {
collectLibraries(manifestDependencies, manifestFiles);
}
}
}
/**
* Creates the manifest for a test variant
*
* @param testApplicationId the application id of the test application
* @param minSdkVersion the minSdkVersion of the test application
* @param targetSdkVersion the targetSdkVersion of the test application
* @param testedApplicationId the application id of the tested application
* @param instrumentationRunner the name of the instrumentation runner
* @param handleProfiling whether or not the Instrumentation object will turn profiling on and off
* @param functionalTest whether or not the Instrumentation class should run as a functional test
* @param testManifestFile optionally user provided AndroidManifest.xml for testing application
* @param libraries the library dependency graph
* @param outManifest the output location for the merged manifest
*
* @see VariantConfiguration#getApplicationId()
* @see VariantConfiguration#getTestedConfig()
* @see VariantConfiguration#getMinSdkVersion()
* @see VariantConfiguration#getTestedApplicationId()
* @see VariantConfiguration#getInstrumentationRunner()
* @see VariantConfiguration#getHandleProfiling()
* @see VariantConfiguration#getFunctionalTest()
* @see VariantConfiguration#getDirectLibraries()
*/
public void processTestManifest(
@NonNull String testApplicationId,
@Nullable String minSdkVersion,
@Nullable String targetSdkVersion,
@NonNull String testedApplicationId,
@NonNull String instrumentationRunner,
@NonNull Boolean handleProfiling,
@NonNull Boolean functionalTest,
@Nullable File testManifestFile,
@NonNull List extends ManifestDependency> libraries,
@NonNull Map manifestPlaceholders,
@NonNull File outManifest,
@NonNull File tmpDir) {
checkNotNull(testApplicationId, "testApplicationId cannot be null.");
checkNotNull(testedApplicationId, "testedApplicationId cannot be null.");
checkNotNull(instrumentationRunner, "instrumentationRunner cannot be null.");
checkNotNull(handleProfiling, "handleProfiling cannot be null.");
checkNotNull(functionalTest, "functionalTest cannot be null.");
checkNotNull(libraries, "libraries cannot be null.");
checkNotNull(outManifest, "outManifestLocation cannot be null.");
try {
tmpDir.mkdirs();
File generatedTestManifest = libraries.isEmpty() && testManifestFile == null
? outManifest : File.createTempFile("manifestMerger", ".xml", tmpDir);
mLogger.verbose("Generating in %1$s", generatedTestManifest.getAbsolutePath());
generateTestManifest(
testApplicationId,
minSdkVersion,
targetSdkVersion.equals("-1") ? null : targetSdkVersion,
testedApplicationId,
instrumentationRunner,
handleProfiling,
functionalTest,
generatedTestManifest);
if (testManifestFile != null) {
File mergedTestManifest = File.createTempFile("manifestMerger", ".xml", tmpDir);
mLogger.verbose("Merging user supplied manifest in %1$s",
generatedTestManifest.getAbsolutePath());
Invoker invoker = ManifestMerger2.newMerger(
testManifestFile, mLogger, ManifestMerger2.MergeType.APPLICATION)
.setOverride(SystemProperty.PACKAGE, testApplicationId)
.setPlaceHolderValues(manifestPlaceholders)
.setPlaceHolderValue(PlaceholderHandler.INSTRUMENTATION_RUNNER,
instrumentationRunner)
.addLibraryManifests(generatedTestManifest);
if (minSdkVersion != null) {
invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
}
if (!targetSdkVersion.equals("-1")) {
invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
}
MergingReport mergingReport = invoker.merge();
if (libraries.isEmpty()) {
handleMergingResult(mergingReport, outManifest);
} else {
handleMergingResult(mergingReport, mergedTestManifest);
generatedTestManifest = mergedTestManifest;
}
}
if (!libraries.isEmpty()) {
MergingReport mergingReport = ManifestMerger2.newMerger(
generatedTestManifest, mLogger, ManifestMerger2.MergeType.APPLICATION)
.withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)
.setOverride(SystemProperty.PACKAGE, testApplicationId)
.addLibraryManifests(collectLibraries(libraries))
.setPlaceHolderValues(manifestPlaceholders)
.merge();
handleMergingResult(mergingReport, outManifest);
}
} catch(Exception e) {
throw new RuntimeException(e);
}
}
private void handleMergingResult(@NonNull MergingReport mergingReport, @NonNull File outFile) {
switch (mergingReport.getResult()) {
case WARNING:
mergingReport.log(mLogger);
// fall through since these are just warnings.
case SUCCESS:
XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
try {
String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
mLogger.verbose(annotatedDocument);
} catch (Exception e) {
mLogger.error(e, "cannot print resulting xml");
}
save(xmlDocument, outFile);
mLogger.info("Merged manifest saved to " + outFile);
break;
case ERROR:
mergingReport.log(mLogger);
throw new RuntimeException(mergingReport.getReportString());
default:
throw new RuntimeException("Unhandled result type : "
+ mergingReport.getResult());
}
}
private static void generateTestManifest(
@NonNull String testApplicationId,
@Nullable String minSdkVersion,
@Nullable String targetSdkVersion,
@NonNull String testedApplicationId,
@NonNull String instrumentationRunner,
@NonNull Boolean handleProfiling,
@NonNull Boolean functionalTest,
@NonNull File outManifestLocation) {
TestManifestGenerator generator = new TestManifestGenerator(
outManifestLocation,
testApplicationId,
minSdkVersion,
targetSdkVersion,
testedApplicationId,
instrumentationRunner,
handleProfiling,
functionalTest);
try {
generator.generate();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Process the resources and generate R.java and/or the packaged resources.
*
* @param aaptCommand aapt command invocation parameters.
* @param enforceUniquePackageName if true method will fail if some libraries share the same
* package name
*
* @throws IOException
* @throws InterruptedException
* @throws ProcessException
*/
public void processResources(
@NonNull AaptPackageProcessBuilder aaptCommand,
boolean enforceUniquePackageName)
throws IOException, InterruptedException, ProcessException {
checkState(mTargetInfo != null,
"Cannot call processResources() before setTargetInfo() is called.");
// launch aapt: create the command line
ProcessInfo processInfo = aaptCommand.build(
mTargetInfo.getBuildTools(), mTargetInfo.getTarget(), mLogger);
ProcessResult result = mProcessExecutor.execute(processInfo, mProcessOutputHandler);
result.rethrowFailure().assertNormalExitValue();
// now if the project has libraries, R needs to be created for each libraries,
// but only if the current project is not a library.
if (aaptCommand.getSourceOutputDir() != null
&& aaptCommand.getType() != VariantType.LIBRARY
&& !aaptCommand.getLibraries().isEmpty()) {
SymbolLoader fullSymbolValues = null;
// First pass processing the libraries, collecting them by packageName,
// and ignoring the ones that have the same package name as the application
// (since that R class was already created).
String appPackageName = aaptCommand.getPackageForR();
if (appPackageName == null) {
appPackageName = VariantConfiguration.getManifestPackage(aaptCommand.getManifestFile());
}
// list of all the symbol loaders per package names.
Multimap libMap = ArrayListMultimap.create();
for (SymbolFileProvider lib : aaptCommand.getLibraries()) {
String packageName = VariantConfiguration.getManifestPackage(lib.getManifest());
if (appPackageName == null) {
continue;
}
if (appPackageName.equals(packageName)) {
if (enforceUniquePackageName) {
String msg = String.format(
"Error: A library uses the same package as this project: %s",
packageName);
throw new RuntimeException(msg);
}
// ignore libraries that have the same package name as the app
continue;
}
File rFile = lib.getSymbolFile();
// if the library has no resource, this file won't exist.
if (rFile.isFile()) {
// load the full values if that's not already been done.
// Doing it lazily allow us to support the case where there's no
// resources anywhere.
if (fullSymbolValues == null) {
fullSymbolValues = new SymbolLoader(new File(aaptCommand.getSymbolOutputDir(), "R.txt"),
mLogger);
fullSymbolValues.load();
}
SymbolLoader libSymbols = new SymbolLoader(rFile, mLogger);
libSymbols.load();
// store these symbols by associating them with the package name.
libMap.put(packageName, libSymbols);
}
}
// now loop on all the package name, merge all the symbols to write, and write them
for (String packageName : libMap.keySet()) {
Collection symbols = libMap.get(packageName);
if (enforceUniquePackageName && symbols.size() > 1) {
String msg = String.format(
"Error: more than one library with package name '%s'\n" +
"You can temporarily disable this error with android.enforceUniquePackageName=false\n" +
"However, this is temporary and will be enforced in 1.0", packageName);
throw new RuntimeException(msg);
}
SymbolWriter writer = new SymbolWriter(aaptCommand.getSourceOutputDir(), packageName,
fullSymbolValues);
for (SymbolLoader symbolLoader : symbols) {
writer.addSymbolsToWrite(symbolLoader);
}
writer.write();
}
}
}
public void generateApkData(
@NonNull File apkFile,
@NonNull File outResFolder,
@NonNull String mainPkgName,
@NonNull String resName) throws ProcessException, IOException {
// need to run aapt to get apk information
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
if (aapt == null) {
throw new IllegalStateException(
"Unable to get aapt location from Build Tools " + buildToolInfo.getRevision());
}
ApkInfoParser parser = new ApkInfoParser(new File(aapt), mProcessExecutor);
ApkInfoParser.ApkInfo apkInfo = parser.parseApk(apkFile);
if (!apkInfo.getPackageName().equals(mainPkgName)) {
throw new RuntimeException("The main and the micro apps do not have the same package name.");
}
String content = String.format(
"\n" +
"\n" +
" %2$s \n" +
" %3$s \n" +
" %4$s \n" +
" ",
apkInfo.getPackageName(),
apkInfo.getVersionCode(),
apkInfo.getVersionName(),
resName);
// xml folder
File resXmlFile = new File(outResFolder, FD_RES_XML);
resXmlFile.mkdirs();
Files.write(content,
new File(resXmlFile, ANDROID_WEAR_MICRO_APK + DOT_XML),
Charsets.UTF_8);
}
public static void generateApkDataEntryInManifest(
int minSdkVersion,
int targetSdkVersion,
@NonNull File manifestFile)
throws InterruptedException, LoggedErrorException, IOException {
StringBuilder content = new StringBuilder();
content.append("\n")
.append("\n")
.append(" \n");
content.append(" \n")
.append(" \n")
.append(" \n")
.append(" \n");
Files.write(content, manifestFile, Charsets.UTF_8);
}
/**
* Compiles all the aidl files found in the given source folders.
*
* @param sourceFolders all the source folders to find files to compile
* @param sourceOutputDir the output dir in which to generate the source code
* @param importFolders import folders
* @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
* of the compilation.
* @throws IOException
* @throws InterruptedException
* @throws LoggedErrorException
*/
public void compileAllAidlFiles(@NonNull List sourceFolders,
@NonNull File sourceOutputDir,
@Nullable File parcelableOutputDir,
@NonNull List importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor)
throws IOException, InterruptedException, LoggedErrorException, ProcessException {
checkNotNull(sourceFolders, "sourceFolders cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
checkState(mTargetInfo != null,
"Cannot call compileAllAidlFiles() before setTargetInfo() is called.");
IAndroidTarget target = mTargetInfo.getTarget();
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
if (aidl == null || !new File(aidl).isFile()) {
throw new IllegalStateException("aidl is missing");
}
List fullImportList = Lists.newArrayListWithCapacity(
sourceFolders.size() + importFolders.size());
fullImportList.addAll(sourceFolders);
fullImportList.addAll(importFolders);
AidlProcessor processor = new AidlProcessor(
aidl,
target.getPath(IAndroidTarget.ANDROID_AIDL),
fullImportList,
sourceOutputDir,
parcelableOutputDir,
dependencyFileProcessor != null ?
dependencyFileProcessor : sNoOpDependencyFileProcessor,
mProcessExecutor,
mProcessOutputHandler);
SourceSearcher searcher = new SourceSearcher(sourceFolders, "aidl");
searcher.setUseExecutor(true);
searcher.search(processor);
}
/**
* Compiles the given aidl file.
*
* @param aidlFile the AIDL file to compile
* @param sourceOutputDir the output dir in which to generate the source code
* @param importFolders all the import folders, including the source folders.
* @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
* of the compilation.
* @throws IOException
* @throws InterruptedException
* @throws LoggedErrorException
*/
public void compileAidlFile(@NonNull File sourceFolder,
@NonNull File aidlFile,
@NonNull File sourceOutputDir,
@Nullable File parcelableOutputDir,
@NonNull List importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor)
throws IOException, InterruptedException, LoggedErrorException, ProcessException {
checkNotNull(aidlFile, "aidlFile cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
checkState(mTargetInfo != null,
"Cannot call compileAidlFile() before setTargetInfo() is called.");
IAndroidTarget target = mTargetInfo.getTarget();
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
if (aidl == null || !new File(aidl).isFile()) {
throw new IllegalStateException("aidl is missing");
}
AidlProcessor processor = new AidlProcessor(
aidl,
target.getPath(IAndroidTarget.ANDROID_AIDL),
importFolders,
sourceOutputDir,
parcelableOutputDir,
dependencyFileProcessor != null ?
dependencyFileProcessor : sNoOpDependencyFileProcessor,
mProcessExecutor,
mProcessOutputHandler);
processor.processFile(sourceFolder, aidlFile);
}
/**
* Compiles all the renderscript files found in the given source folders.
*
* Right now this is the only way to compile them as the renderscript compiler requires all
* renderscript files to be passed for all compilation.
*
* Therefore whenever a renderscript file or header changes, all must be recompiled.
*
* @param sourceFolders all the source folders to find files to compile
* @param importFolders all the import folders.
* @param sourceOutputDir the output dir in which to generate the source code
* @param resOutputDir the output dir in which to generate the bitcode file
* @param targetApi the target api
* @param debugBuild whether the build is debug
* @param optimLevel the optimization level
* @param ndkMode
* @param supportMode support mode flag to generate .so files.
* @param abiFilters ABI filters in case of support mode
*
* @throws IOException
* @throws InterruptedException
* @throws LoggedErrorException
*/
public void compileAllRenderscriptFiles(@NonNull List sourceFolders,
@NonNull List importFolders,
@NonNull File sourceOutputDir,
@NonNull File resOutputDir,
@NonNull File objOutputDir,
@NonNull File libOutputDir,
int targetApi,
boolean debugBuild,
int optimLevel,
boolean ndkMode,
boolean supportMode,
@Nullable Set abiFilters)
throws InterruptedException, ProcessException, LoggedErrorException, IOException {
checkNotNull(sourceFolders, "sourceFolders cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(resOutputDir, "resOutputDir cannot be null.");
checkState(mTargetInfo != null,
"Cannot call compileAllRenderscriptFiles() before setTargetInfo() is called.");
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
String renderscript = buildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
if (renderscript == null || !new File(renderscript).isFile()) {
throw new IllegalStateException("llvm-rs-cc is missing");
}
RenderScriptProcessor processor = new RenderScriptProcessor(
sourceFolders,
importFolders,
sourceOutputDir,
resOutputDir,
objOutputDir,
libOutputDir,
buildToolInfo,
targetApi,
debugBuild,
optimLevel,
ndkMode,
supportMode,
abiFilters);
processor.build(mProcessExecutor, mProcessOutputHandler);
}
/**
* Computes and returns the leaf folders based on a given file extension.
*
* This looks through all the given root import folders, and recursively search for leaf
* folders containing files matching the given extensions. All the leaf folders are gathered
* and returned in the list.
*
* @param extension the extension to search for.
* @param importFolders an array of list of root folders.
* @return a list of leaf folder, never null.
*/
@NonNull
public List getLeafFolders(@NonNull String extension, List... importFolders) {
List results = Lists.newArrayList();
if (importFolders != null) {
for (List folders : importFolders) {
SourceSearcher searcher = new SourceSearcher(folders, extension);
searcher.setUseExecutor(false);
LeafFolderGatherer processor = new LeafFolderGatherer();
try {
searcher.search(processor);
} catch (InterruptedException e) {
// wont happen as we're not using the executor, and our processor
// doesn't throw those.
} catch (IOException e) {
// wont happen as we're not using the executor, and our processor
// doesn't throw those.
} catch (LoggedErrorException e) {
// wont happen as we're not using the executor, and our processor
// doesn't throw those.
} catch (ProcessException e) {
// wont happen as we're not using the executor, and our processor
// doesn't throw those.
}
results.addAll(processor.getFolders());
}
}
return results;
}
/**
* Converts the bytecode to Dalvik format
* @param inputs the input files
* @param preDexedLibraries the list of pre-dexed libraries
* @param outDexFolder the location of the output folder
* @param dexOptions dex options
* @param additionalParameters list of additional parameters to give to dx
* @param incremental true if it should attempt incremental dex if applicable
*
* @throws IOException
* @throws InterruptedException
* @throws ProcessException
*/
public void convertByteCode(
@NonNull Collection inputs,
@NonNull Collection preDexedLibraries,
@NonNull File outDexFolder,
boolean multidex,
boolean multidexLegacy,
@Nullable File mainDexList,
@NonNull DexOptions dexOptions,
@Nullable List additionalParameters,
@NonNull File tmpFolder,
boolean incremental,
boolean optimize) throws IOException, InterruptedException, ProcessException {
checkNotNull(inputs, "inputs cannot be null.");
checkNotNull(preDexedLibraries, "preDexedLibraries cannot be null.");
checkNotNull(outDexFolder, "outDexFolder cannot be null.");
checkNotNull(dexOptions, "dexOptions cannot be null.");
checkNotNull(tmpFolder, "tmpFolder cannot be null");
checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
checkArgument(tmpFolder.isDirectory(), "tmpFolder must be a folder");
checkState(mTargetInfo != null,
"Cannot call convertByteCode() before setTargetInfo() is called.");
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);
builder.setVerbose(mVerboseExec)
.setIncremental(incremental)
.setNoOptimize(!optimize)
.setMultiDex(multidex)
.setMainDexList(mainDexList)
.addInputs(preDexedLibraries)
.addInputs(inputs);
if (additionalParameters != null) {
builder.additionalParameters(additionalParameters);
}
JavaProcessInfo javaProcessInfo = builder.build(buildToolInfo, dexOptions);
ProcessResult result = mJavaProcessExecutor.execute(javaProcessInfo, mProcessOutputHandler);
result.rethrowFailure().assertNormalExitValue();
}
public Set createMainDexList(
@NonNull File allClassesJarFile,
@NonNull File jarOfRoots) throws ProcessException {
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
ProcessInfoBuilder builder = new ProcessInfoBuilder();
String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
if (dx == null || !new File(dx).isFile()) {
throw new IllegalStateException("dx.jar is missing");
}
builder.setClasspath(dx);
builder.setMain("com.android.multidex.ClassReferenceListBuilder");
builder.addArgs(jarOfRoots.getAbsolutePath());
builder.addArgs(allClassesJarFile.getAbsolutePath());
CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler();
mJavaProcessExecutor.execute(builder.createJavaProcess(), processOutputHandler)
.rethrowFailure()
.assertNormalExitValue();
String content = processOutputHandler.getProcessOutput().getStandardOutputAsString();
return Sets.newHashSet(Splitter.on('\n').split(content));
}
/**
* Converts the bytecode to Dalvik format
* @param inputFile the input file
* @param outFile the output file or folder if multi-dex is enabled.
* @param multiDex whether multidex is enabled.
* @param dexOptions dex options
*
* @throws IOException
* @throws InterruptedException
* @throws ProcessException
*/
public void preDexLibrary(
@NonNull File inputFile,
@NonNull File outFile,
boolean multiDex,
@NonNull DexOptions dexOptions)
throws IOException, InterruptedException, ProcessException {
checkState(mTargetInfo != null,
"Cannot call preDexLibrary() before setTargetInfo() is called.");
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
PreDexCache.getCache().preDexLibrary(
inputFile,
outFile,
multiDex,
dexOptions,
buildToolInfo,
mVerboseExec,
mJavaProcessExecutor,
mProcessOutputHandler);
}
/**
* Converts the bytecode to Dalvik format
*
* @param inputFile the input file
* @param outFile the output file or folder if multi-dex is enabled.
* @param multiDex whether multidex is enabled.
* @param dexOptions the dex options
* @param buildToolInfo the build tools info
* @param verbose verbose flag
* @param processExecutor the java process executor
* @return the list of generated files.
* @throws ProcessException
*/
@NonNull
public static List preDexLibrary(
@NonNull File inputFile,
@NonNull File outFile,
boolean multiDex,
@NonNull DexOptions dexOptions,
@NonNull BuildToolInfo buildToolInfo,
boolean verbose,
@NonNull JavaProcessExecutor processExecutor,
@NonNull ProcessOutputHandler processOutputHandler)
throws ProcessException {
checkNotNull(inputFile, "inputFile cannot be null.");
checkNotNull(outFile, "outFile cannot be null.");
checkNotNull(dexOptions, "dexOptions cannot be null.");
DexProcessBuilder builder = new DexProcessBuilder(outFile);
builder.setVerbose(verbose)
.setMultiDex(multiDex)
.addInput(inputFile);
JavaProcessInfo javaProcessInfo = builder.build(buildToolInfo, dexOptions);
ProcessResult result = processExecutor.execute(javaProcessInfo, processOutputHandler);
result.rethrowFailure().assertNormalExitValue();
if (multiDex) {
File[] files = outFile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.endsWith(DOT_DEX);
}
});
if (files == null || files.length == 0) {
throw new RuntimeException("No dex files created at " + outFile.getAbsolutePath());
}
return Lists.newArrayList(files);
} else {
return Collections.singletonList(outFile);
}
}
public void convertByteCodeWithJack(
@NonNull File dexOutputFolder,
@NonNull File jackOutputFile,
@NonNull String classpath,
@NonNull Collection packagedLibraries,
@NonNull File ecjOptionFile,
@Nullable Collection proguardFiles,
@Nullable File mappingFile,
boolean multiDex,
int minSdkVersion,
boolean debugLog,
String javaMaxHeapSize) throws ProcessException {
JackProcessBuilder builder = new JackProcessBuilder();
builder.setDebugLog(debugLog)
.setVerbose(mVerboseExec)
.setJavaMaxHeapSize(javaMaxHeapSize)
.setClasspath(classpath)
.setDexOutputFolder(dexOutputFolder)
.setJackOutputFile(jackOutputFile)
.addImportFiles(packagedLibraries)
.setEcjOptionFile(ecjOptionFile);
if (proguardFiles != null) {
builder.addProguardFiles(proguardFiles).setMappingFile(mappingFile);
}
if (multiDex) {
builder.setMultiDex(true).setMinSdkVersion(minSdkVersion);
}
mJavaProcessExecutor.execute(
builder.build(mTargetInfo.getBuildTools()), mProcessOutputHandler)
.rethrowFailure().assertNormalExitValue();
}
/**
* Converts the bytecode of a library to the jack format
* @param inputFile the input file
* @param outFile the location of the output classes.dex file
* @param dexOptions dex options
*
* @throws ProcessException
* @throws IOException
* @throws InterruptedException
*/
public void convertLibraryToJack(
@NonNull File inputFile,
@NonNull File outFile,
@NonNull DexOptions dexOptions)
throws ProcessException, IOException, InterruptedException {
checkState(mTargetInfo != null,
"Cannot call preJackLibrary() before setTargetInfo() is called.");
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
JackConversionCache.getCache().convertLibrary(
inputFile,
outFile,
dexOptions,
buildToolInfo,
mVerboseExec,
mJavaProcessExecutor,
mProcessOutputHandler);
}
public static List convertLibraryToJack(
@NonNull File inputFile,
@NonNull File outFile,
@NonNull DexOptions dexOptions,
@NonNull BuildToolInfo buildToolInfo,
boolean verbose,
@NonNull JavaProcessExecutor processExecutor,
@NonNull ProcessOutputHandler processOutputHandler)
throws ProcessException {
checkNotNull(inputFile, "inputFile cannot be null.");
checkNotNull(outFile, "outFile cannot be null.");
checkNotNull(dexOptions, "dexOptions cannot be null.");
// launch dx: create the command line
ProcessInfoBuilder builder = new ProcessInfoBuilder();
String jill = buildToolInfo.getPath(BuildToolInfo.PathId.JILL);
if (jill == null || !new File(jill).isFile()) {
throw new IllegalStateException("jill.jar is missing");
}
builder.setClasspath(jill);
builder.setMain("com.android.jill.Main");
if (dexOptions.getJavaMaxHeapSize() != null) {
builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
}
builder.addArgs(inputFile.getAbsolutePath());
builder.addArgs("--output");
builder.addArgs(outFile.getAbsolutePath());
if (verbose) {
builder.addArgs("--verbose");
}
JavaProcessInfo javaProcessInfo = builder.createJavaProcess();
ProcessResult result = processExecutor.execute(javaProcessInfo, processOutputHandler);
result.rethrowFailure().assertNormalExitValue();
return Collections.singletonList(outFile);
}
/**
* Packages the apk.
*
* @param androidResPkgLocation the location of the packaged resource file
* @param dexFolder the folder with the dex file.
* @param dexedLibraries optional collection of additional dex files to put in the apk.
* @param packagedJars the jars that are packaged (libraries + jar dependencies)
* @param javaResourcesLocation the processed Java resource folder
* @param jniLibsFolders the folders containing jni shared libraries
* @param abiFilters optional ABI filter
* @param jniDebugBuild whether the app should include jni debug data
* @param signingConfig the signing configuration
* @param packagingOptions the packaging options
* @param outApkLocation location of the APK.
* @throws DuplicateFileException
* @throws FileNotFoundException if the store location was not found
* @throws KeytoolException
* @throws PackagerException
* @throws SigningException when the key cannot be read from the keystore
*
* @see VariantConfiguration#getPackagedJars()
*/
public void packageApk(
@NonNull String androidResPkgLocation,
@Nullable File dexFolder,
@NonNull Collection dexedLibraries,
@NonNull Collection packagedJars,
@Nullable String javaResourcesLocation,
@Nullable Collection jniLibsFolders,
@Nullable Set abiFilters,
boolean jniDebugBuild,
@Nullable SigningConfig signingConfig,
@Nullable PackagingOptions packagingOptions,
@NonNull String outApkLocation)
throws DuplicateFileException, FileNotFoundException,
KeytoolException, PackagerException, SigningException {
checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
checkNotNull(outApkLocation, "outApkLocation cannot be null.");
CertificateInfo certificateInfo = null;
if (signingConfig != null && signingConfig.isSigningReady()) {
//noinspection ConstantConditions
certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
signingConfig.getStoreFile(), signingConfig.getStorePassword(),
signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
if (certificateInfo == null) {
throw new SigningException("Failed to read key from keystore");
}
}
try {
Packager packager = new Packager(
outApkLocation, androidResPkgLocation,
certificateInfo, mCreatedBy, packagingOptions, mLogger);
// add dex folder to the apk root.
if (dexFolder != null) {
if (!dexFolder.isDirectory()) {
throw new IllegalArgumentException("dexFolder must be a directory");
}
packager.addDexFiles(dexFolder, dexedLibraries);
}
packager.setJniDebugMode(jniDebugBuild);
// figure out conflicts!
JavaResourceProcessor resProcessor = new JavaResourceProcessor(packager);
if (javaResourcesLocation != null) {
resProcessor.addSourceFolder(javaResourcesLocation);
}
// add the resources from the jar files.
Set hashs = Sets.newHashSet();
for (File jar : packagedJars) {
// TODO remove once we can properly add a library as a dependency of its test.
String hash = getFileHash(jar);
if (hash == null) {
throw new PackagerException("Unable to compute hash of " + jar.getAbsolutePath());
}
if (hashs.contains(hash)) {
continue;
}
hashs.add(hash);
packager.addResourcesFromJar(jar);
}
// also add resources from library projects and jars
if (jniLibsFolders != null) {
for (File jniFolder : jniLibsFolders) {
if (jniFolder.isDirectory()) {
packager.addNativeLibraries(jniFolder, abiFilters);
}
}
}
packager.sealApk();
} catch (SealedPackageException e) {
// shouldn't happen since we control the package from start to end.
throw new RuntimeException(e);
}
}
/**
* Signs a single jar file using the passed {@link SigningConfig}.
* @param in the jar file to sign.
* @param signingConfig the signing configuration
* @param out the file path for the signed jar.
* @throws IOException
* @throws KeytoolException
* @throws SigningException
* @throws NoSuchAlgorithmException
* @throws SignedJarBuilder.IZipEntryFilter.ZipAbortException
* @throws com.android.builder.signing.SigningException
*/
public void signApk(File in, SigningConfig signingConfig, File out)
throws IOException, KeytoolException, SigningException, NoSuchAlgorithmException,
SignedJarBuilder.IZipEntryFilter.ZipAbortException,
com.android.builder.signing.SigningException {
CertificateInfo certificateInfo = null;
if (signingConfig != null && signingConfig.isSigningReady()) {
certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
signingConfig.getStoreFile(), signingConfig.getStorePassword(),
signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
if (certificateInfo == null) {
throw new SigningException("Failed to read key from keystore");
}
}
SignedJarBuilder signedJarBuilder = new SignedJarBuilder(
new FileOutputStream(out),
certificateInfo != null ? certificateInfo.getKey() : null,
certificateInfo != null ? certificateInfo.getCertificate() : null,
Packager.getLocalVersion(), mCreatedBy);
signedJarBuilder.writeZip(new FileInputStream(in), null);
signedJarBuilder.close();
}
/**
* Returns the hash of a file.
* @param file the file to hash
* @return the hash or null if an error happened
*/
@Nullable
private static String getFileHash(@NonNull File file) {
try {
HashCode hashCode = Files.hash(file, Hashing.sha1());
return hashCode.toString();
} catch (IOException ignored) {
}
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy