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.Task; 22 import org.apache.tools.ant.taskdefs.ExecTask; 23 import org.apache.tools.ant.types.Path; 24 25 import java.io.File; 26 import java.util.ArrayList; 27 28 /** 29 * Task to execute aapt. 30 * 31 * <p>It does not follow the exec task format, instead it has its own parameters, which maps 32 * directly to aapt.</p> 33 * <p>It is able to run aapt several times if library setup requires generating several 34 * R.java files. 35 * <p>The following map shows how to use the task for each supported aapt command line 36 * parameter.</p> 37 * 38 * <table border="1"> 39 * <tr><td><b>Aapt Option</b></td><td><b>Ant Name</b></td><td><b>Type</b></td></tr> 40 * <tr><td>path to aapt</td><td>executable</td><td>attribute (Path)</td> 41 * <tr><td>command</td><td>command</td><td>attribute (String)</td> 42 * <tr><td>-v</td><td>verbose</td><td>attribute (boolean)</td></tr> 43 * <tr><td>-f</td><td>force</td><td>attribute (boolean)</td></tr> 44 * <tr><td>-M AndroidManifest.xml</td><td>manifest</td><td>attribute (Path)</td></tr> 45 * <tr><td>-I base-package</td><td>androidjar</td><td>attribute (Path)</td></tr> 46 * <tr><td>-A asset-source-dir</td><td>assets</td><td>attribute (Path</td></tr> 47 * <tr><td>-S resource-sources</td><td><res path=""></td><td>nested element(s)<br>with attribute (Path)</td></tr> 48 * <tr><td>-0 extension</td><td><nocompress extension=""><br><nocompress></td><td>nested element(s)<br>with attribute (String)</td></tr> 49 * <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> 50 * <tr><td>-J R-file-dir</td><td>rfolder</td><td>attribute (Path)<br>-m always enabled</td></tr> 51 * <tr><td></td><td></td><td></td></tr> 52 * </table> 53 */ 54 public final class AaptExecLoopTask extends Task { 55 56 /** 57 * Class representing a <nocompress> node in the main task XML. 58 * This let the developers prevent compression of some files in assets/ and res/raw/ 59 * by extension. 60 * If the extension is null, this will disable compression for all files in assets/ and 61 * res/raw/ 62 */ 63 public final static class NoCompress { 64 String mExtension; 65 66 /** 67 * Sets the value of the "extension" attribute. 68 * @param extention the extension. 69 */ 70 public void setExtension(String extention) { 71 mExtension = extention; 72 } 73 } 74 75 private String mExecutable; 76 private String mCommand; 77 private boolean mForce = true; // true due to legacy reasons 78 private boolean mVerbose = false; 79 private int mVersionCode = 0; 80 private String mManifest; 81 private ArrayList<Path> mResources; 82 private String mAssets; 83 private String mAndroidJar; 84 private String mApkFolder; 85 @Deprecated private String mApkBaseName; 86 private String mApkName; 87 private String mResourceFilter; 88 private String mRFolder; 89 private final ArrayList<NoCompress> mNoCompressList = new ArrayList<NoCompress>(); 90 91 /** 92 * Sets the value of the "executable" attribute. 93 * @param executable the value. 94 */ 95 public void setExecutable(Path executable) { 96 mExecutable = TaskHelper.checkSinglePath("executable", executable); 97 } 98 99 /** 100 * Sets the value of the "command" attribute. 101 * @param command the value. 102 */ 103 public void setCommand(String command) { 104 mCommand = command; 105 } 106 107 /** 108 * Sets the value of the "force" attribute. 109 * @param force the value. 110 */ 111 public void setForce(boolean force) { 112 mForce = force; 113 } 114 115 /** 116 * Sets the value of the "verbose" attribute. 117 * @param verbose the value. 118 */ 119 public void setVerbose(boolean verbose) { 120 mVerbose = verbose; 121 } 122 123 public void setVersioncode(String versionCode) { 124 if (versionCode.length() > 0) { 125 try { 126 mVersionCode = Integer.decode(versionCode); 127 } catch (NumberFormatException e) { 128 System.out.println(String.format( 129 "WARNING: Ignoring invalid version code value '%s'.", versionCode)); 130 } 131 } 132 } 133 134 /** 135 * Sets the value of the "manifest" attribute. 136 * @param manifest the value. 137 */ 138 public void setManifest(Path manifest) { 139 mManifest = TaskHelper.checkSinglePath("manifest", manifest); 140 } 141 142 /** 143 * Sets the value of the "resources" attribute. 144 * @param resources the value. 145 * 146 * @deprecated Use nested element(s) <res path="value" /> 147 */ 148 @Deprecated 149 public void setResources(Path resources) { 150 System.out.println("WARNNG: Using deprecated 'resources' attribute in AaptExecLoopTask." + 151 "Use nested element(s) <res path=\"value\" /> instead."); 152 if (mResources == null) { 153 mResources = new ArrayList<Path>(); 154 } 155 156 mResources.add(new Path(getProject(), resources.toString())); 157 } 158 159 /** 160 * Sets the value of the "assets" attribute. 161 * @param assets the value. 162 */ 163 public void setAssets(Path assets) { 164 mAssets = TaskHelper.checkSinglePath("assets", assets); 165 } 166 167 /** 168 * Sets the value of the "androidjar" attribute. 169 * @param androidJar the value. 170 */ 171 public void setAndroidjar(Path androidJar) { 172 mAndroidJar = TaskHelper.checkSinglePath("androidjar", androidJar); 173 } 174 175 /** 176 * Sets the value of the "outfolder" attribute. 177 * @param outFolder the value. 178 * @deprecated use {@link #setApkfolder(Path)} 179 */ 180 @Deprecated 181 public void setOutfolder(Path outFolder) { 182 System.out.println("WARNNG: Using deprecated 'outfolder' attribute in AaptExecLoopTask." + 183 "Use 'apkfolder' (path) instead."); 184 mApkFolder = TaskHelper.checkSinglePath("outfolder", outFolder); 185 } 186 187 /** 188 * Sets the value of the "apkfolder" attribute. 189 * @param apkFolder the value. 190 */ 191 public void setApkfolder(Path apkFolder) { 192 mApkFolder = TaskHelper.checkSinglePath("apkfolder", apkFolder); 193 } 194 195 /** 196 * Sets the value of the "basename" attribute. 197 * @param baseName the value. 198 * @deprecated use {@link #setApkbasename(String)} 199 */ 200 @Deprecated 201 public void setBasename(String baseName) { 202 System.out.println("WARNNG: Using deprecated 'basename' attribute in AaptExecLoopTask." + 203 "Use 'resourcefilename' (string) instead."); 204 mApkBaseName = baseName; 205 } 206 207 /** 208 * Sets the value of the "apkbasename" attribute. 209 * @param apkbaseName the value. 210 */ 211 public void setApkbasename(String apkbaseName) { 212 System.out.println("WARNNG: Using deprecated 'apkbasename' attribute in AaptExecLoopTask." + 213 "Use 'resourcefilename' (string) instead."); 214 mApkBaseName = apkbaseName; 215 } 216 217 /** 218 * Sets the value of the resourcefilename attribute 219 * @param apkName the value 220 */ 221 public void setResourcefilename(String apkName) { 222 mApkName = apkName; 223 } 224 225 /** 226 * Sets the value of the "rfolder" attribute. 227 * @param rFolder the value. 228 */ 229 public void setRfolder(Path rFolder) { 230 mRFolder = TaskHelper.checkSinglePath("rfolder", rFolder); 231 } 232 233 public void setresourcefilter(String filter) { 234 if (filter != null && filter.length() > 0) { 235 mResourceFilter = filter; 236 } 237 } 238 239 /** 240 * Returns an object representing a nested <var>nocompress</var> element. 241 */ 242 public Object createNocompress() { 243 NoCompress nc = new NoCompress(); 244 mNoCompressList.add(nc); 245 return nc; 246 } 247 248 /** 249 * Returns an object representing a nested <var>res</var> element. 250 */ 251 public Object createRes() { 252 if (mResources == null) { 253 mResources = new ArrayList<Path>(); 254 } 255 256 Path path = new Path(getProject()); 257 mResources.add(path); 258 259 return path; 260 } 261 262 /* 263 * (non-Javadoc) 264 * 265 * Executes the loop. Based on the values inside default.properties, this will 266 * create alternate temporary ap_ files. 267 * 268 * @see org.apache.tools.ant.Task#execute() 269 */ 270 @Override 271 public void execute() throws BuildException { 272 Project taskProject = getProject(); 273 274 // first do a full resource package 275 callAapt(null /*customPackage*/); 276 277 // if the parameters indicate generation of the R class, check if 278 // more R classes need to be created for libraries. 279 if (mRFolder != null && new File(mRFolder).isDirectory()) { 280 String libPkgProp = taskProject.getProperty("android.libraries.package"); 281 if (libPkgProp != null) { 282 // get the main package to compare in case the libraries use the same 283 String mainPackage = taskProject.getProperty("manifest.package"); 284 285 String[] libPkgs = libPkgProp.split(";"); 286 for (String libPkg : libPkgs) { 287 if (libPkg.length() > 0 && mainPackage.equals(libPkg) == false) { 288 // FIXME: instead of recreating R.java from scratch, maybe copy 289 // the files (R.java and manifest.java)? This would force to replace 290 // the package line on the fly. 291 callAapt(libPkg); 292 } 293 } 294 } 295 } 296 } 297 298 /** 299 * Calls aapt with the given parameters. 300 * @param resourceFilter the resource configuration filter to pass to aapt (if configName is 301 * non null) 302 * @param customPackage an optional custom package. 303 */ 304 private void callAapt(String customPackage) { 305 Project taskProject = getProject(); 306 307 final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory(); 308 309 if (generateRClass) { 310 } else if (mResourceFilter == null) { 311 System.out.println("Creating full resource package..."); 312 } else { 313 System.out.println(String.format( 314 "Creating resource package with filter: (%1$s)...", 315 mResourceFilter)); 316 } 317 318 // create a task for the default apk. 319 ExecTask task = new ExecTask(); 320 task.setExecutable(mExecutable); 321 task.setFailonerror(true); 322 323 // aapt command. Only "package" is supported at this time really. 324 task.createArg().setValue(mCommand); 325 326 // force flag 327 if (mForce) { 328 task.createArg().setValue("-f"); 329 } 330 331 // verbose flag 332 if (mVerbose) { 333 task.createArg().setValue("-v"); 334 } 335 336 if (generateRClass) { 337 task.createArg().setValue("-m"); 338 } 339 340 // filters if needed 341 if (mResourceFilter != null) { 342 task.createArg().setValue("-c"); 343 task.createArg().setValue(mResourceFilter); 344 } 345 346 // no compress flag 347 // first look to see if there's a NoCompress object with no specified extension 348 boolean compressNothing = false; 349 for (NoCompress nc : mNoCompressList) { 350 if (nc.mExtension == null) { 351 task.createArg().setValue("-0"); 352 task.createArg().setValue(""); 353 compressNothing = true; 354 break; 355 } 356 } 357 358 if (compressNothing == false) { 359 for (NoCompress nc : mNoCompressList) { 360 task.createArg().setValue("-0"); 361 task.createArg().setValue(nc.mExtension); 362 } 363 } 364 365 if (customPackage != null) { 366 task.createArg().setValue("--custom-package"); 367 task.createArg().setValue(customPackage); 368 } 369 370 // if the project contains libraries, force auto-add-overlay 371 Object libSrc = taskProject.getReference("android.libraries.res"); 372 if (libSrc != null) { 373 task.createArg().setValue("--auto-add-overlay"); 374 } 375 376 if (mVersionCode != 0) { 377 task.createArg().setValue("--version-code"); 378 task.createArg().setValue(Integer.toString(mVersionCode)); 379 } 380 381 // manifest location 382 if (mManifest != null) { 383 task.createArg().setValue("-M"); 384 task.createArg().setValue(mManifest); 385 } 386 387 // resources locations. 388 if (mResources.size() > 0) { 389 for (Path pathList : mResources) { 390 for (String path : pathList.list()) { 391 // This may not exists, and aapt doesn't like it, so we check first. 392 File res = new File(path); 393 if (res.isDirectory()) { 394 task.createArg().setValue("-S"); 395 task.createArg().setValue(path); 396 } 397 } 398 } 399 } 400 401 // add other resources coming from library project 402 Object libPath = taskProject.getReference("android.libraries.res"); 403 if (libPath instanceof Path) { 404 for (String path : ((Path)libPath).list()) { 405 // This may not exists, and aapt doesn't like it, so we check first. 406 File res = new File(path); 407 if (res.isDirectory()) { 408 task.createArg().setValue("-S"); 409 task.createArg().setValue(path); 410 } 411 } 412 } 413 414 // assets location. This may not exists, and aapt doesn't like it, so we check first. 415 if (mAssets != null && new File(mAssets).isDirectory()) { 416 task.createArg().setValue("-A"); 417 task.createArg().setValue(mAssets); 418 } 419 420 // android.jar 421 if (mAndroidJar != null) { 422 task.createArg().setValue("-I"); 423 task.createArg().setValue(mAndroidJar); 424 } 425 426 // apk file. This is based on the apkFolder, apkBaseName, and the configName (if applicable) 427 String filename = null; 428 if (mApkName != null) { 429 filename = mApkName; 430 } else if (mApkBaseName != null) { 431 filename = mApkBaseName + ".ap_"; 432 } 433 434 if (filename != null) { 435 File file = new File(mApkFolder, filename); 436 task.createArg().setValue("-F"); 437 task.createArg().setValue(file.getAbsolutePath()); 438 } 439 440 // R class generation 441 if (generateRClass) { 442 task.createArg().setValue("-J"); 443 task.createArg().setValue(mRFolder); 444 } 445 446 // final setup of the task 447 task.setProject(taskProject); 448 task.setOwningTarget(getOwningTarget()); 449 450 // execute it. 451 task.execute(); 452 } 453 } 454