]>
git.saurik.com Git - android/aapt.git/blob - Package.cpp
   2 // Copyright 2006 The Android Open Source Project 
   4 // Package assets into Zip files. 
   7 #include "AaptAssets.h" 
   8 #include "ResourceTable.h" 
  10 #include <utils/Log.h> 
  11 #include <utils/threads.h> 
  12 #include <utils/List.h> 
  13 #include <utils/Errors.h> 
  15 #include <sys/types.h> 
  20 using namespace android
; 
  22 static const char* kExcludeExtension 
= ".EXCLUDE"; 
  24 /* these formats are already compressed, or don't compress well */ 
  25 static const char* kNoCompressExt
[] = { 
  26     ".jpg", ".jpeg", ".png", ".gif", 
  27     ".wav", ".mp2", ".mp3", ".ogg", ".aac", 
  28     ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", 
  29     ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", 
  30     ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", 
  31     ".amr", ".awb", ".wma", ".wmv" 
  34 /* fwd decls, so I can write this downward */ 
  35 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptAssets
>& assets
); 
  36 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptDir
>& dir
, 
  37                         const AaptGroupEntry
& ge
, const ResourceFilter
* filter
); 
  38 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
  39                         const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
); 
  40 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
); 
  41 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
); 
  44  * The directory hierarchy looks like this: 
  45  * "outputDir" and "assetRoot" are existing directories. 
  47  * On success, "bundle->numPackages" will be the number of Zip packages 
  50 status_t 
writeAPK(Bundle
* bundle
, const sp
<AaptAssets
>& assets
, 
  51                        const String8
& outputFile
) 
  53     status_t result 
= NO_ERROR
; 
  57     //bundle->setPackageCount(0); 
  60      * Prep the Zip archive. 
  62      * If the file already exists, fail unless "update" or "force" is set. 
  63      * If "update" is set, update the contents of the existing archive. 
  64      * Else, if "force" is set, remove the existing archive. 
  66     FileType fileType 
