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 com.android.sdklib.build.ApkBuilder; 20 import com.android.sdklib.build.ApkCreationException; 21 import com.android.sdklib.build.DuplicateFileException; 22 import com.android.sdklib.build.SealedApkException; 23 import com.android.sdklib.build.ApkBuilder.FileEntry; 24 25 import org.apache.tools.ant.BuildException; 26 import org.apache.tools.ant.types.Path; 27 28 import java.io.File; 29 import java.io.FilenameFilter; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.regex.Pattern; 33 34 public class ApkBuilderTask extends BaseTask { 35 36 private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$", 37 Pattern.CASE_INSENSITIVE); 38 39 private String mOutFolder; 40 private String mApkFilepath; 41 private String mResourceFile; 42 private boolean mVerbose = false; 43 private boolean mDebugPackaging = false; 44 private boolean mDebugSigning = false; 45 private boolean mHasCode = true; 46 47 private Path mDexPath; 48 49 private final ArrayList<Path> mZipList = new ArrayList<Path>(); 50 private final ArrayList<Path> mSourceList = new ArrayList<Path>(); 51 private final ArrayList<Path> mJarfolderList = new ArrayList<Path>(); 52 private final ArrayList<Path> mJarfileList = new ArrayList<Path>(); 53 private final ArrayList<Path> mNativeList = new ArrayList<Path>(); 54 55 private static class SourceFolderInputPath extends InputPath { 56 public SourceFolderInputPath(File file) { 57 super(file); 58 } 59 60 @Override 61 public boolean ignores(File file) { 62 if (file.isDirectory()) { 63 return !ApkBuilder.checkFolderForPackaging(file.getName()); 64 } else { 65 return !ApkBuilder.checkFileForPackaging(file.getName()); 66 } 67 } 68 } 69 70 /** 71 * Sets the value of the "outfolder" attribute. 72 * @param outFolder the value. 73 */ 74 public void setOutfolder(Path outFolder) { 75 mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder); 76 } 77 78 /** 79 * Sets the full filepath to the apk to generate. 80 * @param filepath 81 */ 82 public void setApkfilepath(String filepath) { 83 mApkFilepath = filepath; 84 } 85 86 /** 87 * Sets the resourcefile attribute 88 * @param resourceFile 89 */ 90 public void setResourcefile(String resourceFile) { 91 mResourceFile = resourceFile; 92 } 93 94 /** 95 * Sets the value of the "verbose" attribute. 96 * @param verbose the value. 97 */ 98 public void setVerbose(boolean verbose) { 99 mVerbose = verbose; 100 } 101 102 /** 103 * Sets the value of the "debug" attribute. 104 * @param debug the debug mode value. 105 */ 106 public void setDebug(boolean debug) { 107 System.out.println("WARNNG: Using deprecated 'debug' attribute in ApkBuilderTask." + 108 "Use 'debugpackaging' and 'debugsigning' instead."); 109 mDebugPackaging = debug; 110 mDebugSigning = debug; 111 } 112 113 /** 114 * Sets the value of the "debugpackaging" attribute. 115 * @param debug the debug mode value. 116 */ 117 public void setDebugpackaging(boolean debug) { 118 mDebugPackaging = debug; 119 } 120 121 /** 122 * Sets the value of the "debugsigning" attribute. 123 * @param debug the debug mode value. 124 */ 125 public void setDebugsigning(boolean debug) { 126 mDebugSigning = debug; 127 } 128 129 /** 130 * Sets the hascode attribute. Default is true. 131 * If set to false, then <dex> and <sourcefolder> nodes are ignored and not processed. 132 * @param hasCode the value of the attribute. 133 */ 134 public void setHascode(boolean hasCode) { 135 mHasCode = hasCode; 136 } 137 138 /** 139 * Returns an object representing a nested <var>zip</var> element. 140 */ 141 public Object createZip() { 142 Path path = new Path(getProject()); 143 mZipList.add(path); 144 return path; 145 } 146 147 /** 148 * Returns an object representing a nested <var>dex</var> element. 149 * This is similar to a nested <var>file</var> element, except when {@link #mHasCode} 150 * is <code>false</code> in which case it's ignored. 151 */ 152 public Object createDex() { 153 if (mDexPath == null) { 154 return mDexPath = new Path(getProject()); 155 } else { 156 throw new BuildException("Only one <dex> inner element can be provided"); 157 } 158 } 159 160 /** 161 * Returns an object representing a nested <var>sourcefolder</var> element. 162 */ 163 public Object createSourcefolder() { 164 Path path = new Path(getProject()); 165 mSourceList.add(path); 166 return path; 167 } 168 169 /** 170 * Returns an object representing a nested <var>jarfolder</var> element. 171 */ 172 public Object createJarfolder() { 173 Path path = new Path(getProject()); 174 mJarfolderList.add(path); 175 return path; 176 } 177 178 /** 179 * Returns an object representing a nested <var>jarfile</var> element. 180 */ 181 public Object createJarfile() { 182 Path path = new Path(getProject()); 183 mJarfileList.add(path); 184 return path; 185 } 186 187 /** 188 * Returns an object representing a nested <var>nativefolder</var> element. 189 */ 190 public Object createNativefolder() { 191 Path path = new Path(getProject()); 192 mNativeList.add(path); 193 return path; 194 } 195 196 @Override 197 public void execute() throws BuildException { 198 199 File outputFile; 200 if (mApkFilepath != null) { 201 outputFile = new File(mApkFilepath); 202 } else { 203 throw new BuildException("missing attribute 'apkFilepath'"); 204 } 205 206 if (mResourceFile == null) { 207 throw new BuildException("missing attribute 'resourcefile'"); 208 } 209 210 if (mOutFolder == null) { 211 throw new BuildException("missing attribute 'outfolder'"); 212 } 213 214 // check dexPath is only one file. 215 File dexFile = null; 216 if (mHasCode) { 217 String[] dexFiles = mDexPath.list(); 218 if (dexFiles.length != 1) { 219 throw new BuildException(String.format( 220 "Expected one dex file but path value resolve to %d files.", 221 dexFiles.length)); 222 } 223 dexFile = new File(dexFiles[0]); 224 } 225 226 try { 227 // build list of input files/folders to compute dependencies 228 // add the content of the zip files. 229 List<InputPath> inputPaths = new ArrayList<InputPath>(); 230 231 // resource file 232 InputPath resourceInputPath = new InputPath(new File(mOutFolder, mResourceFile)); 233 inputPaths.add(resourceInputPath); 234 235 // dex file 236 inputPaths.add(new InputPath(dexFile)); 237 238 // zip input files 239 List<File> zipFiles = new ArrayList<File>(); 240 for (Path pathList : mZipList) { 241 for (String path : pathList.list()) { 242 File f = new File(path); 243 zipFiles.add(f); 244 inputPaths.add(new InputPath(f)); 245 } 246 } 247 248 // now go through the list of source folders used to add non java files. 249 List<File> sourceFolderList = new ArrayList<File>(); 250 if (mHasCode) { 251 for (Path pathList : mSourceList) { 252 for (String path : pathList.list()) { 253 File f = new File(path); 254 sourceFolderList.add(f); 255 // because this is a source folder but we only care about non 256 // java files. 257 inputPaths.add(new SourceFolderInputPath(f)); 258 } 259 } 260 } 261 262 // now go through the list of jar folders. 263 List<File> jarFileList = new ArrayList<File>(); 264 for (Path pathList : mJarfolderList) { 265 for (String path : pathList.list()) { 266 // it's ok if top level folders are missing 267 File folder = new File(path); 268 if (folder.isDirectory()) { 269 String[] filenames = folder.list(new FilenameFilter() { 270 public boolean accept(File dir, String name) { 271 return PATTERN_JAR_EXT.matcher(name).matches(); 272 } 273 }); 274 275 for (String filename : filenames) { 276 File f = new File(folder, filename); 277 jarFileList.add(f); 278 inputPaths.add(new InputPath(f)); 279 } 280 } 281 } 282 } 283 284 // now go through the list of jar files. 285 for (Path pathList : mJarfileList) { 286 for (String path : pathList.list()) { 287 File f = new File(path); 288 jarFileList.add(f); 289 inputPaths.add(new InputPath(f)); 290 } 291 } 292 293 // now the native lib folder. 294 List<FileEntry> nativeFileList = new ArrayList<FileEntry>(); 295 for (Path pathList : mNativeList) { 296 for (String path : pathList.list()) { 297 // it's ok if top level folders are missing 298 File folder = new File(path); 299 if (folder.isDirectory()) { 300 List<FileEntry> entries = ApkBuilder.getNativeFiles(folder, 301 mDebugPackaging); 302 // add the list to the list of native files and then create an input 303 // path for each file 304 nativeFileList.addAll(entries); 305 306 for (FileEntry entry : entries) { 307 inputPaths.add(new InputPath(entry.mFile)); 308 } 309 } 310 } 311 } 312 313 // Finally figure out the path to the dependency file. 314 String depFile = outputFile.getAbsolutePath() + ".d"; 315 316 // check dependencies 317 if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) { 318 System.out.println( 319 "No changes. No need to create apk."); 320 return; 321 } 322 323 if (mDebugSigning) { 324 System.out.println(String.format( 325 "Creating %s and signing it with a debug key...", outputFile.getName())); 326 } else { 327 System.out.println(String.format( 328 "Creating %s for release...", outputFile.getName())); 329 } 330 331 ApkBuilder apkBuilder = new ApkBuilder( 332 outputFile, 333 resourceInputPath.getFile(), 334 dexFile, 335 mDebugSigning ? ApkBuilder.getDebugKeystore() : null, 336 mVerbose ? System.out : null); 337 apkBuilder.setDebugMode(mDebugPackaging); 338 339 340 // add the content of the zip files. 341 for (File f : zipFiles) { 342 apkBuilder.addZipFile(f); 343 } 344 345 // now go through the list of file to directly add the to the list. 346 for (File f : sourceFolderList) { 347 apkBuilder.addSourceFolder(f); 348 } 349 350 // now go through the list of jar folders. 351 for (Path pathList : mJarfolderList) { 352 for (String path : pathList.list()) { 353 // it's ok if top level folders are missing 354 File folder = new File(path); 355 if (folder.isDirectory()) { 356 String[] filenames = folder.list(new FilenameFilter() { 357 public boolean accept(File dir, String name) { 358 return PATTERN_JAR_EXT.matcher(name).matches(); 359 } 360 }); 361 362 for (String filename : filenames) { 363 apkBuilder.addResourcesFromJar(new File(folder, filename)); 364 } 365 } 366 } 367 } 368 369 // now go through the list of jar files. 370 for (File f : jarFileList) { 371 apkBuilder.addResourcesFromJar(f); 372 } 373 374 // and finally the native files 375 apkBuilder.addNativeLibraries(nativeFileList); 376 377 // close the archive 378 apkBuilder.sealApk(); 379 380 // and generate the dependency file 381 generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath()); 382 } catch (DuplicateFileException e) { 383 System.err.println(String.format( 384 "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", 385 e.getArchivePath(), e.getFile1(), e.getFile2())); 386 throw new BuildException(e); 387 } catch (ApkCreationException e) { 388 throw new BuildException(e); 389 } catch (SealedApkException e) { 390 throw new BuildException(e); 391 } catch (IllegalArgumentException e) { 392 throw new BuildException(e); 393 } 394 } 395 396 @Override 397 protected String getExecTaskName() { 398 return "apkbuilder"; 399 } 400 } 401