OSDN Git Service

87317328fe76f910a780eb7c05fecc68b3958a7f
[android-x86/sdk.git] / anttasks / src / com / android / ant / AaptExecTask.java
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 file 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 }