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 24 import org.apache.tools.ant.BuildException; 25 import org.apache.tools.ant.Project; 26 import org.apache.tools.ant.Task; 27 import org.apache.tools.ant.types.Path; 28 29 import java.io.File; 30 import java.io.FilenameFilter; 31 import java.util.ArrayList; 32 import java.util.regex.Pattern; 33 34 public class ApkBuilderTask extends Task { 35 36 private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$", 37 Pattern.CASE_INSENSITIVE); 38 39 private String mOutFolder; 40 @Deprecated private String mBaseName; 41 private String mApkFilepath; 42 private String mResourceFile; 43 private boolean mVerbose = false; 44 private boolean mSigned = true; 45 private boolean mDebug = false; 46 private boolean mHasCode = true; 47 private String mAbiFilter = null; 48 49 private Path mDexPath; 50 51 private final ArrayList<Path> mZipList = new ArrayList<Path>(); 52 private final ArrayList<Path> mFileList = new ArrayList<Path>(); 53 private final ArrayList<Path> mSourceList = new ArrayList<Path>(); 54 private final ArrayList<Path> mJarfolderList = new ArrayList<Path>(); 55 private final ArrayList<Path> mJarfileList = new ArrayList<Path>(); 56 private final ArrayList<Path> mNativeList = new ArrayList<Path>(); 57 58 /** 59 * Sets the value of the "outfolder" attribute. 60 * @param outFolder the value. 61 */ 62 public void setOutfolder(Path outFolder) { 63 mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder); 64 } 65 66 /** 67 * Sets the value of the "basename" attribute. 68 * @param baseName the value. 69 * @deprecated 70 */ 71 public void setBasename(String baseName) { 72 System.out.println("WARNING: Using deprecated 'basename' attribute in ApkBuilderTask." + 73 "Use 'apkfilepath' (path) instead."); 74 mBaseName = baseName; 75 } 76 77 /** 78 * Sets the full filepath to the apk to generate. 79 * @param filepath 80 */ 81 public void setApkfilepath(String filepath) { 82 mApkFilepath = filepath; 83 } 84 85 /** 86 * Sets the resourcefile attribute 87 * @param resourceFile 88 */ 89 public void setResourcefile(String resourceFile) { 90 mResourceFile = resourceFile; 91 } 92 93 /** 94 * Sets the value of the "verbose" attribute. 95 * @param verbose the value. 96 */ 97 public void setVerbose(boolean verbose) { 98 mVerbose = verbose; 99 } 100 101 /** 102 * Sets the value of the "signed" attribute. 103 * @param signed the value. 104 */ 105 public void setSigned(boolean signed) { 106 mSigned = signed; 107 } 108 109 /** 110 * Sets the value of the "debug" attribute. 111 * @param debug the debug mode value. 112 */ 113 public void setDebug(boolean debug) { 114 mDebug = debug; 115 } 116 117 /** 118 * Sets an ABI filter. If non <code>null</code>, then only native libraries matching the given 119 * ABI will be packaged with the APK. 120 * @param abiFilter the ABI to accept (and reject all other). If null or empty string, no ABIs 121 * are rejected. This must be a single ABI name as defined by the Android NDK. For a list 122 * of valid ABI names, see $NDK/docs/CPU-ARCH-ABIS.TXT 123 */ 124 public void setAbifilter(String abiFilter) { 125 if (abiFilter != null && abiFilter.length() > 0) { 126 mAbiFilter = abiFilter.trim(); 127 } else { 128 mAbiFilter = null; 129 } 130 } 131 132 /** 133 * Sets the hascode attribute. Default is true. 134 * If set to false, then <dex> and <sourcefolder> nodes are ignored and not processed. 135 * @param hasCode the value of the attribute. 136 */ 137 public void setHascode(boolean hasCode) { 138 mHasCode = hasCode; 139 } 140 141 /** 142 * Returns an object representing a nested <var>zip</var> element. 143 */ 144 public Object createZip() { 145 Path path = new Path(getProject()); 146 mZipList.add(path); 147 return path; 148 } 149 150 /** 151 * Returns an object representing a nested <var>dex</var> element. 152 * This is similar to a nested <var>file</var> element, except when {@link #mHasCode} 153 * is <code>false</code> in which case it's ignored. 154 */ 155 public Object createDex() { 156 if (mDexPath == null) { 157 return mDexPath = new Path(getProject()); 158 } else { 159 throw new BuildException("Only one <dex> inner element can be provided"); 160 } 161 } 162 163 /** 164 * Returns an object representing a nested <var>file</var> element. 165 */ 166 public Object createFile() { 167 System.out.println("WARNING: Using deprecated <file> inner element in ApkBuilderTask." + 168 "Use <dex path=...> instead."); 169 Path path = new Path(getProject()); 170 mFileList.add(path); 171 return path; 172 } 173 174 /** 175 * Returns an object representing a nested <var>sourcefolder</var> element. 176 */ 177 public Object createSourcefolder() { 178 Path path = new Path(getProject()); 179 mSourceList.add(path); 180 return path; 181 } 182 183 /** 184 * Returns an object representing a nested <var>jarfolder</var> element. 185 */ 186 public Object createJarfolder() { 187 Path path = new Path(getProject()); 188 mJarfolderList.add(path); 189 return path; 190 } 191 192 /** 193 * Returns an object representing a nested <var>jarfile</var> element. 194 */ 195 public Object createJarfile() { 196 Path path = new Path(getProject()); 197 mJarfileList.add(path); 198 return path; 199 } 200 201 /** 202 * Returns an object representing a nested <var>nativefolder</var> element. 203 */ 204 public Object createNativefolder() { 205 Path path = new Path(getProject()); 206 mNativeList.add(path); 207 return path; 208 } 209 210 @Override 211 public void execute() throws BuildException { 212 Project antProject = getProject(); 213 214 // get the rules revision to figure out how to build the output file. 215 String rulesRevStr = antProject.getProperty(TaskHelper.PROP_RULES_REV); 216 int rulesRev = 1; 217 try { 218 rulesRev = Integer.parseInt(rulesRevStr); 219 } catch (NumberFormatException e) { 220 // this shouldn't happen since setup task is the one setting up every time. 221 } 222 223 File outputFile; 224 if (mApkFilepath != null) { 225 outputFile = new File(mApkFilepath); 226 } else if (rulesRev == 2) { 227 if (mSigned) { 228 outputFile = new File(mOutFolder, mBaseName + "-debug-unaligned.apk"); 229 } else { 230 outputFile = new File(mOutFolder, mBaseName + "-unsigned.apk"); 231 } 232 } else { 233 throw new BuildException("missing attribute 'apkFilepath'"); 234 } 235 236 // check dexPath is only one file. 237 File dexFile = null; 238 if (mHasCode) { 239 String[] dexFiles = mDexPath.list(); 240 if (dexFiles.length != 1) { 241 throw new BuildException(String.format( 242 "Expected one dex file but path value resolve to %d files.", 243 dexFiles.length)); 244 } 245 dexFile = new File(dexFiles[0]); 246 } 247 248 try { 249 if (mSigned) { 250 System.out.println(String.format( 251 "Creating %s and signing it with a debug key...", outputFile.getName())); 252 } else { 253 System.out.println(String.format( 254 "Creating %s for release...", outputFile.getName())); 255 } 256 257 ApkBuilder apkBuilder = new ApkBuilder( 258 outputFile, 259 new File(mOutFolder, mResourceFile), 260 dexFile, 261 mSigned ? ApkBuilder.getDebugKeystore() : null, 262 mVerbose ? System.out : null); 263 apkBuilder.setDebugMode(mDebug); 264 265 266 // add the content of the zip files. 267 for (Path pathList : mZipList) { 268 for (String path : pathList.list()) { 269 apkBuilder.addZipFile(new File(path)); 270 } 271 } 272 273 // add the files that go to the root of the archive (this is deprecated) 274 for (Path pathList : mFileList) { 275 for (String path : pathList.list()) { 276 File f = new File(path); 277 apkBuilder.addFile(f, f.getName()); 278 } 279 } 280 281 // now go through the list of file to directly add the to the list. 282 if (mHasCode) { 283 for (Path pathList : mSourceList) { 284 for (String path : pathList.list()) { 285 apkBuilder.addSourceFolder(new File(path)); 286 } 287 } 288 } 289 290 // now go through the list of jar folders. 291 for (Path pathList : mJarfolderList) { 292 for (String path : pathList.list()) { 293 // it's ok if top level folders are missing 294 File folder = new File(path); 295 if (folder.isDirectory()) { 296 String[] filenames = folder.list(new FilenameFilter() { 297 public boolean accept(File dir, String name) { 298 return PATTERN_JAR_EXT.matcher(name).matches(); 299 } 300 }); 301 302 for (String filename : filenames) { 303 apkBuilder.addResourcesFromJar(new File(folder, filename)); 304 } 305 } 306 } 307 } 308 309 // now go through the list of jar files. 310 for (Path pathList : mJarfileList) { 311 for (String path : pathList.list()) { 312 apkBuilder.addResourcesFromJar(new File(path)); 313 } 314 } 315 316 // now the native lib folder. 317 for (Path pathList : mNativeList) { 318 for (String path : pathList.list()) { 319 // it's ok if top level folders are missing 320 File folder = new File(path); 321 if (folder.isDirectory()) { 322 apkBuilder.addNativeLibraries(folder, mAbiFilter); 323 } 324 } 325 } 326 327 328 // close the archive 329 apkBuilder.sealApk(); 330 331 } catch (DuplicateFileException e) { 332 System.err.println(String.format( 333 "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", 334 e.getArchivePath(), e.getFile1(), e.getFile2())); 335 throw new BuildException(e); 336 } catch (ApkCreationException e) { 337 throw new BuildException(e); 338 } catch (SealedApkException e) { 339 throw new BuildException(e); 340 } catch (IllegalArgumentException e) { 341 throw new BuildException(e); 342 } 343 } 344 } 345