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