/* * 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.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.LeafFolderGatherer; import com.android.builder.internal.compiler.RenderScriptProcessor; 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.ProductFlavor; 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.collect.Sets; 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 java.util.Set; 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, Boolean, Boolean, 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, DexOptions, boolean)} * {@link #packageApk(String, String, java.util.List, String, java.util.Collection, java.util.Set, boolean, com.android.builder.model.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 *

* This receives an {@link SdkParser} to provide the build with information about the SDK, as * well as an {@link ILogger} to display output. *

* verboseExec 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. */ @NonNull public static List getBootClasspath(@NonNull SdkParser sdkParser) { List 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 the compile classpath for this config. If the config tests a library, this * will include the classpath of the tested config * * @return a non null, but possibly empty set. */ @NonNull public Set getCompileClasspath(@NonNull VariantConfiguration variantConfiguration) { Set compileClasspath = variantConfiguration.getCompileClasspath(); ProductFlavor mergedFlavor = variantConfiguration.getMergedFlavor(); if (mergedFlavor.getRenderscriptSupportMode()) { File renderScriptSupportJar = RenderScriptProcessor.getSupportJar( mBuildTools.getLocation().getAbsolutePath()); Set fullJars = Sets.newHashSetWithExpectedSize(compileClasspath.size() + 1); fullJars.addAll(compileClasspath); 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 * * @return a non null, but possibly empty list. */ @NonNull public List getPackagedJars(@NonNull VariantConfiguration variantConfiguration) { List packagedJars = variantConfiguration.getPackagedJars(); ProductFlavor mergedFlavor = variantConfiguration.getMergedFlavor(); if (mergedFlavor.getRenderscriptSupportMode()) { File renderScriptSupportJar = RenderScriptProcessor.getSupportJar( mBuildTools.getLocation().getAbsolutePath()); List fullJars = Lists.newArrayListWithCapacity(packagedJars.size() + 1); fullJars.addAll(packagedJars); fullJars.add(renderScriptSupportJar); packagedJars = fullJars; } return packagedJars; } @NonNull public File getSupportNativeLibFolder() { return RenderScriptProcessor.getSupportNativeLibFolder( mBuildTools.getLocation().getAbsolutePath()); } /** * Returns an {@link AaptRunner} able to run aapt commands. * @return an AaptRunner object */ @NonNull public AaptRunner getAaptRunner() { return new AaptRunner( mBuildTools.getPath(BuildToolInfo.PathId.AAPT), mCmdLineRunner); } @NonNull public CommandLineRunner getCommandLineRunner() { return 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 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 manifestOverlays, @NonNull List 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 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 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 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#getHandleProfiling() * @see com.android.builder.VariantConfiguration#getFunctionalTest() * @see com.android.builder.VariantConfiguration#getDirectLibraries() */ public void processTestManifest( @NonNull String testPackageName, int minSdkVersion, int targetSdkVersion, @NonNull String testedPackageName, @NonNull String instrumentationRunner, @NonNull Boolean handleProfiling, @NonNull Boolean functionalTest, @NonNull List libraries, @NonNull String outManifestLocation) { checkNotNull(testPackageName, "testPackageName cannot be null."); checkNotNull(testedPackageName, "testedPackageName 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(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, handleProfiling, functionalTest, generatedTestManifest.getAbsolutePath()); mergeLibraryManifests( generatedTestManifest, libraries, new File(outManifestLocation), null, null); } catch (IOException e) { throw new RuntimeException(e); } } else { generateTestManifest( testPackageName, minSdkVersion, targetSdkVersion, testedPackageName, instrumentationRunner, handleProfiling, functionalTest, outManifestLocation); } } private void generateTestManifest( String testPackageName, int minSdkVersion, int targetSdkVersion, String testedPackageName, String instrumentationRunner, Boolean handleProfiling, Boolean functionalTest, String outManifestLocation) { TestManifestGenerator generator = new TestManifestGenerator( outManifestLocation, testPackageName, minSdkVersion, targetSdkVersion, testedPackageName, instrumentationRunner, handleProfiling, functionalTest); try { generator.generate(); } catch (IOException e) { throw new RuntimeException(e); } } @NonNull private Map getAttributeInjectionMap( int versionCode, @Nullable String versionName, int minSdkVersion, int targetSdkVersion) { Map 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 directLibraries, File outManifest, Map attributeInjection, String packageOverride) throws IOException { List manifests = Lists.newArrayList(); for (ManifestDependency library : directLibraries) { List 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 injectionMap, String packageOverride) { List list = Collections.emptyList(); doMerge(merger, output, input, list, injectionMap, packageOverride); } private void doMerge(ManifestMerger merger, File output, File input, List subManifests, Map 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 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 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 (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 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 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 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 sourceFolders, @NonNull File sourceOutputDir, @NonNull List 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 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 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 sourceFolders, @NonNull List importFolders, @NonNull File sourceOutputDir, @NonNull File resOutputDir, @NonNull File objOutputDir, @NonNull File libOutputDir, int targetApi, boolean debugBuild, int optimLevel, boolean supportMode, @Nullable Set abiFilters) 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"); } if (supportMode && mBuildTools.getRevision().compareTo(new FullRevision(18,1, 0)) == -1) { throw new IllegalStateException( "RenderScript Support Mode requires buildToolsVersion >= 18.1"); } RenderScriptProcessor processor = new RenderScriptProcessor( sourceFolders, importFolders, sourceOutputDir, resOutputDir, objOutputDir, libOutputDir, mBuildTools, targetApi, debugBuild, optimLevel, supportMode, abiFilters); processor.build(mCmdLineRunner); } /** * 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. } 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 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 inputs, @NonNull Iterable preDexedLibraries, @NonNull File outDexFile, @NonNull DexOptions dexOptions, boolean incremental) throws IOException, InterruptedException, LoggedErrorException { checkNotNull(inputs, "inputs cannot be null."); checkNotNull(preDexedLibraries, "preDexedLibraries cannot be null."); checkNotNull(outDexFile, "outDexFile cannot be null."); checkNotNull(dexOptions, "dexOptions cannot be null."); // launch dx: create the command line ArrayList 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 (dexOptions.getJumboMode()) { command.add("--force-jumbo"); } if (incremental) { command.add("--incremental"); command.add("--no-strict"); } command.add("--output"); command.add(outDexFile.getAbsolutePath()); // clean up input list List inputList = Lists.newArrayList(); for (File f : inputs) { if (f != null && f.exists()) { inputList.add(f.getAbsolutePath()); } } if (!inputList.isEmpty()) { mLogger.verbose("Dex inputs: " + inputList); command.addAll(inputList); } // clean up and add library inputs. List libraryList = Lists.newArrayList(); for (File f : preDexedLibraries) { if (f != null && f.exists()) { libraryList.add(f.getAbsolutePath()); } } if (!libraryList.isEmpty()) { mLogger.verbose("Dex pre-dexed inputs: " + libraryList); command.addAll(libraryList); } mCmdLineRunner.runCmdLine(command, null); } /** * Converts the bytecode to Dalvik format * @param inputFile the input file * @param outFile the location of the output classes.dex file * @param dexOptions dex options * * @throws IOException * @throws InterruptedException * @throws LoggedErrorException */ public void preDexLibrary( @NonNull File inputFile, @NonNull File outFile, @NonNull DexOptions dexOptions) throws IOException, InterruptedException, LoggedErrorException { checkNotNull(inputFile, "inputFile cannot be null."); checkNotNull(outFile, "outFile cannot be null."); checkNotNull(dexOptions, "dexOptions cannot be null."); // launch dx: create the command line ArrayList 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 (dexOptions.getJumboMode()) { command.add("--force-jumbo"); } command.add("--output"); command.add(outFile.getAbsolutePath()); command.add(inputFile.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 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 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 packagedJars, @Nullable String javaResourcesLocation, @Nullable Collection jniLibsFolders, @Nullable Set abiFilters, 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 (jniLibsFolders != null) { for (File jniFolder : jniLibsFolders) { 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); } } }