= getFileType(outputFile
.string()); 
  67     if (fileType 
== kFileTypeNonexistent
) { 
  68         // okay, create it below 
  69     } else if (fileType 
== kFileTypeRegular
) { 
  70         if (bundle
->getUpdate()) { 
  71             // okay, open it below 
  72         } else if (bundle
->getForce()) { 
  73             if (unlink(outputFile
.string()) != 0) { 
  74                 fprintf(stderr
, "ERROR: unable to remove '%s': %s\n", outputFile
.string(), 
  79             fprintf(stderr
, "ERROR: '%s' exists (use '-f' to force overwrite)\n", 
  84         fprintf(stderr
, "ERROR: '%s' exists and is not a regular file\n", outputFile
.string()); 
  88     if (bundle
->getVerbose()) { 
  89         printf("%s '%s'\n", (fileType 
== kFileTypeNonexistent
) ? "Creating" : "Opening", 
  95     status 
= zip
->open(outputFile
.string(), ZipFile::kOpenReadWrite 
| ZipFile::kOpenCreate
); 
  96     if (status 
!= NO_ERROR
) { 
  97         fprintf(stderr
, "ERROR: unable to open '%s' as Zip file for writing\n", 
 102     if (bundle
->getVerbose()) { 
 103         printf("Writing all files...\n"); 
 106     count 
= processAssets(bundle
, zip
, assets
); 
 108         fprintf(stderr
, "ERROR: unable to process assets while packaging '%s'\n", 
 109                 outputFile
.string()); 
 114     if (bundle
->getVerbose()) { 
 115         printf("Generated %d file%s\n", count
, (count
==1) ? "" : "s"); 
 118     count 
= processJarFiles(bundle
, zip
); 
 120         fprintf(stderr
, "ERROR: unable to process jar files while packaging '%s'\n", 
 121                 outputFile
.string()); 
 126     if (bundle
->getVerbose()) 
 127         printf("Included %d file%s from jar/zip files.\n", count
, (count
==1) ? "" : "s"); 
 132      * Check for cruft.  We set the "marked" flag on all entries we created 
 133      * or decided not to update.  If the entry isn't already slated for 
 134      * deletion, remove it now. 
 137         if (bundle
->getVerbose()) 
 138             printf("Checking for deleted files\n"); 
 140         for (i 
= 0; i 
< zip
->getNumEntries(); i
++) { 
 141             ZipEntry
* entry 
= zip
->getEntryByIndex(i
); 
 143             if (!entry
->getMarked() && entry
->getDeleted()) { 
 144                 if (bundle
->getVerbose()) { 
 145                     printf("      (removing crufty '%s')\n", 
 146                         entry
->getFileName()); 
 152         if (bundle
->getVerbose() && removed 
> 0) 
 153             printf("Removed %d file%s\n", removed
, (removed
==1) ? "" : "s"); 
 156     /* tell Zip lib to process deletions and other pending changes */ 
 157     result 
= zip
->flush(); 
 158     if (result 
!= NO_ERROR
) { 
 159         fprintf(stderr
, "ERROR: Zip flush failed, archive may be hosed\n"); 
 164     if (zip
->getNumEntries() == 0) { 
 165         if (bundle
->getVerbose()) { 
 166             printf("Archive is empty -- removing %s\n", outputFile
.getPathLeaf().string()); 
 168         delete zip
;        // close the file so we can remove it in Win32 
 170         if (unlink(outputFile
.string()) != 0) { 
 171             fprintf(stderr
, "warning: could not unlink '%s'\n", outputFile
.string()); 
 175     if (bundle
->getGenDependencies()) { 
 176         // Add this file to the dependency file 
 177         String8 dependencyFile 
= outputFile
.getBasePath(); 
 178         dependencyFile
.append(".d"); 
 180         FILE* fp 
= fopen(dependencyFile
.string(), "a"); 
 181         fprintf(fp
, "%s \\\n", outputFile
.string()); 
 185     assert(result 
== NO_ERROR
); 
 188     delete zip
;        // must close before remove in Win32 
 189     if (result 
!= NO_ERROR
) { 
 190         if (bundle
->getVerbose()) { 
 191             printf("Removing %s due to earlier failures\n", outputFile
.string()); 
 193         if (unlink(outputFile
.string()) != 0) { 
 194             fprintf(stderr
, "warning: could not unlink '%s'\n", outputFile
.string()); 
 198     if (result 
== NO_ERROR 
&& bundle
->getVerbose()) 
 203 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
 204                       const sp
<AaptAssets
>& assets
) 
 206     ResourceFilter filter
; 
 207     status_t status 
= filter
.parse(bundle
->getConfigurations()); 
 208     if (status 
!= NO_ERROR
) { 
 214     const size_t N 
= assets
->getGroupEntries().size(); 
 215     for (size_t i
=0; i
<N
; i
++) { 
 216         const AaptGroupEntry
& ge 
= assets
->getGroupEntries()[i
]; 
 218         ssize_t res 
= processAssets(bundle
, zip
, assets
, ge
, &filter
); 
 229 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptDir
>& dir
, 
 230         const AaptGroupEntry
& ge
, const ResourceFilter
* filter
) 
 234     const size_t ND 
= dir
->getDirs().size(); 
 236     for (i
=0; i
<ND
; i
++) { 
 237         const sp
<AaptDir
>& subDir 
= dir
->getDirs().valueAt(i
); 
 239         const bool filterable 
= filter 
!= NULL 
&& subDir
->getLeaf().find("mipmap-") != 0; 
 241         if (filterable 
&& subDir
->getLeaf() != subDir
->getPath() && !filter
->match(ge
.toParams())) { 
 245         ssize_t res 
= processAssets(bundle
, zip
, subDir
, ge
, filterable 
? filter 
: NULL
); 
 252     if (filter 
!= NULL 
&& !filter
->match(ge
.toParams())) { 
 256     const size_t NF 
= dir
->getFiles().size(); 
 257     for (i
=0; i
<NF
; i
++) { 
 258         sp
<AaptGroup
> gp 
= dir
->getFiles().valueAt(i
); 
 259         ssize_t fi 
= gp
->getFiles().indexOfKey(ge
); 
 261             sp
<AaptFile
> fl 
= gp
->getFiles().valueAt(fi
); 
 262             if (!processFile(bundle
, zip
, gp
, fl
)) { 
 263                 return UNKNOWN_ERROR
; 
 273  * Process a regular file, adding it to the archive if appropriate. 
 275  * If we're in "update" mode, and the file already exists in the archive, 
 276  * delete the existing entry before adding the new one. 
 278 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
 279                  const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
) 
 281     const bool hasData 
= file
->hasData(); 
 283     String8 
storageName(group
->getPath()); 
 284     storageName
.convertToResPath(); 
 286     bool fromGzip 
= false; 
 290      * See if the filename ends in ".EXCLUDE".  We can't use 
 291      * String8::getPathExtension() because the length of what it considers 
 292      * to be an extension is capped. 
 294      * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives, 
 295      * so there's no value in adding them (and it makes life easier on 
 296      * the AssetManager lib if we don't). 
 298      * NOTE: this restriction has been removed.  If you're in this code, you 
 299      * should clean this up, but I'm in here getting rid of Path Name, and I 
 300      * don't want to make other potentially breaking changes --joeo 
 302     int fileNameLen 
= storageName
.length(); 
 303     int excludeExtensionLen 
= strlen(kExcludeExtension
); 
 304     if (fileNameLen 
> excludeExtensionLen
 
 305             && (0 == strcmp(storageName
.string() + (fileNameLen 
- excludeExtensionLen
), 
 306                             kExcludeExtension
))) { 
 307         fprintf(stderr
, "warning: '%s' not added to Zip\n", storageName
.string()); 
 311     if (strcasecmp(storageName
.getPathExtension().string(), ".gz") == 0) { 
 313         storageName 
= storageName
.getBasePath(); 
 316     if (bundle
->getUpdate()) { 
 317         entry 
= zip
->getEntryByName(storageName
.string()); 
 319             /* file already exists in archive; there can be only one */ 
 320             if (entry
->getMarked()) { 
 322                         "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", 
 323                         file
->getPrintableSource().string()); 
 327                 const String8
& srcName 
= file
->getSourceFile(); 
 329                 fileModWhen 
= getFileModDate(srcName
.string()); 
 330                 if (fileModWhen 
== (time_t) -1) { // file existence tested earlier, 
 331                     return false;                 //  not expecting an error here 
 334                 if (fileModWhen 
> entry
->getModWhen()) { 
 335                     // mark as deleted so add() will succeed 
 336                     if (bundle
->getVerbose()) { 
 337                         printf("      (removing old '%s')\n", storageName
.string()); 
 342                     // version in archive is newer 
 343                     if (bundle
->getVerbose()) { 
 344                         printf("      (not updating '%s')\n", storageName
.string()); 
 346                     entry
->setMarked(true); 
 350                 // Generated files are always replaced. 
 356     //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); 
 359         result 
= zip
->addGzip(file
->getSourceFile().string(), storageName
.string(), &entry
); 
 360     } else if (!hasData
) { 
 361         /* don't compress certain files, e.g. PNGs */ 
 362         int compressionMethod 
= bundle
->getCompressionMethod(); 
 363         if (!okayToCompress(bundle
, storageName
)) { 
 364             compressionMethod 
= ZipEntry::kCompressStored
; 
 366         result 
= zip
->add(file
->getSourceFile().string(), storageName
.string(), compressionMethod
, 
 369         result 
= zip
->add(file
->getData(), file
->getSize(), storageName
.string(), 
 370                            file
->getCompressionMethod(), &entry
); 
 372     if (result 
== NO_ERROR
) { 
 373         if (bundle
->getVerbose()) { 
 374             printf("      '%s'%s", storageName
.string(), fromGzip 
? " (from .gz)" : ""); 
 375             if (entry
->getCompressionMethod() == ZipEntry::kCompressStored
) { 
 376                 printf(" (not compressed)\n"); 
 378                 printf(" (compressed %d%%)\n", calcPercent(entry
->getUncompressedLen(), 
 379                             entry
->getCompressedLen())); 
 382         entry
->setMarked(true); 
 384         if (result 
== ALREADY_EXISTS
) { 
 385             fprintf(stderr
, "      Unable to add '%s': file already in archive (try '-u'?)\n", 
 386                     file
->getPrintableSource().string()); 
 388             fprintf(stderr
, "      Unable to add '%s': Zip add failed\n",  
 389                     file
->getPrintableSource().string()); 
 398  * Determine whether or not we want to try to compress this file based 
 399  * on the file extension. 
 401 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
) 
 403     String8 ext 
= pathName
.getPathExtension(); 
 406     if (ext
.length() == 0) 
 409     for (i 
= 0; i 
< NELEM(kNoCompressExt
); i
++) { 
 410         if (strcasecmp(ext
.string(), kNoCompressExt
[i
]) == 0) 
 414     const android::Vector
<const char*>& others(bundle
->getNoCompressExtensions()); 
 415     for (i 
= 0; i 
< (int)others
.size(); i
++) { 
 416         const char* str 
= others
[i
]; 
 417         int pos 
= pathName
.length() - strlen(str
); 
 421         const char* path 
= pathName
.string(); 
 422         if (strcasecmp(path 
+ pos
, str
) == 0) { 
 430 bool endsWith(const char* haystack
, const char* needle
) 
 432     size_t a 
= strlen(haystack
); 
 433     size_t b 
= strlen(needle
); 
 434     if (a 
< b
) return false; 
 435     return strcasecmp(haystack
+(a
-b
), needle
) == 0; 
 438 ssize_t 
processJarFile(ZipFile
* jar
, ZipFile
* out
) 
 441     size_t N 
= jar
->getNumEntries(); 
 443     for (size_t i
=0; i
<N
; i
++) { 
 444         ZipEntry
* entry 
= jar
->getEntryByIndex(i
); 
 445         const char* storageName 
= entry
->getFileName(); 
 446         if (endsWith(storageName
, ".class")) { 
 447             int compressionMethod 
= entry
->getCompressionMethod(); 
 448             size_t size 
= entry
->getUncompressedLen(); 
 449             const void* data 
= jar
->uncompress(entry
); 
 451                 fprintf(stderr
, "ERROR: unable to uncompress entry '%s'\n", 
 455             out
->add(data
, size
, storageName
, compressionMethod
, NULL
); 
 463 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
) 
 467     const android::Vector
<const char*>& jars 
= bundle
->getJarFiles(); 
 469     size_t N 
= jars
.size(); 
 470     for (size_t i
=0; i
<N
; i
++) { 
 472         err 
= jar
.open(jars
[i
], ZipFile::kOpenReadOnly
); 
 474             fprintf(stderr
, "ERROR: unable to open '%s' as a zip file: %d\n", 
 478         err 
+= processJarFile(&jar
, zip
); 
 480             fprintf(stderr
, "ERROR: unable to process '%s'\n", jars
[i
]);