blob: a7ff5fabf4956f68a96591063b53a70018cf2def [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -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"
Adam Lesinskifab50872014-04-16 14:40:42 -07008#include "OutputSet.h"
Adam Lesinski282e1812014-01-23 18:17:42 -08009#include "ResourceTable.h"
10#include "ResourceFilter.h"
Elliott Hughes338698e2021-07-13 17:15:19 -070011#include "Utils.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080012
13#include <androidfw/misc.h>
14
15#include <utils/Log.h>
16#include <utils/threads.h>
17#include <utils/List.h>
18#include <utils/Errors.h>
19#include <utils/misc.h>
20
21#include <sys/types.h>
22#include <dirent.h>
23#include <ctype.h>
24#include <errno.h>
25
26using namespace android;
27
28static const char* kExcludeExtension = ".EXCLUDE";
29
30/* these formats are already compressed, or don't compress well */
31static const char* kNoCompressExt[] = {
Marco Nelissen18f16d62018-10-25 08:18:22 -070032 ".jpg", ".jpeg", ".png", ".gif", ".opus",
Adam Lesinski282e1812014-01-23 18:17:42 -080033 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
34 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
35 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
36 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
Wonsik Kim6e452f9e2016-04-04 15:39:40 +090037 ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"
Adam Lesinski282e1812014-01-23 18:17:42 -080038};
39
40/* fwd decls, so I can write this downward */
Adam Lesinskifab50872014-04-16 14:40:42 -070041ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet);
42bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file);
Adam Lesinski282e1812014-01-23 18:17:42 -080043bool okayToCompress(Bundle* bundle, const String8& pathName);
44ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
45
46/*
47 * The directory hierarchy looks like this:
48 * "outputDir" and "assetRoot" are existing directories.
49 *
50 * On success, "bundle->numPackages" will be the number of Zip packages
51 * we created.
52 */
Adam Lesinskifab50872014-04-16 14:40:42 -070053status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet)
Adam Lesinski282e1812014-01-23 18:17:42 -080054{
55 #if BENCHMARK
56 fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
57 long startAPKTime = clock();
58 #endif /* BENCHMARK */
59
60 status_t result = NO_ERROR;
61 ZipFile* zip = NULL;
62 int count;
63
64 //bundle->setPackageCount(0);
65
66 /*
67 * Prep the Zip archive.
68 *
69 * If the file already exists, fail unless "update" or "force" is set.
70 * If "update" is set, update the contents of the existing archive.
71 * Else, if "force" is set, remove the existing archive.
72 */
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +000073 FileType fileType = getFileType(outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -080074 if (fileType == kFileTypeNonexistent) {
75 // okay, create it below
76 } else if (fileType == kFileTypeRegular) {
77 if (bundle->getUpdate()) {
78 // okay, open it below
79 } else if (bundle->getForce()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +000080 if (unlink(outputFile.c_str()) != 0) {
81 fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.c_str(),
Adam Lesinski282e1812014-01-23 18:17:42 -080082 strerror(errno));
83 goto bail;
84 }
85 } else {
86 fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +000087 outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -080088 goto bail;
89 }
90 } else {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +000091 fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -080092 goto bail;
93 }
94
95 if (bundle->getVerbose()) {
96 printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +000097 outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -080098 }
99
100 status_t status;
101 zip = new ZipFile;
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000102 status = zip->open(outputFile.c_str(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate);
Adam Lesinski282e1812014-01-23 18:17:42 -0800103 if (status != NO_ERROR) {
104 fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000105 outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800106 goto bail;
107 }
108
109 if (bundle->getVerbose()) {
110 printf("Writing all files...\n");
111 }
112
Adam Lesinskifab50872014-04-16 14:40:42 -0700113 count = processAssets(bundle, zip, outputSet);
Adam Lesinski282e1812014-01-23 18:17:42 -0800114 if (count < 0) {
115 fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000116 outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800117 result = count;
118 goto bail;
119 }
120
121 if (bundle->getVerbose()) {
122 printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
123 }
124
125 count = processJarFiles(bundle, zip);
126 if (count < 0) {
127 fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000128 outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800129 result = count;
130 goto bail;
131 }
132
133 if (bundle->getVerbose())
134 printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
135
136 result = NO_ERROR;
137
138 /*
139 * Check for cruft. We set the "marked" flag on all entries we created
140 * or decided not to update. If the entry isn't already slated for
141 * deletion, remove it now.
142 */
143 {
144 if (bundle->getVerbose())
145 printf("Checking for deleted files\n");
146 int i, removed = 0;
147 for (i = 0; i < zip->getNumEntries(); i++) {
148 ZipEntry* entry = zip->getEntryByIndex(i);
149
150 if (!entry->getMarked() && entry->getDeleted()) {
151 if (bundle->getVerbose()) {
152 printf(" (removing crufty '%s')\n",
153 entry->getFileName());
154 }
155 zip->remove(entry);
156 removed++;
157 }
158 }
159 if (bundle->getVerbose() && removed > 0)
160 printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
161 }
162
163 /* tell Zip lib to process deletions and other pending changes */
164 result = zip->flush();
165 if (result != NO_ERROR) {
166 fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
167 goto bail;
168 }
169
170 /* anything here? */
171 if (zip->getNumEntries() == 0) {
172 if (bundle->getVerbose()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000173 printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800174 }
175 delete zip; // close the file so we can remove it in Win32
176 zip = NULL;
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000177 if (unlink(outputFile.c_str()) != 0) {
178 fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800179 }
180 }
181
182 // If we've been asked to generate a dependency file for the .ap_ package,
183 // do so here
184 if (bundle->getGenDependencies()) {
185 // The dependency file gets output to the same directory
186 // as the specified output file with an additional .d extension.
187 // e.g. bin/resources.ap_.d
188 String8 dependencyFile = outputFile;
189 dependencyFile.append(".d");
190
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000191 FILE* fp = fopen(dependencyFile.c_str(), "a");
Adam Lesinski282e1812014-01-23 18:17:42 -0800192 // Add this file to the dependency file
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000193 fprintf(fp, "%s \\\n", outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800194 fclose(fp);
195 }
196
197 assert(result == NO_ERROR);
198
199bail:
200 delete zip; // must close before remove in Win32
201 if (result != NO_ERROR) {
202 if (bundle->getVerbose()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000203 printf("Removing %s due to earlier failures\n", outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800204 }
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000205 if (unlink(outputFile.c_str()) != 0) {
206 fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800207 }
208 }
209
210 if (result == NO_ERROR && bundle->getVerbose())
211 printf("Done!\n");
212
213 #if BENCHMARK
214 fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0);
215 #endif /* BENCHMARK */
216 return result;
217}
218
Adam Lesinskifab50872014-04-16 14:40:42 -0700219ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet)
Adam Lesinski282e1812014-01-23 18:17:42 -0800220{
221 ssize_t count = 0;
Adam Lesinskifab50872014-04-16 14:40:42 -0700222 const std::set<OutputEntry>& entries = outputSet->getEntries();
223 std::set<OutputEntry>::const_iterator iter = entries.begin();
224 for (; iter != entries.end(); iter++) {
225 const OutputEntry& entry = *iter;
226 if (entry.getFile() == NULL) {
227 fprintf(stderr, "warning: null file being processed.\n");
228 } else {
229 String8 storagePath(entry.getPath());
Elliott Hughes338698e2021-07-13 17:15:19 -0700230 convertToResPath(storagePath);
Adam Lesinskifab50872014-04-16 14:40:42 -0700231 if (!processFile(bundle, zip, storagePath, entry.getFile())) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800232 return UNKNOWN_ERROR;
233 }
234 count++;
235 }
236 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800237 return count;
238}
239
240/*
241 * Process a regular file, adding it to the archive if appropriate.
242 *
243 * If we're in "update" mode, and the file already exists in the archive,
244 * delete the existing entry before adding the new one.
245 */
246bool processFile(Bundle* bundle, ZipFile* zip,
Adam Lesinskifab50872014-04-16 14:40:42 -0700247 String8 storageName, const sp<const AaptFile>& file)
Adam Lesinski282e1812014-01-23 18:17:42 -0800248{
249 const bool hasData = file->hasData();
250
Adam Lesinski282e1812014-01-23 18:17:42 -0800251 ZipEntry* entry;
252 bool fromGzip = false;
253 status_t result;
254
255 /*
256 * See if the filename ends in ".EXCLUDE". We can't use
257 * String8::getPathExtension() because the length of what it considers
258 * to be an extension is capped.
259 *
260 * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives,
261 * so there's no value in adding them (and it makes life easier on
262 * the AssetManager lib if we don't).
263 *
264 * NOTE: this restriction has been removed. If you're in this code, you
265 * should clean this up, but I'm in here getting rid of Path Name, and I
266 * don't want to make other potentially breaking changes --joeo
267 */
268 int fileNameLen = storageName.length();
269 int excludeExtensionLen = strlen(kExcludeExtension);
270 if (fileNameLen > excludeExtensionLen
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000271 && (0 == strcmp(storageName.c_str() + (fileNameLen - excludeExtensionLen),
Adam Lesinski282e1812014-01-23 18:17:42 -0800272 kExcludeExtension))) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000273 fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800274 return true;
275 }
276
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000277 if (strcasecmp(storageName.getPathExtension().c_str(), ".gz") == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800278 fromGzip = true;
279 storageName = storageName.getBasePath();
280 }
281
282 if (bundle->getUpdate()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000283 entry = zip->getEntryByName(storageName.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800284 if (entry != NULL) {
285 /* file already exists in archive; there can be only one */
286 if (entry->getMarked()) {
287 fprintf(stderr,
288 "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000289 file->getPrintableSource().c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800290 return false;
291 }
292 if (!hasData) {
293 const String8& srcName = file->getSourceFile();
294 time_t fileModWhen;
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000295 fileModWhen = getFileModDate(srcName.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800296 if (fileModWhen == (time_t) -1) { // file existence tested earlier,
297 return false; // not expecting an error here
298 }
299
300 if (fileModWhen > entry->getModWhen()) {
301 // mark as deleted so add() will succeed
302 if (bundle->getVerbose()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000303 printf(" (removing old '%s')\n", storageName.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800304 }
305
306 zip->remove(entry);
307 } else {
308 // version in archive is newer
309 if (bundle->getVerbose()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000310 printf(" (not updating '%s')\n", storageName.c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800311 }
312 entry->setMarked(true);
313 return true;
314 }
315 } else {
316 // Generated files are always replaced.
317 zip->remove(entry);
318 }
319 }
320 }
321
322 //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE);
323
324 if (fromGzip) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000325 result = zip->addGzip(file->getSourceFile().c_str(), storageName.c_str(), &entry);
Adam Lesinski282e1812014-01-23 18:17:42 -0800326 } else if (!hasData) {
327 /* don't compress certain files, e.g. PNGs */
328 int compressionMethod = bundle->getCompressionMethod();
329 if (!okayToCompress(bundle, storageName)) {
330 compressionMethod = ZipEntry::kCompressStored;
331 }
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000332 result = zip->add(file->getSourceFile().c_str(), storageName.c_str(), compressionMethod,
Adam Lesinski282e1812014-01-23 18:17:42 -0800333 &entry);
334 } else {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000335 result = zip->add(file->getData(), file->getSize(), storageName.c_str(),
Adam Lesinski282e1812014-01-23 18:17:42 -0800336 file->getCompressionMethod(), &entry);
337 }
338 if (result == NO_ERROR) {
339 if (bundle->getVerbose()) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000340 printf(" '%s'%s", storageName.c_str(), fromGzip ? " (from .gz)" : "");
Adam Lesinski282e1812014-01-23 18:17:42 -0800341 if (entry->getCompressionMethod() == ZipEntry::kCompressStored) {
342 printf(" (not compressed)\n");
343 } else {
344 printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(),
345 entry->getCompressedLen()));
346 }
347 }
348 entry->setMarked(true);
349 } else {
350 if (result == ALREADY_EXISTS) {
351 fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000352 file->getPrintableSource().c_str());
Adam Lesinski282e1812014-01-23 18:17:42 -0800353 } else {
Adam Lesinskifab50872014-04-16 14:40:42 -0700354 fprintf(stderr, " Unable to add '%s': Zip add failed (%d)\n",
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000355 file->getPrintableSource().c_str(), result);
Adam Lesinski282e1812014-01-23 18:17:42 -0800356 }
357 return false;
358 }
359
360 return true;
361}
362
363/*
364 * Determine whether or not we want to try to compress this file based
365 * on the file extension.
366 */
367bool okayToCompress(Bundle* bundle, const String8& pathName)
368{
369 String8 ext = pathName.getPathExtension();
370 int i;
371
372 if (ext.length() == 0)
373 return true;
374
375 for (i = 0; i < NELEM(kNoCompressExt); i++) {
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000376 if (strcasecmp(ext.c_str(), kNoCompressExt[i]) == 0)
Adam Lesinski282e1812014-01-23 18:17:42 -0800377 return false;
378 }
379
380 const android::Vector<const char*>& others(bundle->getNoCompressExtensions());
381 for (i = 0; i < (int)others.size(); i++) {
382 const char* str = others[i];
383 int pos = pathName.length() - strlen(str);
384 if (pos < 0) {
385 continue;
386 }
Tomasz Wasilczykd2a69832023-08-10 23:54:44 +0000387 const char* path = pathName.c_str();
Adam Lesinski282e1812014-01-23 18:17:42 -0800388 if (strcasecmp(path + pos, str) == 0) {
389 return false;
390 }
391 }
392
393 return true;
394}
395
396bool endsWith(const char* haystack, const char* needle)
397{
398 size_t a = strlen(haystack);
399 size_t b = strlen(needle);
400 if (a < b) return false;
401 return strcasecmp(haystack+(a-b), needle) == 0;
402}
403
404ssize_t processJarFile(ZipFile* jar, ZipFile* out)
405{
Adam Lesinski282e1812014-01-23 18:17:42 -0800406 size_t N = jar->getNumEntries();
407 size_t count = 0;
408 for (size_t i=0; i<N; i++) {
409 ZipEntry* entry = jar->getEntryByIndex(i);
410 const char* storageName = entry->getFileName();
411 if (endsWith(storageName, ".class")) {
412 int compressionMethod = entry->getCompressionMethod();
413 size_t size = entry->getUncompressedLen();
414 const void* data = jar->uncompress(entry);
415 if (data == NULL) {
416 fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n",
417 storageName);
418 return -1;
419 }
420 out->add(data, size, storageName, compressionMethod, NULL);
421 free((void*)data);
422 }
423 count++;
424 }
425 return count;
426}
427
428ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
429{
430 status_t err;
431 ssize_t count = 0;
432 const android::Vector<const char*>& jars = bundle->getJarFiles();
433
434 size_t N = jars.size();
435 for (size_t i=0; i<N; i++) {
436 ZipFile jar;
437 err = jar.open(jars[i], ZipFile::kOpenReadOnly);
438 if (err != 0) {
439 fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %d\n",
440 jars[i], err);
441 return err;
442 }
443 err += processJarFile(&jar, zip);
444 if (err < 0) {
445 fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]);
446 return err;
447 }
448 count += err;
449 }
450
451 return count;
452}