/* * Copyright (C) 2009 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.ant; import com.android.sdklib.build.ApkBuilder; import com.android.sdklib.build.ApkBuilder.FileEntry; import com.android.sdklib.build.ApkCreationException; import com.android.sdklib.build.DuplicateFileException; import com.android.sdklib.build.SealedApkException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.Path; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; public class ApkBuilderTask extends SingleDependencyTask { private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$", Pattern.CASE_INSENSITIVE); private String mOutFolder; private String mApkFilepath; private String mResourceFile; private boolean mVerbose = false; private boolean mDebugPackaging = false; private boolean mDebugSigning = false; private boolean mHasCode = true; private Path mDexPath; private final ArrayList mZipList = new ArrayList(); private final ArrayList mSourceList = new ArrayList(); private final ArrayList mJarfolderList = new ArrayList(); private final ArrayList mJarfileList = new ArrayList(); private final ArrayList mNativeList = new ArrayList(); private static class SourceFolderInputPath extends InputPath { public SourceFolderInputPath(File file) { super(file); } @Override public boolean ignores(File file) { if (file.isDirectory()) { return !ApkBuilder.checkFolderForPackaging(file.getName()); } else { return !ApkBuilder.checkFileForPackaging(file.getName()); } } } /** * Sets the value of the "outfolder" attribute. * @param outFolder the value. */ public void setOutfolder(Path outFolder) { mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder); } /** * Sets the full filepath to the apk to generate. * @param filepath */ public void setApkfilepath(String filepath) { mApkFilepath = filepath; } /** * Sets the resourcefile attribute * @param resourceFile */ public void setResourcefile(String resourceFile) { mResourceFile = resourceFile; } /** * Sets the value of the "verbose" attribute. * @param verbose the value. */ public void setVerbose(boolean verbose) { mVerbose = verbose; } /** * Sets the value of the "debug" attribute. * @param debug the debug mode value. */ public void setDebug(boolean debug) { System.out.println("WARNNG: Using deprecated 'debug' attribute in ApkBuilderTask." + "Use 'debugpackaging' and 'debugsigning' instead."); mDebugPackaging = debug; mDebugSigning = debug; } /** * Sets the value of the "debugpackaging" attribute. * @param debug the debug mode value. */ public void setDebugpackaging(boolean debug) { mDebugPackaging = debug; } /** * Sets the value of the "debugsigning" attribute. * @param debug the debug mode value. */ public void setDebugsigning(boolean debug) { mDebugSigning = debug; } /** * Sets the hascode attribute. Default is true. * If set to false, then and nodes are ignored and not processed. * @param hasCode the value of the attribute. */ public void setHascode(boolean hasCode) { mHasCode = hasCode; } /** * Returns an object representing a nested zip element. */ public Object createZip() { Path path = new Path(getProject()); mZipList.add(path); return path; } /** * Returns an object representing a nested dex element. * This is similar to a nested file element, except when {@link #mHasCode} * is false in which case it's ignored. */ public Object createDex() { if (mDexPath == null) { return mDexPath = new Path(getProject()); } else { throw new BuildException("Only one inner element can be provided"); } } /** * Returns an object representing a nested sourcefolder element. */ public Object createSourcefolder() { Path path = new Path(getProject()); mSourceList.add(path); return path; } /** * Returns an object representing a nested jarfolder element. */ public Object createJarfolder() { Path path = new Path(getProject()); mJarfolderList.add(path); return path; } /** * Returns an object representing a nested jarfile element. */ public Object createJarfile() { Path path = new Path(getProject()); mJarfileList.add(path); return path; } /** * Returns an object representing a nested nativefolder element. */ public Object createNativefolder() { Path path = new Path(getProject()); mNativeList.add(path); return path; } @Override public void execute() throws BuildException { File outputFile; if (mApkFilepath != null) { outputFile = new File(mApkFilepath); } else { throw new BuildException("missing attribute 'apkFilepath'"); } if (mResourceFile == null) { throw new BuildException("missing attribute 'resourcefile'"); } if (mOutFolder == null) { throw new BuildException("missing attribute 'outfolder'"); } // check dexPath is only one file. File dexFile = null; if (mHasCode) { String[] dexFiles = mDexPath.list(); if (dexFiles.length != 1) { throw new BuildException(String.format( "Expected one dex file but path value resolve to %d files.", dexFiles.length)); } dexFile = new File(dexFiles[0]); } try { // build list of input files/folders to compute dependencies // add the content of the zip files. List inputPaths = new ArrayList(); // resource file InputPath resourceInputPath = new InputPath(new File(mOutFolder, mResourceFile)); inputPaths.add(resourceInputPath); // dex file if (dexFile != null) { inputPaths.add(new InputPath(dexFile)); } // zip input files List zipFiles = new ArrayList(); for (Path pathList : mZipList) { for (String path : pathList.list()) { File f = new File(path); zipFiles.add(f); inputPaths.add(new InputPath(f)); } } // now go through the list of source folders used to add non java files. List sourceFolderList = new ArrayList(); if (mHasCode) { for (Path pathList : mSourceList) { for (String path : pathList.list()) { File f = new File(path); sourceFolderList.add(f); // because this is a source folder but we only care about non // java files. inputPaths.add(new SourceFolderInputPath(f)); } } } // now go through the list of jar folders. List jarFileList = new ArrayList(); for (Path pathList : mJarfolderList) { for (String path : pathList.list()) { // it's ok if top level folders are missing File folder = new File(path); if (folder.isDirectory()) { String[] filenames = folder.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return PATTERN_JAR_EXT.matcher(name).matches(); } }); for (String filename : filenames) { File f = new File(folder, filename); jarFileList.add(f); inputPaths.add(new InputPath(f)); } } } } // now go through the list of jar files. for (Path pathList : mJarfileList) { for (String path : pathList.list()) { File f = new File(path); jarFileList.add(f); inputPaths.add(new InputPath(f)); } } // now the native lib folder. List nativeFileList = new ArrayList(); for (Path pathList : mNativeList) { for (String path : pathList.list()) { // it's ok if top level folders are missing File folder = new File(path); if (folder.isDirectory()) { List entries = ApkBuilder.getNativeFiles(folder, mDebugPackaging); // add the list to the list of native files and then create an input // path for each file nativeFileList.addAll(entries); for (FileEntry entry : entries) { inputPaths.add(new InputPath(entry.mFile)); } } } } // Finally figure out the path to the dependency file. String depFile = outputFile.getAbsolutePath() + ".d"; // check dependencies if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) { System.out.println( "No changes. No need to create apk."); return; } if (mDebugSigning) { System.out.println(String.format( "Creating %s and signing it with a debug key...", outputFile.getName())); } else { System.out.println(String.format( "Creating %s for release...", outputFile.getName())); } ApkBuilder apkBuilder = new ApkBuilder( outputFile, resourceInputPath.getFile(), dexFile, mDebugSigning ? ApkBuilder.getDebugKeystore() : null, mVerbose ? System.out : null); apkBuilder.setDebugMode(mDebugPackaging); // add the content of the zip files. for (File f : zipFiles) { if (mVerbose) { System.out.println("Zip Input: " + f.getAbsolutePath()); } apkBuilder.addZipFile(f); } // now go through the list of file to directly add the to the list. for (File f : sourceFolderList) { if (mVerbose) { System.out.println("Source Folder Input: " + f.getAbsolutePath()); } apkBuilder.addSourceFolder(f); } // now go through the list of jar files. for (File f : jarFileList) { if (mVerbose) { System.out.println("Jar Input: " + f.getAbsolutePath()); } apkBuilder.addResourcesFromJar(f); } // and finally the native files apkBuilder.addNativeLibraries(nativeFileList); // close the archive apkBuilder.sealApk(); // and generate the dependency file generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath()); } catch (DuplicateFileException e) { System.err.println(String.format( "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", e.getArchivePath(), e.getFile1(), e.getFile2())); throw new BuildException(e); } catch (ApkCreationException e) { throw new BuildException(e); } catch (SealedApkException e) { throw new BuildException(e); } catch (IllegalArgumentException e) { throw new BuildException(e); } } @Override protected String getExecTaskName() { return "apkbuilder"; } }