All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.builder.internal.packaging.OldPackager Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
/*
 * Copyright (C) 2010 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.internal.packaging;

import static com.android.SdkConstants.DOT_CLASS;
import static com.android.SdkConstants.FN_APK_CLASSES_DEX;
import static com.android.SdkConstants.FN_APK_CLASSES_N_DEX;

import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.files.FileModificationType;
import com.android.builder.files.RelativeFile;
import com.android.builder.packaging.ApkCreator;
import com.android.builder.packaging.ApkCreatorFactory;
import com.android.builder.packaging.DuplicateFileException;
import com.android.builder.packaging.PackagerException;
import com.android.builder.packaging.ZipAbortException;
import com.android.builder.packaging.ZipEntryFilter;
import com.android.builder.signing.SignedJarApkCreatorFactory;
import com.android.utils.ILogger;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.io.Closer;

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

/**
 * This is the old implementation of the {@link IncrementalPackager} class maintained to allow
 * using the old packaging code in case the new one is not usable in some scenario that was not
 * tested. This class will eventually be removed in a future version (along with all other old
 * packaging code).
 *
 * 

Previous documentation: * *

Class making the final app package. * The inputs are: * - packaged resources (output of aapt) * - code file (output of dx) * - Java resources coming from the project, its libraries, and its jar files * - Native libraries from the project or its library. * * @deprecated the {@link IncrementalPackager} should be used from now one that allows both full * and incremental packaging. */ @Deprecated public final class OldPackager implements Closeable { /** * Filter to detect duplicate entries * */ private final class DuplicateZipFilter implements ZipEntryFilter { private File mInputFile; void reset(File inputFile) { mInputFile = inputFile; } @Override public boolean checkEntry(String archivePath) throws ZipAbortException { mLogger.verbose("=> %s", archivePath); File duplicate = checkFileForDuplicate(archivePath); if (duplicate != null) { // we have a duplicate but it might be the same source file, in this case, // we just ignore the duplicate, and of course, we don't add it again. File potentialDuplicate = new File(mInputFile, archivePath); if (!duplicate.getAbsolutePath().equals(potentialDuplicate.getAbsolutePath())) { throw new DuplicateFileException(archivePath, duplicate, mInputFile); } return false; } else { mAddedFiles.put(archivePath, mInputFile); } return true; } } /** * A filter to filter out binary files like .class */ private static final class NoJavaClassZipFilter implements ZipEntryFilter { @NonNull private final ZipEntryFilter parentFilter; private NoJavaClassZipFilter(@NonNull ZipEntryFilter parentFilter) { this.parentFilter = parentFilter; } @Override public boolean checkEntry(String archivePath) throws ZipAbortException { return parentFilter.checkEntry(archivePath) && !archivePath.endsWith(DOT_CLASS); } } /** * APK creator. {@code null} if not open. */ @Nullable private ApkCreator mApkCreator; private final ILogger mLogger; private final DuplicateZipFilter mNoDuplicateFilter = new DuplicateZipFilter(); private final NoJavaClassZipFilter mNoJavaClassZipFilter = new NoJavaClassZipFilter( mNoDuplicateFilter); private final HashMap mAddedFiles = new HashMap(); /** * Creates a new instance. * *

This creates a new builder that will create the specified output file. * * @param creationData APK creation data * @param resLocation location of the zip with the resources, if any * @param logger the logger * @throws PackagerException failed to create the initial APK * @throws IOException failed to create the APK */ public OldPackager(@NonNull ApkCreatorFactory.CreationData creationData, @Nullable String resLocation, @NonNull ILogger logger) throws PackagerException, IOException { checkOutputFile(creationData.getApkPath()); Closer closer = Closer.create(); try { checkOutputFile(creationData.getApkPath()); File resFile = null; if (resLocation != null) { resFile = new File(resLocation); checkInputFile(resFile); } mLogger = logger; ApkCreatorFactory factory = new SignedJarApkCreatorFactory(); mApkCreator = factory.make(creationData); mLogger.verbose("Packaging %s", creationData.getApkPath().getName()); // add the resources if (resFile != null) { addZipFile(resFile); } } catch (Throwable e) { closer.register(mApkCreator); mApkCreator = null; throw closer.rethrow(e, PackagerException.class); } finally { closer.close(); } } public void addDexFiles(@NonNull Set dexFolders) throws PackagerException, IOException { Preconditions.checkNotNull(mApkCreator, "mApkCreator == null"); // If there is a single folder that's either no multi-dex or pre-21 multidex (where // dx has merged them all into 2+ dex files). // IF there are 2+ folders then we are directly adding the pre-dexing output. if (dexFolders.size() == 1 ) { File[] dexFiles = Iterables.getOnlyElement(dexFolders).listFiles( new FilenameFilter() { @Override public boolean accept(File file, String name) { return name.endsWith(SdkConstants.DOT_DEX); } }); if (dexFiles != null) { for (File dexFile : dexFiles) { addFile(dexFile, dexFile.getName()); } } } else { // in 21+ mode we can simply include all the dex files, and rename them as we // go so that their indices are contiguous. int dexIndex = 1; for (File folderEntry : dexFolders) { dexIndex = addContentOfDexFolder(folderEntry, dexIndex); } } } private int addContentOfDexFolder(@NonNull File dexFolder, int dexIndex) throws PackagerException, IOException { File[] dexFiles = dexFolder.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String name) { return name.endsWith(SdkConstants.DOT_DEX); } }); if (dexFiles != null) { for (File dexFile : dexFiles) { addFile(dexFile, dexIndex == 1 ? FN_APK_CLASSES_DEX : String.format(FN_APK_CLASSES_N_DEX, dexIndex)); dexIndex++; } } return dexIndex; } /** * Adds a file to the APK at a given path. * * @param file the file to add * @param archivePath the path of the file inside the APK archive. * @throws PackagerException if an error occurred * @throws IOException if an error occurred */ public void addFile(File file, String archivePath) throws PackagerException, IOException { Preconditions.checkState(mApkCreator != null, "mApkCreator == null"); doAddFile(file, archivePath); } /** * Adds the content from a zip file. * All file keep the same path inside the archive. * * @param zipFile the zip File. * @throws PackagerException if an error occurred * @throws IOException if an error occurred */ void addZipFile(File zipFile) throws PackagerException, IOException { Preconditions.checkState(mApkCreator != null, "mApkCreator == null"); mLogger.verbose("%s:", zipFile); // reset the filter with this input. mNoDuplicateFilter.reset(zipFile); /* * Predicate does not allow exceptions so we mask the ZipAbortException inside a * RuntimeException. */ try { // ask the builder to add the content of the file. mApkCreator.writeZip(zipFile, null, input -> { try { return !mNoDuplicateFilter.checkEntry(input); } catch (ZipAbortException e) { throw new RuntimeException(e); } }); } catch (RuntimeException e) { if (e.getCause() instanceof ZipAbortException) { throw (ZipAbortException) e.getCause(); } throw e; } } /** * Incrementally updates a resource in the packaging. The resource can be added or removed, * depending on the change made to the file. * * @param file the file to update * @param modificationType the type of file modification * @throws PackagerException failed to update the package */ public void updateResource(@NonNull RelativeFile file, @NonNull FileModificationType modificationType) throws PackagerException { if (modificationType == FileModificationType.NEW || modificationType == FileModificationType.CHANGED) { doAddFile(file.getFile(), file.getOsIndependentRelativePath()); } else { throw new UnsupportedOperationException("Cannot remove a file from archive."); } } /** * Incrementally updates resources in the packaging. The resources can be added or removed, * depending on the changes made to the file. Updating an archive file as modified will update * the entries, but will not remove archive entries tht are no longer in the archive. * * @param file the archive file (zip) * @param modificationType the type of file modification * @param isIgnored the filter to apply to the contents of the archive; the filter is applied * before processing: filtered out files are exactly the same as inexistent files; the filter * applies to the path stored in the zip * @throws PackagerException failed to update the package */ public void updateResourceArchive(@NonNull File file, @NonNull FileModificationType modificationType, @NonNull final Predicate isIgnored) throws PackagerException { Preconditions.checkNotNull(mApkCreator, "mApkCreator == null"); if (modificationType == FileModificationType.NEW || modificationType == FileModificationType.CHANGED) { try { Closer closer = Closer.create(); try { /* * Note that ZipAbortException has to be masked because it is not allowed in * the Predicate interface. */ Predicate newIsIgnored = input -> { try { if (!mNoJavaClassZipFilter.checkEntry(input)) { return true; } } catch (ZipAbortException e) { throw new RuntimeException(e); } return isIgnored.apply(input); }; mApkCreator.writeZip(file, null, newIsIgnored::apply); } catch (Throwable t) { throw closer.rethrow(t, ZipAbortException.class); } finally { closer.close(); } } catch (IOException e) { throw new PackagerException(e); } } } private void doAddFile( @NonNull File file, @NonNull String archivePath) throws PackagerException { Preconditions.checkNotNull(mApkCreator, "mApkCreator == null"); mAddedFiles.put(archivePath, file); try { mApkCreator.writeFile(file, archivePath); } catch (IOException e) { throw new PackagerException(e); } } /** * Checks if the given path in the APK archive has not already been used and if it has been, * then returns a {@link File} object for the source of the duplicate * @param archivePath the archive path to test. * @return A File object of either a file at the same location or an archive that contains a * file that was put at the same location. */ private File checkFileForDuplicate(String archivePath) { return mAddedFiles.get(archivePath); } /** * Checks an output {@link File} object. * This checks the following: * - the file is not an existing directory. * - if the file exists, that it can be modified. * - if it doesn't exists, that a new file can be created. * @param file the File to check * @throws PackagerException If the check fails */ private static void checkOutputFile(File file) throws PackagerException { if (file.isDirectory()) { throw new PackagerException("%s is a directory!", file); } if (file.exists()) { // will be a file in this case. if (!file.canWrite()) { throw new PackagerException("Cannot write %s", file); } } else { try { if (!file.createNewFile()) { throw new PackagerException("Failed to create %s", file); } /* * We succeeded at creating the file. Now, delete it because a zero-byte file is * not a valid APK and some ApkCreator implementations (e.g., the ZFile one) * complain if open on top of an invalid zip file. */ if (!file.delete()) { throw new PackagerException("Failed to delete newly created %s", file); } } catch (IOException e) { throw new PackagerException( "Failed to create '%1$ss': %2$s", file, e.getMessage()); } } } /** * Checks an input {@link File} object. * This checks the following: * - the file is not an existing directory. * - that the file exists and can be read. * @param file the File to check * @throws FileNotFoundException if the file is not here. * @throws PackagerException If the file is a folder or a file that cannot be read. */ private static void checkInputFile(File file) throws FileNotFoundException, PackagerException { if (file.isDirectory()) { throw new PackagerException("%s is a directory!", file); } if (file.exists()) { if (!file.canRead()) { throw new PackagerException("Cannot read %s", file); } } else { throw new FileNotFoundException(String.format("%s does not exist", file)); } } public static String getLocalVersion() { Class clazz = IncrementalPackager.class; String className = clazz.getSimpleName() + ".class"; String classPath = clazz.getResource(className).toString(); if (!classPath.startsWith("jar")) { // Class not from JAR, unlikely return null; } try { String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) + "/META-INF/MANIFEST.MF"; URLConnection jarConnection = new URL(manifestPath).openConnection(); jarConnection.setUseCaches(false); InputStream jarInputStream = jarConnection.getInputStream(); Attributes attr = new Manifest(jarInputStream).getMainAttributes(); jarInputStream.close(); return attr.getValue("Builder-Version"); } catch (MalformedURLException ignored) { } catch (IOException ignored) { } return null; } @Override public void close() throws IOException { if (mApkCreator == null) { return; } ApkCreator builder = mApkCreator; mApkCreator = null; builder.close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy