Package.cpp revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1// 2// Copyright 2006 The Android Open Source Project 3// 4// Package assets into Zip files. 5// 6#include "Main.h" 7#include "AaptAssets.h" 8 9#include <utils.h> 10#include <utils/ZipFile.h> 11 12#include <sys/types.h> 13#include <dirent.h> 14#include <ctype.h> 15#include <errno.h> 16 17using namespace android; 18 19static const char* kExcludeExtension = ".EXCLUDE"; 20 21/* these formats are already compressed, or don't compress well */ 22static const char* kNoCompressExt[] = { 23 ".jpg", ".jpeg", ".png", ".gif", 24 ".wav", ".mp2", ".mp3", ".ogg", ".aac", 25 ".mpg", ".mpeg", ".mid", ".midi", ".smf", 26 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", 27 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", 28 ".amr", ".awb", ".wma", ".wmv" 29}; 30 31/* fwd decls, so I can write this downward */ 32ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); 33ssize_t processAssets(Bundle* bundle, ZipFile* zip, 34 const sp<AaptDir>& dir, const AaptGroupEntry& ge); 35bool processFile(Bundle* bundle, ZipFile* zip, 36 const sp<AaptGroup>& group, const sp<AaptFile>& file); 37bool okayToCompress(const String8& pathName); 38ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); 39 40/* 41 * The directory hierarchy looks like this: 42 * "outputDir" and "assetRoot" are existing directories. 43 * 44 * On success, "bundle->numPackages" will be the number of Zip packages 45 * we created. 46 */ 47status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, 48 const String8& outputFile) 49{ 50 status_t result = NO_ERROR; 51 ZipFile* zip = NULL; 52 int count; 53 54 //bundle->setPackageCount(0); 55 56 /* 57 * Prep the Zip archive. 58 * 59 * If the file already exists, fail unless "update" or "force" is set. 60 * If "update" is set, update the contents of the existing archive. 61 * Else, if "force" is set, remove the existing archive. 62 */ 63 FileType fileType = getFileType(outputFile.string()); 64 if (fileType == kFileTypeNonexistent) { 65 // okay, create it below 66 } else if (fileType == kFileTypeRegular) { 67 if (bundle->getUpdate()) { 68 // okay, open it below 69 } else if (bundle->getForce()) { 70 if (unlink(outputFile.string()) != 0) { 71 fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(), 72 strerror(errno)); 73 goto bail; 74 } 75 } else { 76 fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n", 77 outputFile.string()); 78 goto bail; 79 } 80 } else { 81 fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string()); 82 goto bail; 83 } 84 85 if (bundle->getVerbose()) { 86 printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening", 87 outputFile.string()); 88 } 89 90 status_t status; 91 zip = new ZipFile; 92 status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate); 93 if (status != NO_ERROR) { 94 fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n", 95 outputFile.string()); 96 goto bail; 97 } 98 99 if (bundle->getVerbose()) { 100 printf("Writing all files...\n"); 101 } 102 103 count = processAssets(bundle, zip, assets); 104 if (count < 0) { 105 fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", 106 outputFile.string()); 107 result = count; 108 goto bail; 109 } 110 111 if (bundle->getVerbose()) { 112 printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); 113 } 114 115 count = processJarFiles(bundle, zip); 116 if (count < 0) { 117 fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", 118 outputFile.string()); 119 result = count; 120 goto bail; 121 } 122 123 if (bundle->getVerbose()) 124 printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); 125 126 result = NO_ERROR; 127 128 /* 129 * Check for cruft. We set the "marked" flag on all entries we created 130 * or decided not to update. If the entry isn't already slated for 131 * deletion, remove it now. 132 */ 133 { 134 if (bundle->getVerbose()) 135 printf("Checking for deleted files\n"); 136 int i, removed = 0; 137 for (i = 0; i < zip->getNumEntries(); i++) { 138 ZipEntry* entry = zip->getEntryByIndex(i); 139 140 if (!entry->getMarked() && entry->getDeleted()) { 141 if (bundle->getVerbose()) { 142 printf(" (removing crufty '%s')\n", 143 entry->getFileName()); 144 } 145 zip->remove(entry); 146 removed++; 147 } 148 } 149 if (bundle->getVerbose() && removed > 0) 150 printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); 151 } 152 153 /* tell Zip lib to process deletions and other pending changes */ 154 result = zip->flush(); 155 if (result != NO_ERROR) { 156 fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); 157 goto bail; 158 } 159 160 /* anything here? */ 161 if (zip->getNumEntries() == 0) { 162 if (bundle->getVerbose()) { 163 printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string()); 164 } 165 delete zip; // close the file so we can remove it in Win32 166 zip = NULL; 167 if (unlink(outputFile.string()) != 0) { 168 fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string()); 169 } 170 } 171 172 assert(result == NO_ERROR); 173 174bail: 175 delete zip; // must close before remove in Win32 176 if (result != NO_ERROR) { 177 if (bundle->getVerbose()) { 178 printf("Removing %s due to earlier failures\n", outputFile.string()); 179 } 180 if (unlink(outputFile.string()) != 0) { 181 fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string()); 182 } 183 } 184 185 if (result == NO_ERROR && bundle->getVerbose()) 186 printf("Done!\n"); 187 return result; 188} 189 190ssize_t processAssets(Bundle* bundle, ZipFile* zip, 191 const sp<AaptAssets>& assets) 192{ 193 ssize_t count = 0; 194 195 const size_t N = assets->getGroupEntries().size(); 196 for (size_t i=0; i<N; i++) { 197 const AaptGroupEntry& ge = assets->getGroupEntries()[i]; 198 ssize_t res = processAssets(bundle, zip, assets, ge); 199 if (res < 0) { 200 return res; 201 } 202 count += res; 203 } 204 205 return count; 206} 207 208ssize_t processAssets(Bundle* bundle, ZipFile* zip, 209 const sp<AaptDir>& dir, const AaptGroupEntry& ge) 210{ 211 ssize_t count = 0; 212 213 const size_t ND = dir->getDirs().size(); 214 size_t i; 215 for (i=0; i<ND; i++) { 216 ssize_t res = processAssets(bundle, zip, dir->getDirs().valueAt(i), ge); 217 if (res < 0) { 218 return res; 219 } 220 count += res; 221 } 222 223 const size_t NF = dir->getFiles().size(); 224 for (i=0; i<NF; i++) { 225 sp<AaptGroup> gp = dir->getFiles().valueAt(i); 226 ssize_t fi = gp->getFiles().indexOfKey(ge); 227 if (fi >= 0) { 228 sp<AaptFile> fl = gp->getFiles().valueAt(fi); 229 if (!processFile(bundle, zip, gp, fl)) { 230 return UNKNOWN_ERROR; 231 } 232 count++; 233 } 234 } 235 236 return count; 237} 238 239/* 240 * Process a regular file, adding it to the archive if appropriate. 241 * 242 * If we're in "update" mode, and the file already exists in the archive, 243 * delete the existing entry before adding the new one. 244 */ 245bool processFile(Bundle* bundle, ZipFile* zip, 246 const sp<AaptGroup>& group, const sp<AaptFile>& file) 247{ 248 const bool hasData = file->hasData(); 249 250 String8 storageName(group->getPath()); 251 storageName.convertToResPath(); 252 ZipEntry* entry; 253 bool fromGzip = false; 254 status_t result; 255 256 /* 257 * See if the filename ends in ".EXCLUDE". We can't use 258 * String8::getPathExtension() because the length of what it considers 259 * to be an extension is capped. 260 * 261 * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives, 262 * so there's no value in adding them (and it makes life easier on 263 * the AssetManager lib if we don't). 264 * 265 * NOTE: this restriction has been removed. If you're in this code, you 266 * should clean this up, but I'm in here getting rid of Path Name, and I 267 * don't want to make other potentially breaking changes --joeo 268 */ 269 int fileNameLen = storageName.length(); 270 int excludeExtensionLen = strlen(kExcludeExtension); 271 if (fileNameLen > excludeExtensionLen 272 && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen), 273 kExcludeExtension))) { 274 fprintf(stderr, "WARNING: '%s' not added to Zip\n", storageName.string()); 275 return true; 276 } 277 278 if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) { 279 fromGzip = true; 280 storageName = storageName.getBasePath(); 281 } 282 283 if (bundle->getUpdate()) { 284 entry = zip->getEntryByName(storageName.string()); 285 if (entry != NULL) { 286 /* file already exists in archive; there can be only one */ 287 if (entry->getMarked()) { 288 fprintf(stderr, 289 "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", 290 file->getPrintableSource().string()); 291 return false; 292 } 293 if (!hasData) { 294 const String8& srcName = file->getSourceFile(); 295 time_t fileModWhen; 296 fileModWhen = getFileModDate(srcName.string()); 297 if (fileModWhen == (time_t) -1) { // file existence tested earlier, 298 return false; // not expecting an error here 299 } 300 301 if (fileModWhen > entry->getModWhen()) { 302 // mark as deleted so add() will succeed 303 if (bundle->getVerbose()) { 304 printf(" (removing old '%s')\n", storageName.string()); 305 } 306 307 zip->remove(entry); 308 } else { 309 // version in archive is newer 310 if (bundle->getVerbose()) { 311 printf(" (not updating '%s')\n", storageName.string()); 312 } 313 entry->setMarked(true); 314 return true; 315 } 316 } else { 317 // Generated files are always replaced. 318 zip->remove(entry); 319 } 320 } 321 } 322 323 //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); 324 325 if (fromGzip) { 326 result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry); 327 } else if (!hasData) { 328 /* don't compress certain files, e.g. PNGs */ 329 int compressionMethod = bundle->getCompressionMethod(); 330 if (!okayToCompress(storageName)) { 331 compressionMethod = ZipEntry::kCompressStored; 332 } 333 result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod, 334 &entry); 335 } else { 336 result = zip->add(file->getData(), file->getSize(), storageName.string(), 337 file->getCompressionMethod(), &entry); 338 } 339 if (result == NO_ERROR) { 340 if (bundle->getVerbose()) { 341 printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : ""); 342 if (entry->getCompressionMethod() == ZipEntry::kCompressStored) { 343 printf(" (not compressed)\n"); 344 } else { 345 printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(), 346 entry->getCompressedLen())); 347 } 348 } 349 entry->setMarked(true); 350 } else { 351 if (result == ALREADY_EXISTS) { 352 fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", 353 file->getPrintableSource().string()); 354 } else { 355 fprintf(stderr, " Unable to add '%s': Zip add failed\n", 356 file->getPrintableSource().string()); 357 } 358 return false; 359 } 360 361 return true; 362} 363 364/* 365 * Determine whether or not we want to try to compress this file based 366 * on the file extension. 367 */ 368bool okayToCompress(const String8& pathName) 369{ 370 String8 ext = pathName.getPathExtension(); 371 int i; 372 373 if (ext.length() == 0) 374 return true; 375 376 for (i = 0; i < NELEM(kNoCompressExt); i++) { 377 if (strcasecmp(ext.string(), kNoCompressExt[i]) == 0) 378 return false; 379 } 380 381 return true; 382} 383 384bool endsWith(const char* haystack, const char* needle) 385{ 386 size_t a = strlen(haystack); 387 size_t b = strlen(needle); 388 if (a < b) return false; 389 return strcasecmp(haystack+(a-b), needle) == 0; 390} 391 392ssize_t processJarFile(ZipFile* jar, ZipFile* out) 393{ 394 status_t err; 395 size_t N = jar->getNumEntries(); 396 size_t count = 0; 397 for (size_t i=0; i<N; i++) { 398 ZipEntry* entry = jar->getEntryByIndex(i); 399 const char* storageName = entry->getFileName(); 400 if (endsWith(storageName, ".class")) { 401 int compressionMethod = entry->getCompressionMethod(); 402 size_t size = entry->getUncompressedLen(); 403 const void* data = jar->uncompress(entry); 404 if (data == NULL) { 405 fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n", 406 storageName); 407 return -1; 408 } 409 out->add(data, size, storageName, compressionMethod, NULL); 410 free((void*)data); 411 } 412 count++; 413 } 414 return count; 415} 416 417ssize_t processJarFiles(Bundle* bundle, ZipFile* zip) 418{ 419 ssize_t err; 420 ssize_t count = 0; 421 const android::Vector<const char*>& jars = bundle->getJarFiles(); 422 423 size_t N = jars.size(); 424 for (size_t i=0; i<N; i++) { 425 ZipFile jar; 426 err = jar.open(jars[i], ZipFile::kOpenReadOnly); 427 if (err != 0) { 428 fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %zd\n", 429 jars[i], err); 430 return err; 431 } 432 err += processJarFile(&jar, zip); 433 if (err < 0) { 434 fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]); 435 return err; 436 } 437 count += err; 438 } 439 440 return count; 441} 442