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.ApkBuilder.FileEntry; 21 import com.android.sdklib.build.ApkCreationException; 22 import com.android.sdklib.build.DuplicateFileException; 23 import com.android.sdklib.build.SealedApkException; 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 SingleDependencyTask { 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 { SourceFolderInputPath(File file)56 public SourceFolderInputPath(File file) { 57 super(file); 58 } 59 60 @Override ignores(File file)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 */ setOutfolder(Path outFolder)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 */ setApkfilepath(String filepath)82 public void setApkfilepath(String filepath) { 83 mApkFilepath = filepath; 84 } 85 86 /** 87 * Sets the resourcefile attribute 88 * @param resourceFile 89 */ setResourcefile(String resourceFile)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 */ setVerbose(boolean verbose)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 */ setDebug(boolean debug)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 */ setDebugpackaging(boolean debug)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 */ setDebugsigning(boolean debug)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 */ setHascode(boolean hasCode)134 public void setHascode(boolean hasCode) { 135 mHasCode = hasCode; 136 } 137 138 /** 139 * Returns an object representing a nested <var>zip</var> element. 140 */ createZip()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 */ createDex()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 */ createSourcefolder()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 */ createJarfolder()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 */ createJarfile()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 */ createNativefolder()190 public Object createNativefolder() { 191 Path path = new Path(getProject()); 192 mNativeList.add(path); 193 return path; 194 } 195 196 @Override execute()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 if (dexFile != null) { 237 inputPaths.add(new InputPath(dexFile)); 238 } 239 240 // zip input files 241 List<File> zipFiles = new ArrayList<File>(); 242 for (Path pathList : mZipList) { 243 for (String path : pathList.list()) { 244 File f = new File(path); 245 zipFiles.add(f); 246 inputPaths.add(new InputPath(f)); 247 } 248 } 249 250 // now go through the list of source folders used to add non java files. 251 List<File> sourceFolderList = new ArrayList<File>(); 252 if (mHasCode) { 253 for (Path pathList : mSourceList) { 254 for (String path : pathList.list()) { 255 File f = new File(path); 256 sourceFolderList.add(f); 257 // because this is a source folder but we only care about non 258 // java files. 259 inputPaths.add(new SourceFolderInputPath(f)); 260 } 261 } 262 } 263 264 // now go through the list of jar folders. 265 List<File> jarFileList = new ArrayList<File>(); 266 for (Path pathList : mJarfolderList) { 267 for (String path : pathList.list()) { 268 // it's ok if top level folders are missing 269 File folder = new File(path); 270 if (folder.isDirectory()) { 271 String[] filenames = folder.list(new FilenameFilter() { 272 @Override 273 public boolean accept(File dir, String name) { 274 return PATTERN_JAR_EXT.matcher(name).matches(); 275 } 276 }); 277 278 for (String filename : filenames) { 279 File f = new File(folder, filename); 280 jarFileList.add(f); 281 inputPaths.add(new InputPath(f)); 282 } 283 } 284 } 285 } 286 287 // now go through the list of jar files. 288 for (Path pathList : mJarfileList) { 289 for (String path : pathList.list()) { 290 File f = new File(path); 291 jarFileList.add(f); 292 inputPaths.add(new InputPath(f)); 293 } 294 } 295 296 // now the native lib folder. 297 List<FileEntry> nativeFileList = new ArrayList<FileEntry>(); 298 for (Path pathList : mNativeList) { 299 for (String path : pathList.list()) { 300 // it's ok if top level folders are missing 301 File folder = new File(path); 302 if (folder.isDirectory()) { 303 List<FileEntry> entries = ApkBuilder.getNativeFiles(folder, 304 mDebugPackaging); 305 // add the list to the list of native files and then create an input 306 // path for each file 307 nativeFileList.addAll(entries); 308 309 for (FileEntry entry : entries) { 310 inputPaths.add(new InputPath(entry.mFile)); 311 } 312 } 313 } 314 } 315 316 // Finally figure out the path to the dependency file. 317 String depFile = outputFile.getAbsolutePath() + ".d"; 318 319 // check dependencies 320 if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) { 321 System.out.println( 322 "No changes. No need to create apk."); 323 return; 324 } 325 326 if (mDebugSigning) { 327 System.out.println(String.format( 328 "Creating %s and signing it with a debug key...", outputFile.getName())); 329 } else { 330 System.out.println(String.format( 331 "Creating %s for release...", outputFile.getName())); 332 } 333 334 ApkBuilder apkBuilder = new ApkBuilder( 335 outputFile, 336 resourceInputPath.getFile(), 337 dexFile, 338 mDebugSigning ? ApkBuilder.getDebugKeystore() : null, 339 mVerbose ? System.out : null); 340 apkBuilder.setDebugMode(mDebugPackaging); 341 342 343 // add the content of the zip files. 344 for (File f : zipFiles) { 345 if (mVerbose) { 346 System.out.println("Zip Input: " + f.getAbsolutePath()); 347 } 348 apkBuilder.addZipFile(f); 349 } 350 351 // now go through the list of file to directly add the to the list. 352 for (File f : sourceFolderList) { 353 if (mVerbose) { 354 System.out.println("Source Folder Input: " + f.getAbsolutePath()); 355 } 356 apkBuilder.addSourceFolder(f); 357 } 358 359 // now go through the list of jar files. 360 for (File f : jarFileList) { 361 if (mVerbose) { 362 System.out.println("Jar Input: " + f.getAbsolutePath()); 363 } 364 apkBuilder.addResourcesFromJar(f); 365 } 366 367 // and finally the native files 368 apkBuilder.addNativeLibraries(nativeFileList); 369 370 // close the archive 371 apkBuilder.sealApk(); 372 373 // and generate the dependency file 374 generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath()); 375 } catch (DuplicateFileException e) { 376 System.err.println(String.format( 377 "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", 378 e.getArchivePath(), e.getFile1(), e.getFile2())); 379 throw new BuildException(e); 380 } catch (ApkCreationException e) { 381 throw new BuildException(e); 382 } catch (SealedApkException e) { 383 throw new BuildException(e); 384 } catch (IllegalArgumentException e) { 385 throw new BuildException(e); 386 } 387 } 388 389 @Override getExecTaskName()390 protected String getExecTaskName() { 391 return "apkbuilder"; 392 } 393 } 394