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 org.apache.tools.ant.BuildException;
     20 import org.apache.tools.ant.Project;
     21 import org.apache.tools.ant.taskdefs.ExecTask;
     22 import org.apache.tools.ant.types.Path;
     23 
     24 import java.io.File;
     25 import java.util.ArrayList;
     26 import java.util.Collections;
     27 import java.util.List;
     28 import java.util.Set;
     29 
     30 /**
     31  * Task to execute aapt.
     32  *
     33  * <p>It does not follow the exec task format, instead it has its own parameters, which maps
     34  * directly to aapt.</p>
     35  * <p>It is able to run aapt several times if library setup requires generating several
     36  * R.java files.
     37  * <p>The following map shows how to use the task for each supported aapt command line
     38  * parameter.</p>
     39  *
     40  * <table border="1">
     41  * <tr><td><b>Aapt Option</b></td><td><b>Ant Name</b></td><td><b>Type</b></td></tr>
     42  * <tr><td>path to aapt</td><td>executable</td><td>attribute (Path)</td>
     43  * <tr><td>command</td><td>command</td><td>attribute (String)</td>
     44  * <tr><td>-v</td><td>verbose</td><td>attribute (boolean)</td></tr>
     45  * <tr><td>-f</td><td>force</td><td>attribute (boolean)</td></tr>
     46  * <tr><td>-M AndroidManifest.xml</td><td>manifest</td><td>attribute (Path)</td></tr>
     47  * <tr><td>-I base-package</td><td>androidjar</td><td>attribute (Path)</td></tr>
     48  * <tr><td>-A asset-source-dir</td><td>assets</td><td>attribute (Path</td></tr>
     49  * <tr><td>-S resource-sources</td><td>&lt;res path=""&gt;</td><td>nested element(s)<br>with attribute (Path)</td></tr>
     50  * <tr><td>-0 extension</td><td>&lt;nocompress extension=""&gt;<br>&lt;nocompress&gt;</td><td>nested element(s)<br>with attribute (String)</td></tr>
     51  * <tr><td>-F apk-file</td><td>apkfolder<br>outfolder<br>apkbasename<br>basename</td><td>attribute (Path)<br>attribute (Path) deprecated<br>attribute (String)<br>attribute (String) deprecated</td></tr>
     52  * <tr><td>-J R-file-dir</td><td>rfolder</td><td>attribute (Path)<br>-m always enabled</td></tr>
     53  * <tr><td>--rename-manifest-package package-name</td><td>manifestpackage</td><td>attribute (String)</td></tr>
     54  * <tr><td></td><td></td><td></td></tr>
     55  * </table>
     56  */
     57 public final class AaptExecTask extends SingleDependencyTask {
     58 
     59     /**
     60      * Class representing a &lt;nocompress&gt; node in the main task XML.
     61      * This let the developers prevent compression of some files in assets/ and res/raw/
     62      * by extension.
     63      * If the extension is null, this will disable compression for all  files in assets/ and
     64      * res/raw/
     65      */
     66     public final static class NoCompress {
     67         String mExtension;
     68 
     69         /**
     70          * Sets the value of the "extension" attribute.
     71          * @param extention the extension.
     72          */
     73         public void setExtension(String extention) {
     74             mExtension = extention;
     75         }
     76     }
     77 
     78     private String mExecutable;
     79     private String mCommand;
     80     private boolean mForce = true; // true due to legacy reasons
     81     private boolean mDebug = false;
     82     private boolean mVerbose = false;
     83     private boolean mUseCrunchCache = false;
     84     private int mVersionCode = 0;
     85     private String mVersionName;
     86     private String mManifest;
     87     private String mManifestPackage;
     88     private ArrayList<Path> mResources;
     89     private String mAssets;
     90     private String mAndroidJar;
     91     private String mApkFolder;
     92     private String mApkName;
     93     private String mResourceFilter;
     94     private String mRFolder;
     95     private final ArrayList<NoCompress> mNoCompressList = new ArrayList<NoCompress>();
     96     private String mLibraryResFolderPathRefid;
     97     private String mLibraryPackagesRefid;
     98     private boolean mNonConstantId;
     99 
    100     /**
    101      * Input path that ignores the same folders/files that aapt does.
    102      */
    103     private static class ResFolderInputPath extends InputPath {
    104         public ResFolderInputPath(File file, Set<String> extensionsToCheck) {
    105             super(file, extensionsToCheck);
    106         }
    107 
    108         @Override
    109         public boolean ignores(File file) {
    110             String name = file.getName();
    111             char firstChar = name.charAt(0);
    112 
    113             if (firstChar == '.' || (firstChar == '_' && file.isDirectory()) ||
    114                     name.charAt(name.length()-1) == '~') {
    115                 return true;
    116             }
    117 
    118             if ("CVS".equals(name) ||
    119                     "thumbs.db".equalsIgnoreCase(name) ||
    120                     "picasa.ini".equalsIgnoreCase(name)) {
    121                 return true;
    122             }
    123 
    124             String ext = getExtension(name);
    125             if ("scc".equalsIgnoreCase(ext)) {
    126                 return true;
    127             }
    128 
    129             return false;
    130         }
    131     }
    132 
    133     private final static InputPathFactory sPathFactory = new InputPathFactory() {
    134 
    135         @Override
    136         public InputPath createPath(File file, Set<String> extensionsToCheck) {
    137             return new ResFolderInputPath(file, extensionsToCheck);
    138         }
    139     };
    140 
    141     /**
    142      * Sets the value of the "executable" attribute.
    143      * @param executable the value.
    144      */
    145     public void setExecutable(Path executable) {
    146         mExecutable = TaskHelper.checkSinglePath("executable", executable);
    147     }
    148 
    149     /**
    150      * Sets the value of the "command" attribute.
    151      * @param command the value.
    152      */
    153     public void setCommand(String command) {
    154         mCommand = command;
    155     }
    156 
    157     /**
    158      * Sets the value of the "force" attribute.
    159      * @param force the value.
    160      */
    161     public void setForce(boolean force) {
    162         mForce = force;
    163     }
    164 
    165     /**
    166      * Sets the value of the "verbose" attribute.
    167      * @param verbose the value.
    168      */
    169     public void setVerbose(boolean verbose) {
    170         mVerbose = verbose;
    171     }
    172 
    173     /**
    174      * Sets the value of the "usecrunchcache" attribute
    175      * @param usecrunch whether to use the crunch cache.
    176      */
    177     public void setNoCrunch(boolean nocrunch) {
    178         mUseCrunchCache = nocrunch;
    179     }
    180 
    181     public void setNonConstantId(boolean nonConstantId) {
    182         mNonConstantId = nonConstantId;
    183     }
    184 
    185     public void setVersioncode(String versionCode) {
    186         if (versionCode.length() > 0) {
    187             try {
    188                 mVersionCode = Integer.decode(versionCode);
    189             } catch (NumberFormatException e) {
    190                 System.out.println(String.format(
    191                         "WARNING: Ignoring invalid version code value '%s'.", versionCode));
    192             }
    193         }
    194     }
    195 
    196     /**
    197      * Sets the value of the "versionName" attribute
    198      * @param versionName the value
    199      */
    200     public void setVersionname(String versionName) {
    201         mVersionName = versionName;
    202     }
    203 
    204     public void setDebug(boolean value) {
    205         mDebug = value;
    206     }
    207 
    208     /**
    209      * Sets the value of the "manifest" attribute.
    210      * @param manifest the value.
    211      */
    212     public void setManifest(Path manifest) {
    213         mManifest = TaskHelper.checkSinglePath("manifest", manifest);
    214     }
    215 
    216     /**
    217      * Sets a custom manifest package ID to be used during packaging.<p>
    218      * The manifest will be rewritten so that its package ID becomes the value given here.
    219      * Relative class names in the manifest (e.g. ".Foo") will be rewritten to absolute names based
    220      * on the existing package name, meaning that no code changes need to be made.
    221      *
    222      * @param packageName The package ID the APK should have.
    223      */
    224     public void setManifestpackage(String packageName) {
    225         if (packageName != null && packageName.length() != 0) {
    226             mManifestPackage = packageName;
    227         }
    228     }
    229 
    230     /**
    231      * Sets the value of the "resources" attribute.
    232      * @param resources the value.
    233      *
    234      * @deprecated Use nested element(s) <res path="value" />
    235      */
    236     @Deprecated
    237     public void setResources(Path resources) {
    238         System.out.println("WARNNG: Using deprecated 'resources' attribute in AaptExecLoopTask." +
    239                 "Use nested element(s) <res path=\"value\" /> instead.");
    240         if (mResources == null) {
    241             mResources = new ArrayList<Path>();
    242         }
    243 
    244         mResources.add(new Path(getProject(), resources.toString()));
    245     }
    246 
    247     /**
    248      * Sets the value of the "assets" attribute.
    249      * @param assets the value.
    250      */
    251     public void setAssets(Path assets) {
    252         mAssets = TaskHelper.checkSinglePath("assets", assets);
    253     }
    254 
    255     /**
    256      * Sets the value of the "androidjar" attribute.
    257      * @param androidJar the value.
    258      */
    259     public void setAndroidjar(Path androidJar) {
    260         mAndroidJar = TaskHelper.checkSinglePath("androidjar", androidJar);
    261     }
    262 
    263     /**
    264      * Sets the value of the "outfolder" attribute.
    265      * @param outFolder the value.
    266      * @deprecated use {@link #setApkfolder(Path)}
    267      */
    268     @Deprecated
    269     public void setOutfolder(Path outFolder) {
    270         System.out.println("WARNNG: Using deprecated 'outfolder' attribute in AaptExecLoopTask." +
    271                 "Use 'apkfolder' (path) instead.");
    272         mApkFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
    273     }
    274 
    275     /**
    276      * Sets the value of the "apkfolder" attribute.
    277      * @param apkFolder the value.
    278      */
    279     public void setApkfolder(Path apkFolder) {
    280         mApkFolder = TaskHelper.checkSinglePath("apkfolder", apkFolder);
    281     }
    282 
    283     /**
    284      * Sets the value of the resourcefilename attribute
    285      * @param apkName the value
    286      */
    287     public void setResourcefilename(String apkName) {
    288         mApkName = apkName;
    289     }
    290 
    291     /**
    292      * Sets the value of the "rfolder" attribute.
    293      * @param rFolder the value.
    294      */
    295     public void setRfolder(Path rFolder) {
    296         mRFolder = TaskHelper.checkSinglePath("rfolder", rFolder);
    297     }
    298 
    299     public void setresourcefilter(String filter) {
    300         if (filter != null && filter.length() > 0) {
    301             mResourceFilter = filter;
    302         }
    303     }
    304 
    305     /**
    306      * Set the property name of the property that contains the list of res folder for
    307      * Library Projects. This sets the name and not the value itself to handle the case where
    308      * it doesn't exist.
    309      * @param projectLibrariesResName
    310      */
    311     public void setLibraryResFolderPathRefid(String libraryResFolderPathRefid) {
    312         mLibraryResFolderPathRefid = libraryResFolderPathRefid;
    313     }
    314 
    315     public void setLibraryPackagesRefid(String libraryPackagesRefid) {
    316         mLibraryPackagesRefid = libraryPackagesRefid;
    317     }
    318 
    319     /**
    320      * Returns an object representing a nested <var>nocompress</var> element.
    321      */
    322     public Object createNocompress() {
    323         NoCompress nc = new NoCompress();
    324         mNoCompressList.add(nc);
    325         return nc;
    326     }
    327 
    328     /**
    329      * Returns an object representing a nested <var>res</var> element.
    330      */
    331     public Object createRes() {
    332         if (mResources == null) {
    333             mResources = new ArrayList<Path>();
    334         }
    335 
    336         Path path = new Path(getProject());
    337         mResources.add(path);
    338 
    339         return path;
    340     }
    341 
    342     /*
    343      * (non-Javadoc)
    344      *
    345      * Executes the loop. Based on the values inside project.properties, this will
    346      * create alternate temporary ap_ files.
    347      *
    348      * @see org.apache.tools.ant.Task#execute()
    349      */
    350     @Override
    351     public void execute() throws BuildException {
    352         if (mLibraryResFolderPathRefid == null) {
    353             throw new BuildException("Missing attribute libraryResFolderPathRefid");
    354         }
    355         if (mLibraryPackagesRefid == null) {
    356             throw new BuildException("Missing attribute libraryPackagesRefid");
    357         }
    358 
    359         Project taskProject = getProject();
    360 
    361         String libPkgProp = null;
    362 
    363         // if the parameters indicate generation of the R class, check if
    364         // more R classes need to be created for libraries, only if this project itself
    365         // is not a library
    366         if (mNonConstantId == false && mRFolder != null && new File(mRFolder).isDirectory()) {
    367             libPkgProp = taskProject.getProperty(mLibraryPackagesRefid);
    368             if (libPkgProp != null) {
    369                 // Replace ";" with ":" since that's what aapt expects
    370                 libPkgProp = libPkgProp.replace(';', ':');
    371             }
    372         }
    373         // Call aapt. If there are libraries, we'll pass a non-null string of libs.
    374         callAapt(libPkgProp);
    375     }
    376 
    377     @Override
    378     protected String getExecTaskName() {
    379         return "aapt";
    380     }
    381 
    382     /**
    383      * Calls aapt with the given parameters.
    384      * @param resourceFilter the resource configuration filter to pass to aapt (if configName is
    385      * non null)
    386      * @param extraPackages an optional list of colon-separated packages. Can be null
    387      *        Ex: com.foo.one:com.foo.two:com.foo.lib
    388      */
    389     private void callAapt(String extraPackages) {
    390         Project taskProject = getProject();
    391 
    392         final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory();
    393 
    394         // Get whether we have libraries
    395         Object libResRef = taskProject.getReference(mLibraryResFolderPathRefid);
    396 
    397         // Set up our input paths that matter for dependency checks
    398         ArrayList<File> paths = new ArrayList<File>();
    399 
    400         // the project res folder is an input path of course
    401         for (Path pathList : mResources) {
    402             for (String path : pathList.list()) {
    403                 paths.add(new File(path));
    404             }
    405         }
    406 
    407         // and if libraries exist, their res folders folders too.
    408         if (libResRef instanceof Path) {
    409             for (String path : ((Path)libResRef).list()) {
    410                 paths.add(new File(path));
    411             }
    412         }
    413 
    414         // Now we figure out what we need to do
    415         if (generateRClass) {
    416             // in this case we only want to run aapt if an XML file was touched, or if any
    417             // file is added/removed
    418             List<InputPath> inputPaths = getInputPaths(paths, Collections.singleton("xml"),
    419                     sPathFactory);
    420 
    421             // let's not forget the manifest as an input path (with no extension restrictions).
    422             if (mManifest != null) {
    423                 inputPaths.add(new InputPath(new File(mManifest)));
    424             }
    425 
    426             // Check to see if our dependencies have changed. If not, then skip
    427             if (initDependencies(mRFolder + File.separator + "R.java.d", inputPaths)
    428                               && dependenciesHaveChanged() == false) {
    429                 System.out.println("No changed resources. R.java and Manifest.java untouched.");
    430                 return;
    431             } else {
    432                 System.out.println("Generating resource IDs...");
    433             }
    434         } else {
    435             // in this case we want to run aapt if any file was updated/removed/added in any of the
    436             // input paths
    437             List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/,
    438                     sPathFactory);
    439 
    440             // let's not forget the manifest as an input path.
    441             if (mManifest != null) {
    442                 inputPaths.add(new InputPath(new File(mManifest)));
    443             }
    444 
    445             // If we're here to generate a .ap_ file we need to use assets as an input path as well.
    446             if (mAssets != null) {
    447                 File assetsDir = new File(mAssets);
    448                 if (assetsDir.isDirectory()) {
    449                     inputPaths.add(new InputPath(assetsDir));
    450                 }
    451             }
    452 
    453             // Find our dependency file. It should have the same name as our target .ap_ but
    454             // with a .d extension
    455             String dependencyFilePath = mApkFolder + File.separator + mApkName;
    456             dependencyFilePath += ".d";
    457 
    458             // Check to see if our dependencies have changed
    459             if (initDependencies(dependencyFilePath, inputPaths)
    460                             && dependenciesHaveChanged() == false) {
    461                 System.out.println("No changed resources or assets. " + mApkName
    462                                     + " remains untouched");
    463                 return;
    464             }
    465             if (mResourceFilter == null) {
    466                 System.out.println("Creating full resource package...");
    467             } else {
    468                 System.out.println(String.format(
    469                         "Creating resource package with filter: (%1$s)...",
    470                         mResourceFilter));
    471             }
    472         }
    473 
    474         // create a task for the default apk.
    475         ExecTask task = new ExecTask();
    476         task.setExecutable(mExecutable);
    477         task.setFailonerror(true);
    478 
    479         task.setTaskName(getExecTaskName());
    480 
    481         // aapt command. Only "package" is supported at this time really.
    482         task.createArg().setValue(mCommand);
    483 
    484         // No crunch flag
    485         if (mUseCrunchCache) {
    486             task.createArg().setValue("--no-crunch");
    487         }
    488 
    489         if (mNonConstantId) {
    490             task.createArg().setValue("--non-constant-id");
    491         }
    492 
    493         // force flag
    494         if (mForce) {
    495             task.createArg().setValue("-f");
    496         }
    497 
    498         // verbose flag
    499         if (mVerbose) {
    500             task.createArg().setValue("-v");
    501         }
    502 
    503         if (mDebug) {
    504             task.createArg().setValue("--debug-mode");
    505         }
    506 
    507         if (generateRClass) {
    508             task.createArg().setValue("-m");
    509         }
    510 
    511         // filters if needed
    512         if (mResourceFilter != null && mResourceFilter.length() > 0) {
    513             task.createArg().setValue("-c");
    514             task.createArg().setValue(mResourceFilter);
    515         }
    516 
    517         // no compress flag
    518         // first look to see if there's a NoCompress object with no specified extension
    519         boolean compressNothing = false;
    520         for (NoCompress nc : mNoCompressList) {
    521             if (nc.mExtension == null) {
    522                 task.createArg().setValue("-0");
    523                 task.createArg().setValue("");
    524                 compressNothing = true;
    525                 break;
    526             }
    527         }
    528 
    529         if (compressNothing == false) {
    530             for (NoCompress nc : mNoCompressList) {
    531                 task.createArg().setValue("-0");
    532                 task.createArg().setValue(nc.mExtension);
    533             }
    534         }
    535 
    536         if (mNonConstantId == false && extraPackages != null && extraPackages.length() > 0) {
    537             task.createArg().setValue("--extra-packages");
    538             task.createArg().setValue(extraPackages);
    539         }
    540 
    541         // if the project contains libraries, force auto-add-overlay
    542         if (libResRef != null) {
    543             task.createArg().setValue("--auto-add-overlay");
    544         }
    545 
    546         if (mVersionCode != 0) {
    547             task.createArg().setValue("--version-code");
    548             task.createArg().setValue(Integer.toString(mVersionCode));
    549         }
    550 
    551         if (mVersionName != null && mVersionName.length() > 0) {
    552             task.createArg().setValue("--version-name");
    553             task.createArg().setValue(mVersionName);
    554         }
    555 
    556         // manifest location
    557         if (mManifest != null && mManifest.length() > 0) {
    558             task.createArg().setValue("-M");
    559             task.createArg().setValue(mManifest);
    560         }
    561 
    562         // Rename manifest package
    563         if (mManifestPackage != null) {
    564             task.createArg().setValue("--rename-manifest-package");
    565             task.createArg().setValue(mManifestPackage);
    566         }
    567 
    568         // resources locations.
    569         if (mResources.size() > 0) {
    570             for (Path pathList : mResources) {
    571                 for (String path : pathList.list()) {
    572                     // This may not exists, and aapt doesn't like it, so we check first.
    573                     File res = new File(path);
    574                     if (res.isDirectory()) {
    575                         task.createArg().setValue("-S");
    576                         task.createArg().setValue(path);
    577                     }
    578                 }
    579             }
    580         }
    581 
    582         // add other resources coming from library project
    583         if (libResRef instanceof Path) {
    584             for (String path : ((Path)libResRef).list()) {
    585                 // This may not exists, and aapt doesn't like it, so we check first.
    586                 File res = new File(path);
    587                 if (res.isDirectory()) {
    588                     task.createArg().setValue("-S");
    589                     task.createArg().setValue(path);
    590                 }
    591             }
    592         }
    593 
    594         // assets location. This may not exists, and aapt doesn't like it, so we check first.
    595         if (mAssets != null && new File(mAssets).isDirectory()) {
    596             task.createArg().setValue("-A");
    597             task.createArg().setValue(mAssets);
    598         }
    599 
    600         // android.jar
    601         if (mAndroidJar != null) {
    602             task.createArg().setValue("-I");
    603             task.createArg().setValue(mAndroidJar);
    604         }
    605 
    606         // apk file. This is based on the apkFolder, apkBaseName, and the configName (if applicable)
    607         String filename = null;
    608         if (mApkName != null) {
    609             filename = mApkName;
    610         }
    611 
    612         if (filename != null) {
    613             File file = new File(mApkFolder, filename);
    614             task.createArg().setValue("-F");
    615             task.createArg().setValue(file.getAbsolutePath());
    616         }
    617 
    618         // R class generation
    619         if (generateRClass) {
    620             task.createArg().setValue("-J");
    621             task.createArg().setValue(mRFolder);
    622         }
    623 
    624         // Use dependency generation
    625         task.createArg().setValue("--generate-dependencies");
    626 
    627         // final setup of the task
    628         task.setProject(taskProject);
    629         task.setOwningTarget(getOwningTarget());
    630 
    631         // execute it.
    632         task.execute();
    633     }
    634 }
    635