Home | History | Annotate | Download | only in ant
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ant;
     18 
     19 import com.android.sdklib.build.ApkBuilder;
     20 import com.android.sdklib.build.ApkCreationException;
     21 import com.android.sdklib.build.DuplicateFileException;
     22 import com.android.sdklib.build.SealedApkException;
     23 
     24 import org.apache.tools.ant.BuildException;
     25 import org.apache.tools.ant.Project;
     26 import org.apache.tools.ant.Task;
     27 import org.apache.tools.ant.types.Path;
     28 
     29 import java.io.File;
     30 import java.io.FilenameFilter;
     31 import java.util.ArrayList;
     32 import java.util.regex.Pattern;
     33 
     34 public class ApkBuilderTask extends Task {
     35 
     36     private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
     37             Pattern.CASE_INSENSITIVE);
     38 
     39     private String mOutFolder;
     40     @Deprecated private String mBaseName;
     41     private String mApkFilepath;
     42     private String mResourceFile;
     43     private boolean mVerbose = false;
     44     private boolean mSigned = true;
     45     private boolean mDebug = false;
     46     private boolean mHasCode = true;
     47     private String mAbiFilter = null;
     48 
     49     private Path mDexPath;
     50 
     51     private final ArrayList<Path> mZipList = new ArrayList<Path>();
     52     private final ArrayList<Path> mFileList = new ArrayList<Path>();
     53     private final ArrayList<Path> mSourceList = new ArrayList<Path>();
     54     private final ArrayList<Path> mJarfolderList = new ArrayList<Path>();
     55     private final ArrayList<Path> mJarfileList = new ArrayList<Path>();
     56     private final ArrayList<Path> mNativeList = new ArrayList<Path>();
     57 
     58     /**
     59      * Sets the value of the "outfolder" attribute.
     60      * @param outFolder the value.
     61      */
     62     public void setOutfolder(Path outFolder) {
     63         mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
     64     }
     65 
     66     /**
     67      * Sets the value of the "basename" attribute.
     68      * @param baseName the value.
     69      * @deprecated
     70      */
     71     public void setBasename(String baseName) {
     72         System.out.println("WARNING: Using deprecated 'basename' attribute in ApkBuilderTask." +
     73                 "Use 'apkfilepath' (path) instead.");
     74         mBaseName = baseName;
     75     }
     76 
     77     /**
     78      * Sets the full filepath to the apk to generate.
     79      * @param filepath
     80      */
     81     public void setApkfilepath(String filepath) {
     82         mApkFilepath = filepath;
     83     }
     84 
     85     /**
     86      * Sets the resourcefile attribute
     87      * @param resourceFile
     88      */
     89     public void setResourcefile(String resourceFile) {
     90         mResourceFile = resourceFile;
     91     }
     92 
     93     /**
     94      * Sets the value of the "verbose" attribute.
     95      * @param verbose the value.
     96      */
     97     public void setVerbose(boolean verbose) {
     98         mVerbose = verbose;
     99     }
    100 
    101     /**
    102      * Sets the value of the "signed" attribute.
    103      * @param signed the value.
    104      */
    105     public void setSigned(boolean signed) {
    106         mSigned = signed;
    107     }
    108 
    109     /**
    110      * Sets the value of the "debug" attribute.
    111      * @param debug the debug mode value.
    112      */
    113     public void setDebug(boolean debug) {
    114         mDebug = debug;
    115     }
    116 
    117     /**
    118      * Sets an ABI filter. If non <code>null</code>, then only native libraries matching the given
    119      * ABI will be packaged with the APK.
    120      * @param abiFilter the ABI to accept (and reject all other). If null or empty string, no ABIs
    121      * are rejected. This must be a single ABI name as defined by the Android NDK. For a list
    122      * of valid ABI names, see $NDK/docs/CPU-ARCH-ABIS.TXT
    123      */
    124     public void setAbifilter(String abiFilter) {
    125         if (abiFilter != null && abiFilter.length() > 0) {
    126             mAbiFilter = abiFilter.trim();
    127         } else {
    128             mAbiFilter = null;
    129         }
    130     }
    131 
    132     /**
    133      * Sets the hascode attribute. Default is true.
    134      * If set to false, then <dex> and <sourcefolder> nodes are ignored and not processed.
    135      * @param hasCode the value of the attribute.
    136      */
    137     public void setHascode(boolean hasCode) {
    138         mHasCode   = hasCode;
    139     }
    140 
    141     /**
    142      * Returns an object representing a nested <var>zip</var> element.
    143      */
    144     public Object createZip() {
    145         Path path = new Path(getProject());
    146         mZipList.add(path);
    147         return path;
    148     }
    149 
    150     /**
    151      * Returns an object representing a nested <var>dex</var> element.
    152      * This is similar to a nested <var>file</var> element, except when {@link #mHasCode}
    153      * is <code>false</code> in which case it's ignored.
    154      */
    155     public Object createDex() {
    156         if (mDexPath == null) {
    157             return mDexPath = new Path(getProject());
    158         } else {
    159             throw new BuildException("Only one <dex> inner element can be provided");
    160         }
    161     }
    162 
    163     /**
    164      * Returns an object representing a nested <var>file</var> element.
    165      */
    166     public Object createFile() {
    167         System.out.println("WARNING: Using deprecated <file> inner element in ApkBuilderTask." +
    168         "Use <dex path=...> instead.");
    169         Path path = new Path(getProject());
    170         mFileList.add(path);
    171         return path;
    172     }
    173 
    174     /**
    175      * Returns an object representing a nested <var>sourcefolder</var> element.
    176      */
    177     public Object createSourcefolder() {
    178         Path path = new Path(getProject());
    179         mSourceList.add(path);
    180         return path;
    181     }
    182 
    183     /**
    184      * Returns an object representing a nested <var>jarfolder</var> element.
    185      */
    186     public Object createJarfolder() {
    187         Path path = new Path(getProject());
    188         mJarfolderList.add(path);
    189         return path;
    190     }
    191 
    192     /**
    193      * Returns an object representing a nested <var>jarfile</var> element.
    194      */
    195     public Object createJarfile() {
    196         Path path = new Path(getProject());
    197         mJarfileList.add(path);
    198         return path;
    199     }
    200 
    201     /**
    202      * Returns an object representing a nested <var>nativefolder</var> element.
    203      */
    204     public Object createNativefolder() {
    205         Path path = new Path(getProject());
    206         mNativeList.add(path);
    207         return path;
    208     }
    209 
    210     @Override
    211     public void execute() throws BuildException {
    212         Project antProject = getProject();
    213 
    214         // get the rules revision to figure out how to build the output file.
    215         String rulesRevStr = antProject.getProperty(TaskHelper.PROP_RULES_REV);
    216         int rulesRev = 1;
    217         try {
    218             rulesRev = Integer.parseInt(rulesRevStr);
    219         } catch (NumberFormatException e) {
    220             // this shouldn't happen since setup task is the one setting up every time.
    221         }
    222 
    223         File outputFile;
    224         if (mApkFilepath != null) {
    225             outputFile = new File(mApkFilepath);
    226         } else if (rulesRev == 2) {
    227             if (mSigned) {
    228                 outputFile = new File(mOutFolder, mBaseName + "-debug-unaligned.apk");
    229             } else {
    230                 outputFile = new File(mOutFolder, mBaseName + "-unsigned.apk");
    231             }
    232         } else {
    233             throw new BuildException("missing attribute 'apkFilepath'");
    234         }
    235 
    236         // check dexPath is only one file.
    237         File dexFile = null;
    238         if (mHasCode) {
    239             String[] dexFiles = mDexPath.list();
    240             if (dexFiles.length != 1) {
    241                 throw new BuildException(String.format(
    242                         "Expected one dex file but path value resolve to %d files.",
    243                         dexFiles.length));
    244             }
    245             dexFile = new File(dexFiles[0]);
    246         }
    247 
    248         try {
    249             if (mSigned) {
    250                 System.out.println(String.format(
    251                         "Creating %s and signing it with a debug key...", outputFile.getName()));
    252             } else {
    253                 System.out.println(String.format(
    254                         "Creating %s for release...", outputFile.getName()));
    255             }
    256 
    257             ApkBuilder apkBuilder = new ApkBuilder(
    258                     outputFile,
    259                     new File(mOutFolder, mResourceFile),
    260                     dexFile,
    261                     mSigned ? ApkBuilder.getDebugKeystore() : null,
    262                     mVerbose ? System.out : null);
    263             apkBuilder.setDebugMode(mDebug);
    264 
    265 
    266             // add the content of the zip files.
    267             for (Path pathList : mZipList) {
    268                 for (String path : pathList.list()) {
    269                     apkBuilder.addZipFile(new File(path));
    270                 }
    271             }
    272 
    273             // add the files that go to the root of the archive (this is deprecated)
    274             for (Path pathList : mFileList) {
    275                 for (String path : pathList.list()) {
    276                     File f = new File(path);
    277                     apkBuilder.addFile(f, f.getName());
    278                 }
    279             }
    280 
    281             // now go through the list of file to directly add the to the list.
    282             if (mHasCode) {
    283                 for (Path pathList : mSourceList) {
    284                     for (String path : pathList.list()) {
    285                         apkBuilder.addSourceFolder(new File(path));
    286                     }
    287                 }
    288             }
    289 
    290             // now go through the list of jar folders.
    291             for (Path pathList : mJarfolderList) {
    292                 for (String path : pathList.list()) {
    293                     // it's ok if top level folders are missing
    294                     File folder = new File(path);
    295                     if (folder.isDirectory()) {
    296                         String[] filenames = folder.list(new FilenameFilter() {
    297                             public boolean accept(File dir, String name) {
    298                                 return PATTERN_JAR_EXT.matcher(name).matches();
    299                             }
    300                         });
    301 
    302                         for (String filename : filenames) {
    303                             apkBuilder.addResourcesFromJar(new File(folder, filename));
    304                         }
    305                     }
    306                 }
    307             }
    308 
    309             // now go through the list of jar files.
    310             for (Path pathList : mJarfileList) {
    311                 for (String path : pathList.list()) {
    312                     apkBuilder.addResourcesFromJar(new File(path));
    313                 }
    314             }
    315 
    316             // now the native lib folder.
    317             for (Path pathList : mNativeList) {
    318                 for (String path : pathList.list()) {
    319                     // it's ok if top level folders are missing
    320                     File folder = new File(path);
    321                     if (folder.isDirectory()) {
    322                         apkBuilder.addNativeLibraries(folder, mAbiFilter);
    323                     }
    324                 }
    325             }
    326 
    327 
    328             // close the archive
    329             apkBuilder.sealApk();
    330 
    331         } catch (DuplicateFileException e) {
    332             System.err.println(String.format(
    333                     "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
    334                     e.getArchivePath(), e.getFile1(), e.getFile2()));
    335             throw new BuildException(e);
    336         } catch (ApkCreationException e) {
    337             throw new BuildException(e);
    338         } catch (SealedApkException e) {
    339             throw new BuildException(e);
    340         } catch (IllegalArgumentException e) {
    341             throw new BuildException(e);
    342         }
    343     }
    344 }
    345