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