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><res path=""></td><td>nested element(s)<br>with attribute (Path)</td></tr> 49 * <tr><td>-0 extension</td><td><nocompress extension=""><br><nocompress></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 <nocompress> 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