diff options
author | 2016-02-02 17:02:58 -0800 | |
---|---|---|
committer | 2016-02-02 17:02:58 -0800 | |
commit | 6a008170cb18666e04c42856f992fc7a0afa1e1f (patch) | |
tree | 5998b2ccc5c4e4006f6b0ab5ed37b40f7502b9dd | |
parent | e909d94e03e2bd3eba0bb47b6295ae0bf6538c31 (diff) |
AAPT2: Support -c configuration filtering
Change-Id: I1e5855ca73440bdc30c21bcbc1dfdd31a9842363
-rw-r--r-- | tools/aapt2/Android.mk | 2 | ||||
-rw-r--r-- | tools/aapt2/Locale.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/Locale.h | 4 | ||||
-rw-r--r-- | tools/aapt2/filter/ConfigFilter.cpp | 77 | ||||
-rw-r--r-- | tools/aapt2/filter/ConfigFilter.h | 61 | ||||
-rw-r--r-- | tools/aapt2/filter/ConfigFilter_test.cpp | 112 | ||||
-rw-r--r-- | tools/aapt2/link/Link.cpp | 208 | ||||
-rw-r--r-- | tools/aapt2/link/TableMerger.cpp | 13 | ||||
-rw-r--r-- | tools/aapt2/link/TableMerger.h | 6 | ||||
-rw-r--r-- | tools/aapt2/link/TableMerger_test.cpp | 33 | ||||
-rw-r--r-- | tools/aapt2/test/Builders.h | 5 |
11 files changed, 430 insertions, 93 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index a4f4ba928855..f74b93abd796 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -30,6 +30,7 @@ sources := \ compile/PseudolocaleGenerator.cpp \ compile/Pseudolocalizer.cpp \ compile/XmlIdCollector.cpp \ + filter/ConfigFilter.cpp \ flatten/Archive.cpp \ flatten/TableFlattener.cpp \ flatten/XmlFlattener.cpp \ @@ -71,6 +72,7 @@ testSources := \ compile/PseudolocaleGenerator_test.cpp \ compile/Pseudolocalizer_test.cpp \ compile/XmlIdCollector_test.cpp \ + filter/ConfigFilter_test.cpp \ flatten/FileExportWriter_test.cpp \ flatten/TableFlattener_test.cpp \ flatten/XmlFlattener_test.cpp \ diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index 03691568ed47..6acf3b09d301 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -70,7 +70,7 @@ static inline bool isNumber(const std::string& str) { return std::all_of(std::begin(str), std::end(str), ::isdigit); } -bool LocaleValue::initFromFilterString(const std::string& str) { +bool LocaleValue::initFromFilterString(const StringPiece& str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". std::vector<std::string> parts = util::splitAndLowercase(str, '_'); diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h index ceec764ba4fd..b1c80ab27641 100644 --- a/tools/aapt2/Locale.h +++ b/tools/aapt2/Locale.h @@ -17,6 +17,8 @@ #ifndef AAPT_LOCALE_VALUE_H #define AAPT_LOCALE_VALUE_H +#include "util/StringPiece.h" + #include <androidfw/ResourceTypes.h> #include <string> #include <vector> @@ -37,7 +39,7 @@ struct LocaleValue { /** * Initialize this LocaleValue from a config string. */ - bool initFromFilterString(const std::string& config); + bool initFromFilterString(const StringPiece& config); /** * Initialize this LocaleValue from parts of a vector. diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp new file mode 100644 index 000000000000..68a017d247f6 --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigDescription.h" +#include "filter/ConfigFilter.h" + +#include <androidfw/ResourceTypes.h> + +namespace aapt { + +void AxisConfigFilter::addConfig(ConfigDescription config) { + uint32_t diffMask = ConfigDescription::defaultConfig().diff(config); + + // Ignore the version + diffMask &= ~android::ResTable_config::CONFIG_VERSION; + + // Ignore any densities. Those are best handled in --preferred-density + if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) { + config.density = 0; + diffMask &= ~android::ResTable_config::CONFIG_DENSITY; + } + + mConfigs.insert(std::make_pair(config, diffMask)); + mConfigMask |= diffMask; +} + +bool AxisConfigFilter::match(const ConfigDescription& config) const { + const uint32_t mask = ConfigDescription::defaultConfig().diff(config); + if ((mConfigMask & mask) == 0) { + // The two configurations don't have any common axis. + return true; + } + + uint32_t matchedAxis = 0; + for (const auto& entry : mConfigs) { + const ConfigDescription& target = entry.first; + const uint32_t diffMask = entry.second; + uint32_t diff = target.diff(config); + if ((diff & diffMask) == 0) { + // Mark the axis that was matched. + matchedAxis |= diffMask; + } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) { + // If the locales differ, but the languages are the same and + // the locale we are matching only has a language specified, + // we match. + if (config.language[0] && + memcmp(config.language, target.language, sizeof(config.language)) == 0) { + if (config.country[0] == 0) { + matchedAxis |= android::ResTable_config::CONFIG_LOCALE; + } + } + } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) { + // Special case if the smallest screen width doesn't match. We check that the + // config being matched has a smaller screen width than the filter specified. + if (config.smallestScreenWidthDp != 0 && + config.smallestScreenWidthDp < target.smallestScreenWidthDp) { + matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; + } + } + } + return matchedAxis == (mConfigMask & mask); +} + +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h new file mode 100644 index 000000000000..36e9c44255e4 --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FILTER_CONFIGFILTER_H +#define AAPT_FILTER_CONFIGFILTER_H + +#include "ConfigDescription.h" + +#include <set> +#include <utility> + +namespace aapt { + +/** + * Matches ConfigDescriptions based on some pattern. + */ +class IConfigFilter { +public: + virtual ~IConfigFilter() = default; + + /** + * Returns true if the filter matches the configuration, false otherwise. + */ + virtual bool match(const ConfigDescription& config) const = 0; +}; + +/** + * Implements config axis matching. An axis is one component of a configuration, like screen + * density or locale. If an axis is specified in the filter, and the axis is specified in + * the configuration to match, they must be compatible. Otherwise the configuration to match is + * accepted. + * + * Used when handling "-c" options. + */ +class AxisConfigFilter : public IConfigFilter { +public: + void addConfig(ConfigDescription config); + + bool match(const ConfigDescription& config) const override; + +private: + std::set<std::pair<ConfigDescription, uint32_t>> mConfigs; + uint32_t mConfigMask = 0; +}; + +} // namespace aapt + +#endif /* AAPT_FILTER_CONFIGFILTER_H */ diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp new file mode 100644 index 000000000000..f6b49557306d --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter_test.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filter/ConfigFilter.h" +#include "test/Common.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ConfigFilterTest, EmptyFilterMatchesAnything) { + AxisConfigFilter filter; + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr-rFR")); + filter.addConfig(test::parseConfigOrDie("sw360dp")); + filter.addConfig(test::parseConfigOrDie("normal")); + filter.addConfig(test::parseConfigOrDie("en-rUS")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("en"))); +} + +TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_FALSE(filter.match(test::parseConfigOrDie("de"))); +} + +TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr-rFR")); + filter.addConfig(test::parseConfigOrDie("en-rUS")); + filter.addConfig(test::parseConfigOrDie("normal")); + filter.addConfig(test::parseConfigOrDie("large")); + filter.addConfig(test::parseConfigOrDie("xxhdpi")); + filter.addConfig(test::parseConfigOrDie("sw320dp")); + + EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("sw600dp")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("de-rDE")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("de"))); +} + +TEST(ConfigFilterTest, IgnoresVersion) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("normal-v4")); + + // The configs don't match on any axis besides version, which should be ignored. + EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithRegion) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("kok")); + filter.addConfig(test::parseConfigOrDie("kok-rIN")); + filter.addConfig(test::parseConfigOrDie("kok-v419")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN"))); +} + +} // namespace aapt diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 3ecb2c4113d5..fd76e887ab2a 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -17,8 +17,10 @@ #include "AppInfo.h" #include "Debug.h" #include "Flags.h" +#include "Locale.h" #include "NameMangler.h" #include "compile/IdAssigner.h" +#include "filter/ConfigFilter.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" @@ -64,7 +66,7 @@ struct LinkOptions { std::vector<std::string> extensionsToNotCompress; Maybe<std::u16string> privateSymbols; ManifestFixerOptions manifestFixerOptions; - + IConfigFilter* configFilter = nullptr; }; struct LinkContext : public IAaptContext { @@ -97,8 +99,8 @@ struct LinkContext : public IAaptContext { class LinkCommand { public: - LinkCommand(const LinkOptions& options) : - mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) { + LinkCommand(LinkContext* context, const LinkOptions& options) : + mOptions(options), mContext(context), mFinalTable(), mFileCollection(nullptr) { std::unique_ptr<io::FileCollection> fileCollection = util::make_unique<io::FileCollection>(); @@ -117,14 +119,14 @@ public: AssetManagerSymbolTableBuilder builder; for (const std::string& path : mOptions.includePaths) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path"); + mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); } std::unique_ptr<android::AssetManager> assetManager = util::make_unique<android::AssetManager>(); int32_t cookie = 0; if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) { - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage(path) << "failed to load include path"); return {}; } @@ -135,7 +137,7 @@ public: std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) { std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(&mContext, table.get(), source, data, len); + BinaryResourceParser parser(mContext, table.get(), source, data, len); if (!parser.parse()) { return {}; } @@ -207,7 +209,7 @@ public: IArchiveWriter* writer) { std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } @@ -215,7 +217,7 @@ public: std::string errorStr; ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr); if (offset < 0) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr); + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr); return false; } @@ -228,7 +230,7 @@ public: } } - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); return false; } @@ -252,9 +254,9 @@ public: */ bool verifyNoExternalPackages() { auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { - return mContext.getCompilationPackage() != pkg->name || + return mContext->getCompilationPackage() != pkg->name || !pkg->id || - pkg->id.value() != mContext.getPackageId(); + pkg->id.value() != mContext->getPackageId(); }; bool error = false; @@ -270,13 +272,13 @@ public: // 'android' package. This is due to legacy reasons. if (valueCast<Id>(configValue.value.get()) && package->name == u"android") { - mContext.getDiagnostics()->warn( + mContext->getDiagnostics()->warn( DiagMessage(configValue.value->getSource()) << "generated id '" << resName << "' for external package '" << package->name << "'"); } else { - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage(configValue.value->getSource()) << "defined resource '" << resName << "' for external package '" << package->name @@ -297,9 +299,9 @@ public: std::unique_ptr<IArchiveWriter> makeArchiveWriter() { if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); + return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); } else { - return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); + return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); } } @@ -308,7 +310,7 @@ public: TableFlattenerOptions options = {}; options.useExtendedChunks = mOptions.staticLib; TableFlattener flattener(&buffer, options); - if (!flattener.consume(&mContext, table)) { + if (!flattener.consume(mContext, table)) { return false; } @@ -320,7 +322,7 @@ public: } } - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage() << "failed to write resources.arsc to archive"); return false; } @@ -332,7 +334,7 @@ public: options.keepRawValues = mOptions.staticLib; options.maxSdkLevel = maxSdkLevel; XmlFlattener flattener(&buffer, options); - if (!flattener.consume(&mContext, xmlRes)) { + if (!flattener.consume(mContext, xmlRes)) { return false; } @@ -343,7 +345,7 @@ public: } } } - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage() << "failed to write " << path << " to archive"); return false; } @@ -361,13 +363,13 @@ public: std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } JavaClassGenerator generator(table, javaOptions); if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { - mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); + mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); return false; } return true; @@ -380,24 +382,24 @@ public: std::string outPath = mOptions.generateJavaClassPath.value(); file::appendPath(&outPath, - file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage()))); + file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage()))); file::mkdirs(outPath); file::appendPath(&outPath, "Manifest.java"); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } ManifestClassGenerator generator; - if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(), + if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(), manifestXml, &fout)) { return false; } if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } return true; @@ -410,13 +412,13 @@ public: std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } proguard::writeKeepSet(&fout, keepSet); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } return true; @@ -425,7 +427,7 @@ public: bool mergeStaticLibrary(const std::string& input) { // TODO(adamlesinski): Load resources from a static library APK and merge the table into // TableMerger. - mContext.getDiagnostics()->warn(DiagMessage() + mContext->getDiagnostics()->warn(DiagMessage() << "linking static libraries not supported yet: " << input); return true; @@ -433,12 +435,12 @@ public: bool mergeResourceTable(io::IFile* file, bool override) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); + mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); } std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } @@ -460,7 +462,7 @@ public: bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); + mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); } bool result = false; @@ -477,12 +479,12 @@ public: // Add the exports of this file to the table. for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { if (exportedSymbol.name.package.empty()) { - exportedSymbol.name.package = mContext.getCompilationPackage().toString(); + exportedSymbol.name.package = mContext->getCompilationPackage().toString(); } ResourceNameRef resName = exportedSymbol.name; - Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName( + Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( exportedSymbol.name); if (mangledName) { resName = mangledName.value(); @@ -491,7 +493,7 @@ public: std::unique_ptr<Id> id = util::make_unique<Id>(); id->setSource(fileDesc->source.withLine(exportedSymbol.line)); bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id), - mContext.getDiagnostics()); + mContext->getDiagnostics()); if (!result) { return false; } @@ -507,7 +509,7 @@ public: std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( input, &errorStr); if (!collection) { - mContext.getDiagnostics()->error(DiagMessage(input) << errorStr); + mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); return false; } @@ -543,12 +545,12 @@ public: // Try opening the file and looking for an Export header. std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open"); + mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); return false; } std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( - src, data->data(), data->size(), mContext.getDiagnostics()); + src, data->data(), data->size(), mContext->getDiagnostics()); if (resourceFile) { return mergeCompiledFile(file, std::move(resourceFile), override); } @@ -564,62 +566,63 @@ public: int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, - mContext.getDiagnostics()); + mContext->getDiagnostics()); if (!manifestXml) { return 1; } if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { - mContext.mCompilationPackage = maybeAppInfo.value().package; + mContext->mCompilationPackage = maybeAppInfo.value().package; } else { - mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "no package specified in <manifest> tag"); return 1; } - if (!util::isJavaPackageName(mContext.mCompilationPackage)) { - mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + if (!util::isJavaPackageName(mContext->mCompilationPackage)) { + mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "invalid package name '" - << mContext.mCompilationPackage + << mContext->mCompilationPackage << "'"); return 1; } - mContext.mNameMangler = util::make_unique<NameMangler>( - NameManglerPolicy{ mContext.mCompilationPackage }); + mContext->mNameMangler = util::make_unique<NameMangler>( + NameManglerPolicy{ mContext->mCompilationPackage }); - if (mContext.mCompilationPackage == u"android") { - mContext.mPackageId = 0x01; + if (mContext->mCompilationPackage == u"android") { + mContext->mPackageId = 0x01; } else { - mContext.mPackageId = 0x7f; + mContext->mPackageId = 0x7f; } - mContext.mSymbols = createSymbolTableFromIncludePaths(); - if (!mContext.mSymbols) { + mContext->mSymbols = createSymbolTableFromIncludePaths(); + if (!mContext->mSymbols) { return 1; } TableMergerOptions tableMergerOptions; tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; - mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions); + tableMergerOptions.filter = mOptions.configFilter; + mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); if (mOptions.verbose) { - mContext.getDiagnostics()->note( - DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' " - << "with package ID " << std::hex << (int) mContext.mPackageId); + mContext->getDiagnostics()->note( + DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' " + << "with package ID " << std::hex << (int) mContext->mPackageId); } for (const std::string& input : inputFiles) { if (!processFile(input, false)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); + mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); return 1; } } for (const std::string& input : mOptions.overlayFiles) { if (!processFile(input, true)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); + mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); return 1; } } @@ -630,8 +633,8 @@ public: if (!mOptions.staticLib) { PrivateAttributeMover mover; - if (!mover.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error( + if (!mover.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error( DiagMessage() << "failed moving private attributes"); return 1; } @@ -639,23 +642,23 @@ public: { IdAssigner idAssigner; - if (!idAssigner.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); + if (!idAssigner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); return 1; } } - mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{ - mContext.mCompilationPackage, mTableMerger->getMergedPackages() }); - mContext.mSymbols = JoinedSymbolTableBuilder() + mContext->mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{ + mContext->mCompilationPackage, mTableMerger->getMergedPackages() }); + mContext->mSymbols = JoinedSymbolTableBuilder() .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable)) - .addSymbolTable(std::move(mContext.mSymbols)) + .addSymbolTable(std::move(mContext->mSymbols)) .build(); { ReferenceLinker linker; - if (!linker.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed linking references"); + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); return 1; } } @@ -664,24 +667,24 @@ public: std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); if (!archiveWriter) { - mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive"); + mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); return 1; } bool error = false; { ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(&mContext, manifestXml.get())) { + if (!manifestFixer.consume(mContext, manifestXml.get())) { error = true; } // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. - manifestXml->file.name.package = mContext.getCompilationPackage().toString(); + manifestXml->file.name.package = mContext->getCompilationPackage().toString(); XmlReferenceLinker manifestLinker; - if (manifestLinker.consume(&mContext, manifestXml.get())) { + if (manifestLinker.consume(mContext, manifestXml.get())) { if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), manifestXml.get(), &proguardKeepSet)) { @@ -704,7 +707,7 @@ public: } if (error) { - mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest"); + mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); return 1; } @@ -718,13 +721,13 @@ public: (util::stringEndsWith<char>(path, ".xml.flat") || util::stringEndsWith<char>(path, ".xml"))) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "linking " << path); + mContext->getDiagnostics()->note(DiagMessage() << "linking " << path); } io::IFile* file = fileToMerge.file; std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return 1; } @@ -733,9 +736,9 @@ public: if (util::stringEndsWith<char>(path, ".flat")) { xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(), - mContext.getDiagnostics()); + mContext->getDiagnostics()); } else { - xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(), + xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(), file->getSource()); } @@ -751,7 +754,7 @@ public: }; XmlReferenceLinker xmlLinker; - if (xmlLinker.consume(&mContext, xmlRes.get())) { + if (xmlLinker.consume(mContext, xmlRes.get())) { if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), &proguardKeepSet)) { error = true; @@ -778,14 +781,14 @@ public: xmlRes->file.config.sdkVersion = sdkLevel; std::string genResourcePath = ResourceUtils::buildResourceFileName( - xmlRes->file, mContext.getNameMangler()); + xmlRes->file, mContext->getNameMangler()); bool added = mFinalTable.addFileReference( xmlRes->file.name, xmlRes->file.config, xmlRes->file.source, util::utf8ToUtf16(genResourcePath), - mContext.getDiagnostics()); + mContext->getDiagnostics()); if (!added) { error = true; continue; @@ -804,7 +807,7 @@ public: } } else { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "copying " << path); + mContext->getDiagnostics()->note(DiagMessage() << "copying " << path); } if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath, @@ -815,20 +818,20 @@ public: } if (error) { - mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); return 1; } if (!mOptions.noAutoVersion) { AutoVersioner versioner; - if (!versioner.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles"); + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); return 1; } } if (!flattenTable(&mFinalTable, archiveWriter.get())) { - mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); + mContext->getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); return 1; } @@ -840,8 +843,8 @@ public: options.useFinal = false; } - const StringPiece16 actualPackage = mContext.getCompilationPackage(); - StringPiece16 outputPackage = mContext.getCompilationPackage(); + const StringPiece16 actualPackage = mContext->getCompilationPackage(); + StringPiece16 outputPackage = mContext->getCompilationPackage(); if (mOptions.customJavaPackage) { // Override the output java package to the custom one. outputPackage = mOptions.customJavaPackage.value(); @@ -852,7 +855,7 @@ public: // to the original package, and private and public symbols to the private package. options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(), + if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), outputPackage, options)) { return 1; } @@ -886,7 +889,7 @@ public: private: LinkOptions mOptions; - LinkContext mContext; + LinkContext* mContext; ResourceTable mFinalTable; ResourceTable mLocalFileTable; @@ -907,6 +910,7 @@ int link(const std::vector<StringPiece>& args) { Maybe<std::string> versionCode, versionName; Maybe<std::string> customJavaPackage; std::vector<std::string> extraJavaPackages; + Maybe<std::string> configs; bool legacyXFlag = false; bool requireLocalization = false; Flags flags = Flags() @@ -928,6 +932,8 @@ int link(const std::vector<StringPiece>& args) { &legacyXFlag) .optionalSwitch("-z", "Require localization of strings marked 'suggested'", &requireLocalization) + .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" + "is all configurations", &configs) .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " "by -o", &options.outputToDirectory) @@ -967,6 +973,8 @@ int link(const std::vector<StringPiece>& args) { return 1; } + LinkContext context; + if (privateSymbolsPackage) { options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); } @@ -1011,7 +1019,31 @@ int link(const std::vector<StringPiece>& args) { } } - LinkCommand cmd(options); + AxisConfigFilter filter; + if (configs) { + for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { + ConfigDescription config; + LocaleValue lv; + if (lv.initFromFilterString(configStr)) { + lv.writeTo(&config); + } else if (!ConfigDescription::parse(configStr, &config)) { + context.getDiagnostics()->error( + DiagMessage() << "invalid config '" << configStr << "' for -c option"); + return 1; + } + + if (config.density != 0) { + context.getDiagnostics()->warn( + DiagMessage() << "ignoring density '" << config << "' for -c option"); + } else { + filter.addConfig(config); + } + } + + options.configFilter = &filter; + } + + LinkCommand cmd(&context, options); return cmd.run(flags.getArgs()); } diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 27a23bd65103..e01a00401133 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -100,7 +100,7 @@ bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& package return false; } - mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{ + mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{ f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) }; return true; }; @@ -201,6 +201,9 @@ bool TableMerger::doMerge(const Source& src, auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(), srcValue.config, cmp::lessThanConfig); + const bool stripConfig = mOptions.filter ? + !mOptions.filter->match(srcValue.config) : false; + if (iter != dstEntry->values.end() && iter->config == srcValue.config) { const int collisionResult = ResourceTable::resolveValueCollision( iter->value.get(), srcValue.value.get()); @@ -224,11 +227,15 @@ bool TableMerger::doMerge(const Source& src, continue; } - } else { + } else if (!stripConfig){ // Insert a place holder value. We will fill it in below. iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config }); } + if (stripConfig) { + continue; + } + if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) { std::unique_ptr<FileReference> newFileRef; if (manglePackage) { @@ -287,7 +294,7 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, FileReference* newFile, FileReference* oldFile) -> bool { - mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{ + mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{ file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) }; return true; }; diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index e1be5d52e9cf..4539679fa769 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -20,6 +20,7 @@ #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "filter/ConfigFilter.h" #include "io/File.h" #include "process/IResourceTableConsumer.h" #include "util/Util.h" @@ -51,6 +52,11 @@ struct TableMergerOptions { * If true, resources in overlays can be added without previously having existed. */ bool autoAddOverlay = false; + + /** + * A filter that removes resources whose configurations don't match. + */ + IConfigFilter* filter = nullptr; }; /** diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index fa4afd3d852a..45c8c98780b8 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "filter/ConfigFilter.h" #include "io/FileSystem.h" #include "link/TableMerger.h" #include "test/Builders.h" @@ -243,4 +244,36 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { ASSERT_FALSE(merger.mergeOverlay({}, tableB.get())); } +TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) { + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = false; + + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("en")); + options.filter = &filter; + + test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml"); + const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main"); + const ConfigDescription configEn = test::parseConfigOrDie("en"); + const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR"); + + TableMerger merger(mContext.get(), &finalTable, options); + ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA)); + ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB)); + + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable, + u"@com.app.a:layout/main", + configEn)); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable, + u"@com.app.a:layout/main", + configFr)); + + EXPECT_NE(merger.getFilesToMerge().end(), + merger.getFilesToMerge().find(ResourceKeyRef(name, configEn))); + + EXPECT_EQ(merger.getFilesToMerge().end(), + merger.getFilesToMerge().find(ResourceKeyRef(name, configFr))); +} + } // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 93a11b9334a8..579a46ec230f 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -84,6 +84,11 @@ public: util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); } + ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path, + const ConfigDescription& config) { + return addValue(name, {}, config, + util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); + } ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) { |