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