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 import com.android.sdklib.build.ApkBuilder.FileEntry;
     24 
     25 import org.apache.tools.ant.BuildException;
     26 import org.apache.tools.ant.types.Path;
     27 
     28 import java.io.File;
     29 import java.io.FilenameFilter;
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 import java.util.regex.Pattern;
     33 
     34 public class ApkBuilderTask extends BaseTask {
     35 
     36     private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
     37             Pattern.CASE_INSENSITIVE);
     38 
     39     private String mOutFolder;
     40     private String mApkFilepath;
     41     private String mResourceFile;
     42     private boolean mVerbose = false;
     43     private boolean mDebugPackaging = false;
     44     private boolean mDebugSigning = false;
     45     private boolean mHasCode = true;
     46 
     47     private Path mDexPath;
     48 
     49     private final ArrayList<Path> mZipList = new ArrayList<Path>();
     50     private final ArrayList<Path> mSourceList = new ArrayList<Path>();
     51     private final ArrayList<Path> mJarfolderList = new ArrayList<Path>();
     52     private final ArrayList<Path> mJarfileList = new ArrayList<Path>();
     53     private final ArrayList<Path> mNativeList = new ArrayList<Path>();
     54 
     55     private static class SourceFolderInputPath extends InputPath {
     56         public SourceFolderInputPath(File file) {
     57             super(file);
     58         }
     59 
     60         @Override
     61         public boolean ignores(File file) {
     62             if (file.isDirectory()) {
     63                 return !ApkBuilder.checkFolderForPackaging(file.getName());
     64             } else {
     65                 return !ApkBuilder.checkFileForPackaging(file.getName());
     66             }
     67         }
     68     }
     69 
     70     /**
     71      * Sets the value of the "outfolder" attribute.
     72      * @param outFolder the value.
     73      */
     74     public void setOutfolder(Path outFolder) {
     75         mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
     76     }
     77 
     78     /**
     79      * Sets the full filepath to the apk to generate.
     80      * @param filepath
     81      */
     82     public void setApkfilepath(String filepath) {
     83         mApkFilepath = filepath;
     84     }
     85 
     86     /**
     87      * Sets the resourcefile attribute
     88      * @param resourceFile
     89      */
     90     public void setResourcefile(String resourceFile) {
     91         mResourceFile = resourceFile;
     92     }
     93 
     94     /**
     95      * Sets the value of the "verbose" attribute.
     96      * @param verbose the value.
     97      */
     98     public void setVerbose(boolean verbose) {
     99         mVerbose = verbose;
    100     }
    101 
    102     /**
    103      * Sets the value of the "debug" attribute.
    104      * @param debug the debug mode value.
    105      */
    106     public void setDebug(boolean debug) {
    107         System.out.println("WARNNG: Using deprecated 'debug' attribute in ApkBuilderTask." +
    108         "Use 'debugpackaging' and 'debugsigning' instead.");
    109         mDebugPackaging = debug;
    110         mDebugSigning = debug;
    111     }
    112 
    113     /**
    114      * Sets the value of the "debugpackaging" attribute.
    115      * @param debug the debug mode value.
    116      */
    117     public void setDebugpackaging(boolean debug) {
    118         mDebugPackaging = debug;
    119     }
    120 
    121     /**
    122      * Sets the value of the "debugsigning" attribute.
    123      * @param debug the debug mode value.
    124      */
    125     public void setDebugsigning(boolean debug) {
    126         mDebugSigning = debug;
    127     }
    128 
    129     /**
    130      * Sets the hascode attribute. Default is true.
    131      * If set to false, then <dex> and <sourcefolder> nodes are ignored and not processed.
    132      * @param hasCode the value of the attribute.
    133      */
    134     public void setHascode(boolean hasCode) {
    135         mHasCode   = hasCode;
    136     }
    137 
    138     /**
    139      * Returns an object representing a nested <var>zip</var> element.
    140      */
    141     public Object createZip() {
    142         Path path = new Path(getProject());
    143         mZipList.add(path);
    144         return path;
    145     }
    146 
    147     /**
    148      * Returns an object representing a nested <var>dex</var> element.
    149      * This is similar to a nested <var>file</var> element, except when {@link #mHasCode}
    150      * is <code>false</code> in which case it's ignored.
    151      */
    152     public Object createDex() {
    153         if (mDexPath == null) {
    154             return mDexPath = new Path(getProject());
    155         } else {
    156             throw new BuildException("Only one <dex> inner element can be provided");
    157         }
    158     }
    159 
    160     /**
    161      * Returns an object representing a nested <var>sourcefolder</var> element.
    162      */
    163     public Object createSourcefolder() {
    164         Path path = new Path(getProject());
    165         mSourceList.add(path);
    166         return path;
    167     }
    168 
    169     /**
    170      * Returns an object representing a nested <var>jarfolder</var> element.
    171      */
    172     public Object createJarfolder() {
    173         Path path = new Path(getProject());
    174         mJarfolderList.add(path);
    175         return path;
    176     }
    177 
    178     /**
    179      * Returns an object representing a nested <var>jarfile</var> element.
    180      */
    181     public Object createJarfile() {
    182         Path path = new Path(getProject());
    183         mJarfileList.add(path);
    184         return path;
    185     }
    186 
    187     /**
    188      * Returns an object representing a nested <var>nativefolder</var> element.
    189      */
    190     public Object createNativefolder() {
    191         Path path = new Path(getProject());
    192         mNativeList.add(path);
    193         return path;
    194     }
    195 
    196     @Override
    197     public void execute() throws BuildException {
    198 
    199         File outputFile;
    200         if (mApkFilepath != null) {
    201             outputFile = new File(mApkFilepath);
    202         } else {
    203             throw new BuildException("missing attribute 'apkFilepath'");
    204         }
    205 
    206         if (mResourceFile == null) {
    207             throw new BuildException("missing attribute 'resourcefile'");
    208         }
    209 
    210         if (mOutFolder == null) {
    211             throw new BuildException("missing attribute 'outfolder'");
    212         }
    213 
    214         // check dexPath is only one file.
    215         File dexFile = null;
    216         if (mHasCode) {
    217             String[] dexFiles = mDexPath.list();
    218             if (dexFiles.length != 1) {
    219                 throw new BuildException(String.format(
    220                         "Expected one dex file but path value resolve to %d files.",
    221                         dexFiles.length));
    222             }
    223             dexFile = new File(dexFiles[0]);
    224         }
    225 
    226         try {
    227             // build list of input files/folders to compute dependencies
    228             // add the content of the zip files.
    229             List<InputPath> inputPaths = new ArrayList<InputPath>();
    230 
    231             // resource file
    232             InputPath resourceInputPath = new InputPath(new File(mOutFolder, mResourceFile));
    233             inputPaths.add(resourceInputPath);
    234 
    235             // dex file
    236             inputPaths.add(new InputPath(dexFile));
    237 
    238             // zip input files
    239             List<File> zipFiles = new ArrayList<File>();
    240             for (Path pathList : mZipList) {
    241                 for (String path : pathList.list()) {
    242                     File f =  new File(path);
    243                     zipFiles.add(f);
    244                     inputPaths.add(new InputPath(f));
    245                 }
    246             }
    247 
    248             // now go through the list of source folders used to add non java files.
    249             List<File> sourceFolderList = new ArrayList<File>();
    250             if (mHasCode) {
    251                 for (Path pathList : mSourceList) {
    252                     for (String path : pathList.list()) {
    253                         File f =  new File(path);
    254                         sourceFolderList.add(f);
    255                         // because this is a source folder but we only care about non
    256                         // java files.
    257                         inputPaths.add(new SourceFolderInputPath(f));
    258                     }
    259                 }
    260             }
    261 
    262             // now go through the list of jar folders.
    263             List<File> jarFileList = new ArrayList<File>();
    264             for (Path pathList : mJarfolderList) {
    265                 for (String path : pathList.list()) {
    266                     // it's ok if top level folders are missing
    267                     File folder = new File(path);
    268                     if (folder.isDirectory()) {
    269                         String[] filenames = folder.list(new FilenameFilter() {
    270                             public boolean accept(File dir, String name) {
    271                                 return PATTERN_JAR_EXT.matcher(name).matches();
    272                             }
    273                         });
    274 
    275                         for (String filename : filenames) {
    276                             File f = new File(folder, filename);
    277                             jarFileList.add(f);
    278                             inputPaths.add(new InputPath(f));
    279                         }
    280                     }
    281                 }
    282             }
    283 
    284             // now go through the list of jar files.
    285             for (Path pathList : mJarfileList) {
    286                 for (String path : pathList.list()) {
    287                     File f = new File(path);
    288                     jarFileList.add(f);
    289                     inputPaths.add(new InputPath(f));
    290                 }
    291             }
    292 
    293             // now the native lib folder.
    294             List<FileEntry> nativeFileList = new ArrayList<FileEntry>();
    295             for (Path pathList : mNativeList) {
    296                 for (String path : pathList.list()) {
    297                     // it's ok if top level folders are missing
    298                     File folder = new File(path);
    299                     if (folder.isDirectory()) {
    300                         List<FileEntry> entries = ApkBuilder.getNativeFiles(folder,
    301                                 mDebugPackaging);
    302                         // add the list to the list of native files and then create an input
    303                         // path for each file
    304                         nativeFileList.addAll(entries);
    305 
    306                         for (FileEntry entry : entries) {
    307                             inputPaths.add(new InputPath(entry.mFile));
    308                         }
    309                     }
    310                 }
    311             }
    312 
    313             // Finally figure out the path to the dependency file.
    314             String depFile = outputFile.getAbsolutePath() + ".d";
    315 
    316             // check dependencies
    317             if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) {
    318                 System.out.println(
    319                         "No changes. No need to create apk.");
    320                 return;
    321             }
    322 
    323             if (mDebugSigning) {
    324                 System.out.println(String.format(
    325                         "Creating %s and signing it with a debug key...", outputFile.getName()));
    326             } else {
    327                 System.out.println(String.format(
    328                         "Creating %s for release...", outputFile.getName()));
    329             }
    330 
    331             ApkBuilder apkBuilder = new ApkBuilder(
    332                     outputFile,
    333                     resourceInputPath.getFile(),
    334                     dexFile,
    335                     mDebugSigning ? ApkBuilder.getDebugKeystore() : null,
    336                     mVerbose ? System.out : null);
    337             apkBuilder.setDebugMode(mDebugPackaging);
    338 
    339 
    340             // add the content of the zip files.
    341             for (File f : zipFiles) {
    342                 apkBuilder.addZipFile(f);
    343             }
    344 
    345             // now go through the list of file to directly add the to the list.
    346             for (File f : sourceFolderList) {
    347                 apkBuilder.addSourceFolder(f);
    348             }
    349 
    350             // now go through the list of jar folders.
    351             for (Path pathList : mJarfolderList) {
    352                 for (String path : pathList.list()) {
    353                     // it's ok if top level folders are missing
    354                     File folder = new File(path);
    355                     if (folder.isDirectory()) {
    356                         String[] filenames = folder.list(new FilenameFilter() {
    357                             public boolean accept(File dir, String name) {
    358                                 return PATTERN_JAR_EXT.matcher(name).matches();
    359                             }
    360                         });
    361 
    362                         for (String filename : filenames) {
    363                             apkBuilder.addResourcesFromJar(new File(folder, filename));
    364                         }
    365                     }
    366                 }
    367             }
    368 
    369             // now go through the list of jar files.
    370             for (File f : jarFileList) {
    371                 apkBuilder.addResourcesFromJar(f);
    372             }
    373 
    374             // and finally the native files
    375             apkBuilder.addNativeLibraries(nativeFileList);
    376 
    377             // close the archive
    378             apkBuilder.sealApk();
    379 
    380             // and generate the dependency file
    381             generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());
    382         } catch (DuplicateFileException e) {
    383             System.err.println(String.format(
    384                     "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
    385                     e.getArchivePath(), e.getFile1(), e.getFile2()));
    386             throw new BuildException(e);
    387         } catch (ApkCreationException e) {
    388             throw new BuildException(e);
    389         } catch (SealedApkException e) {
    390             throw new BuildException(e);
    391         } catch (IllegalArgumentException e) {
    392             throw new BuildException(e);
    393         }
    394     }
    395 
    396     @Override
    397     protected String getExecTaskName() {
    398         return "apkbuilder";
    399     }
    400 }
    401