/* * 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.AaptOptions; 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.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.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.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; 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.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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 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, String, ILogger, boolean)} * * then build steps can be done with * {@link #mergeManifests(java.io.File, java.util.List, java.util.List, String, int, String, String, String, Integer, String, com.android.manifmerger.ManifestMerger2.MergeType, java.util.Map)} * {@link #processTestManifest(String, String, String, String, String, Boolean, Boolean, java.io.File, java.util.List, java.io.File, java.io.File)} * {@link #processResources(java.io.File, java.io.File, java.io.File, java.util.List, String, String, String, String, String, com.android.builder.core.VariantConfiguration.Type, boolean, com.android.builder.model.AaptOptions, java.util.Collection, boolean, java.util.Collection)} * {@link #compileAllAidlFiles(java.util.List, java.io.File, java.io.File, java.util.List, com.android.builder.compiling.DependencyFileProcessor)} * {@link #convertByteCode(Iterable, Iterable, java.io.File, boolean, java.io.File, DexOptions, java.util.List, java.io.File, 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 FullRevision MIN_MULTIDEX_BUILD_TOOLS_REV = new FullRevision(21, 0, 0); private static final FullRevision MIN_BUILD_TOOLS_REVISION_FOR_DEX_INPUT_LIST = new FullRevision(21, 0, 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 CommandLineRunner mCmdLineRunner; private final boolean mVerboseExec; @Nullable private String mCreatedBy; private SdkInfo mSdkInfo; private TargetInfo mTargetInfo; /** * Creates an AndroidBuilder. * <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 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 ILogger logger, boolean verboseExec) { mProjectId = projectId; mCreatedBy = createdBy; mLogger = checkNotNull(logger); mVerboseExec = verboseExec; mCmdLineRunner = new CommandLineRunner(mLogger); } @VisibleForTesting AndroidBuilder( @NonNull String projectId, @NonNull CommandLineRunner cmdLineRunner, @NonNull ILogger logger, boolean verboseExec) { mProjectId = projectId; mCmdLineRunner = checkNotNull(cmdLineRunner); 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<File> getBootClasspath() { checkState(mTargetInfo != null, "Cannot call getBootClasspath() before setTargetInfo() is called."); List<File> 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<String> getBootClasspathAsStrings() { checkState(mTargetInfo != null, "Cannot call getBootClasspath() before setTargetInfo() is called."); List<String> 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<File> getCompileClasspath(@NonNull VariantConfiguration<?,?,?> variantConfiguration) { Set<File> compileClasspath = variantConfiguration.getCompileClasspath(); if (variantConfiguration.getRenderscriptSupportModeEnabled()) { File renderScriptSupportJar = getRenderScriptSupportJar(); Set<File> 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<File> getPackagedJars(@NonNull VariantConfiguration<?,?,?> variantConfiguration) { Set<File> 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), mCmdLineRunner); } @NonNull public CommandLineRunner getCommandLineRunner() { return mCmdLineRunner; } @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<File> manifestOverlays, @NonNull List<? extends ManifestDependency> libraries, String packageOverride, int versionCode, String versionName, @Nullable String minSdkVersion, @Nullable String targetSdkVersion, @Nullable Integer maxSdkVersion, @NonNull String outManifestLocation, ManifestMerger2.MergeType mergeType, Map<String, String> placeHolders) { try { Invoker manifestMergerInvoker = ManifestMerger2.newMerger(mainManifest, mLogger, mergeType) .setPlaceHolderValues(placeHolders) .addFlavorAndBuildTypeManifests( manifestOverlays.toArray(new File[manifestOverlays.size()])) .addLibraryManifests(collectLibraries(libraries)); 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)); 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<Pair<String, File>> collectLibraries( List<? extends ManifestDependency> libraries) { ImmutableList.Builder<Pair<String, File>> 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<Pair<String, File>> 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 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) .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)) .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); } } @NonNull private static Map<String, String> getAttributeInjectionMap( int versionCode, @Nullable String versionName, @Nullable String minSdkVersion, @Nullable String 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 != null) { attributeInjection.put( "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion", minSdkVersion); } if (targetSdkVersion != null) { attributeInjection.put( "/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion", targetSdkVersion); } return attributeInjection; } /** * 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 symbolOutputDir the folder to write symbols into * @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} * @param resourceConfigs a list of resource config filters to pass to aapt. * @param enforceUniquePackageName if true method will fail if some libraries share the same * package name * @param splits optional list of split dimensions values (like a density or an abi). This * will be used by aapt to generate the corresponding pure split apks. * * @throws IOException * @throws InterruptedException * @throws LoggedErrorException */ public void processResources( @NonNull File manifestFile, @NonNull File resFolder, @Nullable File assetsDir, @Nullable 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, @NonNull Collection<String> resourceConfigs, boolean enforceUniquePackageName, @Nullable Collection<String> splits) throws IOException, InterruptedException, LoggedErrorException { checkNotNull(manifestFile, "manifestFile cannot be null."); checkNotNull(resFolder, "resFolder 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"); checkState(mTargetInfo != null, "Cannot call processResources() before setTargetInfo() is called."); if (symbolOutputDir != null || sourceOutputDir != null) { checkNotNull(libraries, "libraries cannot be null if symbolOutputDir or sourceOutputDir is non-null"); } BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools(); IAndroidTarget target = mTargetInfo.getTarget(); // launch aapt: create the command line ArrayList<String> command = Lists.newArrayList(); String aapt = buildToolInfo.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(target.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); } if (splits != null) { for (String split : splits) { command.add("--split"); command.add(split); } } // options controlled by build variants if (debuggable) { command.add("--debug-mode"); } if (type != VariantConfiguration.Type.TEST) { 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); } if (options.getFailOnMissingConfigEntry()) { if (buildToolInfo.getRevision().getMajor() > 20) { command.add("--error-on-missing-config-entry"); } else { throw new IllegalStateException("aaptOptions:failOnMissingConfigEntry cannot be used" + " with SDK Build Tools revision earlier than 21.0.0"); } } // never compress apks. command.add("-0"); command.add("apk"); // add custom no-compress extensions Collection<String> noCompressList = options.getNoCompress(); if (noCompressList != null) { for (String noCompress : noCompressList) { command.add("-0"); command.add(noCompress); } } if (!resourceConfigs.isEmpty()) { command.add("-c"); Joiner joiner = Joiner.on(','); command.add(joiner.join(resourceConfigs)); } 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 (sourceOutputDir != null && 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) { 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\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); } // 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(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); 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(sourceOutputDir, 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 InterruptedException, LoggedErrorException, IOException { // need to run aapt to get apk information BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools(); // launch aapt: create the command line ArrayList<String> command = Lists.newArrayList(); String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT); if (aapt == null || !new File(aapt).isFile()) { throw new IllegalStateException("aapt is missing"); } command.add(aapt); command.add("dump"); command.add("badging"); command.add(apkFile.getPath()); final List<String> aaptOutput = Lists.newArrayList(); mCmdLineRunner.runCmdLine(command, new CommandLineRunner.CommandLineOutput() { @Override public void out(@Nullable String line) { if (line != null) { aaptOutput.add(line); } } @Override public void err(@Nullable String line) { super.err(line); } }, null /*env vars*/); Pattern p = Pattern.compile("^package: name='(.+)' versionCode='([0-9]*)' versionName='(.*)'$"); String pkgName = null, versionCode = null, versionName = null; for (String line : aaptOutput) { Matcher m = p.matcher(line); if (m.matches()) { pkgName = m.group(1); versionCode = m.group(2); versionName = m.group(3); break; } } if (pkgName == null) { throw new RuntimeException("Failed to find apk information with aapt"); } if (!pkgName.equals(mainPkgName)) { throw new RuntimeException("The main and the micro apps do not have the same package name."); } String content = String.format( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<wearableApp package=\"%1$s\">\n" + " <versionCode>%2$s</versionCode>\n" + " <versionName>%3$s</versionName>\n" + " <rawPathResId>%4$s</rawPathResId>\n" + "</wearableApp>", pkgName, versionCode, versionName, 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 void generateApkDataEntryInManifest(@NonNull File manifestFile) throws InterruptedException, LoggedErrorException, IOException { String content = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<manifest package=\"\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" + " <application>\n" + " <meta-data android:name=\"" + ANDROID_WEAR + "\"\n" + " android:resource=\"@xml/" + ANDROID_WEAR_MICRO_APK + "\" />\n" + " </application>\n" + "</manifest>\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<File> sourceFolders, @NonNull File sourceOutputDir, @Nullable File parcelableOutputDir, @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."); 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<File> 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, 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 sourceFolder, @NonNull File aidlFile, @NonNull File sourceOutputDir, @Nullable File parcelableOutputDir, @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."); 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, mCmdLineRunner); 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<File> sourceFolders, @NonNull List<File> 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<String> 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."); 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(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<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 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 LoggedErrorException */ public void convertByteCode( @NonNull Iterable<File> inputs, @NonNull Iterable<File> preDexedLibraries, @NonNull File outDexFolder, boolean multidex, @Nullable File mainDexList, @NonNull DexOptions dexOptions, @Nullable List<String> additionalParameters, @NonNull File tmpFolder, boolean incremental) throws IOException, InterruptedException, LoggedErrorException { 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(); checkState( !multidex || buildToolInfo.getRevision().compareTo(MIN_MULTIDEX_BUILD_TOOLS_REV) >= 0, "Multi dex requires Build Tools " + MIN_MULTIDEX_BUILD_TOOLS_REV.toString() + " / Current: " + buildToolInfo.getRevision().toShortString()); // launch dx: create the command line ArrayList<String> command = Lists.newArrayList(); String dx = buildToolInfo.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.getJumboMode()) { command.add("--force-jumbo"); } if (incremental) { command.add("--incremental"); command.add("--no-strict"); } if (multidex) { command.add("--multi-dex"); if (mainDexList != null ) { command.add("--main-dex-list"); command.add(mainDexList.getAbsolutePath()); } } /** * This seems to trigger some error in dx, so disable for now. if (dexOptions.getThreadCount() > 1) { command.add("--num-threads=" + dexOptions.getThreadCount()); } */ if (additionalParameters != null) { for (String arg : additionalParameters) { command.add(arg); } } command.add("--output"); command.add(outDexFolder.getAbsolutePath()); Iterable<File> allInputs = Iterables.concat(preDexedLibraries, inputs); command.addAll(getFilesToAdd(allInputs, buildToolInfo, tmpFolder)); mCmdLineRunner.runCmdLine(command, null); } private List<String> getFilesToAdd(Iterable<File> includeFiles, BuildToolInfo buildToolInfo, File tmpFolder) throws IOException { // clean up and add library inputs. List<String> filePathList = Lists.newArrayList(); for (File f : includeFiles) { if (f != null && f.exists()) { filePathList.add(f.getAbsolutePath()); } } if (filePathList.isEmpty()) { throw new IOException("No files to pass to dex."); } if (buildToolInfo.getRevision().compareTo(MIN_BUILD_TOOLS_REVISION_FOR_DEX_INPUT_LIST) >= 0) { File inputListFile = new File(tmpFolder, "libraryList.txt"); // Write each library line by line to file Files.asCharSink(inputListFile, Charsets.UTF_8).writeLines(filePathList); return Lists.newArrayList("--input-list=" + inputListFile.getAbsolutePath()); } else { return filePathList; } } /** * 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 LoggedErrorException */ public void preDexLibrary( @NonNull File inputFile, @NonNull File outFile, boolean multiDex, @NonNull DexOptions dexOptions) throws IOException, InterruptedException, LoggedErrorException { checkState(mTargetInfo != null, "Cannot call preDexLibrary() before setTargetInfo() is called."); BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools(); PreDexCache.getCache().preDexLibrary(inputFile, outFile, multiDex, dexOptions, buildToolInfo, mVerboseExec, mCmdLineRunner); } /** * 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 commandLineRunner the command line runner * @return the list of generated files. * @throws IOException * @throws InterruptedException * @throws LoggedErrorException */ @NonNull public static List<File> preDexLibrary( @NonNull File inputFile, @NonNull File outFile, boolean multiDex, @NonNull DexOptions dexOptions, @NonNull BuildToolInfo buildToolInfo, boolean verbose, @NonNull CommandLineRunner commandLineRunner) 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<String> command = Lists.newArrayList(); String dx = buildToolInfo.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 (verbose) { command.add("--verbose"); } if (dexOptions.getJumboMode()) { command.add("--force-jumbo"); } if (multiDex) { command.add("--multi-dex"); } command.add("--output"); command.add(outFile.getAbsolutePath()); command.add(inputFile.getAbsolutePath()); commandLineRunner.runCmdLine(command, null); 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); } } /** * 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 IOException * @throws InterruptedException * @throws LoggedErrorException */ public void convertLibraryToJack( @NonNull File inputFile, @NonNull File outFile, @NonNull DexOptions dexOptions) throws IOException, InterruptedException, LoggedErrorException { checkState(mTargetInfo != null, "Cannot call preJackLibrary() before setTargetInfo() is called."); BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools(); JackConversionCache.getCache().convertLibrary(inputFile, outFile, dexOptions, buildToolInfo, mVerboseExec, mCmdLineRunner); } public static List<File> convertLibraryToJack( @NonNull File inputFile, @NonNull File outFile, @NonNull DexOptions dexOptions, @NonNull BuildToolInfo buildToolInfo, boolean verbose, @NonNull CommandLineRunner commandLineRunner) 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<String> command = Lists.newArrayList(); String jill = buildToolInfo.getPath(BuildToolInfo.PathId.JILL); if (jill == null || !new File(jill).isFile()) { throw new IllegalStateException("jill.jar is missing"); } command.add("java"); if (dexOptions.getJavaMaxHeapSize() != null) { command.add("-JXmx" + dexOptions.getJavaMaxHeapSize()); } command.add("-jar"); command.add(jill); command.add(inputFile.getAbsolutePath()); command.add("--output"); command.add(outFile.getAbsolutePath()); commandLineRunner.runCmdLine(command, null); 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, @NonNull File dexFolder, @Nullable Collection<File> dexedLibraries, @NonNull Collection<File> packagedJars, @Nullable String javaResourcesLocation, @Nullable Collection<File> jniLibsFolders, @Nullable Set<String> 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(dexFolder, "dexFolder cannot be null."); checkArgument(dexFolder.isDirectory(), "dexFolder is not a directory"); 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, dexFolder, certificateInfo, mCreatedBy, packagingOptions, mLogger); if (dexedLibraries != null) { for (File dexedLibrary : dexedLibraries) { packager.addDexFile(dexedLibrary); } } 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<String> 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; } }