blob: 872d95c509f796de9dfd152bb42487bcfd419068 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Package assets into Zip files.
5//
6#include "Main.h"
7#include "AaptAssets.h"
8#include "ResourceTable.h"
Dianne Hackborne6b68032011-10-13 16:26:02 -07009#include "ResourceFilter.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080010
Mathias Agopian1f5762e2013-05-06 20:20:34 -070011#include <androidfw/misc.h>
12
Mathias Agopian3b4062e2009-05-31 19:13:00 -070013#include <utils/Log.h>
14#include <utils/threads.h>
15#include <utils/List.h>
16#include <utils/Errors.h>
Mathias Agopian1f5762e2013-05-06 20:20:34 -070017#include <utils/misc.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
19#include <sys/types.h>
20#include <dirent.h>
21#include <ctype.h>
22#include <errno.h>
23
24using namespace android;
25
26static const char* kExcludeExtension = ".EXCLUDE";
27
28/* these formats are already compressed, or don't compress well */
29static const char* kNoCompressExt[] = {
30 ".jpg", ".jpeg", ".png", ".gif",
31 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
32 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
33 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
34 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
35 ".amr", ".awb", ".wma", ".wmv"
36};
37
38/* fwd decls, so I can write this downward */
39ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
Kenny Root7c710232010-11-22 22:28:37 -080040ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
41 const AaptGroupEntry& ge, const ResourceFilter* filter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042bool processFile(Bundle* bundle, ZipFile* zip,
43 const sp<AaptGroup>& group, const sp<AaptFile>& file);
44bool okayToCompress(Bundle* bundle, const String8& pathName);
45ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
46
47/*
48 * The directory hierarchy looks like this:
49 * "outputDir" and "assetRoot" are existing directories.
50 *
51 * On success, "bundle->numPackages" will be the number of Zip packages
52 * we created.
53 */
54status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
55 const String8& outputFile)
56{
Josiah Gaskin8a39da82011-06-06 17:00:35 -070057 #if BENCHMARK
58 fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
59 long startAPKTime = clock();
60 #endif /* BENCHMARK */
61
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 status_t result = NO_ERROR;
63 ZipFile* zip = NULL;
64 int count;
65
66 //bundle->setPackageCount(0);
67
68 /*
69 * Prep the Zip archive.
70 *
71 * If the file already exists, fail unless "update" or "force" is set.
72 * If "update" is set, update the contents of the existing archive.
73 * Else, if "force" is set, remove the existing archive.
74 */
75 FileType fileType = getFileType(outputFile.string());
76 if (fileType == kFileTypeNonexistent) {
77 // okay, create it below
78 } else if (fileType == kFileTypeRegular) {
79 if (bundle->getUpdate()) {
80 // okay, open it below
81 } else if (bundle->getForce()) {
82 if (unlink(outputFile.string()) != 0) {
83 fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(),
84 strerror(errno));
85 goto bail;
86 }
87 } else {
88 fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n",
89 outputFile.string());
90 goto bail;
91 }
92 } else {
93 fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string());
94 goto bail;
95 }
96
97 if (bundle->getVerbose()) {
98 printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening",
99 outputFile.string());
100 }
101
102 status_t status;
103 zip = new ZipFile;
104 status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate);
105 if (status != NO_ERROR) {
106 fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n",
107 outputFile.string());
108 goto bail;
109 }
110
111 if (bundle->getVerbose()) {
112 printf("Writing all files...\n");
113 }
114
115 count = processAssets(bundle, zip, assets);
116 if (count < 0) {
117 fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
118 outputFile.string());
119 result = count;
120 goto bail;
121 }
122
123 if (bundle->getVerbose()) {
124 printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
125 }
126
127 count = processJarFiles(bundle, zip);
128 if (count < 0) {
129 fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
130 outputFile.string());
131 result = count;
132 goto bail;
133 }
134
135 if (bundle->getVerbose())
136 printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
137
138 result = NO_ERROR;
139
140 /*
141 * Check for cruft. We set the "marked" flag on all entries we created
142 * or decided not to update. If the entry isn't already slated for
143 * deletion, remove it now.
144 */
145 {
146 if (bundle->getVerbose())
147 printf("Checking for deleted files\n");
148 int i, removed = 0;
149 for (i = 0; i < zip->getNumEntries(); i++) {
150 ZipEntry* entry = zip->getEntryByIndex(i);
151
152 if (!entry->getMarked() && entry->getDeleted()) {
153 if (bundle->getVerbose()) {
154 printf(" (removing crufty '%s')\n",
155 entry->getFileName());
156 }
157 zip->remove(entry);
158 removed++;
159 }
160 }
161 if (bundle->getVerbose() && removed > 0)
162 printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
163 }
164
165 /* tell Zip lib to process deletions and other pending changes */
166 result = zip->flush();
167 if (result != NO_ERROR) {
168 fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
169 goto bail;
170 }
171
172 /* anything here? */
173 if (zip->getNumEntries() == 0) {
174 if (bundle->getVerbose()) {
175 printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string());
176 }
177 delete zip; // close the file so we can remove it in Win32
178 zip = NULL;
179 if (unlink(outputFile.string()) != 0) {
Marco Nelissendd931862009-07-13 13:02:33 -0700180 fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182 }
183
Josiah Gaskinb711f3f2011-08-15 18:33:44 -0700184 // If we've been asked to generate a dependency file for the .ap_ package,
185 // do so here
Josiah Gaskin03589cc2011-06-27 16:26:02 -0700186 if (bundle->getGenDependencies()) {
Josiah Gaskinb711f3f2011-08-15 18:33:44 -0700187 // The dependency file gets output to the same directory
188 // as the specified output file with an additional .d extension.
189 // e.g. bin/resources.ap_.d
190 String8 dependencyFile = outputFile;
Josiah Gaskin03589cc2011-06-27 16:26:02 -0700191 dependencyFile.append(".d");
192
193 FILE* fp = fopen(dependencyFile.string(), "a");
Josiah Gaskinb711f3f2011-08-15 18:33:44 -0700194 // Add this file to the dependency file
Josiah Gaskin03589cc2011-06-27 16:26:02 -0700195 fprintf(fp, "%s \\\n", outputFile.string());
196 fclose(fp);
197 }
198
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 assert(result == NO_ERROR);
200
201bail:
202 delete zip; // must close before remove in Win32
203 if (result != NO_ERROR) {
204 if (bundle->getVerbose()) {
205 printf("Removing %s due to earlier failures\n", outputFile.string());
206 }
207 if (unlink(outputFile.string()) != 0) {
Marco Nelissendd931862009-07-13 13:02:33 -0700208 fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 }
210 }
211
212 if (result == NO_ERROR && bundle->getVerbose())
213 printf("Done!\n");
Josiah Gaskin8a39da82011-06-06 17:00:35 -0700214
215 #if BENCHMARK
216 fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0);
217 #endif /* BENCHMARK */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 return result;
219}
220
221ssize_t processAssets(Bundle* bundle, ZipFile* zip,
222 const sp<AaptAssets>& assets)
223{
224 ResourceFilter filter;
225 status_t status = filter.parse(bundle->getConfigurations());
226 if (status != NO_ERROR) {
227 return -1;
228 }
229
230 ssize_t count = 0;
231
232 const size_t N = assets->getGroupEntries().size();
233 for (size_t i=0; i<N; i++) {
234 const AaptGroupEntry& ge = assets->getGroupEntries()[i];
Kenny Root7c710232010-11-22 22:28:37 -0800235
236 ssize_t res = processAssets(bundle, zip, assets, ge, &filter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 if (res < 0) {
238 return res;
239 }
Kenny Root7c710232010-11-22 22:28:37 -0800240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 count += res;
242 }
243
244 return count;
245}
246
Kenny Root7c710232010-11-22 22:28:37 -0800247ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
248 const AaptGroupEntry& ge, const ResourceFilter* filter)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249{
250 ssize_t count = 0;
251
252 const size_t ND = dir->getDirs().size();
253 size_t i;
254 for (i=0; i<ND; i++) {
Kenny Root7c710232010-11-22 22:28:37 -0800255 const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
256
257 const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0;
258
259 if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) {
260 continue;
261 }
262
263 ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 if (res < 0) {
265 return res;
266 }
267 count += res;
268 }
269
Kenny Root7c710232010-11-22 22:28:37 -0800270 if (filter != NULL && !filter->match(ge.toParams())) {
271 return count;
272 }
273
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 const size_t NF = dir->getFiles().size();
275 for (i=0; i<NF; i++) {
276 sp<AaptGroup> gp = dir->getFiles().valueAt(i);
277 ssize_t fi = gp->getFiles().indexOfKey(ge);
278 if (fi >= 0) {
279 sp<AaptFile> fl = gp->getFiles().valueAt(fi);
280 if (!processFile(bundle, zip, gp, fl)) {
281 return UNKNOWN_ERROR;
282 }
283 count++;
284 }
285 }
286
287 return count;
288}
289
290/*
291 * Process a regular file, adding it to the archive if appropriate.
292 *
293 * If we're in "update" mode, and the file already exists in the archive,
294 * delete the existing entry before adding the new one.
295 */
296bool processFile(Bundle* bundle, ZipFile* zip,
297 const sp<AaptGroup>& group, const sp<AaptFile>& file)
298{
299 const bool hasData = file->hasData();
300
301 String8 storageName(group->getPath());
302 storageName.convertToResPath();
303 ZipEntry* entry;
304 bool fromGzip = false;
305 status_t result;
306
307 /*
308 * See if the filename ends in ".EXCLUDE". We can't use
309 * String8::getPathExtension() because the length of what it considers
310 * to be an extension is capped.
311 *
312 * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives,
313 * so there's no value in adding them (and it makes life easier on
314 * the AssetManager lib if we don't).
315 *
316 * NOTE: this restriction has been removed. If you're in this code, you
317 * should clean this up, but I'm in here getting rid of Path Name, and I
318 * don't want to make other potentially breaking changes --joeo
319 */
320 int fileNameLen = storageName.length();
321 int excludeExtensionLen = strlen(kExcludeExtension);
322 if (fileNameLen > excludeExtensionLen
323 && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen),
324 kExcludeExtension))) {
Marco Nelissendd931862009-07-13 13:02:33 -0700325 fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.string());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 return true;
327 }
328
329 if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) {
330 fromGzip = true;
331 storageName = storageName.getBasePath();
332 }
333
334 if (bundle->getUpdate()) {
335 entry = zip->getEntryByName(storageName.string());
336 if (entry != NULL) {
337 /* file already exists in archive; there can be only one */
338 if (entry->getMarked()) {
339 fprintf(stderr,
340 "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
341 file->getPrintableSource().string());
342 return false;
343 }
344 if (!hasData) {
345 const String8& srcName = file->getSourceFile();
346 time_t fileModWhen;
347 fileModWhen = getFileModDate(srcName.string());
348 if (fileModWhen == (time_t) -1) { // file existence tested earlier,
349 return false; // not expecting an error here
350 }
351
352 if (fileModWhen > entry->getModWhen()) {
353 // mark as deleted so add() will succeed
354 if (bundle->getVerbose()) {
355 printf(" (removing old '%s')\n", storageName.string());
356 }
357
358 zip->remove(entry);
359 } else {
360 // version in archive is newer
361 if (bundle->getVerbose()) {
362 printf(" (not updating '%s')\n", storageName.string());
363 }
364 entry->setMarked(true);
365 return true;
366 }
367 } else {
368 // Generated files are always replaced.
369 zip->remove(entry);
370 }
371 }
372 }
373
374 //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE);
375
376 if (fromGzip) {
377 result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry);
378 } else if (!hasData) {
379 /* don't compress certain files, e.g. PNGs */
380 int compressionMethod = bundle->getCompressionMethod();
381 if (!okayToCompress(bundle, storageName)) {
382 compressionMethod = ZipEntry::kCompressStored;
383 }
384 result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod,
385 &entry);
386 } else {
387 result = zip->add(file->getData(), file->getSize(), storageName.string(),
388 file->getCompressionMethod(), &entry);
389 }
390 if (result == NO_ERROR) {
391 if (bundle->getVerbose()) {
392 printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : "");
393 if (entry->getCompressionMethod() == ZipEntry::kCompressStored) {
394 printf(" (not compressed)\n");
395 } else {
396 printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(),
397 entry->getCompressedLen()));
398 }
399 }
400 entry->setMarked(true);
401 } else {
402 if (result == ALREADY_EXISTS) {
403 fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n",
404 file->getPrintableSource().string());
405 } else {
406 fprintf(stderr, " Unable to add '%s': Zip add failed\n",
407 file->getPrintableSource().string());
408 }
409 return false;
410 }
411
412 return true;
413}
414
415/*
416 * Determine whether or not we want to try to compress this file based
417 * on the file extension.
418 */
419bool okayToCompress(Bundle* bundle, const String8& pathName)
420{
421 String8 ext = pathName.getPathExtension();
422 int i;
423
424 if (ext.length() == 0)
425 return true;
426
427 for (i = 0; i < NELEM(kNoCompressExt); i++) {
428 if (strcasecmp(ext.string(), kNoCompressExt[i]) == 0)
429 return false;
430 }
431
432 const android::Vector<const char*>& others(bundle->getNoCompressExtensions());
433 for (i = 0; i < (int)others.size(); i++) {
434 const char* str = others[i];
435 int pos = pathName.length() - strlen(str);
436 if (pos < 0) {
437 continue;
438 }
439 const char* path = pathName.string();
440 if (strcasecmp(path + pos, str) == 0) {
441 return false;
442 }
443 }
444
445 return true;
446}
447
448bool endsWith(const char* haystack, const char* needle)
449{
450 size_t a = strlen(haystack);
451 size_t b = strlen(needle);
452 if (a < b) return false;
453 return strcasecmp(haystack+(a-b), needle) == 0;
454}
455
456ssize_t processJarFile(ZipFile* jar, ZipFile* out)
457{
458 status_t err;
459 size_t N = jar->getNumEntries();
460 size_t count = 0;
461 for (size_t i=0; i<N; i++) {
462 ZipEntry* entry = jar->getEntryByIndex(i);
463 const char* storageName = entry->getFileName();
464 if (endsWith(storageName, ".class")) {
465 int compressionMethod = entry->getCompressionMethod();
466 size_t size = entry->getUncompressedLen();
467 const void* data = jar->uncompress(entry);
468 if (data == NULL) {
469 fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n",
470 storageName);
471 return -1;
472 }
473 out->add(data, size, storageName, compressionMethod, NULL);
474 free((void*)data);
475 }
476 count++;
477 }
478 return count;
479}
480
481ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
482{
Steve Blockf1ff21a2010-06-14 17:34:04 +0100483 status_t err;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 ssize_t count = 0;
485 const android::Vector<const char*>& jars = bundle->getJarFiles();
486
487 size_t N = jars.size();
488 for (size_t i=0; i<N; i++) {
489 ZipFile jar;
490 err = jar.open(jars[i], ZipFile::kOpenReadOnly);
491 if (err != 0) {
Kenny Rootddb76c42010-11-24 12:56:06 -0800492 fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %d\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 jars[i], err);
494 return err;
495 }
496 err += processJarFile(&jar, zip);
497 if (err < 0) {
498 fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]);
499 return err;
500 }
501 count += err;
502 }
503
504 return count;
505}