blob: eea430692936199bf6f652ea3c27ee18c11ca58e [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/**
48 * This will merge packages with the same package name (or no package name).
49 */
Adam Lesinskia6fe3452015-12-09 15:20:52 -080050bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
Adam Lesinski64587af2016-02-18 18:33:06 -080051 io::IFileCollection* collection,
Adam Lesinskia6fe3452015-12-09 15:20:52 -080052 bool overlay, bool allowNew) {
Adam Lesinski085f4952016-08-30 14:25:51 -070053 const uint8_t desiredPackageId = mContext->getPackageId();
54
Adam Lesinski1ab598f2015-08-14 14:26:04 -070055 bool error = false;
56 for (auto& package : table->packages) {
Adam Lesinski085f4952016-08-30 14:25:51 -070057 // Warn of packages with an unrelated ID.
58 const Maybe<ResourceId>& id = package->id;
59 if (id && id.value() != 0x0 && id.value() != desiredPackageId) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070060 mContext->getDiagnostics()->warn(DiagMessage(src)
61 << "ignoring package " << package->name);
62 continue;
63 }
64
Adam Lesinski085f4952016-08-30 14:25:51 -070065 // Only merge an empty package or the package we're building.
66 // Other packages may exist, which likely contain attribute definitions.
67 // This is because at compile time it is unknown if the attributes are simply
68 // uses of the attribute or definitions.
69 if (package->name.empty() || mContext->getCompilationPackage() == package->name) {
70 FileMergeCallback callback;
71 if (collection) {
72 callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
73 FileReference* newFile, FileReference* oldFile) -> bool {
74 // The old file's path points inside the APK, so we can use it as is.
75 io::IFile* f = collection->findFile(*oldFile->path);
76 if (!f) {
77 mContext->getDiagnostics()->error(DiagMessage(src) << "file '"
78 << *oldFile->path
79 << "' not found");
80 return false;
81 }
Adam Lesinski64587af2016-02-18 18:33:06 -080082
Adam Lesinski085f4952016-08-30 14:25:51 -070083 newFile->file = f;
84 return true;
85 };
86 }
87
88 // Merge here. Once the entries are merged and mangled, any references to
89 // them are still valid. This is because un-mangled references are
90 // mangled, then looked up at resolution time.
91 // Also, when linking, we convert references with no package name to use
92 // the compilation package name.
93 error |= !doMerge(src, table, package.get(), false /* mangle */, overlay, allowNew,
94 callback);
Adam Lesinski83f22552015-11-07 11:51:23 -080095 }
96 }
97 return !error;
98}
99
100/**
101 * This will merge and mangle resources from a static library.
102 */
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700103bool TableMerger::mergeAndMangle(const Source& src, const StringPiece& packageName,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800104 ResourceTable* table, io::IFileCollection* collection) {
Adam Lesinski83f22552015-11-07 11:51:23 -0800105 bool error = false;
106 for (auto& package : table->packages) {
107 // Warn of packages with an unrelated ID.
108 if (packageName != package->name) {
109 mContext->getDiagnostics()->warn(DiagMessage(src)
110 << "ignoring package " << package->name);
111 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700112 }
113
Adam Lesinski83f22552015-11-07 11:51:23 -0800114 bool mangle = packageName != mContext->getCompilationPackage();
115 mMergedPackages.insert(package->name);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800116
117 auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
118 FileReference* newFile, FileReference* oldFile) -> bool {
119 // The old file's path points inside the APK, so we can use it as is.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700120 io::IFile* f = collection->findFile(*oldFile->path);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800121 if (!f) {
122 mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path
123 << "' not found");
124 return false;
125 }
126
Adam Lesinski355f2852016-02-13 20:26:45 -0800127 newFile->file = f;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800128 return true;
129 };
130
131 error |= !doMerge(src, table, package.get(),
132 mangle, false /* overlay */, true /* allow new */, callback);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700133 }
134 return !error;
135}
136
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700137static bool mergeType(IAaptContext* context, const Source& src, ResourceTableType* dstType,
138 ResourceTableType* srcType) {
139 if (dstType->symbolStatus.state < srcType->symbolStatus.state) {
140 // The incoming type's visibility is stronger, so we should override
141 // the visibility.
142 if (srcType->symbolStatus.state == SymbolState::kPublic) {
143 // Only copy the ID if the source is public, or else the ID is meaningless.
144 dstType->id = srcType->id;
145 }
146 dstType->symbolStatus = std::move(srcType->symbolStatus);
147 } else if (dstType->symbolStatus.state == SymbolState::kPublic
148 && srcType->symbolStatus.state == SymbolState::kPublic
149 && dstType->id && srcType->id
150 && dstType->id.value() != srcType->id.value()) {
151 // Both types are public and have different IDs.
152 context->getDiagnostics()->error(DiagMessage(src)
153 << "cannot merge type '" << srcType->type
154 << "': conflicting public IDs");
155 return false;
156 }
157 return true;
158}
159
160static bool mergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dstEntry,
161 ResourceEntry* srcEntry) {
162 if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) {
163 // The incoming type's visibility is stronger, so we should override
164 // the visibility.
165 if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
166 // Only copy the ID if the source is public, or else the ID is meaningless.
167 dstEntry->id = srcEntry->id;
168 }
169 dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
170 } else if (srcEntry->symbolStatus.state == SymbolState::kPublic
171 && dstEntry->symbolStatus.state == SymbolState::kPublic
172 && dstEntry->id && srcEntry->id
173 && dstEntry->id.value() != srcEntry->id.value()) {
174 // Both entries are public and have different IDs.
175 context->getDiagnostics()->error(DiagMessage(src)
176 << "cannot merge entry '" << srcEntry->name
177 << "': conflicting public IDs");
178 return false;
179 }
180 return true;
181}
182
183/**
184 * Modified CollisionResolver which will merge Styleables. Used with overlays.
185 *
186 * Styleables are not actual resources, but they are treated as such during the
187 * compilation phase. Styleables don't simply overlay each other, their definitions merge
188 * and accumulate. If both values are Styleables, we just merge them into the existing value.
189 */
190static ResourceTable::CollisionResult resolveMergeCollision(Value* existing, Value* incoming) {
191 if (Styleable* existingStyleable = valueCast<Styleable>(existing)) {
192 if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) {
193 // Styleables get merged.
194 existingStyleable->mergeWith(incomingStyleable);
195 return ResourceTable::CollisionResult::kKeepOriginal;
196 }
197 }
198 // Delegate to the default handler.
199 return ResourceTable::resolveValueCollision(existing, incoming);
200}
201
202static ResourceTable::CollisionResult mergeConfigValue(IAaptContext* context,
203 const ResourceNameRef& resName,
204 const bool overlay,
205 ResourceConfigValue* dstConfigValue,
206 ResourceConfigValue* srcConfigValue) {
207 using CollisionResult = ResourceTable::CollisionResult;
208
209 Value* dstValue = dstConfigValue->value.get();
210 Value* srcValue = srcConfigValue->value.get();
211
212 CollisionResult collisionResult;
213 if (overlay) {
214 collisionResult = resolveMergeCollision(dstValue, srcValue);
215 } else {
216 collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue);
217 }
218
219 if (collisionResult == CollisionResult::kConflict) {
220 if (overlay) {
221 return CollisionResult::kTakeNew;
222 }
223
224 // Error!
225 context->getDiagnostics()->error(DiagMessage(srcValue->getSource())
226 << "resource '" << resName
227 << "' has a conflicting value for "
228 << "configuration ("
229 << srcConfigValue->config << ")");
230 context->getDiagnostics()->note(DiagMessage(dstValue->getSource())
231 << "originally defined here");
232 return CollisionResult::kConflict;
233 }
234 return collisionResult;
235}
236
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800237bool TableMerger::doMerge(const Source& src,
238 ResourceTable* srcTable,
239 ResourceTablePackage* srcPackage,
240 const bool manglePackage,
241 const bool overlay,
242 const bool allowNewResources,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700243 const FileMergeCallback& callback) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700244 bool error = false;
245
246 for (auto& srcType : srcPackage->types) {
247 ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700248 if (!mergeType(mContext, src, dstType, srcType.get())) {
249 error = true;
250 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700251 }
252
253 for (auto& srcEntry : srcType->entries) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700254 std::string entryName = srcEntry->name;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700255 if (manglePackage) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700256 entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800257 }
258
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700259 ResourceEntry* dstEntry;
260 if (allowNewResources) {
261 dstEntry = dstType->findOrCreateEntry(entryName);
262 } else {
263 dstEntry = dstType->findEntry(entryName);
264 }
265
266 const ResourceNameRef resName(srcPackage->name, srcType->type, srcEntry->name);
267
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800268 if (!dstEntry) {
269 mContext->getDiagnostics()->error(DiagMessage(src)
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700270 << "resource " << resName
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800271 << " does not override an existing resource");
272 mContext->getDiagnostics()->note(DiagMessage(src)
273 << "define an <add-resource> tag or use "
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700274 << "--auto-add-overlay");
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800275 error = true;
276 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700277 }
278
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700279 if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) {
280 error = true;
281 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700282 }
283
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700284 for (auto& srcConfigValue : srcEntry->values) {
285 using CollisionResult = ResourceTable::CollisionResult;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800286
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700287 ResourceConfigValue* dstConfigValue = dstEntry->findValue(srcConfigValue->config,
288 srcConfigValue->product);
289 if (dstConfigValue) {
290 CollisionResult collisionResult = mergeConfigValue(
291 mContext, resName, overlay, dstConfigValue, srcConfigValue.get());
292 if (collisionResult == CollisionResult::kConflict) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700293 error = true;
294 continue;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700295 } else if (collisionResult == CollisionResult::kKeepOriginal) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700296 continue;
297 }
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700298 } else {
299 dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config,
300 srcConfigValue->product);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700301 }
302
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700303 // Continue if we're taking the new resource.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800304
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700305 if (FileReference* f = valueCast<FileReference>(srcConfigValue->value.get())) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800306 std::unique_ptr<FileReference> newFileRef;
307 if (manglePackage) {
308 newFileRef = cloneAndMangleFile(srcPackage->name, *f);
309 } else {
310 newFileRef = std::unique_ptr<FileReference>(f->clone(
311 &mMasterTable->stringPool));
312 }
313
314 if (callback) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700315 if (!callback(resName, srcConfigValue->config, newFileRef.get(), f)) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800316 error = true;
317 continue;
318 }
319 }
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700320 dstConfigValue->value = std::move(newFileRef);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800321
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700322 } else {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700323 dstConfigValue->value = std::unique_ptr<Value>(srcConfigValue->value->clone(
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800324 &mMasterTable->stringPool));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700325 }
326 }
327 }
328 }
329 return !error;
330}
331
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700332std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::string& package,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800333 const FileReference& fileRef) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700334 StringPiece prefix, entry, suffix;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800335 if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700336 std::string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
337 std::string newPath = prefix.toString() + mangledEntry + suffix.toString();
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800338 std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>(
339 mMasterTable->stringPool.makeRef(newPath));
340 newFileRef->setComment(fileRef.getComment());
341 newFileRef->setSource(fileRef.getSource());
342 return newFileRef;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700343 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800344 return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700345}
346
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800347bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) {
348 ResourceTable table;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700349 std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800350 std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
351 table.stringPool.makeRef(path));
352 fileRef->setSource(fileDesc.source);
Adam Lesinski355f2852016-02-13 20:26:45 -0800353 fileRef->file = file;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800354
355 ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
356 pkg->findOrCreateType(fileDesc.name.type)
357 ->findOrCreateEntry(fileDesc.name.entry)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800358 ->findOrCreateValue(fileDesc.config, {})
359 ->value = std::move(fileRef);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800360
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800361 return doMerge(file->getSource(), &table, pkg,
Adam Lesinski355f2852016-02-13 20:26:45 -0800362 false /* mangle */, overlay /* overlay */, true /* allow new */, {});
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800363}
364
365bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
366 return mergeFileImpl(fileDesc, file, false /* overlay */);
367}
368
369bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
370 return mergeFileImpl(fileDesc, file, true /* overlay */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700371}
372
373} // namespace aapt