]>
git.saurik.com Git - android/aapt.git/blob - Package.cpp
eb7d6f559ecb3d62dbaaeb0752a5f5b308052946
   2 // Copyright 2006 The Android Open Source Project 
   4 // Package assets into Zip files. 
   7 #include "AaptAssets.h" 
   8 #include "ResourceTable.h" 
  11 #include <utils/ZipFile.h> 
  13 #include <sys/types.h> 
  18 using namespace android
; 
  20 static const char* kExcludeExtension 
= ".EXCLUDE"; 
  22 /* these formats are already compressed, or don't compress well */ 
  23 static 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" 
  32 /* fwd decls, so I can write this downward */ 
  33 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptAssets
>& assets
); 
  34 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
  35                         const sp
<AaptDir
>& dir
, const AaptGroupEntry
& ge
); 
  36 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
  37                         const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
); 
  38 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
); 
  39 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
); 
  42  * The directory hierarchy looks like this: 
  43  * "outputDir" and "assetRoot" are existing directories. 
  45  * On success, "bundle->numPackages" will be the number of Zip packages 
  48 status_t 
writeAPK(Bundle
* bundle
, const sp
<AaptAssets
>& assets
, 
  49                        const String8
& outputFile
) 
  51     status_t result 
= NO_ERROR
; 
  55     //bundle->setPackageCount(0); 
  58      * Prep the Zip archive. 
  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. 
  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(), 
  77             fprintf(stderr
, "ERROR: '%s' exists (use '-f' to force overwrite)\n", 
  82         fprintf(stderr
, "ERROR: '%s' exists and is not a regular file\n", outputFile
.string()); 
  86     if (bundle
->getVerbose()) { 
  87         printf("%s '%s'\n", (fileType 
== kFileTypeNonexistent
) ? "Creating" : "Opening", 
  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", 
 100     if (bundle
->getVerbose()) { 
 101         printf("Writing all files...\n"); 
 104     count 
= processAssets(bundle
, zip
, assets
); 
 106         fprintf(stderr
, "ERROR: unable to process assets while packaging '%s'\n", 
 107                 outputFile
.string()); 
 112     if (bundle
->getVerbose()) { 
 113         printf("Generated %d file%s\n", count
, (count
==1) ? "" : "s"); 
 116     count 
= processJarFiles(bundle
, zip
); 
 118         fprintf(stderr
, "ERROR: unable to process jar files while packaging '%s'\n", 
 119                 outputFile
.string()); 
 124     if (bundle
->getVerbose()) 
 125         printf("Included %d file%s from jar/zip files.\n", count
, (count
==1) ? "" : "s"); 
 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. 
 135         if (bundle
->getVerbose()) 
 136             printf("Checking for deleted files\n"); 
 138         for (i 
= 0; i 
< zip
->getNumEntries(); i
++) { 
 139             ZipEntry
* entry 
= zip
->getEntryByIndex(i
); 
 141             if (!entry
->getMarked() && entry
->getDeleted()) { 
 142                 if (bundle
->getVerbose()) { 
 143                     printf("      (removing crufty '%s')\n", 
 144                         entry
->getFileName()); 
 150         if (bundle
->getVerbose() && removed 
> 0) 
 151             printf("Removed %d file%s\n", removed
, (removed
==1) ? "" : "s"); 
 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"); 
 162     if (zip
->getNumEntries() == 0) { 
 163         if (bundle
->getVerbose()) { 
 164             printf("Archive is empty -- removing %s\n", outputFile
.getPathLeaf().string()); 
 166         delete zip
;        // close the file so we can remove it in Win32 
 168         if (unlink(outputFile
.string()) != 0) { 
 169             fprintf(stderr
, "WARNING: could not unlink '%s'\n", outputFile
.string()); 
 173     assert(result 
== NO_ERROR
); 
 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()); 
 181         if (unlink(outputFile
.string()) != 0) { 
 182             fprintf(stderr
, "WARNING: could not unlink '%s'\n", outputFile
.string()); 
 186     if (result 
== NO_ERROR 
&& bundle
->getVerbose()) 
 191 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
 192                       const sp
<AaptAssets
>& assets
) 
 194     ResourceFilter filter
; 
 195     status_t status 
= filter
.parse(bundle
->getConfigurations()); 
 196     if (status 
!= NO_ERROR
) { 
 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())) { 
 208         ssize_t res 
= processAssets(bundle
, zip
, assets
, ge
); 
 218 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
 219                       const sp
<AaptDir
>& dir
, const AaptGroupEntry
& ge
) 
 223     const size_t ND 
= dir
->getDirs().size(); 
 225     for (i
=0; i
<ND
; i
++) { 
 226         ssize_t res 
= processAssets(bundle
, zip
, dir
->getDirs().valueAt(i
), ge
); 
 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
); 
 238             sp
<AaptFile
> fl 
= gp
->getFiles().valueAt(fi
); 
 239             if (!processFile(bundle
, zip
, gp
, fl
)) { 
 240                 return UNKNOWN_ERROR
; 
 250  * Process a regular file, adding it to the archive if appropriate. 
 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. 
 255 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
 256                  const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
) 
 258     const bool hasData 
= file
->hasData(); 
 260     String8 
storageName(group
->getPath()); 
 261     storageName
.convertToResPath(); 
 263     bool fromGzip 
= false; 
 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. 
 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). 
 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 
 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()); 
 288     if (strcasecmp(storageName
.getPathExtension().string(), ".gz") == 0) { 
 290         storageName 
= storageName
.getBasePath(); 
 293     if (bundle
->getUpdate()) { 
 294         entry 
= zip
->getEntryByName(storageName
.string()); 
 296             /* file already exists in archive; there can be only one */ 
 297             if (entry
->getMarked()) { 
 299                         "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", 
 300                         file
->getPrintableSource().string()); 
 304                 const String8
& srcName 
= file
->getSourceFile(); 
 306                 fileModWhen 
= getFileModDate(srcName
.string()); 
 307                 if (fileModWhen 
== (time_t) -1) { // file existence tested earlier, 
 308                     return false;                 //  not expecting an error here 
 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()); 
 319                     // version in archive is newer 
 320                     if (bundle
->getVerbose()) { 
 321                         printf("      (not updating '%s')\n", storageName
.string()); 
 323                     entry
->setMarked(true); 
 327                 // Generated files are always replaced. 
 333     //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); 
 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
; 
 343         result 
= zip
->add(file
->getSourceFile().string(), storageName
.string(), compressionMethod
, 
 346         result 
= zip
->add(file
->getData(), file
->getSize(), storageName
.string(), 
 347                            file
->getCompressionMethod(), &entry
); 
 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"); 
 355                 printf(" (compressed %d%%)\n", calcPercent(entry
->getUncompressedLen(), 
 356                             entry
->getCompressedLen())); 
 359         entry
->setMarked(true); 
 361         if (result 
== ALREADY_EXISTS
) { 
 362             fprintf(stderr
, "      Unable to add '%s': file already in archive (try '-u'?)\n", 
 363                     file
->getPrintableSource().string()); 
 365             fprintf(stderr
, "      Unable to add '%s': Zip add failed\n",  
 366                     file
->getPrintableSource().string()); 
 375  * Determine whether or not we want to try to compress this file based 
 376  * on the file extension. 
 378 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
) 
 380     String8 ext 
= pathName
.getPathExtension(); 
 383     if (ext
.length() == 0) 
 386     for (i 
= 0; i 
< NELEM(kNoCompressExt
); i
++) { 
 387         if (strcasecmp(ext
.string(), kNoCompressExt
[i
]) == 0) 
 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
); 
 398         const char* path 
= pathName
.string(); 
 399         if (strcasecmp(path 
+ pos
, str
) == 0) { 
 407 bool endsWith(const char* haystack
, const char* needle
) 
 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; 
 415 ssize_t 
processJarFile(ZipFile
* jar
, ZipFile
* out
) 
 418     size_t N 
= jar
->getNumEntries(); 
 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
); 
 428                 fprintf(stderr
, "ERROR: unable to uncompress entry '%s'\n", 
 432             out
->add(data
, size
, storageName
, compressionMethod
, NULL
); 
 440 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
) 
 444     const android::Vector
<const char*>& jars 
= bundle
->getJarFiles(); 
 446     size_t N 
= jars
.size(); 
 447     for (size_t i
=0; i
<N
; i
++) { 
 449         err 
= jar
.open(jars
[i
], ZipFile::kOpenReadOnly
); 
 451             fprintf(stderr
, "ERROR: unable to open '%s' as a zip file: %zd\n", 
 455         err 
+= processJarFile(&jar
, zip
); 
 457             fprintf(stderr
, "ERROR: unable to process '%s'\n", jars
[i
]);