blob: 9442f4d35577557e0aad665e1f08569abf5704ba [file] [log] [blame]
/*
* 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;
import com.android.SdkConstants;
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.BuildConfigGenerator;
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.FileGatherer;
import com.android.builder.internal.compiler.LeafFolderGatherer;
import com.android.builder.internal.compiler.SourceSearcher;
import com.android.builder.internal.packaging.JavaResourceProcessor;
import com.android.builder.internal.packaging.Packager;
import com.android.builder.model.AaptOptions;
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.signing.CertificateInfo;
import com.android.builder.signing.KeystoreHelper;
import com.android.builder.signing.KeytoolException;
import com.android.ide.common.internal.AaptRunner;
import com.android.ide.common.internal.CommandLineRunner;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.manifmerger.ManifestMerger;
import com.android.manifmerger.MergerLog;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repository.FullRevision;
import com.android.utils.ILogger;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.io.Files;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* 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(SdkParser, String, ILogger, boolean)}
*
* then build steps can be done with
* {@link #generateBuildConfig(String, boolean, java.util.List, String)}
* {@link #processManifest(java.io.File, java.util.List, java.util.List, String, int, String, int, int, String)}
* {@link #processTestManifest(String, int, int, String, String, java.util.List, String)}
* {@link #processResources(java.io.File, java.io.File, java.io.File, java.util.List, String, String, String, String, String, com.android.builder.VariantConfiguration.Type, boolean, com.android.builder.model.AaptOptions)}
* {@link #compileAllAidlFiles(java.util.List, java.io.File, java.util.List, com.android.builder.compiling.DependencyFileProcessor)}
* {@link #convertByteCode(Iterable, Iterable, File, String, DexOptions, boolean)}
* {@link #packageApk(String, String, java.util.List, String, String, boolean, SigningConfig, String)}
*
* Java compilation is not handled but the builder provides the bootclasspath with
* {@link #getBootClasspath(SdkParser)}.
*/
public class AndroidBuilder {
private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(16, 0, 0);
private static final DependencyFileProcessor sNoOpDependencyFileProcessor = new DependencyFileProcessor() {
@Override
public boolean processFile(@NonNull File dependencyFile) {
return true;
}
};
private final SdkParser mSdkParser;
private final ILogger mLogger;
private final CommandLineRunner mCmdLineRunner;
private final boolean mVerboseExec;
@NonNull
private final IAndroidTarget mTarget;
@NonNull
private final BuildToolInfo mBuildTools;
private String mCreatedBy;
/**
* Creates an AndroidBuilder
* <p/>
* This receives an {@link SdkParser} to provide the build with information about the SDK, as
* well as an {@link ILogger} to display output.
* <p/>
* <var>verboseExec</var> is needed on top of the ILogger due to remote exec tools not being
* able to output info and verbose messages separately.
*
* @param sdkParser the SdkParser
* @param logger the Logger
* @param verboseExec whether external tools are launched in verbose mode
*/
public AndroidBuilder(
@NonNull SdkParser sdkParser,
@Nullable String createdBy,
@NonNull ILogger logger,
boolean verboseExec) {
mCreatedBy = createdBy;
mSdkParser = checkNotNull(sdkParser);
mLogger = checkNotNull(logger);
mVerboseExec = verboseExec;
mCmdLineRunner = new CommandLineRunner(mLogger);
BuildToolInfo buildToolInfo = mSdkParser.getBuildTools();
FullRevision buildToolsRevision = buildToolInfo.getRevision();
if (buildToolsRevision.compareTo(MIN_BUILD_TOOLS_REV) < 0) {
throw new IllegalArgumentException(String.format(
"The SDK Build Tools revision (%1$s) is too low. Minimum required is %2$s",
buildToolsRevision, MIN_BUILD_TOOLS_REV));
}
mTarget = mSdkParser.getTarget();
mBuildTools = mSdkParser.getBuildTools();
}
@VisibleForTesting
AndroidBuilder(
@NonNull SdkParser sdkParser,
@NonNull CommandLineRunner cmdLineRunner,
@NonNull ILogger logger,
boolean verboseExec) {
mSdkParser = checkNotNull(sdkParser);
mCmdLineRunner = checkNotNull(cmdLineRunner);
mLogger = checkNotNull(logger);
mVerboseExec = verboseExec;
mTarget = mSdkParser.getTarget();
mBuildTools = mSdkParser.getBuildTools();
}
/**
* Helper method to get the boot classpath to be used during compilation.
*/
public static List<String> getBootClasspath(@NonNull SdkParser sdkParser) {
List<String> classpath = Lists.newArrayList();
IAndroidTarget target = sdkParser.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(sdkParser.getAnnotationsJar());
}
return classpath;
}
/**
* Returns an {@link AaptRunner} able to run aapt commands.
* @return an AaptRunner object
*/
public AaptRunner getAaptRunner() {
return new AaptRunner(
mBuildTools.getPath(BuildToolInfo.PathId.AAPT),
mCmdLineRunner);
}
/**
* Generate the BuildConfig class for the project.
* @param packageName the package in which to generate the class
* @param debuggable whether the app is considered debuggable
* @param javaLines additional java lines to put in the class. These must be valid Java lines.
* @param sourceOutputDir directory where to put this. This is the source folder, not the
* package folder.
* @throws IOException
*/
public void generateBuildConfig(
@NonNull String packageName,
boolean debuggable,
@NonNull List<String> javaLines,
@NonNull String sourceOutputDir) throws IOException {
BuildConfigGenerator generator = new BuildConfigGenerator(
sourceOutputDir, packageName, debuggable);
generator.generate(javaLines);
}
/**
* Merges all the manifests into a single manifest
*
* @param mainManifest The main manifest of the application.
* @param manifestOverlays manifest overlays coming from flavors and build types
* @param libraries the library dependency graph
* @param packageOverride a package name override. Can be null.
* @param versionCode a version code to inject in the manifest or -1 to do nothing.
* @param versionName a version name to inject in the manifest or null to do nothing.
* @param minSdkVersion a minSdkVersion to inject in the manifest or -1 to do nothing.
* @param targetSdkVersion a targetSdkVersion to inject in the manifest or -1 to do nothing.
* @param outManifestLocation the output location for the merged manifest
*
* @see com.android.builder.VariantConfiguration#getMainManifest()
* @see com.android.builder.VariantConfiguration#getManifestOverlays()
* @see com.android.builder.VariantConfiguration#getDirectLibraries()
* @see com.android.builder.VariantConfiguration#getMergedFlavor()
* @see DefaultProductFlavor#getVersionCode()
* @see DefaultProductFlavor#getVersionName()
* @see DefaultProductFlavor#getMinSdkVersion()
* @see DefaultProductFlavor#getTargetSdkVersion()
*/
public void processManifest(
@NonNull File mainManifest,
@NonNull List<File> manifestOverlays,
@NonNull List<? extends ManifestDependency> libraries,
String packageOverride,
int versionCode,
String versionName,
int minSdkVersion,
int targetSdkVersion,
@NonNull String outManifestLocation) {
checkNotNull(mainManifest, "mainManifest cannot be null.");
checkNotNull(manifestOverlays, "manifestOverlays cannot be null.");
checkNotNull(libraries, "libraries cannot be null.");
checkNotNull(outManifestLocation, "outManifestLocation cannot be null.");
try {
Map<String, String> attributeInjection = getAttributeInjectionMap(
versionCode, versionName, minSdkVersion, targetSdkVersion);
if (manifestOverlays.isEmpty() && libraries.isEmpty()) {
// if no manifest to merge, just copy to location, unless we have to inject
// attributes
if (attributeInjection.isEmpty() && packageOverride == null) {
Files.copy(mainManifest, new File(outManifestLocation));
} else {
ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), null);
doMerge(merger, new File(outManifestLocation), mainManifest,
attributeInjection, packageOverride);
}
} else {
File outManifest = new File(outManifestLocation);
// first merge the app manifest.
if (!manifestOverlays.isEmpty()) {
File mainManifestOut = outManifest;
// if there is also libraries, put this in a temp file.
if (!libraries.isEmpty()) {
// TODO find better way of storing intermediary file?
mainManifestOut = File.createTempFile("manifestMerge", ".xml");
mainManifestOut.deleteOnExit();
}
ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), null);
doMerge(merger, mainManifestOut, mainManifest, manifestOverlays,
attributeInjection, packageOverride);
// now the main manifest is the newly merged one
mainManifest = mainManifestOut;
// and the attributes have been inject, no need to do it below
attributeInjection = null;
}
if (!libraries.isEmpty()) {
// recursively merge all manifests starting with the leaves and up toward the
// root (the app)
mergeLibraryManifests(mainManifest, libraries,
new File(outManifestLocation), attributeInjection, packageOverride);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Creates the manifest for a test variant
*
* @param testPackageName the package name of the test application
* @param minSdkVersion the minSdkVersion of the test application
* @param targetSdkVersion the targetSdkVersion of the test application
* @param testedPackageName the package name of the tested application
* @param instrumentationRunner the name of the instrumentation runner
* @param libraries the library dependency graph
* @param outManifestLocation the output location for the merged manifest
*
* @see com.android.builder.VariantConfiguration#getPackageName()
* @see com.android.builder.VariantConfiguration#getTestedConfig()
* @see com.android.builder.VariantConfiguration#getMinSdkVersion()
* @see com.android.builder.VariantConfiguration#getTestedPackageName()
* @see com.android.builder.VariantConfiguration#getInstrumentationRunner()
* @see com.android.builder.VariantConfiguration#getDirectLibraries()
*/
public void processTestManifest(
@NonNull String testPackageName,
int minSdkVersion,
int targetSdkVersion,
@NonNull String testedPackageName,
@NonNull String instrumentationRunner,
@NonNull List<? extends ManifestDependency> libraries,
@NonNull String outManifestLocation) {
checkNotNull(testPackageName, "testPackageName cannot be null.");
checkNotNull(testedPackageName, "testedPackageName cannot be null.");
checkNotNull(instrumentationRunner, "instrumentationRunner cannot be null.");
checkNotNull(libraries, "libraries cannot be null.");
checkNotNull(outManifestLocation, "outManifestLocation cannot be null.");
if (!libraries.isEmpty()) {
try {
// create the test manifest, merge the libraries in it
File generatedTestManifest = File.createTempFile("manifestMerge", ".xml");
generateTestManifest(
testPackageName,
minSdkVersion,
targetSdkVersion,
testedPackageName,
instrumentationRunner,
generatedTestManifest.getAbsolutePath());
mergeLibraryManifests(
generatedTestManifest,
libraries,
new File(outManifestLocation),
null, null);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
generateTestManifest(
testPackageName,
minSdkVersion,
targetSdkVersion,
testedPackageName,
instrumentationRunner,
outManifestLocation);
}
}
private void generateTestManifest(
String testPackageName,
int minSdkVersion,
int targetSdkVersion,
String testedPackageName,
String instrumentationRunner,
String outManifestLocation) {
TestManifestGenerator generator = new TestManifestGenerator(
outManifestLocation,
testPackageName,
minSdkVersion,
targetSdkVersion,
testedPackageName,
instrumentationRunner);
try {
generator.generate();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@NonNull
private Map<String, String> getAttributeInjectionMap(
int versionCode,
@Nullable String versionName,
int minSdkVersion,
int targetSdkVersion) {
Map<String, String> attributeInjection = Maps.newHashMap();
if (versionCode != -1) {
attributeInjection.put(
"/manifest|http://schemas.android.com/apk/res/android versionCode",
Integer.toString(versionCode));
}
if (versionName != null) {
attributeInjection.put(
"/manifest|http://schemas.android.com/apk/res/android versionName",
versionName);
}
if (minSdkVersion != -1) {
attributeInjection.put(
"/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion",
Integer.toString(minSdkVersion));
}
if (targetSdkVersion != -1) {
attributeInjection.put(
"/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion",
Integer.toString(targetSdkVersion));
}
return attributeInjection;
}
/**
* Merges library manifests into a main manifest.
* @param mainManifest the main manifest
* @param directLibraries the libraries to merge
* @param outManifest the output file
* @throws IOException
*/
private void mergeLibraryManifests(
File mainManifest,
Iterable<? extends ManifestDependency> directLibraries,
File outManifest, Map<String, String> attributeInjection, String packageOverride)
throws IOException {
List<File> manifests = Lists.newArrayList();
for (ManifestDependency library : directLibraries) {
List<? extends ManifestDependency> subLibraries = library.getManifestDependencies();
if (subLibraries.isEmpty()) {
manifests.add(library.getManifest());
} else {
File mergeLibManifest = File.createTempFile("manifestMerge", ".xml");
mergeLibManifest.deleteOnExit();
// don't insert the attribute injection into libraries
mergeLibraryManifests(
library.getManifest(), subLibraries, mergeLibManifest, null, null);
manifests.add(mergeLibManifest);
}
}
ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), null);
doMerge(merger, outManifest, mainManifest, manifests, attributeInjection, packageOverride);
}
private void doMerge(ManifestMerger merger, File output, File input,
Map<String, String> injectionMap, String packageOverride) {
List<File> list = Collections.emptyList();
doMerge(merger, output, input, list, injectionMap, packageOverride);
}
private void doMerge(ManifestMerger merger, File output, File input, List<File> subManifests,
Map<String, String> injectionMap, String packageOverride) {
if (!merger.process(output, input,
subManifests.toArray(new File[subManifests.size()]),
injectionMap, packageOverride)) {
throw new RuntimeException("Manifest merging failed. See console for more info.");
}
}
/**
* Process the resources and generate R.java and/or the packaged resources.
*
* @param manifestFile the location of the manifest file
* @param resFolder the merged res folder
* @param assetsDir the merged asset folder
* @param libraries the flat list of libraries
* @param packageForR Package override to generate the R class in a different package.
* @param sourceOutputDir optional source folder to generate R.java
* @param resPackageOutput optional filepath for packaged resources
* @param proguardOutput optional filepath for proguard file to generate
* @param type the type of the variant being built
* @param debuggable whether the app is debuggable
* @param options the {@link com.android.builder.model.AaptOptions}
*
*
* @throws IOException
* @throws InterruptedException
* @throws LoggedErrorException
*/
public void processResources(
@NonNull File manifestFile,
@NonNull File resFolder,
@Nullable File assetsDir,
@NonNull List<? extends SymbolFileProvider> libraries,
@Nullable String packageForR,
@Nullable String sourceOutputDir,
@Nullable String symbolOutputDir,
@Nullable String resPackageOutput,
@Nullable String proguardOutput,
VariantConfiguration.Type type,
boolean debuggable,
@NonNull AaptOptions options)
throws IOException, InterruptedException, LoggedErrorException {
checkNotNull(manifestFile, "manifestFile cannot be null.");
checkNotNull(resFolder, "resFolder cannot be null.");
checkNotNull(libraries, "libraries cannot be null.");
checkNotNull(options, "options cannot be null.");
// if both output types are empty, then there's nothing to do and this is an error
checkArgument(sourceOutputDir != null || resPackageOutput != null,
"No output provided for aapt task");
// launch aapt: create the command line
ArrayList<String> command = Lists.newArrayList();
String aapt = mBuildTools.getPath(BuildToolInfo.PathId.AAPT);
if (aapt == null || !new File(aapt).isFile()) {
throw new IllegalStateException("aapt is missing");
}
command.add(aapt);
command.add("package");
if (mVerboseExec) {
command.add("-v");
}
command.add("-f");
command.add("--no-crunch");
// inputs
command.add("-I");
command.add(mTarget.getPath(IAndroidTarget.ANDROID_JAR));
command.add("-M");
command.add(manifestFile.getAbsolutePath());
if (resFolder.isDirectory()) {
command.add("-S");
command.add(resFolder.getAbsolutePath());
}
if (assetsDir != null && assetsDir.isDirectory()) {
command.add("-A");
command.add(assetsDir.getAbsolutePath());
}
// outputs
if (sourceOutputDir != null) {
command.add("-m");
command.add("-J");
command.add(sourceOutputDir);
}
if (type != VariantConfiguration.Type.LIBRARY && resPackageOutput != null) {
command.add("-F");
command.add(resPackageOutput);
if (proguardOutput != null) {
command.add("-G");
command.add(proguardOutput);
}
}
// options controlled by build variants
if (debuggable) {
command.add("--debug-mode");
}
if (type == VariantConfiguration.Type.DEFAULT) {
if (packageForR != null) {
command.add("--custom-package");
command.add(packageForR);
mLogger.verbose("Custom package for R class: '%s'", packageForR);
}
}
// library specific options
if (type == VariantConfiguration.Type.LIBRARY) {
command.add("--non-constant-id");
}
// AAPT options
String ignoreAssets = options.getIgnoreAssets();
if (ignoreAssets != null) {
command.add("--ignore-assets");
command.add(ignoreAssets);
}
List<String> noCompressList = options.getNoCompress();
if (noCompressList != null) {
for (String noCompress : noCompressList) {
command.add("-0");
command.add(noCompress);
}
}
if (symbolOutputDir != null &&
(type == VariantConfiguration.Type.LIBRARY || !libraries.isEmpty())) {
command.add("--output-text-symbols");
command.add(symbolOutputDir);
}
mCmdLineRunner.runCmdLine(command, null);
// 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 (type != VariantConfiguration.Type.LIBRARY && !libraries.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 = packageForR;
if (appPackageName == null) {
appPackageName = VariantConfiguration.getManifestPackage(manifestFile);
}
// list of all the symbol loaders per package names.
Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
for (SymbolFileProvider lib : libraries) {
File rFile = lib.getSymbolFile();
// if the library has no resource, this file won't exist.
if (rFile.isFile()) {
String packageName = VariantConfiguration.getManifestPackage(lib.getManifest());
if (appPackageName.equals(packageName)) {
// ignore libraries that have the same package name as the app
continue;
}
// 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(symbolOutputDir, "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<SymbolLoader> symbols = libMap.get(packageName);
SymbolWriter writer = new SymbolWriter(sourceOutputDir, packageName,
fullSymbolValues);
for (SymbolLoader symbolLoader : symbols) {
writer.addSymbolsToWrite(symbolLoader);
}
writer.write();
}
}
}
/**
* 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<File> sourceFolders,
@NonNull File sourceOutputDir,
@NonNull List<File> importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor)
throws IOException, InterruptedException, LoggedErrorException {
checkNotNull(sourceFolders, "sourceFolders cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
String aidl = mBuildTools.getPath(BuildToolInfo.PathId.AIDL);
if (aidl == null || !new File(aidl).isFile()) {
throw new IllegalStateException("aidl is missing");
}
List<File> fullImportList = Lists.newArrayListWithCapacity(
sourceFolders.size() + importFolders.size());
fullImportList.addAll(sourceFolders);
fullImportList.addAll(importFolders);
AidlProcessor processor = new AidlProcessor(
aidl,
mTarget.getPath(IAndroidTarget.ANDROID_AIDL),
fullImportList,
sourceOutputDir,
dependencyFileProcessor != null ?
dependencyFileProcessor : sNoOpDependencyFileProcessor,
mCmdLineRunner);
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 aidlFile,
@NonNull File sourceOutputDir,
@NonNull List<File> importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor)
throws IOException, InterruptedException, LoggedErrorException {
checkNotNull(aidlFile, "aidlFile cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
String aidl = mBuildTools.getPath(BuildToolInfo.PathId.AIDL);
if (aidl == null || !new File(aidl).isFile()) {
throw new IllegalStateException("aidl is missing");
}
AidlProcessor processor = new AidlProcessor(
aidl,
mTarget.getPath(IAndroidTarget.ANDROID_AIDL),
importFolders,
sourceOutputDir,
dependencyFileProcessor != null ?
dependencyFileProcessor : sNoOpDependencyFileProcessor,
mCmdLineRunner);
processor.processFile(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
*
* @throws IOException
* @throws InterruptedException
* @throws LoggedErrorException
*/
public void compileAllRenderscriptFiles(@NonNull List<File> sourceFolders,
@NonNull List<File> importFolders,
@NonNull File sourceOutputDir,
@NonNull File resOutputDir,
int targetApi,
boolean debugBuild,
int optimLevel)
throws IOException, InterruptedException, LoggedErrorException {
checkNotNull(sourceFolders, "sourceFolders cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(resOutputDir, "resOutputDir cannot be null.");
String renderscript = mBuildTools.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
if (renderscript == null || !new File(renderscript).isFile()) {
throw new IllegalStateException("llvm-rs-cc is missing");
}
// gather the files to compile
FileGatherer fileGatherer = new FileGatherer();
SourceSearcher searcher = new SourceSearcher(sourceFolders, "rs", "fs");
searcher.setUseExecutor(false);
searcher.search(fileGatherer);
List<File> renderscriptFiles = fileGatherer.getFiles();
if (renderscriptFiles.isEmpty()) {
return;
}
String rsPath = mBuildTools.getPath(BuildToolInfo.PathId.ANDROID_RS);
String rsClangPath = mBuildTools.getPath(BuildToolInfo.PathId.ANDROID_RS_CLANG);
// the renderscript compiler doesn't expect the top res folder,
// but the raw folder directly.
File rawFolder = new File(resOutputDir, SdkConstants.FD_RES_RAW);
// compile all the files in a single pass
ArrayList<String> command = Lists.newArrayList();
command.add(renderscript);
if (debugBuild) {
command.add("-g");
}
command.add("-O");
command.add(Integer.toString(optimLevel));
// add all import paths
command.add("-I");
command.add(rsPath);
command.add("-I");
command.add(rsClangPath);
for (File importPath : importFolders) {
if (importPath.isDirectory()) {
command.add("-I");
command.add(importPath.getAbsolutePath());
}
}
// source output
command.add("-p");
command.add(sourceOutputDir.getAbsolutePath());
// res output
command.add("-o");
command.add(rawFolder.getAbsolutePath());
command.add("-target-api");
command.add(Integer.toString(targetApi < 11 ? 11 : targetApi));
// input files
for (File sourceFile : renderscriptFiles) {
command.add(sourceFile.getAbsolutePath());
}
Map<String, String> env = null;
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
env = Maps.newHashMap();
env.put("DYLD_LIBRARY_PATH", mBuildTools.getLocation().getAbsolutePath());
} else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
env = Maps.newHashMap();
env.put("LD_LIBRARY_PATH", mBuildTools.getLocation().getAbsolutePath());
}
mCmdLineRunner.runCmdLine(command, env);
}
/**
* 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<File> getLeafFolders(@NonNull String extension, List<File>... importFolders) {
List<File> results = Lists.newArrayList();
if (importFolders != null) {
for (List<File> 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.
}
results.addAll(processor.getFolders());
}
}
return results;
}
/**
* Converts the bytecode to Dalvik format
* @param classesLocation the location of the compiler output
* @param libraries the list of libraries
* @param outDexFile the location of the output classes.dex file
* @param dexOptions dex options
* @param incremental true if it should attempt incremental dex if applicable
*
* @throws IOException
* @throws InterruptedException
* @throws LoggedErrorException
*/
public void convertByteCode(
@NonNull Iterable<File> classesLocation,
@NonNull Iterable<File> libraries,
@Nullable File proguardFile,
@NonNull String outDexFile,
@NonNull DexOptions dexOptions,
boolean incremental) throws IOException, InterruptedException, LoggedErrorException {
checkNotNull(classesLocation, "classesLocation cannot be null.");
checkNotNull(libraries, "libraries cannot be null.");
checkNotNull(outDexFile, "outDexFile cannot be null.");
checkNotNull(dexOptions, "dexOptions cannot be null.");
// launch dx: create the command line
ArrayList<String> command = Lists.newArrayList();
String dx = mBuildTools.getPath(BuildToolInfo.PathId.DX);
if (dx == null || !new File(dx).isFile()) {
throw new IllegalStateException("dx is missing");
}
command.add(dx);
if (dexOptions.getJavaMaxHeapSize() != null) {
command.add("-JXmx" + dexOptions.getJavaMaxHeapSize());
}
command.add("--dex");
if (mVerboseExec) {
command.add("--verbose");
}
if (dexOptions.isCoreLibrary()) {
command.add("--core-library");
}
if (incremental) {
command.add("--incremental");
command.add("--no-strict");
}
command.add("--output");
command.add(outDexFile);
// clean up and add class inputs
List<String> classesList = Lists.newArrayList();
for (File f : classesLocation) {
if (f != null && f.exists()) {
classesList.add(f.getAbsolutePath());
}
}
if (!classesList.isEmpty()) {
mLogger.verbose("Dex class inputs: " + classesList);
command.addAll(classesList);
}
// clean up and add library inputs.
List<String> libraryList = Lists.newArrayList();
for (File f : libraries) {
if (f != null && f.exists()) {
libraryList.add(f.getAbsolutePath());
}
}
if (!libraryList.isEmpty()) {
mLogger.verbose("Dex library inputs: " + libraryList);
command.addAll(libraryList);
}
if (proguardFile != null && proguardFile.exists()) {
mLogger.verbose("ProGuarded inputs " + proguardFile);
command.add(proguardFile.getAbsolutePath());
}
mCmdLineRunner.runCmdLine(command, null);
}
/**
* Packages the apk.
*
* @param androidResPkgLocation the location of the packaged resource file
* @param classesDexLocation the location of the classes.dex file
* @param packagedJars the jars that are packaged (libraries + jar dependencies)
* @param javaResourcesLocation the processed Java resource folder
* @param jniLibsLocation the location of the compiled JNI libraries
* @param jniDebugBuild whether the app should include jni debug data
* @param signingConfig the signing configuration
* @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 com.android.builder.VariantConfiguration#getPackagedJars()
*/
public void packageApk(
@NonNull String androidResPkgLocation,
@NonNull String classesDexLocation,
@NonNull List<File> packagedJars,
@Nullable String javaResourcesLocation,
@Nullable String jniLibsLocation,
boolean jniDebugBuild,
@Nullable SigningConfig signingConfig,
@NonNull String outApkLocation) throws DuplicateFileException, FileNotFoundException,
KeytoolException, PackagerException, SigningException {
checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
checkNotNull(classesDexLocation, "classesDexLocation cannot be null.");
checkNotNull(outApkLocation, "outApkLocation cannot be null.");
CertificateInfo certificateInfo = null;
if (signingConfig != null && signingConfig.isSigningReady()) {
certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig);
if (certificateInfo == null) {
throw new SigningException("Failed to read key from keystore");
}
}
try {
Packager packager = new Packager(
outApkLocation, androidResPkgLocation, classesDexLocation,
certificateInfo, mCreatedBy, mLogger);
packager.setJniDebugMode(jniDebugBuild);
// figure out conflicts!
JavaResourceProcessor resProcessor = new JavaResourceProcessor(packager);
if (javaResourcesLocation != null) {
resProcessor.addSourceFolder(javaResourcesLocation);
}
// add the resources from the jar files.
for (File jar : packagedJars) {
packager.addResourcesFromJar(jar);
}
// also add resources from library projects and jars
if (jniLibsLocation != null) {
packager.addNativeLibraries(jniLibsLocation);
}
packager.sealApk();
} catch (SealedPackageException e) {
// shouldn't happen since we control the package from start to end.
throw new RuntimeException(e);
}
}
}