blob: 20b7b59642caa47a072a189bd30a04d0066b303f [file] [log] [blame]
Adam Lesinski458b8772016-04-25 14:20:21 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "Flags.h"
18#include "ResourceTable.h"
19#include "io/ZipArchive.h"
20#include "process/IResourceTableConsumer.h"
21#include "process/SymbolTable.h"
22#include "unflatten/BinaryResourceParser.h"
23
24#include <android-base/macros.h>
25
26namespace aapt {
27
28class DiffContext : public IAaptContext {
29public:
30 const std::u16string& getCompilationPackage() override {
31 return mEmpty;
32 }
33
34 uint8_t getPackageId() override {
35 return 0x0;
36 }
37
38 IDiagnostics* getDiagnostics() override {
39 return &mDiagnostics;
40 }
41
42 NameMangler* getNameMangler() override {
43 return &mNameMangler;
44 }
45
46 SymbolTable* getExternalSymbols() override {
47 return &mSymbolTable;
48 }
49
50 bool verbose() override {
51 return false;
52 }
53
54private:
55 std::u16string mEmpty;
56 StdErrDiagnostics mDiagnostics;
57 NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
58 SymbolTable mSymbolTable;
59};
60
61class LoadedApk {
62public:
63 LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
64 std::unique_ptr<ResourceTable> table) :
65 mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
66 }
67
68 io::IFileCollection* getFileCollection() {
69 return mApk.get();
70 }
71
72 ResourceTable* getResourceTable() {
73 return mTable.get();
74 }
75
76 const Source& getSource() {
77 return mSource;
78 }
79
80private:
81 Source mSource;
82 std::unique_ptr<io::IFileCollection> mApk;
83 std::unique_ptr<ResourceTable> mTable;
84
85 DISALLOW_COPY_AND_ASSIGN(LoadedApk);
86};
87
88static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
89 Source source(path);
90 std::string error;
91 std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
92 if (!apk) {
93 context->getDiagnostics()->error(DiagMessage(source) << error);
94 return {};
95 }
96
97 io::IFile* file = apk->findFile("resources.arsc");
98 if (!file) {
99 context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
100 return {};
101 }
102
103 std::unique_ptr<io::IData> data = file->openAsData();
104 if (!data) {
105 context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
106 return {};
107 }
108
109 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
110 BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
111 if (!parser.parse()) {
112 return {};
113 }
114
115 return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
116}
117
118static void emitDiffLine(const Source& source, const StringPiece& message) {
119 std::cerr << source << ": " << message << "\n";
120}
121
122static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
123 return symbolA.state != symbolB.state;
124}
125
126template <typename Id>
127static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
128 const Symbol& symbolB, const Maybe<Id>& idB) {
129 if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
130 return idA != idB;
131 }
132 return false;
133}
134
135static bool emitResourceConfigValueDiff(IAaptContext* context,
136 LoadedApk* apkA,
137 ResourceTablePackage* pkgA,
138 ResourceTableType* typeA,
139 ResourceEntry* entryA,
140 ResourceConfigValue* configValueA,
141 LoadedApk* apkB,
142 ResourceTablePackage* pkgB,
143 ResourceTableType* typeB,
144 ResourceEntry* entryB,
145 ResourceConfigValue* configValueB) {
146 Value* valueA = configValueA->value.get();
147 Value* valueB = configValueB->value.get();
148 if (!valueA->equals(valueB)) {
149 std::stringstream strStream;
150 strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
151 << " config=" << configValueA->config << " does not match:\n";
152 valueA->print(&strStream);
153 strStream << "\n vs \n";
154 valueB->print(&strStream);
155 emitDiffLine(apkB->getSource(), strStream.str());
156 return true;
157 }
158 return false;
159}
160
161static bool emitResourceEntryDiff(IAaptContext* context,
162 LoadedApk* apkA,
163 ResourceTablePackage* pkgA,
164 ResourceTableType* typeA,
165 ResourceEntry* entryA,
166 LoadedApk* apkB,
167 ResourceTablePackage* pkgB,
168 ResourceTableType* typeB,
169 ResourceEntry* entryB) {
170 bool diff = false;
171 for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
172 ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
173 if (!configValueB) {
174 std::stringstream strStream;
175 strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
176 << " config=" << configValueA->config;
177 emitDiffLine(apkB->getSource(), strStream.str());
178 diff = true;
179 } else {
180 diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
181 configValueA.get(), apkB, pkgB, typeB, entryB,
182 configValueB);
183 }
184 }
185
186 // Check for any newly added config values.
187 for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
188 ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
189 if (!configValueA) {
190 std::stringstream strStream;
191 strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
192 << " config=" << configValueB->config;
193 emitDiffLine(apkB->getSource(), strStream.str());
194 diff = true;
195 }
196 }
197 return false;
198}
199
200static bool emitResourceTypeDiff(IAaptContext* context,
201 LoadedApk* apkA,
202 ResourceTablePackage* pkgA,
203 ResourceTableType* typeA,
204 LoadedApk* apkB,
205 ResourceTablePackage* pkgB,
206 ResourceTableType* typeB) {
207 bool diff = false;
208 for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
209 ResourceEntry* entryB = typeB->findEntry(entryA->name);
210 if (!entryB) {
211 std::stringstream strStream;
212 strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
213 emitDiffLine(apkB->getSource(), strStream.str());
214 diff = true;
215 } else {
216 if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
217 std::stringstream strStream;
218 strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
219 << " has different visibility (";
220 if (entryB->symbolStatus.state == SymbolState::kPublic) {
221 strStream << "PUBLIC";
222 } else {
223 strStream << "PRIVATE";
224 }
225 strStream << " vs ";
226 if (entryA->symbolStatus.state == SymbolState::kPublic) {
227 strStream << "PUBLIC";
228 } else {
229 strStream << "PRIVATE";
230 }
231 strStream << ")";
232 emitDiffLine(apkB->getSource(), strStream.str());
233 diff = true;
234 } else if (isIdDiff(entryA->symbolStatus, entryA->id,
235 entryB->symbolStatus, entryB->id)) {
236 std::stringstream strStream;
237 strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
238 << " has different public ID (";
239 if (entryB->id) {
240 strStream << "0x" << std::hex << entryB->id.value();
241 } else {
242 strStream << "none";
243 }
244 strStream << " vs ";
245 if (entryA->id) {
246 strStream << "0x " << std::hex << entryA->id.value();
247 } else {
248 strStream << "none";
249 }
250 strStream << ")";
251 emitDiffLine(apkB->getSource(), strStream.str());
252 diff = true;
253 }
254 diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
255 apkB, pkgB, typeB, entryB);
256 }
257 }
258
259 // Check for any newly added entries.
260 for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
261 ResourceEntry* entryA = typeA->findEntry(entryB->name);
262 if (!entryA) {
263 std::stringstream strStream;
264 strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
265 emitDiffLine(apkB->getSource(), strStream.str());
266 diff = true;
267 }
268 }
269 return diff;
270}
271
272static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
273 ResourceTablePackage* pkgA,
274 LoadedApk* apkB, ResourceTablePackage* pkgB) {
275 bool diff = false;
276 for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
277 ResourceTableType* typeB = pkgB->findType(typeA->type);
278 if (!typeB) {
279 std::stringstream strStream;
280 strStream << "missing " << pkgA->name << ":" << typeA->type;
281 emitDiffLine(apkA->getSource(), strStream.str());
282 diff = true;
283 } else {
284 if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
285 std::stringstream strStream;
286 strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
287 if (typeB->symbolStatus.state == SymbolState::kPublic) {
288 strStream << "PUBLIC";
289 } else {
290 strStream << "PRIVATE";
291 }
292 strStream << " vs ";
293 if (typeA->symbolStatus.state == SymbolState::kPublic) {
294 strStream << "PUBLIC";
295 } else {
296 strStream << "PRIVATE";
297 }
298 strStream << ")";
299 emitDiffLine(apkB->getSource(), strStream.str());
300 diff = true;
301 } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
302 std::stringstream strStream;
303 strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
304 if (typeB->id) {
305 strStream << "0x" << std::hex << typeB->id.value();
306 } else {
307 strStream << "none";
308 }
309 strStream << " vs ";
310 if (typeA->id) {
311 strStream << "0x " << std::hex << typeA->id.value();
312 } else {
313 strStream << "none";
314 }
315 strStream << ")";
316 emitDiffLine(apkB->getSource(), strStream.str());
317 diff = true;
318 }
319 diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
320 }
321 }
322
323 // Check for any newly added types.
324 for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
325 ResourceTableType* typeA = pkgA->findType(typeB->type);
326 if (!typeA) {
327 std::stringstream strStream;
328 strStream << "new type " << pkgB->name << ":" << typeB->type;
329 emitDiffLine(apkB->getSource(), strStream.str());
330 diff = true;
331 }
332 }
333 return diff;
334}
335
336static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
337 ResourceTable* tableA = apkA->getResourceTable();
338 ResourceTable* tableB = apkB->getResourceTable();
339
340 bool diff = false;
341 for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
342 ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
343 if (!pkgB) {
344 std::stringstream strStream;
345 strStream << "missing package " << pkgA->name;
346 emitDiffLine(apkB->getSource(), strStream.str());
347 diff = true;
348 } else {
349 if (pkgA->id != pkgB->id) {
350 std::stringstream strStream;
351 strStream << "package '" << pkgA->name << "' has different id (";
352 if (pkgB->id) {
353 strStream << "0x" << std::hex << pkgB->id.value();
354 } else {
355 strStream << "none";
356 }
357 strStream << " vs ";
358 if (pkgA->id) {
359 strStream << "0x" << std::hex << pkgA->id.value();
360 } else {
361 strStream << "none";
362 }
363 strStream << ")";
364 emitDiffLine(apkB->getSource(), strStream.str());
365 diff = true;
366 }
367 diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
368 }
369 }
370
371 // Check for any newly added packages.
372 for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
373 ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
374 if (!pkgA) {
375 std::stringstream strStream;
376 strStream << "new package " << pkgB->name;
377 emitDiffLine(apkB->getSource(), strStream.str());
378 diff = true;
379 }
380 }
381 return diff;
382}
383
384int diff(const std::vector<StringPiece>& args) {
385 DiffContext context;
386
387 Flags flags;
388 if (!flags.parse("aapt2 diff", args, &std::cerr)) {
389 return 1;
390 }
391
392 if (flags.getArgs().size() != 2u) {
393 std::cerr << "must have two apks as arguments.\n\n";
394 flags.usage("aapt2 diff", &std::cerr);
395 return 1;
396 }
397
398 std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
399 std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
400 if (!apkA || !apkB) {
401 return 1;
402 }
403
404 if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
405 // We emitted a diff, so return 1 (failure).
406 return 1;
407 }
408 return 0;
409}
410
411} // namespace aapt