TableMerger.cpp revision 64587af8179affd38ee26543b748f2d63b7f67bb
1/*
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"
18#include "ResourceUtils.h"
19#include "ResourceValues.h"
20#include "ValueVisitor.h"
21#include "link/TableMerger.h"
22#include "util/Util.h"
23
24#include <cassert>
25
26namespace aapt {
27
28TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable,
29                         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");
35}
36
37bool 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
47/**
48 * This will merge packages with the same package name (or no package name).
49 */
50bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
51                            io::IFileCollection* collection,
52                            bool overlay, bool allowNew) {
53    const uint8_t desiredPackageId = mContext->getPackageId();
54
55    bool error = false;
56    for (auto& package : table->packages) {
57        // Warn of packages with an unrelated ID.
58        if (package->id && package->id.value() != 0x0 && package->id.value() != desiredPackageId) {
59            mContext->getDiagnostics()->warn(DiagMessage(src)
60                                             << "ignoring package " << package->name);
61            continue;
62        }
63
64        if (package->name.empty() || mContext->getCompilationPackage() == package->name) {
65            FileMergeCallback callback;
66            if (collection) {
67                callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
68                               FileReference* newFile, FileReference* oldFile) -> bool {
69                    // The old file's path points inside the APK, so we can use it as is.
70                    io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path));
71                    if (!f) {
72                        mContext->getDiagnostics()->error(DiagMessage(src) << "file '"
73                                                          << *oldFile->path
74                                                          << "' not found");
75                        return false;
76                    }
77
78                    newFile->file = f;
79                    return true;
80                };
81            }
82
83            // Merge here. Once the entries are merged and mangled, any references to
84            // them are still valid. This is because un-mangled references are
85            // mangled, then looked up at resolution time.
86            // Also, when linking, we convert references with no package name to use
87            // the compilation package name.
88            error |= !doMerge(src, table, package.get(),
89                              false /* mangle */, overlay, allowNew, callback);
90        }
91    }
92    return !error;
93}
94
95/**
96 * This will merge and mangle resources from a static library.
97 */
98bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& packageName,
99                                 ResourceTable* table, io::IFileCollection* collection) {
100    bool error = false;
101    for (auto& package : table->packages) {
102        // Warn of packages with an unrelated ID.
103        if (packageName != package->name) {
104            mContext->getDiagnostics()->warn(DiagMessage(src)
105                                             << "ignoring package " << package->name);
106            continue;
107        }
108
109        bool mangle = packageName != mContext->getCompilationPackage();
110        mMergedPackages.insert(package->name);
111
112        auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
113                            FileReference* newFile, FileReference* oldFile) -> bool {
114            // The old file's path points inside the APK, so we can use it as is.
115            io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path));
116            if (!f) {
117                mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path
118                                                  << "' not found");
119                return false;
120            }
121
122            newFile->file = f;
123            return true;
124        };
125
126        error |= !doMerge(src, table, package.get(),
127                          mangle, false /* overlay */, true /* allow new */, callback);
128    }
129    return !error;
130}
131
132bool TableMerger::doMerge(const Source& src,
133                          ResourceTable* srcTable,
134                          ResourceTablePackage* srcPackage,
135                          const bool manglePackage,
136                          const bool overlay,
137                          const bool allowNewResources,
138                          FileMergeCallback callback) {
139    bool error = false;
140
141    for (auto& srcType : srcPackage->types) {
142        ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
143        if (srcType->symbolStatus.state == SymbolState::kPublic) {
144            if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id
145                    && dstType->id.value() == srcType->id.value()) {
146                // Both types are public and have different IDs.
147                mContext->getDiagnostics()->error(DiagMessage(src)
148                                                  << "can not merge type '"
149                                                  << srcType->type
150                                                  << "': conflicting public IDs");
151                error = true;
152                continue;
153            }
154
155            dstType->symbolStatus = std::move(srcType->symbolStatus);
156            dstType->id = srcType->id;
157        }
158
159        for (auto& srcEntry : srcType->entries) {
160            ResourceEntry* dstEntry;
161            if (manglePackage) {
162                std::u16string mangledName = NameMangler::mangleEntry(srcPackage->name,
163                                                                      srcEntry->name);
164                if (allowNewResources) {
165                    dstEntry = dstType->findOrCreateEntry(mangledName);
166                } else {
167                    dstEntry = dstType->findEntry(mangledName);
168                }
169            } else {
170                if (allowNewResources) {
171                    dstEntry = dstType->findOrCreateEntry(srcEntry->name);
172                } else {
173                    dstEntry = dstType->findEntry(srcEntry->name);
174                }
175            }
176
177            if (!dstEntry) {
178                mContext->getDiagnostics()->error(DiagMessage(src)
179                                                  << "resource "
180                                                  << ResourceNameRef(srcPackage->name,
181                                                                     srcType->type,
182                                                                     srcEntry->name)
183                                                  << " does not override an existing resource");
184                mContext->getDiagnostics()->note(DiagMessage(src)
185                                                 << "define an <add-resource> tag or use "
186                                                    "--auto-add-overlay");
187                error = true;
188                continue;
189            }
190
191            if (srcEntry->symbolStatus.state != SymbolState::kUndefined) {
192                if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
193                    if (dstEntry->symbolStatus.state == SymbolState::kPublic &&
194                            dstEntry->id && srcEntry->id &&
195                            dstEntry->id.value() != srcEntry->id.value()) {
196                        // Both entries are public and have different IDs.
197                        mContext->getDiagnostics()->error(DiagMessage(src)
198                                                          << "can not merge entry '"
199                                                          << srcEntry->name
200                                                          << "': conflicting public IDs");
201                        error = true;
202                        continue;
203                    }
204
205                    if (srcEntry->id) {
206                        dstEntry->id = srcEntry->id;
207                    }
208                }
209
210                if (dstEntry->symbolStatus.state != SymbolState::kPublic &&
211                        dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) {
212                    dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
213                }
214            }
215
216            ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name);
217
218            for (auto& srcValue : srcEntry->values) {
219                ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
220                                                                    srcValue->product);
221                if (dstValue) {
222                    const int collisionResult = ResourceTable::resolveValueCollision(
223                            dstValue->value.get(), srcValue->value.get());
224                    if (collisionResult == 0 && !overlay) {
225                        // Error!
226                        ResourceNameRef resourceName(srcPackage->name,
227                                                     srcType->type,
228                                                     srcEntry->name);
229
230                        mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource())
231                                                          << "resource '" << resourceName
232                                                          << "' has a conflicting value for "
233                                                          << "configuration ("
234                                                          << srcValue->config << ")");
235                        mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource())
236                                                         << "originally defined here");
237                        error = true;
238                        continue;
239                    } else if (collisionResult < 0) {
240                        // Keep our existing value.
241                        continue;
242                    }
243
244                }
245
246                if (!dstValue) {
247                    // Force create the entry if we didn't have it.
248                    dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
249                }
250
251                if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) {
252                    std::unique_ptr<FileReference> newFileRef;
253                    if (manglePackage) {
254                        newFileRef = cloneAndMangleFile(srcPackage->name, *f);
255                    } else {
256                        newFileRef = std::unique_ptr<FileReference>(f->clone(
257                                &mMasterTable->stringPool));
258                    }
259
260                    if (callback) {
261                        if (!callback(resName, srcValue->config, newFileRef.get(), f)) {
262                            error = true;
263                            continue;
264                        }
265                    }
266                    dstValue->value = std::move(newFileRef);
267
268                } else {
269                    dstValue->value = std::unique_ptr<Value>(srcValue->value->clone(
270                            &mMasterTable->stringPool));
271                }
272            }
273        }
274    }
275    return !error;
276}
277
278std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::u16string& package,
279                                                               const FileReference& fileRef) {
280
281    StringPiece16 prefix, entry, suffix;
282    if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
283        std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
284        std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
285        std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>(
286                mMasterTable->stringPool.makeRef(newPath));
287        newFileRef->setComment(fileRef.getComment());
288        newFileRef->setSource(fileRef.getSource());
289        return newFileRef;
290    }
291    return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool));
292}
293
294bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) {
295    ResourceTable table;
296    std::u16string path = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(fileDesc,
297                                                                                 nullptr));
298    std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
299            table.stringPool.makeRef(path));
300    fileRef->setSource(fileDesc.source);
301    fileRef->file = file;
302
303    ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
304    pkg->findOrCreateType(fileDesc.name.type)
305            ->findOrCreateEntry(fileDesc.name.entry)
306            ->findOrCreateValue(fileDesc.config, {})
307            ->value = std::move(fileRef);
308
309    return doMerge(file->getSource(), &table, pkg,
310                   false /* mangle */, overlay /* overlay */, true /* allow new */, {});
311}
312
313bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
314    return mergeFileImpl(fileDesc, file, false /* overlay */);
315}
316
317bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
318    return mergeFileImpl(fileDesc, file, true /* overlay */);
319}
320
321} // namespace aapt
322