blob: adf83a468cccc83b21e50c9184150f94785ada68 [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
Adam Lesinskicacb28f2016-10-19 12:18:14 -070017#include "link/TableMerger.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "ResourceTable.h"
Adam Lesinskia6fe3452015-12-09 15:20:52 -080019#include "ResourceUtils.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070020#include "ResourceValues.h"
21#include "ValueVisitor.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070022#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,
Adam Lesinskicacb28f2016-10-19 12:18:14 -070029 const TableMergerOptions& options)
30 : mContext(context), mMasterTable(outTable), mOptions(options) {
31 // 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");
Adam Lesinski1ab598f2015-08-14 14:26:04 -070035}
36
Adam Lesinski64587af2016-02-18 18:33:06 -080037bool TableMerger::merge(const Source& src, ResourceTable* table,
38 io::IFileCollection* collection) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070039 return mergeImpl(src, table, collection, false /* overlay */,
40 true /* allow new */);
Adam Lesinski64587af2016-02-18 18:33:06 -080041}
42
43bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table,
44 io::IFileCollection* collection) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070045 return mergeImpl(src, table, collection, true /* overlay */,
46 mOptions.autoAddOverlay);
Adam Lesinski64587af2016-02-18 18:33:06 -080047}
48
Adam Lesinski83f22552015-11-07 11:51:23 -080049/**
50 * This will merge packages with the same package name (or no package name).
51 */
Adam Lesinskia6fe3452015-12-09 15:20:52 -080052bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
Adam Lesinskicacb28f2016-10-19 12:18:14 -070053 io::IFileCollection* collection, bool overlay,
54 bool allowNew) {
55 const uint8_t desiredPackageId = mContext->getPackageId();
Adam Lesinski085f4952016-08-30 14:25:51 -070056
Adam Lesinskicacb28f2016-10-19 12:18:14 -070057 bool error = false;
58 for (auto& package : table->packages) {
59 // Warn of packages with an unrelated ID.
60 const Maybe<ResourceId>& id = package->id;
61 if (id && id.value() != 0x0 && id.value() != desiredPackageId) {
62 mContext->getDiagnostics()->warn(DiagMessage(src) << "ignoring package "
63 << package->name);
64 continue;
Adam Lesinski83f22552015-11-07 11:51:23 -080065 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070066
67 // Only merge an empty package or the package we're building.
68 // Other packages may exist, which likely contain attribute definitions.
69 // This is because at compile time it is unknown if the attributes are
70 // simply
71 // uses of the attribute or definitions.
72 if (package->name.empty() ||
73 mContext->getCompilationPackage() == package->name) {
74 FileMergeCallback callback;
75 if (collection) {
76 callback = [&](const ResourceNameRef& name,
77 const ConfigDescription& config, FileReference* newFile,
78 FileReference* oldFile) -> bool {
79 // The old file's path points inside the APK, so we can use it as is.
80 io::IFile* f = collection->findFile(*oldFile->path);
81 if (!f) {
82 mContext->getDiagnostics()->error(DiagMessage(src)
83 << "file '" << *oldFile->path
84 << "' not found");
85 return false;
86 }
87
88 newFile->file = f;
89 return true;
90 };
91 }
92
93 // Merge here. Once the entries are merged and mangled, any references to
94 // them are still valid. This is because un-mangled references are
95 // mangled, then looked up at resolution time.
96 // Also, when linking, we convert references with no package name to use
97 // the compilation package name.
98 error |= !doMerge(src, table, package.get(), false /* mangle */, overlay,
99 allowNew, callback);
100 }
101 }
102 return !error;
Adam Lesinski83f22552015-11-07 11:51:23 -0800103}
104
105/**
106 * This will merge and mangle resources from a static library.
107 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700108bool TableMerger::mergeAndMangle(const Source& src,
109 const StringPiece& packageName,
110 ResourceTable* table,
111 io::IFileCollection* collection) {
112 bool error = false;
113 for (auto& package : table->packages) {
114 // Warn of packages with an unrelated ID.
115 if (packageName != package->name) {
116 mContext->getDiagnostics()->warn(DiagMessage(src) << "ignoring package "
117 << package->name);
118 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700119 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700120
121 bool mangle = packageName != mContext->getCompilationPackage();
122 mMergedPackages.insert(package->name);
123
124 auto callback = [&](const ResourceNameRef& name,
125 const ConfigDescription& config, FileReference* newFile,
126 FileReference* oldFile) -> bool {
127 // The old file's path points inside the APK, so we can use it as is.
128 io::IFile* f = collection->findFile(*oldFile->path);
129 if (!f) {
130 mContext->getDiagnostics()->error(
131 DiagMessage(src) << "file '" << *oldFile->path << "' not found");
132 return false;
133 }
134
135 newFile->file = f;
136 return true;
137 };
138
139 error |= !doMerge(src, table, package.get(), mangle, false /* overlay */,
140 true /* allow new */, callback);
141 }
142 return !error;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700143}
144
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700145static bool mergeType(IAaptContext* context, const Source& src,
146 ResourceTableType* dstType, ResourceTableType* srcType) {
147 if (dstType->symbolStatus.state < srcType->symbolStatus.state) {
148 // The incoming type's visibility is stronger, so we should override
149 // the visibility.
150 if (srcType->symbolStatus.state == SymbolState::kPublic) {
151 // Only copy the ID if the source is public, or else the ID is
152 // meaningless.
153 dstType->id = srcType->id;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700154 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700155 dstType->symbolStatus = std::move(srcType->symbolStatus);
156 } else if (dstType->symbolStatus.state == SymbolState::kPublic &&
157 srcType->symbolStatus.state == SymbolState::kPublic &&
158 dstType->id && srcType->id &&
159 dstType->id.value() != srcType->id.value()) {
160 // Both types are public and have different IDs.
161 context->getDiagnostics()->error(DiagMessage(src)
162 << "cannot merge type '" << srcType->type
163 << "': conflicting public IDs");
164 return false;
165 }
166 return true;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700167}
168
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700169static bool mergeEntry(IAaptContext* context, const Source& src,
170 ResourceEntry* dstEntry, ResourceEntry* srcEntry) {
171 if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) {
172 // The incoming type's visibility is stronger, so we should override
173 // the visibility.
174 if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
175 // Only copy the ID if the source is public, or else the ID is
176 // meaningless.
177 dstEntry->id = srcEntry->id;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700178 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700179 dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
180 } else if (srcEntry->symbolStatus.state == SymbolState::kPublic &&
181 dstEntry->symbolStatus.state == SymbolState::kPublic &&
182 dstEntry->id && srcEntry->id &&
183 dstEntry->id.value() != srcEntry->id.value()) {
184 // Both entries are public and have different IDs.
185 context->getDiagnostics()->error(DiagMessage(src)
186 << "cannot merge entry '" << srcEntry->name
187 << "': conflicting public IDs");
188 return false;
189 }
190 return true;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700191}
192
193/**
194 * Modified CollisionResolver which will merge Styleables. Used with overlays.
195 *
196 * Styleables are not actual resources, but they are treated as such during the
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700197 * compilation phase. Styleables don't simply overlay each other, their
198 * definitions merge
199 * and accumulate. If both values are Styleables, we just merge them into the
200 * existing value.
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700201 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700202static ResourceTable::CollisionResult resolveMergeCollision(Value* existing,
203 Value* incoming) {
204 if (Styleable* existingStyleable = valueCast<Styleable>(existing)) {
205 if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) {
206 // Styleables get merged.
207 existingStyleable->mergeWith(incomingStyleable);
208 return ResourceTable::CollisionResult::kKeepOriginal;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700209 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700210 }
211 // Delegate to the default handler.
212 return ResourceTable::resolveValueCollision(existing, incoming);
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700213}
214
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700215static ResourceTable::CollisionResult mergeConfigValue(
216 IAaptContext* context, const ResourceNameRef& resName, const bool overlay,
217 ResourceConfigValue* dstConfigValue, ResourceConfigValue* srcConfigValue) {
218 using CollisionResult = ResourceTable::CollisionResult;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700219
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700220 Value* dstValue = dstConfigValue->value.get();
221 Value* srcValue = srcConfigValue->value.get();
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700222
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700223 CollisionResult collisionResult;
224 if (overlay) {
225 collisionResult = resolveMergeCollision(dstValue, srcValue);
226 } else {
227 collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue);
228 }
229
230 if (collisionResult == CollisionResult::kConflict) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700231 if (overlay) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700232 return CollisionResult::kTakeNew;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700233 }
234
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700235 // Error!
236 context->getDiagnostics()->error(
237 DiagMessage(srcValue->getSource())
238 << "resource '" << resName << "' has a conflicting value for "
239 << "configuration (" << srcConfigValue->config << ")");
240 context->getDiagnostics()->note(DiagMessage(dstValue->getSource())
241 << "originally defined here");
242 return CollisionResult::kConflict;
243 }
244 return collisionResult;
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700245}
246
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700247bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800248 ResourceTablePackage* srcPackage,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700249 const bool manglePackage, const bool overlay,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800250 const bool allowNewResources,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700251 const FileMergeCallback& callback) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700252 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700253
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700254 for (auto& srcType : srcPackage->types) {
255 ResourceTableType* dstType =
256 mMasterPackage->findOrCreateType(srcType->type);
257 if (!mergeType(mContext, src, dstType, srcType.get())) {
258 error = true;
259 continue;
260 }
261
262 for (auto& srcEntry : srcType->entries) {
263 std::string entryName = srcEntry->name;
264 if (manglePackage) {
265 entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name);
266 }
267
268 ResourceEntry* dstEntry;
269 if (allowNewResources) {
270 dstEntry = dstType->findOrCreateEntry(entryName);
271 } else {
272 dstEntry = dstType->findEntry(entryName);
273 }
274
275 const ResourceNameRef resName(srcPackage->name, srcType->type,
276 srcEntry->name);
277
278 if (!dstEntry) {
279 mContext->getDiagnostics()->error(
280 DiagMessage(src) << "resource " << resName
281 << " does not override an existing resource");
282 mContext->getDiagnostics()->note(
283 DiagMessage(src) << "define an <add-resource> tag or use "
284 << "--auto-add-overlay");
285 error = true;
286 continue;
287 }
288
289 if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) {
290 error = true;
291 continue;
292 }
293
294 for (auto& srcConfigValue : srcEntry->values) {
295 using CollisionResult = ResourceTable::CollisionResult;
296
297 ResourceConfigValue* dstConfigValue = dstEntry->findValue(
298 srcConfigValue->config, srcConfigValue->product);
299 if (dstConfigValue) {
300 CollisionResult collisionResult = mergeConfigValue(
301 mContext, resName, overlay, dstConfigValue, srcConfigValue.get());
302 if (collisionResult == CollisionResult::kConflict) {
Adam Lesinski5c3464c2016-08-24 16:03:48 -0700303 error = true;
304 continue;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700305 } else if (collisionResult == CollisionResult::kKeepOriginal) {
306 continue;
307 }
308 } else {
309 dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config,
310 srcConfigValue->product);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700311 }
312
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700313 // Continue if we're taking the new resource.
314
315 if (FileReference* f =
316 valueCast<FileReference>(srcConfigValue->value.get())) {
317 std::unique_ptr<FileReference> newFileRef;
318 if (manglePackage) {
319 newFileRef = cloneAndMangleFile(srcPackage->name, *f);
320 } else {
321 newFileRef = std::unique_ptr<FileReference>(
322 f->clone(&mMasterTable->stringPool));
323 }
324
325 if (callback) {
326 if (!callback(resName, srcConfigValue->config, newFileRef.get(),
327 f)) {
328 error = true;
329 continue;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800330 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700331 }
332 dstConfigValue->value = std::move(newFileRef);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800333
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700334 } else {
335 dstConfigValue->value = std::unique_ptr<Value>(
336 srcConfigValue->value->clone(&mMasterTable->stringPool));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700337 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700338 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700339 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700340 }
341 return !error;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700342}
343
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700344std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(
345 const std::string& package, const FileReference& fileRef) {
346 StringPiece prefix, entry, suffix;
347 if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
348 std::string mangledEntry =
349 NameMangler::mangleEntry(package, entry.toString());
350 std::string newPath = prefix.toString() + mangledEntry + suffix.toString();
351 std::unique_ptr<FileReference> newFileRef =
352 util::make_unique<FileReference>(
353 mMasterTable->stringPool.makeRef(newPath));
354 newFileRef->setComment(fileRef.getComment());
355 newFileRef->setSource(fileRef.getSource());
356 return newFileRef;
357 }
358 return std::unique_ptr<FileReference>(
359 fileRef.clone(&mMasterTable->stringPool));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700360}
361
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700362bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file,
363 bool overlay) {
364 ResourceTable table;
365 std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr);
366 std::unique_ptr<FileReference> fileRef =
367 util::make_unique<FileReference>(table.stringPool.makeRef(path));
368 fileRef->setSource(fileDesc.source);
369 fileRef->file = file;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800370
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700371 ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
372 pkg->findOrCreateType(fileDesc.name.type)
373 ->findOrCreateEntry(fileDesc.name.entry)
374 ->findOrCreateValue(fileDesc.config, {})
375 ->value = std::move(fileRef);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800376
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700377 return doMerge(file->getSource(), &table, pkg, false /* mangle */,
378 overlay /* overlay */, true /* allow new */, {});
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800379}
380
381bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700382 return mergeFileImpl(fileDesc, file, false /* overlay */);
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800383}
384
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700385bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc,
386 io::IFile* file) {
387 return mergeFileImpl(fileDesc, file, true /* overlay */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700388}
389
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700390} // namespace aapt