]>
git.saurik.com Git - android/aapt.git/blob - Package.cpp
23f641a98eb7e12a349f28e53f232fa83d52602d
   2 // Copyright 2006 The Android Open Source Project 
   4 // Package assets into Zip files. 
   7 #include "AaptAssets.h" 
  10 #include <utils/ZipFile.h> 
  12 #include <sys/types.h> 
  17 using namespace android
; 
  19 static const char* kExcludeExtension 
= ".EXCLUDE"; 
  21 /* these formats are already compressed, or don't compress well */ 
  22 static 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" 
  31 /* fwd decls, so I can write this downward */ 
  32 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptAssets
>& assets
); 
  33 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
  34                         const sp
<AaptDir
>& dir
, const AaptGroupEntry
& ge
); 
  35 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
  36                         const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
); 
  37 bool okayToCompress(const String8
& pathName
); 
  38 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
); 
  41  * The directory hierarchy looks like this: 
  42  * "outputDir" and "assetRoot" are existing directories. 
  44  * On success, "bundle->numPackages" will be the number of Zip packages 
  47 status_t 
writeAPK(Bundle
* bundle
, const sp
<AaptAssets
>& assets
, 
  48                        const String8
& outputFile
) 
  50     status_t result 
= NO_ERROR
; 
  54     //bundle->setPackageCount(0); 
  57      * Prep the Zip archive. 
  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. 
  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(), 
  76             fprintf(stderr
, "ERROR: '%s' exists (use '-f' to force overwrite)\n", 
  81         fprintf(stderr
, "ERROR: '%s' exists and is not a regular file\n", outputFile
.string()); 
  85     if (bundle
->getVerbose()) { 
  86         printf("%s '%s'\n", (fileType 
== kFileTypeNonexistent
) ? "Creating" : "Opening", 
  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", 
  99     if (bundle
->getVerbose()) { 
 100         printf("Writing all files...\n"); 
 103     count 
= processAssets(bundle
, zip
, assets
); 
 105         fprintf(stderr
, "ERROR: unable to process assets while packaging '%s'\n", 
 106                 outputFile
.string()); 
 111     if (bundle
->getVerbose()) { 
 112         printf("Generated %d file%s\n", count
, (count
==1) ? "" : "s"); 
 115     count 
= processJarFiles(bundle
, zip
); 
 117         fprintf(stderr
, "ERROR: unable to process jar files while packaging '%s'\n", 
 118                 outputFile
.string()); 
 123     if (bundle
->getVerbose()) 
 124         printf("Included %d file%s from jar/zip files.\n", count
, (count
==1) ? "" : "s"); 
 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. 
 134         if (bundle
->getVerbose()) 
 135             printf("Checking for deleted files\n"); 
 137         for (i 
= 0; i 
< zip
->getNumEntries(); i
++) { 
 138             ZipEntry
* entry 
= zip
->getEntryByIndex(i
); 
 140             if (!entry
->getMarked() && entry
->getDeleted()) { 
 141                 if (bundle
->getVerbose()) { 
 142                     printf("      (removing crufty '%s')\n", 
 143                         entry
->getFileName()); 
 149         if (bundle
->getVerbose() && removed 
> 0) 
 150             printf("Removed %d file%s\n", removed
, (removed
==1) ? "" : "s"); 
 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"); 
 161     if (zip
->getNumEntries() == 0) { 
 162         if (bundle
->getVerbose()) { 
 163             printf("Archive is empty -- removing %s\n", outputFile
.getPathLeaf().string()); 
 165         delete zip
;        // close the file so we can remove it in Win32 
 167         if (unlink(outputFile
.string()) != 0) { 
 168             fprintf(stderr
, "WARNING: could not unlink '%s'\n", outputFile
.string()); 
 172     assert(result 
== NO_ERROR
); 
 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()); 
 180         if (unlink(outputFile
.string()) != 0) { 
 181             fprintf(stderr
, "WARNING: could not unlink '%s'\n", outputFile
.string()); 
 185     if (result 
== NO_ERROR 
&& bundle
->getVerbose()) 
 190 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
 191                       const sp
<AaptAssets
>& assets
) 
 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
); 
 208 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
 209                       const sp
<AaptDir
>& dir
, const AaptGroupEntry
& ge
) 
 213     const size_t ND 
= dir
->getDirs().size(); 
 215     for (i
=0; i
<ND
; i
++) { 
 216         ssize_t res 
= processAssets(bundle
, zip
, dir
->getDirs().valueAt(i
), ge
); 
 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
); 
 228             sp
<AaptFile
> fl 
= gp
->getFiles().valueAt(fi
); 
 229             if (!processFile(bundle
, zip
, gp
, fl
)) { 
 230                 return UNKNOWN_ERROR
; 
 240  * Process a regular file, adding it to the archive if appropriate. 
 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. 
 245 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
 246                  const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
) 
 248     const bool hasData 
= file
->hasData(); 
 250     String8 
storageName(group
->getPath()); 
 251     storageName
.convertToResPath(); 
 253     bool fromGzip 
= false; 
 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. 
 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). 
 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 
 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()); 
 278     if (strcasecmp(storageName
.getPathExtension().string(), ".gz") == 0) { 
 280         storageName 
= storageName
.getBasePath(); 
 283     if (bundle
->getUpdate()) { 
 284         entry 
= zip
->getEntryByName(storageName
.string()); 
 286             /* file already exists in archive; there can be only one */ 
 287             if (entry
->getMarked()) { 
 289                         "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", 
 290                         file
->getPrintableSource().string()); 
 294                 const String8
& srcName 
= file
->getSourceFile(); 
 296                 fileModWhen 
= getFileModDate(srcName
.string()); 
 297                 if (fileModWhen 
== (time_t) -1) { // file existence tested earlier, 
 298                     return false;                 //  not expecting an error here 
 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()); 
 309                     // version in archive is newer 
 310                     if (bundle
->getVerbose()) { 
 311                         printf("      (not updating '%s')\n", storageName
.string()); 
 313                     entry
->setMarked(true); 
 317                 // Generated files are always replaced. 
 323     //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); 
 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
; 
 333         result 
= zip
->add(file
->getSourceFile().string(), storageName
.string(), compressionMethod
, 
 336         result 
= zip
->add(file
->getData(), file
->getSize(), storageName
.string(), 
 337                            file
->getCompressionMethod(), &entry
); 
 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"); 
 345                 printf(" (compressed %d%%)\n", calcPercent(entry
->getUncompressedLen(), 
 346                             entry
->getCompressedLen())); 
 349         entry
->setMarked(true); 
 351         if (result 
== ALREADY_EXISTS
) { 
 352             fprintf(stderr
, "      Unable to add '%s': file already in archive (try '-u'?)\n", 
 353                     file
->getPrintableSource().string()); 
 355             fprintf(stderr
, "      Unable to add '%s': Zip add failed\n",  
 356                     file
->getPrintableSource().string()); 
 365  * Determine whether or not we want to try to compress this file based 
 366  * on the file extension. 
 368 bool okayToCompress(const String8
& pathName
) 
 370     String8 ext 
= pathName
.getPathExtension(); 
 373     if (ext
.length() == 0) 
 376     for (i 
= 0; i 
< NELEM(kNoCompressExt
); i
++) { 
 377         if (strcasecmp(ext
.string(), kNoCompressExt
[i
]) == 0) 
 384 bool endsWith(const char* haystack
, const char* needle
) 
 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; 
 392 ssize_t 
processJarFile(ZipFile
* jar
, ZipFile
* out
) 
 395     size_t N 
= jar
->getNumEntries(); 
 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
); 
 405                 fprintf(stderr
, "ERROR: unable to uncompress entry '%s'\n", 
 409             out
->add(data
, size
, storageName
, compressionMethod
, NULL
); 
 417 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
) 
 421     const android::Vector
<const char*>& jars 
= bundle
->getJarFiles(); 
 423     size_t N 
= jars
.size(); 
 424     for (size_t i
=0; i
<N
; i
++) { 
 426         err 
= jar
.open(jars
[i
], ZipFile::kOpenReadOnly
); 
 428             fprintf(stderr
, "ERROR: unable to open '%s' as a zip file: %zd\n", 
 432         err 
+= processJarFile(&jar
, zip
); 
 434             fprintf(stderr
, "ERROR: unable to process '%s'\n", jars
[i
]);