Merge "AAPT2: Support -c configuration filtering"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b8bcd19975
@@ -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 \
|
||||
|
||||
@@ -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, '_');
|
||||
|
||||
@@ -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.
|
||||
|
||||
77
tools/aapt2/filter/ConfigFilter.cpp
Normal file
77
tools/aapt2/filter/ConfigFilter.cpp
Normal file
@@ -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
|
||||
61
tools/aapt2/filter/ConfigFilter.h
Normal file
61
tools/aapt2/filter/ConfigFilter.h
Normal file
@@ -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 */
|
||||
112
tools/aapt2/filter/ConfigFilter_test.cpp
Normal file
112
tools/aapt2/filter/ConfigFilter_test.cpp
Normal file
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user