blob: 67333a228cdfdb38f70639383a66b6a72eb87e5d [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
Adam Lesinskifb6312f2016-06-28 14:40:32 -070054 int getMinSdkVersion() override {
55 return 0;
56 }
57
Adam Lesinski458b8772016-04-25 14:20:21 -070058private:
59 std::u16string mEmpty;
60 StdErrDiagnostics mDiagnostics;
61 NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
62 SymbolTable mSymbolTable;
63};
64
65class LoadedApk {
66public:
67 LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
68 std::unique_ptr<ResourceTable> table) :
69 mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
70 }
71
72 io::IFileCollection* getFileCollection() {
73 return mApk.get();
74 }
75
76 ResourceTable* getResourceTable() {
77 return mTable.get();
78 }
79
80 const Source& getSource() {
81 return mSource;
82 }
83
84private:
85 Source mSource;
86 std::unique_ptr<io::IFileCollection> mApk;
87 std::unique_ptr<ResourceTable> mTable;
88
89 DISALLOW_COPY_AND_ASSIGN(LoadedApk);
90};
91
92static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
93 Source source(path);
94 std::string error;
95 std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
96 if (!apk) {
97 context->getDiagnostics()->error(DiagMessage(source) << error);
98 return {};
99 }
100
101 io::IFile* file = apk->findFile("resources.arsc");
102 if (!file) {
103 context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
104 return {};
105 }
106
107 std::unique_ptr<io::IData> data = file->openAsData();
108 if (!data) {
109 context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
110 return {};
111 }
112
113 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
114 BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
115 if (!parser.parse()) {
116 return {};
117 }
118
119 return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
120}
121
122static void emitDiffLine(const Source& source, const StringPiece& message) {
123 std::cerr << source << ": " << message << "\n";
124}
125
126static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
127 return symbolA.state != symbolB.state;
128}
129
130template <typename Id>
131static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
132 const Symbol& symbolB, const Maybe<Id>& idB) {
133 if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
134 return idA != idB;
135 }
136 return false;
137}
138
139static bool emitResourceConfigValueDiff(IAaptContext* context,
140 LoadedApk* apkA,
141 ResourceTablePackage* pkgA,
142 ResourceTableType* typeA,
143 ResourceEntry* entryA,
144 ResourceConfigValue* configValueA,
145 LoadedApk* apkB,
146 ResourceTablePackage* pkgB,
147 ResourceTableType* typeB,
148 ResourceEntry* entryB,
149 ResourceConfigValue* configValueB) {
150 Value* valueA = configValueA->value.get();
151 Value* valueB = configValueB->value.get();
152 if (!valueA->equals(valueB)) {
153 std::stringstream strStream;
154 strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
155 << " config=" << configValueA->config << " does not match:\n";
156 valueA->print(&strStream);
157 strStream << "\n vs \n";
158 valueB->print(&strStream);
159 emitDiffLine(apkB->getSource(), strStream.str());
160 return true;
161 }
162 return false;
163}
164
165static bool emitResourceEntryDiff(IAaptContext* context,
166 LoadedApk* apkA,
167 ResourceTablePackage* pkgA,
168 ResourceTableType* typeA,
169 ResourceEntry* entryA,
170 LoadedApk* apkB,
171 ResourceTablePackage* pkgB,
172 ResourceTableType* typeB,
173 ResourceEntry* entryB) {
174 bool diff = false;
175 for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
176 ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
177 if (!configValueB) {
178 std::stringstream strStream;
179 strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
180 << " config=" << configValueA->config;
181 emitDiffLine(apkB->getSource(), strStream.str());
182 diff = true;
183 } else {
184 diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
185 configValueA.get(), apkB, pkgB, typeB, entryB,
186 configValueB);
187 }
188 }
189
190 // Check for any newly added config values.
191 for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
192 ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
193 if (!configValueA) {
194 std::stringstream strStream;
195 strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
196 << " config=" << configValueB->config;
197 emitDiffLine(apkB->getSource(), strStream.str());
198 diff = true;
199 }
200 }
201 return false;
202}
203
204static bool emitResourceTypeDiff(IAaptContext* context,
205 LoadedApk* apkA,
206 ResourceTablePackage* pkgA,
207 ResourceTableType* typeA,
208 LoadedApk* apkB,
209 ResourceTablePackage* pkgB,
210 ResourceTableType* typeB) {
211 bool diff = false;
212 for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
213 ResourceEntry* entryB = typeB->findEntry(entryA->name);
214 if (!entryB) {
215 std::stringstream strStream;
216 strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
217 emitDiffLine(apkB->getSource(), strStream.str());
218 diff = true;
219 } else {
220 if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
221 std::stringstream strStream;
222 strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
223 << " has different visibility (";
224 if (entryB->symbolStatus.state == SymbolState::kPublic) {
225 strStream << "PUBLIC";
226 } else {
227 strStream << "PRIVATE";
228 }
229 strStream << " vs ";
230 if (entryA->symbolStatus.state == SymbolState::kPublic) {
231 strStream << "PUBLIC";
232 } else {
233 strStream << "PRIVATE";
234 }
235 strStream << ")";
236 emitDiffLine(apkB->getSource(), strStream.str());
237 diff = true;
238 } else if (isIdDiff(entryA->symbolStatus, entryA->id,
239 entryB->symbolStatus, entryB->id)) {
240 std::stringstream strStream;
241 strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
242 << " has different public ID (";
243 if (entryB->id) {
244 strStream << "0x" << std::hex << entryB->id.value();
245 } else {
246 strStream << "none";
247 }
248 strStream << " vs ";
249 if (entryA->id) {
250 strStream << "0x " << std::hex << entryA->id.value();
251 } else {
252 strStream << "none";
253 }
254 strStream << ")";
255 emitDiffLine(apkB->getSource(), strStream.str());
256 diff = true;
257 }
258 diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
259 apkB, pkgB, typeB, entryB);
260 }
261 }
262
263 // Check for any newly added entries.
264 for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
265 ResourceEntry* entryA = typeA->findEntry(entryB->name);
266 if (!entryA) {
267 std::stringstream strStream;
268 strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
269 emitDiffLine(apkB->getSource(), strStream.str());
270 diff = true;
271 }
272 }
273 return diff;
274}
275
276static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
277 ResourceTablePackage* pkgA,
278 LoadedApk* apkB, ResourceTablePackage* pkgB) {
279 bool diff = false;
280 for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
281 ResourceTableType* typeB = pkgB->findType(typeA->type);
282 if (!typeB) {
283 std::stringstream strStream;
284 strStream << "missing " << pkgA->name << ":" << typeA->type;
285 emitDiffLine(apkA->getSource(), strStream.str());
286 diff = true;
287 } else {
288 if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
289 std::stringstream strStream;
290 strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
291 if (typeB->symbolStatus.state == SymbolState::kPublic) {
292 strStream << "PUBLIC";
293 } else {
294 strStream << "PRIVATE";
295 }
296 strStream << " vs ";
297 if (typeA->symbolStatus.state == SymbolState::kPublic) {
298 strStream << "PUBLIC";
299 } else {
300 strStream << "PRIVATE";
301 }
302 strStream << ")";
303 emitDiffLine(apkB->getSource(), strStream.str());
304 diff = true;
305 } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
306 std::stringstream strStream;
307 strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
308 if (typeB->id) {
309 strStream << "0x" << std::hex << typeB->id.value();
310 } else {
311 strStream << "none";
312 }
313 strStream << " vs ";
314 if (typeA->id) {
315 strStream << "0x " << std::hex << typeA->id.value();
316 } else {
317 strStream << "none";
318 }
319 strStream << ")";
320 emitDiffLine(apkB->getSource(), strStream.str());
321 diff = true;
322 }
323 diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
324 }
325 }
326
327 // Check for any newly added types.
328 for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
329 ResourceTableType* typeA = pkgA->findType(typeB->type);
330 if (!typeA) {
331 std::stringstream strStream;
332 strStream << "new type " << pkgB->name << ":" << typeB->type;
333 emitDiffLine(apkB->getSource(), strStream.str());
334 diff = true;
335 }
336 }
337 return diff;
338}
339
340static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
341 ResourceTable* tableA = apkA->getResourceTable();
342 ResourceTable* tableB = apkB->getResourceTable();
343
344 bool diff = false;
345 for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
346 ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
347 if (!pkgB) {
348 std::stringstream strStream;
349 strStream << "missing package " << pkgA->name;
350 emitDiffLine(apkB->getSource(), strStream.str());
351 diff = true;
352 } else {
353 if (pkgA->id != pkgB->id) {
354 std::stringstream strStream;
355 strStream << "package '" << pkgA->name << "' has different id (";
356 if (pkgB->id) {
357 strStream << "0x" << std::hex << pkgB->id.value();
358 } else {
359 strStream << "none";
360 }
361 strStream << " vs ";
362 if (pkgA->id) {
363 strStream << "0x" << std::hex << pkgA->id.value();
364 } else {
365 strStream << "none";
366 }
367 strStream << ")";
368 emitDiffLine(apkB->getSource(), strStream.str());
369 diff = true;
370 }
371 diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
372 }
373 }
374
375 // Check for any newly added packages.
376 for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
377 ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
378 if (!pkgA) {
379 std::stringstream strStream;
380 strStream << "new package " << pkgB->name;
381 emitDiffLine(apkB->getSource(), strStream.str());
382 diff = true;
383 }
384 }
385 return diff;
386}
387
388int diff(const std::vector<StringPiece>& args) {
389 DiffContext context;
390
391 Flags flags;
392 if (!flags.parse("aapt2 diff", args, &std::cerr)) {
393 return 1;
394 }
395
396 if (flags.getArgs().size() != 2u) {
397 std::cerr << "must have two apks as arguments.\n\n";
398 flags.usage("aapt2 diff", &std::cerr);
399 return 1;
400 }
401
402 std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
403 std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
404 if (!apkA || !apkB) {
405 return 1;
406 }
407
408 if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
409 // We emitted a diff, so return 1 (failure).
410 return 1;
411 }
412 return 0;
413}
414
415} // namespace aapt