2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.ant;
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;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
31 * Task to execute aapt.
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
37 * <p>The following map shows how to use the task for each supported aapt command line
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><res path=""></td><td>nested element(s)<br>with attribute (Path)</td></tr>
50 * <tr><td>-0 extension</td><td><nocompress extension=""><br><nocompress></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>
56 public final class AaptExecTask extends SingleDependencyTask {
59 * Class representing a <nocompress> node in the main task XML.
60 * This let the developers prevent compression of some files in assets/ and res/raw/
62 * If the extension is null, this will disable compression for all files in assets/ and
65 public final static class NoCompress {
69 * Sets the value of the "extension" attribute.
70 * @param extention the extension.
72 public void setExtension(String extention) {
73 mExtension = extention;
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;
99 * Input path that ignores the same file that aapt does.
101 private static class ResFolderInputPath extends InputPath {
102 public ResFolderInputPath(File file, Set<String> extensionsToCheck) {
103 super(file, extensionsToCheck);
107 public boolean ignores(File file) {
108 String name = file.getName();
109 char firstChar = name.charAt(0);
111 if (firstChar == '.' || (firstChar == '_' && file.isDirectory()) ||
112 name.charAt(name.length()-1) == '~') {
116 if ("CVS".equals(name) ||
117 "thumbs.db".equalsIgnoreCase(name) ||
118 "picasa.ini".equalsIgnoreCase(name)) {
122 String ext = getExtension(name);
123 if ("scc".equalsIgnoreCase(ext)) {
131 private final static InputPathFactory sPathFactory = new InputPathFactory() {
133 public InputPath createPath(File file, Set<String> extensionsToCheck) {
134 return new ResFolderInputPath(file, extensionsToCheck);
139 * Sets the value of the "executable" attribute.
140 * @param executable the value.
142 public void setExecutable(Path executable) {
143 mExecutable = TaskHelper.checkSinglePath("executable", executable);
147 * Sets the value of the "command" attribute.
148 * @param command the value.
150 public void setCommand(String command) {
155 * Sets the value of the "force" attribute.
156 * @param force the value.
158 public void setForce(boolean force) {
163 * Sets the value of the "verbose" attribute.
164 * @param verbose the value.
166 public void setVerbose(boolean verbose) {
171 * Sets the value of the "usecrunchcache" attribute
172 * @param usecrunch whether to use the crunch cache.
174 public void setNoCrunch(boolean nocrunch) {
175 mUseCrunchCache = nocrunch;
178 public void setNonConstantId(boolean nonConstantId) {
179 mNonConstantId = nonConstantId;
182 public void setVersioncode(String versionCode) {
183 if (versionCode.length() > 0) {
185 mVersionCode = Integer.decode(versionCode);
186 } catch (NumberFormatException e) {
187 System.out.println(String.format(
188 "WARNING: Ignoring invalid version code value '%s'.", versionCode));
194 * Sets the value of the "versionName" attribute
195 * @param versionName the value
197 public void setVersionname(String versionName) {
198 mVersionName = versionName;
201 public void setDebug(boolean value) {
206 * Sets the value of the "manifest" attribute.
207 * @param manifest the value.
209 public void setManifest(Path manifest) {
210 mManifest = TaskHelper.checkSinglePath("manifest", manifest);
214 * Sets the value of the "resources" attribute.
215 * @param resources the value.
217 * @deprecated Use nested element(s) <res path="value" />
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>();
227 mResources.add(new Path(getProject(), resources.toString()));
231 * Sets the value of the "assets" attribute.
232 * @param assets the value.
234 public void setAssets(Path assets) {
235 mAssets = TaskHelper.checkSinglePath("assets", assets);
239 * Sets the value of the "androidjar" attribute.
240 * @param androidJar the value.
242 public void setAndroidjar(Path androidJar) {
243 mAndroidJar = TaskHelper.checkSinglePath("androidjar", androidJar);
247 * Sets the value of the "outfolder" attribute.
248 * @param outFolder the value.
249 * @deprecated use {@link #setApkfolder(Path)}
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);
259 * Sets the value of the "apkfolder" attribute.
260 * @param apkFolder the value.
262 public void setApkfolder(Path apkFolder) {
263 mApkFolder = TaskHelper.checkSinglePath("apkfolder", apkFolder);
267 * Sets the value of the resourcefilename attribute
268 * @param apkName the value
270 public void setResourcefilename(String apkName) {
275 * Sets the value of the "rfolder" attribute.
276 * @param rFolder the value.
278 public void setRfolder(Path rFolder) {
279 mRFolder = TaskHelper.checkSinglePath("rfolder", rFolder);
282 public void setresourcefilter(String filter) {
283 if (filter != null && filter.length() > 0) {
284 mResourceFilter = filter;
288 public void setProjectLibrariesResName(String projectLibrariesResName) {
289 mProjectLibrariesResName = projectLibrariesResName;
292 public void setProjectLibrariesPackageName(String projectLibrariesPackageName) {
293 mProjectLibrariesPackageName = projectLibrariesPackageName;
298 * Returns an object representing a nested <var>nocompress</var> element.
300 public Object createNocompress() {
301 NoCompress nc = new NoCompress();
302 mNoCompressList.add(nc);
307 * Returns an object representing a nested <var>res</var> element.
309 public Object createRes() {
310 if (mResources == null) {
311 mResources = new ArrayList<Path>();
314 Path path = new Path(getProject());
315 mResources.add(path);
323 * Executes the loop. Based on the values inside project.properties, this will
324 * create alternate temporary ap_ files.
326 * @see org.apache.tools.ant.Task#execute()
329 public void execute() throws BuildException {
330 if (mProjectLibrariesResName == null) {
331 throw new BuildException("Missing attribute projectLibrariesResName");
333 if (mProjectLibrariesPackageName == null) {
334 throw new BuildException("Missing attribute projectLibrariesPackageName");
337 Project taskProject = getProject();
339 String libPkgProp = null;
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(';', ':');
350 // Call aapt. If there are libraries, we'll pass a non-null string of libs.
351 callAapt(libPkgProp);
355 protected String getExecTaskName() {
360 * Calls aapt with the given parameters.
361 * @param resourceFilter the resource configuration filter to pass to aapt (if configName is
363 * @param extraPackages an optional list of colon-separated packages. Can be null
364 * Ex: com.foo.one:com.foo.two:com.foo.lib
366 private void callAapt(String extraPackages) {
367 Project taskProject = getProject();
369 final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory();
371 // Get whether we have libraries
372 Object libResRef = taskProject.getReference(mProjectLibrariesResName);
374 // Set up our input paths that matter for dependency checks
375 ArrayList<File> paths = new ArrayList<File>();
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));
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));
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"),
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)));
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.");
409 System.out.println("Generating resource IDs...");
412 // in this case we want to run aapt if any file was updated/removed/added in any of the
414 List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/,
417 // let's not forget the manifest as an input path.
418 if (mManifest != null) {
419 inputPaths.add(new InputPath(new File(mManifest)));
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));
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";
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");
442 if (mResourceFilter == null) {
443 System.out.println("Creating full resource package...");
445 System.out.println(String.format(
446 "Creating resource package with filter: (%1$s)...",
451 // create a task for the default apk.
452 ExecTask task = new ExecTask();
453 task.setExecutable(mExecutable);
454 task.setFailonerror(true);
456 task.setTaskName(getExecTaskName());
458 // aapt command. Only "package" is supported at this time really.
459 task.createArg().setValue(mCommand);
462 if (mUseCrunchCache) {
463 task.createArg().setValue("--no-crunch");
466 if (mNonConstantId) {
467 task.createArg().setValue("--non-constant-id");
472 task.createArg().setValue("-f");
477 task.createArg().setValue("-v");
481 task.createArg().setValue("--debug-mode");
484 if (generateRClass) {
485 task.createArg().setValue("-m");
489 if (mResourceFilter != null) {
490 task.createArg().setValue("-c");
491 task.createArg().setValue(mResourceFilter);
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;
506 if (compressNothing == false) {
507 for (NoCompress nc : mNoCompressList) {
508 task.createArg().setValue("-0");
509 task.createArg().setValue(nc.mExtension);
513 if (extraPackages != null) {
514 task.createArg().setValue("--extra-packages");
515 task.createArg().setValue(extraPackages);
518 // if the project contains libraries, force auto-add-overlay
519 if (libResRef != null) {
520 task.createArg().setValue("--auto-add-overlay");
523 if (mVersionCode != 0) {
524 task.createArg().setValue("--version-code");
525 task.createArg().setValue(Integer.toString(mVersionCode));
528 if ((mVersionName != null) && (mVersionName.length() > 0)) {
529 task.createArg().setValue("--version-name");
530 task.createArg().setValue(mVersionName);
534 if (mManifest != null) {
535 task.createArg().setValue("-M");
536 task.createArg().setValue(mManifest);
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);
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);
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);
572 if (mAndroidJar != null) {
573 task.createArg().setValue("-I");
574 task.createArg().setValue(mAndroidJar);
577 // apk file. This is based on the apkFolder, apkBaseName, and the configName (if applicable)
578 String filename = null;
579 if (mApkName != null) {
583 if (filename != null) {
584 File file = new File(mApkFolder, filename);
585 task.createArg().setValue("-F");
586 task.createArg().setValue(file.getAbsolutePath());
589 // R class generation
590 if (generateRClass) {
591 task.createArg().setValue("-J");
592 task.createArg().setValue(mRFolder);
595 // Use dependency generation
596 task.createArg().setValue("--generate-dependencies");
598 // final setup of the task
599 task.setProject(taskProject);
600 task.setOwningTarget(getOwningTarget());