blob: 889ac70ae4585596c8edbeed473f3688c140393d [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
2 * Copyright (C) 2015 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 "ResourceTable.h"
Adam Lesinskia6fe3452015-12-09 15:20:52 -080018#include "ResourceUtils.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070019#include "ResourceValues.h"
20#include "ValueVisitor.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "link/TableMerger.h"
22#include "util/Util.h"
23
24#include <cassert>
25
26namespace aapt {
27
Adam Lesinskia6fe3452015-12-09 15:20:52 -080028TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable,
29 const TableMergerOptions& options) :
30 mContext(context), mMasterTable(outTable), mOptions(options) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070031 // Create the desired package that all tables will be merged into.
32 mMasterPackage = mMasterTable->createPackage(
33 mContext->getCompilationPackage(), mContext->getPackageId());
34 assert(mMasterPackage && "package name or ID already taken");
35}
36
Adam Lesinski64587af2016-02-18 18:33:06 -080037bool TableMerger::merge(const Source& src, ResourceTable* table,
38 io::IFileCollection* collection) {
39 return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */);
40}
41
42bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table,
43 io::IFileCollection* collection) {
44 return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay);
45}
46
Adam Lesinski83f22552015-11-07 11:51:23 -080047/**
Adam Lesinski5c3464c2016-08-24 16:03:48 -070048 * Ignore packages with an ID that is not our desired package ID or 0x0, or if the name
49 * is not equal to the package we are compiling.
50 */
51static bool shouldIgnorePackage(IAaptContext* context, ResourceTablePackage* package) {
52 const Maybe<ResourceId>& id = package->id;
53 const std::string& packageName = package->name;
54 return (id && id.value() != 0x0 && id.value() != context->getPackageId())
55 || (!packageName.empty() && packageName != context->getCompilationPackage());
56}
57
58/**
Adam Lesinski83f22552015-11-07 11:51:23 -080059 * This will merge packages with the same package name (or no package name).
60 */
Adam Lesinskia6fe3452015-12-09 15:20:52 -080061bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
Adam Lesinski64587af2016-02-18 18:33:06 -080062 io::IFileCollection* collection,
Adam Lesinskia6fe3452015-12-09 15:20:52 -080063 bool overlay, bool allowNew) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070064 bool error = false;
65 for (auto& package : table->packages) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -070066 // Warn of packages with an unrelated ID or name.
67 if (shouldIgnorePackage(mContext, package.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070068 mContext->getDiagnostics()->warn(DiagMessage(src)
69 << "ignoring package " << package->name);
70 continue;
71 }
72
Adam Lesinski5c3464c2016-08-24 16:03:48 -070073 FileMergeCallback callback;
74 if (collection) {
75 callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
76 FileReference* newFile, FileReference* oldFile) -> bool {
77 // The old file's path points inside the APK, so we can use it as is.
78 io::IFile* f = collection->findFile(*oldFile->path);
79 if (!f) {
80 mContext->getDiagnostics()->error(DiagMessage(src) << "file '"
81 << *oldFile->path
82 << "' not found");
83 return false;
84 }
Adam Lesinski64587af2016-02-18 18:33:06 -080085
Adam Lesinski5c3464c2016-08-24 16:03:48 -070086 newFile->file = f;
87 return true;
88 };
Adam Lesinski83f22552015-11-07 11:51:23 -080089 }
Adam Lesinski5c3464c2016-08-24 16:03:48 -070090
91 // Merge here. Once the entries are merged and mangled, any references to
92 // them are still valid. This is because un-mangled references are
93 // mangled, then looked up at resolution time.
94 // Also, when linking, we convert references with no package name to use
95 // the compilation package name.
96 error |= !doMerge(src, table, package.get(), false /* mangle */, overlay, allowNew,
97 callback);
Adam Lesinski83f22552015-11-07 11:51:23 -080098 }
99 return !error;
100}
101
102/**
103 * This will merge and mangle resources from a static library.
104 */
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700105bool TableMerger::mergeAndMangle(const Source& src, const StringPiece& packageName,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800106 ResourceTable* table, io::IFileCollection* collection) {
Adam Lesinski83f22552015-11-07 11:51:23 -0800107 bool error = false;
108 for (auto& package : table->packages) {
109 // Warn of packages with an unrelated ID.
110 if (packageName != package->name) {
111 mContext->getDiagnostics()->warn(DiagMessage(src)
112 << "ignoring package " << package->name);
113 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700114 }
115
Adam Lesinski83f22552015-11-07 11:51:23 -0800116 bool mangle = packageName != mContext->getCompilationPackage();
117 mMergedPackages.insert(package->name);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800118
119 auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
120 FileReference* newFile, FileReference* oldFile) -> bool {
121 // The old file's path points inside the APK, so we can use it as is.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700122 io::IFile* f = collection->findFile(*oldFile->path);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800123 if (!f) {
124 mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path
125 << "' not found");
126 return false;
127 }
128
Adam Lesinski355f2852016-02-13 20:26:45 -0800129 newFile->file = f;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800130 return true;
131 };
132
133 error |= !doMerge(src, table, package.get(),
134 mangle, false /* overlay */, true /* allow new */, callback);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700135 }
136 return !error;
137}
138
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700139static bool mergeType(IAaptContext* context, const Source& src, ResourceTableType* dstType,
140 ResourceTableType* srcType) {
141 if (dstType->symbolStatus.state < srcType->symbolStatus.state) {
142 // The incoming type's visibility is stronger, so we should override
143 // the visibility.
144 if (srcType->symbolStatus.state == SymbolState::kPublic) {
145 // Only copy the ID if the source is public, or else the ID is meaningless.
146 dstType->id = srcType->id;
147 }
148 dstType->symbolStatus = std::move(srcType->symbolStatus);
149 } else if (dstType->symbolStatus.state == SymbolState::kPublic
150 && srcType->symbolStatus.state == SymbolState::kPublic
151 && dstType->id && srcType->id
152 && dstType->id.value() != srcType->id.value()) {
153 // Both types are public and have different IDs.
154 context->getDiagnostics()->error(DiagMessage(src)
155 << "cannot merge type '" << srcType->type
156 << "': conflicting public IDs");
157 return false;
158 }
159 return true;
160}
161
162static bool mergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dstEntry,
163 ResourceEntry* srcEntry) {
164 if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) {
165 // The incoming type's visibility is stronger, so we should override
166 // the visibility.
167 if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
168 // Only copy the ID if the source is public, or else the ID is meaningless.
169 dstEntry->id = srcEntry->id;
170 }
171 dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
172 } else if (srcEntry->symbolStatus.state == SymbolState::kPublic
173 && dstEntry->symbolStatus.state == SymbolState::kPublic
174 && dstEntry->id && srcEntry->id
175 && dstEntry->id.value() != srcEntry->id.value()) {
176 // Both entries are public and have different IDs.
177 context->getDiagnostics()->error(DiagMessage(src)
178 << "cannot merge entry '" << srcEntry->name
179 << "': conflicting public IDs");
180 return false;
181 }
182 return true;
183}
184
185/**
186 * Modified CollisionResolver which will merge Styleables. Used with overlays.
187 *
188 * Styleables are not actual resources, but they are treated as such during the
189 * compilation phase. Styleables don't simply overlay each other, their definitions merge
190 * and accumulate. If both values are Styleables, we just merge them into the existing value.
191 */
192static ResourceTable::CollisionResult resolveMergeCollision(Value* existing, Value* incoming) {
193 if (Styleable* existingStyleable = valueCast<Styleable>(existing)) {
194 if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) {
195 // Styleables get merged.
196 existingStyleable->mergeWith(incomingStyleable);
197 return ResourceTable::CollisionResult::kKeepOriginal;
198 }
199 }
200 // Delegate to the default handler.
201 return ResourceTable::resolveValueCollision(existing, incoming);
202}
203
204static ResourceTable::CollisionResult mergeConfigValue(IAaptContext* context,
205 const ResourceNameRef& resName,
206 const bool overlay,
207 ResourceConfigValue* dstConfigValue,
208 ResourceConfigValue* srcConfigValue) {
209 using CollisionResult = ResourceTable::CollisionResult;
210
211 Value* dstValue = dstConfigValue->value.get();
212 Value* srcValue = srcConfigValue->value.get();
213
214 CollisionResult collisionResult;
215 if (overlay) {
216 collisionResult = resolveMergeCollision(dstValue, srcValue);
217 } else {
218 collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue);
219 }
220
221 if (collisionResult == CollisionResult::kConflict) {
222 if (overlay) {
223 return CollisionResult::kTakeNew;
224 }
225
226 // Error!
227 context->getDiagnostics()->error(DiagMessage(srcValue->getSource())
228 << "resource '" << resName
229 << "' has a conflicting value for "
230 << "configuration ("
231 << srcConfigValue->config << ")");
232 context->getDiagnostics()->note(DiagMessage(dstValue->getSource())
233 << "originally defined here");
234 return CollisionResult::kConflict;
235 }
236 return collisionResult;
237}
238
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800239bool TableMerger::doMerge(const Source& src,
240 ResourceTable* srcTable,
241 ResourceTablePackage* srcPackage,
242 const bool manglePackage,
243 const bool overlay,
244 const bool allowNewResources,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700245 const FileMergeCallback& callback) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700246 bool error = false;
247
248 for (auto& srcType : srcPackage->types) {
249 ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700250 if (!mergeType(mContext, src, dstType, srcType.get())) {
251 error = true;
252 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700253 }
254
255 for (auto& srcEntry : srcType->entries) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700256 std::string entryName = srcEntry->name;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700257 if (manglePackage) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700258 entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800259 }
260
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700261 ResourceEntry* dstEntry;
262 if (allowNewResources) {
263 dstEntry = dstType->findOrCreateEntry(entryName);
264 } else {
265 dstEntry = dstType->findEntry(entryName);
266 }
267
268 const ResourceNameRef resName(srcPackage->name, srcType->type, srcEntry->name);
269
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800270 if (!dstEntry) {
271 mContext->getDiagnostics()->error(DiagMessage(src)
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700272 << "resource " << resName
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800273 << " does not override an existing resource");
274 mContext->getDiagnostics()->note(DiagMessage(src)
275 << "define an <add-resource> tag or use "
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700276 << "--auto-add-overlay");
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800277 error = true;
278 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700279 }
280
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700281 if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) {
282 error = true;
283 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700284 }
285
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700286 for (auto& srcConfigValue : srcEntry->values) {
287 using CollisionResult = ResourceTable::CollisionResult;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800288
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700289 ResourceConfigValue* dstConfigValue = dstEntry->findValue(srcConfigValue->config,
290 srcConfigValue->product);
291 if (dstConfigValue) {
292 CollisionResult collisionResult = mergeConfigValue(
293 mContext, resName, overlay, dstConfigValue, srcConfigValue.get());
294 if (collisionResult == CollisionResult::kConflict) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700295 error = true;
296 continue;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700297 } else if (collisionResult == CollisionResult::kKeepOriginal) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700298 continue;
299 }
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700300 } else {
301 dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config,
302 srcConfigValue->product);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700303 }
304
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700305 // Continue if we're taking the new resource.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800306
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700307 if (FileReference* f = valueCast<FileReference>(srcConfigValue->value.get())) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800308 std::unique_ptr<FileReference> newFileRef;
309 if (manglePackage) {
310 newFileRef = cloneAndMangleFile(srcPackage->name, *f);
311 } else {
312 newFileRef = std::unique_ptr<FileReference>(f->clone(
313 &mMasterTable->stringPool));
314 }
315
316 if (callback) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700317 if (!callback(resName, srcConfigValue->config, newFileRef.get(), f)) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800318 error = true;
319 continue;
320 }
321 }
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700322 dstConfigValue->value = std::move(newFileRef);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800323
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700324 } else {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700325 dstConfigValue->value = std::unique_ptr<Value>(srcConfigValue->value->clone(
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800326 &mMasterTable->stringPool));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700327 }
328 }
329 }
330 }
331 return !error;
332}
333
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700334std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::string& package,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800335 const FileReference& fileRef) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700336 StringPiece prefix, entry, suffix;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800337 if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700338 std::string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
339 std::string newPath = prefix.toString() + mangledEntry + suffix.toString();
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800340 std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>(
341 mMasterTable->stringPool.makeRef(newPath));
342 newFileRef->setComment(fileRef.getComment());
343 newFileRef->setSource(fileRef.getSource());
344 return newFileRef;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700345 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800346 return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700347}
348
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800349bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) {
350 ResourceTable table;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700351 std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800352 std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
353 table.stringPool.makeRef(path));
354 fileRef->setSource(fileDesc.source);
Adam Lesinski355f2852016-02-13 20:26:45 -0800355 fileRef->file = file;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800356
357 ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
358 pkg->findOrCreateType(fileDesc.name.type)
359 ->findOrCreateEntry(fileDesc.name.entry)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800360 ->findOrCreateValue(fileDesc.config, {})
361 ->value = std::move(fileRef);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800362
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800363 return doMerge(file->getSource(), &table, pkg,
Adam Lesinski355f2852016-02-13 20:26:45 -0800364 false /* mangle */, overlay /* overlay */, true /* allow new */, {});
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800365}
366
367bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
368 return mergeFileImpl(fileDesc, file, false /* overlay */);
369}
370
371bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
372 return mergeFileImpl(fileDesc, file, true /* overlay */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700373}
374
375} // namespace aapt