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