]>
git.saurik.com Git - android/aapt.git/blob - Package.cpp
3930117b67a2126a2fca50498cac1a523e01a3db
2 // Copyright 2006 The Android Open Source Project
4 // Package assets into Zip files.
7 #include "AaptAssets.h"
8 #include "ResourceTable.h"
9 #include "ResourceFilter.h"
11 #include <utils/Log.h>
12 #include <utils/threads.h>
13 #include <utils/List.h>
14 #include <utils/Errors.h>
16 #include <sys/types.h>
21 using namespace android
;
23 static const char* kExcludeExtension
= ".EXCLUDE";
25 /* these formats are already compressed, or don't compress well */
26 static const char* kNoCompressExt
[] = {
27 ".jpg", ".jpeg", ".png", ".gif",
28 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
29 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
30 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
31 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
32 ".amr", ".awb", ".wma", ".wmv"
35 /* fwd decls, so I can write this downward */
36 ssize_t
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptAssets
>& assets
);
37 ssize_t
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptDir
>& dir
,
38 const AaptGroupEntry
& ge
, const ResourceFilter
* filter
);
39 bool processFile(Bundle
* bundle
, ZipFile
* zip
,
40 const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
);
41 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
);
42 ssize_t
processJarFiles(Bundle
* bundle
, ZipFile
* zip
);
45 * The directory hierarchy looks like this:
46 * "outputDir" and "assetRoot" are existing directories.
48 * On success, "bundle->numPackages" will be the number of Zip packages
51 status_t
writeAPK(Bundle
* bundle
, const sp
<AaptAssets
>& assets
,
52 const String8
& outputFile
)
55 fprintf(stdout
, "BENCHMARK: Starting APK Bundling \n");
56 long startAPKTime
= clock();
57 #endif /* BENCHMARK */
59 status_t result
= NO_ERROR
;
63 //bundle->setPackageCount(0);
66 * Prep the Zip archive.
68 * If the file already exists, fail unless "update" or "force" is set.
69 * If "update" is set, update the contents of the existing archive.
70 * Else, if "force" is set, remove the existing archive.
72 FileType fileType
= getFileType(outputFile
.string());
73 if (fileType
== kFileTypeNonexistent
) {
74 // okay, create it below
75 } else if (fileType
== kFileTypeRegular
) {
76 if (bundle
->getUpdate()) {
77 // okay, open it below
78 } else if (bundle
->getForce()) {
79 if (unlink(outputFile
.string()) != 0) {
80 fprintf(stderr
, "ERROR: unable to remove '%s': %s\n", outputFile
.string(),
85 fprintf(stderr
, "ERROR: '%s' exists (use '-f' to force overwrite)\n",
90 fprintf(stderr
, "ERROR: '%s' exists and is not a regular file\n", outputFile
.string());
94 if (bundle
->getVerbose()) {
95 printf("%s '%s'\n", (fileType
== kFileTypeNonexistent
) ? "Creating" : "Opening",
101 status
= zip
->open(outputFile
.string(), ZipFile::kOpenReadWrite
| ZipFile::kOpenCreate
);
102 if (status
!= NO_ERROR
) {
103 fprintf(stderr
, "ERROR: unable to open '%s' as Zip file for writing\n",
104 outputFile
.string());
108 if (bundle
->getVerbose()) {
109 printf("Writing all files...\n");
112 count
= processAssets(bundle
, zip
, assets
);
114 fprintf(stderr
, "ERROR: unable to process assets while packaging '%s'\n",
115 outputFile
.string());
120 if (bundle
->getVerbose()) {
121 printf("Generated %d file%s\n", count
, (count
==1) ? "" : "s");
124 count
= processJarFiles(bundle
, zip
);
126 fprintf(stderr
, "ERROR: unable to process jar files while packaging '%s'\n",
127 outputFile
.string());
132 if (bundle
->getVerbose())
133 printf("Included %d file%s from jar/zip files.\n", count
, (count
==1) ? "" : "s");
138 * Check for cruft. We set the "marked" flag on all entries we created
139 * or decided not to update. If the entry isn't already slated for
140 * deletion, remove it now.
143 if (bundle
->getVerbose())
144 printf("Checking for deleted files\n");
146 for (i
= 0; i
< zip
->getNumEntries(); i
++) {
147 ZipEntry
* entry
= zip
->getEntryByIndex(i
);
149 if (!entry
->getMarked() && entry
->getDeleted()) {
150 if (bundle
->getVerbose()) {
151 printf(" (removing crufty '%s')\n",
152 entry
->getFileName());
158 if (bundle
->getVerbose() && removed
> 0)
159 printf("Removed %d file%s\n", removed
, (removed
==1) ? "" : "s");
162 /* tell Zip lib to process deletions and other pending changes */
163 result
= zip
->flush();
164 if (result
!= NO_ERROR
) {
165 fprintf(stderr
, "ERROR: Zip flush failed, archive may be hosed\n");
170 if (zip
->getNumEntries() == 0) {
171 if (bundle
->getVerbose()) {
172 printf("Archive is empty -- removing %s\n", outputFile
.getPathLeaf().string());
174 delete zip
; // close the file so we can remove it in Win32
176 if (unlink(outputFile
.string()) != 0) {
177 fprintf(stderr
, "warning: could not unlink '%s'\n", outputFile
.string());
181 // If we've been asked to generate a dependency file for the .ap_ package,
183 if (bundle
->getGenDependencies()) {
184 // The dependency file gets output to the same directory
185 // as the specified output file with an additional .d extension.
186 // e.g. bin/resources.ap_.d
187 String8 dependencyFile
= outputFile
;
188 dependencyFile
.append(".d");
190 FILE* fp
= fopen(dependencyFile
.string(), "a");
191 // Add this file to the dependency file
192 fprintf(fp
, "%s \\\n", outputFile
.string());
196 assert(result
== NO_ERROR
);
199 delete zip
; // must close before remove in Win32
200 if (result
!= NO_ERROR
) {
201 if (bundle
->getVerbose()) {
202 printf("Removing %s due to earlier failures\n", outputFile
.string());
204 if (unlink(outputFile
.string()) != 0) {
205 fprintf(stderr
, "warning: could not unlink '%s'\n", outputFile
.string());
209 if (result
== NO_ERROR
&& bundle
->getVerbose())
213 fprintf(stdout
, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime
)/1000.0);
214 #endif /* BENCHMARK */
218 ssize_t
processAssets(Bundle
* bundle
, ZipFile
* zip
,
219 const sp
<AaptAssets
>& assets
)
221 ResourceFilter filter
;
222 status_t status
= filter
.parse(bundle
->getConfigurations());
223 if (status
!= NO_ERROR
) {
229 const size_t N
= assets
->getGroupEntries().size();
230 for (size_t i
=0; i
<N
; i
++) {
231 const AaptGroupEntry
& ge
= assets
->getGroupEntries()[i
];
233 ssize_t res
= processAssets(bundle
, zip
, assets
, ge
, &filter
);
244 ssize_t
processAssets(Bundle
* bundle
, ZipFile
* zip
, const sp
<AaptDir
>& dir
,
245 const AaptGroupEntry
& ge
, const ResourceFilter
* filter
)
249 const size_t ND
= dir
->getDirs().size();
251 for (i
=0; i
<ND
; i
++) {
252 const sp
<AaptDir
>& subDir
= dir
->getDirs().valueAt(i
);
254 const bool filterable
= filter
!= NULL
&& subDir
->getLeaf().find("mipmap-") != 0;
256 if (filterable
&& subDir
->getLeaf() != subDir
->getPath() && !filter
->match(ge
.toParams())) {
260 ssize_t res
= processAssets(bundle
, zip
, subDir
, ge
, filterable
? filter
: NULL
);
267 if (filter
!= NULL
&& !filter
->match(ge
.toParams())) {
271 const size_t NF
= dir
->getFiles().size();
272 for (i
=0; i
<NF
; i
++) {
273 sp
<AaptGroup
> gp
= dir
->getFiles().valueAt(i
);
274 ssize_t fi
= gp
->getFiles().indexOfKey(ge
);
276 sp
<AaptFile
> fl
= gp
->getFiles().valueAt(fi
);
277 if (!processFile(bundle
, zip
, gp
, fl
)) {
278 return UNKNOWN_ERROR
;
288 * Process a regular file, adding it to the archive if appropriate.
290 * If we're in "update" mode, and the file already exists in the archive,
291 * delete the existing entry before adding the new one.
293 bool processFile(Bundle
* bundle
, ZipFile
* zip
,
294 const sp
<AaptGroup
>& group
, const sp
<AaptFile
>& file
)
296 const bool hasData
= file
->hasData();
298 String8
storageName(group
->getPath());
299 storageName
.convertToResPath();
301 bool fromGzip
= false;
305 * See if the filename ends in ".EXCLUDE". We can't use
306 * String8::getPathExtension() because the length of what it considers
307 * to be an extension is capped.
309 * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives,
310 * so there's no value in adding them (and it makes life easier on
311 * the AssetManager lib if we don't).
313 * NOTE: this restriction has been removed. If you're in this code, you
314 * should clean this up, but I'm in here getting rid of Path Name, and I
315 * don't want to make other potentially breaking changes --joeo
317 int fileNameLen
= storageName
.length();
318 int excludeExtensionLen
= strlen(kExcludeExtension
);
319 if (fileNameLen
> excludeExtensionLen
320 && (0 == strcmp(storageName
.string() + (fileNameLen
- excludeExtensionLen
),
321 kExcludeExtension
))) {
322 fprintf(stderr
, "warning: '%s' not added to Zip\n", storageName
.string());
326 if (strcasecmp(storageName
.getPathExtension().string(), ".gz") == 0) {
328 storageName
= storageName
.getBasePath();
331 if (bundle
->getUpdate()) {
332 entry
= zip
->getEntryByName(storageName
.string());
334 /* file already exists in archive; there can be only one */
335 if (entry
->getMarked()) {
337 "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
338 file
->getPrintableSource().string());
342 const String8
& srcName
= file
->getSourceFile();
344 fileModWhen
= getFileModDate(srcName
.string());
345 if (fileModWhen
== (time_t) -1) { // file existence tested earlier,
346 return false; // not expecting an error here
349 if (fileModWhen
> entry
->getModWhen()) {
350 // mark as deleted so add() will succeed
351 if (bundle
->getVerbose()) {
352 printf(" (removing old '%s')\n", storageName
.string());
357 // version in archive is newer
358 if (bundle
->getVerbose()) {
359 printf(" (not updating '%s')\n", storageName
.string());
361 entry
->setMarked(true);
365 // Generated files are always replaced.
371 //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE);
374 result
= zip
->addGzip(file
->getSourceFile().string(), storageName
.string(), &entry
);
375 } else if (!hasData
) {
376 /* don't compress certain files, e.g. PNGs */
377 int compressionMethod
= bundle
->getCompressionMethod();
378 if (!okayToCompress(bundle
, storageName
)) {
379 compressionMethod
= ZipEntry::kCompressStored
;
381 result
= zip
->add(file
->getSourceFile().string(), storageName
.string(), compressionMethod
,
384 result
= zip
->add(file
->getData(), file
->getSize(), storageName
.string(),
385 file
->getCompressionMethod(), &entry
);
387 if (result
== NO_ERROR
) {
388 if (bundle
->getVerbose()) {
389 printf(" '%s'%s", storageName
.string(), fromGzip
? " (from .gz)" : "");
390 if (entry
->getCompressionMethod() == ZipEntry::kCompressStored
) {
391 printf(" (not compressed)\n");
393 printf(" (compressed %d%%)\n", calcPercent(entry
->getUncompressedLen(),
394 entry
->getCompressedLen()));
397 entry
->setMarked(true);
399 if (result
== ALREADY_EXISTS
) {
400 fprintf(stderr
, " Unable to add '%s': file already in archive (try '-u'?)\n",
401 file
->getPrintableSource().string());
403 fprintf(stderr
, " Unable to add '%s': Zip add failed\n",
404 file
->getPrintableSource().string());
413 * Determine whether or not we want to try to compress this file based
414 * on the file extension.
416 bool okayToCompress(Bundle
* bundle
, const String8
& pathName
)
418 String8 ext
= pathName
.getPathExtension();
421 if (ext
.length() == 0)
424 for (i
= 0; i
< NELEM(kNoCompressExt
); i
++) {
425 if (strcasecmp(ext
.string(), kNoCompressExt
[i
]) == 0)
429 const android::Vector
<const char*>& others(bundle
->getNoCompressExtensions());
430 for (i
= 0; i
< (int)others
.size(); i
++) {
431 const char* str
= others
[i
];
432 int pos
= pathName
.length() - strlen(str
);
436 const char* path
= pathName
.string();
437 if (strcasecmp(path
+ pos
, str
) == 0) {
445 bool endsWith(const char* haystack
, const char* needle
)
447 size_t a
= strlen(haystack
);
448 size_t b
= strlen(needle
);
449 if (a
< b
) return false;
450 return strcasecmp(haystack
+(a
-b
), needle
) == 0;
453 ssize_t
processJarFile(ZipFile
* jar
, ZipFile
* out
)
456 size_t N
= jar
->getNumEntries();
458 for (size_t i
=0; i
<N
; i
++) {
459 ZipEntry
* entry
= jar
->getEntryByIndex(i
);
460 const char* storageName
= entry
->getFileName();
461 if (endsWith(storageName
, ".class")) {
462 int compressionMethod
= entry
->getCompressionMethod();
463 size_t size
= entry
->getUncompressedLen();
464 const void* data
= jar
->uncompress(entry
);
466 fprintf(stderr
, "ERROR: unable to uncompress entry '%s'\n",
470 out
->add(data
, size
, storageName
, compressionMethod
, NULL
);
478 ssize_t
processJarFiles(Bundle
* bundle
, ZipFile
* zip
)
482 const android::Vector
<const char*>& jars
= bundle
->getJarFiles();
484 size_t N
= jars
.size();
485 for (size_t i
=0; i
<N
; i
++) {
487 err
= jar
.open(jars
[i
], ZipFile::kOpenReadOnly
);
489 fprintf(stderr
, "ERROR: unable to open '%s' as a zip file: %d\n",
493 err
+= processJarFile(&jar
, zip
);
495 fprintf(stderr
, "ERROR: unable to process '%s'\n", jars
[i
]);