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