]>
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
) 
  54     fprintf(stdout
, "BENCHMARK: Starting APK Bundling \n"); 
  55     long startAPKTime 
= clock(); 
  56     #endif /* BENCHMARK */ 
  58     status_t result 
= NO_ERROR
; 
  62     //bundle->setPackageCount(0); 
  65      * Prep the Zip archive. 
  67      * If the file already exists, fail unless "update" or "force" is set. 
  68      * If "update" is set, update the contents of the existing archive. 
  69      * Else, if "force" is set, remove the existing archive. 
  71     FileType fileType 
= getFileType(outputFile
.string()); 
  72     if (fileType 
== kFileTypeNonexistent
) { 
  73         // okay, create it below 
  74     } else if (fileType 
== kFileTypeRegular
) { 
  75         if (bundle
->getUpdate()) { 
  76             // okay, open it below 
  77         } else if (bundle
->getForce()) { 
  78             if (unlink(outputFile
.string()) != 0) { 
  79                 fprintf(stderr
, "ERROR: unable to remove '%s': %s\n", outputFile
.string(), 
  84             fprintf(stderr
, "ERROR: '%s' exists (use '-f' to force overwrite)\n", 
  89         fprintf(stderr
, "ERROR: '%s' exists and is not a regular file\n", outputFile
.string()); 
  93     if (bundle
->getVerbose()) { 
  94         printf("%s '%s'\n", (fileType 
== kFileTypeNonexistent
) ? "Creating" : "Opening", 
 100     status 
= zip
->open(outputFile
.string(), ZipFile::kOpenReadWrite 
| ZipFile::kOpenCreate
); 
 101     if (status 
!= NO_ERROR
) { 
 102         fprintf(stderr
, "ERROR: unable to open '%s' as Zip file for writing\n", 
 103                 outputFile
.string()); 
 107     if (bundle
->getVerbose()) { 
 108         printf("Writing all files...\n"); 
 111     count 
= processAssets(bundle
, zip
, assets
); 
 113         fprintf(stderr
, "ERROR: unable to process assets while packaging '%s'\n", 
 114                 outputFile
.string()); 
 119     if (bundle
->getVerbose()) { 
 120         printf("Generated %d file%s\n", count
, (count
==1) ? "" : "s"); 
 123     count 
= processJarFiles(bundle
, zip
); 
 125         fprintf(stderr
, "ERROR: unable to process jar files while packaging '%s'\n", 
 126                 outputFile
.string()); 
 131     if (bundle
->getVerbose()) 
 132         printf("Included %d file%s from jar/zip files.\n", count
, (count
==1) ? "" : "s"); 
 137      * Check for cruft.  We set the "marked" flag on all entries we created 
 138      * or decided not to update.  If the entry isn't already slated for 
 139      * deletion, remove it now. 
 142         if (bundle
->getVerbose()) 
 143             printf("Checking for deleted files\n"); 
 145         for (i 
= 0; i 
< zip
->getNumEntries(); i
++) { 
 146             ZipEntry
* entry 
= zip
->getEntryByIndex(i
); 
 148             if (!entry
->getMarked() && entry
->getDeleted()) { 
 149                 if (bundle
->getVerbose()) { 
 150                     printf("      (removing crufty '%s')\n", 
 151                         entry
->getFileName()); 
 157         if (bundle
->getVerbose() && removed 
> 0) 
 158             printf("Removed %d file%s\n", removed
, (removed
==1) ? "" : "s"); 
 161     /* tell Zip lib to process deletions and other pending changes */ 
 162     result 
= zip
->flush(); 
 163     if (result 
!= NO_ERROR
) { 
 164         fprintf(stderr
, "ERROR: Zip flush failed, archive may be hosed\n"); 
 169     if (zip
->getNumEntries() == 0) { 
 170         if (bundle
->getVerbose()) { 
 171             printf("Archive is empty -- removing %s\n", outputFile
.getPathLeaf().string()); 
 173         delete zip
;        // close the file so we can remove it in Win32 
 175         if (unlink(outputFile
.string()) != 0) { 
 176             fprintf(stderr
, "warning: could not unlink '%s'\n", outputFile
.string()); 
 180     if (bundle
->getGenDependencies()) { 
 181         // Add this file to the dependency file 
 182         String8 dependencyFile 
= outputFile
.getBasePath(); 
 183         dependencyFile
.append(".d"); 
 185         FILE* fp 
= fopen(dependencyFile
.string(), "a"); 
 186         fprintf(fp
, "%s \\\n", outputFile
.string()); 
 190     assert(result 
== NO_ERROR
); 
 193     delete zip
;        // must close before remove in Win32 
 194     if (result 
!= NO_ERROR
) { 
 195         if (bundle
->getVerbose()) { 
 196             printf("Removing %s due to earlier failures\n", outputFile
.string()); 
 198         if (unlink(outputFile
.string()) != 0) { 
 199             fprintf(stderr
, "warning: could not unlink '%s'\n", outputFile
.string()); 
 203     if (result 
== NO_ERROR 
&& bundle
->getVerbose()) 
 207     fprintf(stdout
, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime
)/1000.0); 
 208     #endif /* BENCHMARK */ 
 212 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, 
 213                       const sp
<AaptAssets
>& assets
) 
 215     ResourceFilter filter
; 
 216     status_t status 
= filter
.parse(bundle
->getConfigurations()); 
 217     if (status 
!= NO_ERROR
) { 
 223     const size_t N 
= assets
->getGroupEntries().size(); 
 224     for (size_t i
=0; i
<N
; i
++) { 
 225         const AaptGroupEntry
& ge 
= assets
->getGroupEntries()[i
]; 
 227         ssize_t res 
= processAssets(bundle
, zip
, assets
, ge
, &filter
); 
 238 ssize_t 
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptDir
>& dir
, 
 239         const AaptGroupEntry
& ge
, const ResourceFilter
* filter
) 
 243     const size_t ND 
= dir
->getDirs().size(); 
 245     for (i
=0; i
<ND
; i
++) { 
 246         const sp
<AaptDir
>& subDir 
= dir
->getDirs().valueAt(i
); 
 248         const bool filterable 
= filter 
!= NULL 
&& subDir
->getLeaf().find("mipmap-") != 0; 
 250         if (filterable 
&& subDir
->getLeaf() != subDir
->getPath() && !filter
->match(ge
.toParams())) { 
 254         ssize_t res 
= processAssets(bundle
, zip
, subDir
, ge
, filterable 
? filter 
: NULL
); 
 261     if (filter 
!= NULL 
&& !filter
->match(ge
.toParams())) { 
 265     const size_t NF 
= dir
->getFiles().size(); 
 266     for (i
=0; i
<NF
; i
++) { 
 267         sp
<AaptGroup
> gp 
= dir
->getFiles().valueAt(i
); 
 268         ssize_t fi 
= gp
->getFiles().indexOfKey(ge
); 
 270             sp
<AaptFile
> fl 
= gp
->getFiles().valueAt(fi
); 
 271             if (!processFile(bundle
, zip
, gp
, fl
)) { 
 272                 return UNKNOWN_ERROR
; 
 282  * Process a regular file, adding it to the archive if appropriate. 
 284  * If we're in "update" mode, and the file already exists in the archive, 
 285  * delete the existing entry before adding the new one. 
 287 bool processFile(Bundle
* bundle
, ZipFile
* zip
, 
 288                  const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
) 
 290     const bool hasData 
= file
->hasData(); 
 292     String8 
storageName(group
->getPath()); 
 293     storageName
.convertToResPath(); 
 295     bool fromGzip 
= false; 
 299      * See if the filename ends in ".EXCLUDE".  We can't use 
 300      * String8::getPathExtension() because the length of what it considers 
 301      * to be an extension is capped. 
 303      * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives, 
 304      * so there's no value in adding them (and it makes life easier on 
 305      * the AssetManager lib if we don't). 
 307      * NOTE: this restriction has been removed.  If you're in this code, you 
 308      * should clean this up, but I'm in here getting rid of Path Name, and I 
 309      * don't want to make other potentially breaking changes --joeo 
 311     int fileNameLen 
= storageName
.length(); 
 312     int excludeExtensionLen 
= strlen(kExcludeExtension
); 
 313     if (fileNameLen 
> excludeExtensionLen
 
 314             && (0 == strcmp(storageName
.string() + (fileNameLen 
- excludeExtensionLen
), 
 315                             kExcludeExtension
))) { 
 316         fprintf(stderr
, "warning: '%s' not added to Zip\n", storageName
.string()); 
 320     if (strcasecmp(storageName
.getPathExtension().string(), ".gz") == 0) { 
 322         storageName 
= storageName
.getBasePath(); 
 325     if (bundle
->getUpdate()) { 
 326         entry 
= zip
->getEntryByName(storageName
.string()); 
 328             /* file already exists in archive; there can be only one */ 
 329             if (entry
->getMarked()) { 
 331                         "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", 
 332                         file
->getPrintableSource().string()); 
 336                 const String8
& srcName 
= file
->getSourceFile(); 
 338                 fileModWhen 
= getFileModDate(srcName
.string()); 
 339                 if (fileModWhen 
== (time_t) -1) { // file existence tested earlier, 
 340                     return false;                 //  not expecting an error here 
 343                 if (fileModWhen 
> entry
->getModWhen()) { 
 344                     // mark as deleted so add() will succeed 
 345                     if (bundle
->getVerbose()) { 
 346                         printf("      (removing old '%s')\n", storageName
.string()); 
 351                     // version in archive is newer 
 352                     if (bundle
->getVerbose()) { 
 353                         printf("      (not updating '%s')\n", storageName
.string()); 
 355                     entry
->setMarked(true); 
 359                 // Generated files are always replaced. 
 365     //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); 
 368         result 
= zip
->addGzip(file
->getSourceFile().string(), storageName
.string(), &entry
); 
 369     } else if (!hasData
) { 
 370         /* don't compress certain files, e.g. PNGs */ 
 371         int compressionMethod 
= bundle
->getCompressionMethod(); 
 372         if (!okayToCompress(bundle
, storageName
)) { 
 373             compressionMethod 
= ZipEntry::kCompressStored
; 
 375         result 
= zip
->add(file
->getSourceFile().string(), storageName
.string(), compressionMethod
, 
 378         result 
= zip
->add(file
->getData(), file
->getSize(), storageName
.string(), 
 379                            file
->getCompressionMethod(), &entry
); 
 381     if (result 
== NO_ERROR
) { 
 382         if (bundle
->getVerbose()) { 
 383             printf("      '%s'%s", storageName
.string(), fromGzip 
? " (from .gz)" : ""); 
 384             if (entry
->getCompressionMethod() == ZipEntry::kCompressStored
) { 
 385                 printf(" (not compressed)\n"); 
 387                 printf(" (compressed %d%%)\n", calcPercent(entry
->getUncompressedLen(), 
 388                             entry
->getCompressedLen())); 
 391         entry
->setMarked(true); 
 393         if (result 
== ALREADY_EXISTS
) { 
 394             fprintf(stderr
, "      Unable to add '%s': file already in archive (try '-u'?)\n", 
 395                     file
->getPrintableSource().string()); 
 397             fprintf(stderr
, "      Unable to add '%s': Zip add failed\n",  
 398                     file
->getPrintableSource().string()); 
 407  * Determine whether or not we want to try to compress this file based 
 408  * on the file extension. 
 410 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
) 
 412     String8 ext 
= pathName
.getPathExtension(); 
 415     if (ext
.length() == 0) 
 418     for (i 
= 0; i 
< NELEM(kNoCompressExt
); i
++) { 
 419         if (strcasecmp(ext
.string(), kNoCompressExt
[i
]) == 0) 
 423     const android::Vector
<const char*>& others(bundle
->getNoCompressExtensions()); 
 424     for (i 
= 0; i 
< (int)others
.size(); i
++) { 
 425         const char* str 
= others
[i
]; 
 426         int pos 
= pathName
.length() - strlen(str
); 
 430         const char* path 
= pathName
.string(); 
 431         if (strcasecmp(path 
+ pos
, str
) == 0) { 
 439 bool endsWith(const char* haystack
, const char* needle
) 
 441     size_t a 
= strlen(haystack
); 
 442     size_t b 
= strlen(needle
); 
 443     if (a 
< b
) return false; 
 444     return strcasecmp(haystack
+(a
-b
), needle
) == 0; 
 447 ssize_t 
processJarFile(ZipFile
* jar
, ZipFile
* out
) 
 450     size_t N 
= jar
->getNumEntries(); 
 452     for (size_t i
=0; i
<N
; i
++) { 
 453         ZipEntry
* entry 
= jar
->getEntryByIndex(i
); 
 454         const char* storageName 
= entry
->getFileName(); 
 455         if (endsWith(storageName
, ".class")) { 
 456             int compressionMethod 
= entry
->getCompressionMethod(); 
 457             size_t size 
= entry
->getUncompressedLen(); 
 458             const void* data 
= jar
->uncompress(entry
); 
 460                 fprintf(stderr
, "ERROR: unable to uncompress entry '%s'\n", 
 464             out
->add(data
, size
, storageName
, compressionMethod
, NULL
); 
 472 ssize_t 
processJarFiles(Bundle
* bundle
, ZipFile
* zip
) 
 476     const android::Vector
<const char*>& jars 
= bundle
->getJarFiles(); 
 478     size_t N 
= jars
.size(); 
 479     for (size_t i
=0; i
<N
; i
++) { 
 481         err 
= jar
.open(jars
[i
], ZipFile::kOpenReadOnly
); 
 483             fprintf(stderr
, "ERROR: unable to open '%s' as a zip file: %d\n", 
 487         err 
+= processJarFile(&jar
, zip
); 
 489             fprintf(stderr
, "ERROR: unable to process '%s'\n", jars
[i
]);