Merge "AAPT2: Split APK by ABI."
This commit is contained in:
committed by
Android (Google) Code Review
commit
7cdccf9f2f
@@ -85,6 +85,7 @@ cc_library_host_static {
|
||||
"compile/Pseudolocalizer.cpp",
|
||||
"compile/XmlIdCollector.cpp",
|
||||
"configuration/ConfigurationParser.cpp",
|
||||
"filter/AbiFilter.cpp",
|
||||
"filter/ConfigFilter.cpp",
|
||||
"flatten/Archive.cpp",
|
||||
"flatten/TableFlattener.cpp",
|
||||
|
||||
@@ -57,6 +57,12 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
|
||||
|
||||
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
|
||||
IArchiveWriter* writer) {
|
||||
FilterChain empty;
|
||||
return WriteToArchive(context, options, &empty, writer);
|
||||
}
|
||||
|
||||
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
|
||||
FilterChain* filters, IArchiveWriter* writer) {
|
||||
std::set<std::string> referenced_resources;
|
||||
// List the files being referenced in the resource table.
|
||||
for (auto& pkg : table_->packages) {
|
||||
@@ -89,6 +95,13 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filters->Keep(path)) {
|
||||
if (context->IsVerbose()) {
|
||||
context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// The resource table needs to be re-serialized since it might have changed.
|
||||
if (path == "resources.arsc") {
|
||||
BigBuffer buffer(4096);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "androidfw/StringPiece.h"
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "filter/Filter.h"
|
||||
#include "flatten/Archive.h"
|
||||
#include "flatten/TableFlattener.h"
|
||||
#include "io/ZipArchive.h"
|
||||
@@ -49,6 +50,14 @@ class LoadedApk {
|
||||
bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
|
||||
IArchiveWriter* writer);
|
||||
|
||||
/**
|
||||
* Writes the APK on disk at the given path, while also removing the resource
|
||||
* files that are not referenced in the resource table. The provided filter
|
||||
* chain is applied to each entry in the APK file.
|
||||
*/
|
||||
bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
|
||||
FilterChain* filters, IArchiveWriter* writer);
|
||||
|
||||
static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
|
||||
const android::StringPiece& path);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
#include "androidfw/StringPiece.h"
|
||||
|
||||
#include "Diagnostics.h"
|
||||
@@ -26,6 +27,8 @@
|
||||
#include "SdkConstants.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "cmd/Util.h"
|
||||
#include "configuration/ConfigurationParser.h"
|
||||
#include "filter/AbiFilter.h"
|
||||
#include "flatten/TableFlattener.h"
|
||||
#include "flatten/XmlFlattener.h"
|
||||
#include "io/BigBufferInputStream.h"
|
||||
@@ -33,14 +36,21 @@
|
||||
#include "optimize/ResourceDeduper.h"
|
||||
#include "optimize/VersionCollapser.h"
|
||||
#include "split/TableSplitter.h"
|
||||
#include "util/Files.h"
|
||||
|
||||
using android::StringPiece;
|
||||
using ::aapt::configuration::Abi;
|
||||
using ::aapt::configuration::Artifact;
|
||||
using ::aapt::configuration::Configuration;
|
||||
using ::android::StringPiece;
|
||||
using ::android::base::StringPrintf;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct OptimizeOptions {
|
||||
// Path to the output APK.
|
||||
std::string output_path;
|
||||
Maybe<std::string> output_path;
|
||||
// Path to the output APK directory for splits.
|
||||
Maybe<std::string> output_dir;
|
||||
|
||||
// Details of the app extracted from the AndroidManifest.xml
|
||||
AppInfo app_info;
|
||||
@@ -55,6 +65,9 @@ struct OptimizeOptions {
|
||||
std::vector<SplitConstraints> split_constraints;
|
||||
|
||||
TableFlattenerOptions table_flattener_options;
|
||||
|
||||
// TODO: Come up with a better name for the Configuration struct.
|
||||
Maybe<Configuration> configuration;
|
||||
};
|
||||
|
||||
class OptimizeContext : public IAaptContext {
|
||||
@@ -175,10 +188,52 @@ class OptimizeCommand {
|
||||
++split_constraints_iter;
|
||||
}
|
||||
|
||||
std::unique_ptr<IArchiveWriter> writer =
|
||||
CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path);
|
||||
if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
|
||||
return 1;
|
||||
if (options_.configuration && options_.output_dir) {
|
||||
Configuration& config = options_.configuration.value();
|
||||
|
||||
// For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
|
||||
for (const Artifact& artifact : config.artifacts) {
|
||||
if (artifact.abi_group) {
|
||||
const std::string& group = artifact.abi_group.value();
|
||||
|
||||
auto abi_group = config.abi_groups.find(group);
|
||||
// TODO: Remove validation when configuration parser ensures referential integrity.
|
||||
if (abi_group == config.abi_groups.end()) {
|
||||
context_->GetDiagnostics()->Note(
|
||||
DiagMessage() << "could not find referenced ABI group '" << group << "'");
|
||||
return 1;
|
||||
}
|
||||
FilterChain filters;
|
||||
filters.AddFilter(AbiFilter::FromAbiList(abi_group->second));
|
||||
|
||||
const std::string& path = apk->GetSource().path;
|
||||
const StringPiece ext = file::GetExtension(path);
|
||||
const std::string name = path.substr(0, path.rfind(ext.to_string()));
|
||||
|
||||
// Name is hard coded for now since only one split dimension is supported.
|
||||
// TODO: Incorporate name generation into the configuration objects.
|
||||
const std::string file_name =
|
||||
StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data());
|
||||
std::string out = options_.output_dir.value();
|
||||
file::AppendPath(&out, file_name);
|
||||
|
||||
std::unique_ptr<IArchiveWriter> writer =
|
||||
CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
|
||||
|
||||
if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters,
|
||||
writer.get())) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options_.output_path) {
|
||||
std::unique_ptr<IArchiveWriter> writer =
|
||||
CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
|
||||
if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -214,8 +269,8 @@ class OptimizeCommand {
|
||||
if (file_ref->file == nullptr) {
|
||||
ResourceNameRef name(pkg->name, type->type, entry->name);
|
||||
context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
|
||||
<< "file for resource " << name << " with config '"
|
||||
<< config_value->config << "' not found");
|
||||
<< "file for resource " << name << " with config '"
|
||||
<< config_value->config << "' not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -293,13 +348,16 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
|
||||
int Optimize(const std::vector<StringPiece>& args) {
|
||||
OptimizeContext context;
|
||||
OptimizeOptions options;
|
||||
Maybe<std::string> config_path;
|
||||
Maybe<std::string> target_densities;
|
||||
std::vector<std::string> configs;
|
||||
std::vector<std::string> split_args;
|
||||
bool verbose = false;
|
||||
Flags flags =
|
||||
Flags()
|
||||
.RequiredFlag("-o", "Path to the output APK.", &options.output_path)
|
||||
.OptionalFlag("-o", "Path to the output APK.", &options.output_path)
|
||||
.OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
|
||||
.OptionalFlag("-x", "Path to XML configuration file.", &config_path)
|
||||
.OptionalFlag(
|
||||
"--target-densities",
|
||||
"Comma separated list of the screen densities that the APK will be optimized for.\n"
|
||||
@@ -369,6 +427,22 @@ int Optimize(const std::vector<StringPiece>& args) {
|
||||
}
|
||||
}
|
||||
|
||||
if (config_path) {
|
||||
if (!options.output_dir) {
|
||||
context.GetDiagnostics()->Error(
|
||||
DiagMessage() << "Output directory is required when using a configuration file");
|
||||
return 1;
|
||||
}
|
||||
std::string& path = config_path.value();
|
||||
Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
|
||||
if (for_path) {
|
||||
options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse();
|
||||
} else {
|
||||
context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
@@ -56,15 +57,15 @@ using ::aapt::xml::XmlActionExecutorPolicy;
|
||||
using ::aapt::xml::XmlNodeAction;
|
||||
using ::android::base::ReadFileToString;
|
||||
|
||||
const std::unordered_map<std::string, Abi> kAbiMap = {
|
||||
{"armeabi", Abi::kArmeV6},
|
||||
{"armeabi-v7a", Abi::kArmV7a},
|
||||
{"arm64-v8a", Abi::kArm64V8a},
|
||||
{"x86", Abi::kX86},
|
||||
{"x86_64", Abi::kX86_64},
|
||||
{"mips", Abi::kMips},
|
||||
{"mips64", Abi::kMips64},
|
||||
{"universal", Abi::kUniversal},
|
||||
const std::unordered_map<std::string, Abi> kStringToAbiMap = {
|
||||
{"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a},
|
||||
{"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips},
|
||||
{"mips64", Abi::kMips64}, {"universal", Abi::kUniversal},
|
||||
};
|
||||
const std::map<Abi, std::string> kAbiToStringMap = {
|
||||
{Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"}, {Abi::kArm64V8a, "arm64-v8a"},
|
||||
{Abi::kX86, "x86"}, {Abi::kX86_64, "x86_64"}, {Abi::kMips, "mips"},
|
||||
{Abi::kMips64, "mips64"}, {Abi::kUniversal, "universal"},
|
||||
};
|
||||
|
||||
constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
|
||||
@@ -102,7 +103,13 @@ class NamespaceVisitor : public xml::Visitor {
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace configuration {
|
||||
|
||||
const std::string& AbiToString(Abi abi) {
|
||||
return kAbiToStringMap.find(abi)->second;
|
||||
}
|
||||
|
||||
} // namespace configuration
|
||||
|
||||
/** Returns a ConfigurationParser for the file located at the provided path. */
|
||||
Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
|
||||
@@ -175,6 +182,9 @@ Maybe<Configuration> ConfigurationParser::Parse() {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: Validate all references in the configuration are valid. It should be safe to assume from
|
||||
// this point on that any references from one section to another will be present.
|
||||
|
||||
return {config};
|
||||
}
|
||||
|
||||
@@ -201,7 +211,7 @@ ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
|
||||
DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value);
|
||||
}
|
||||
}
|
||||
config->artifacts[artifact.name] = artifact;
|
||||
config->artifacts.push_back(artifact);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -236,7 +246,7 @@ ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
|
||||
for (auto& node : child->children) {
|
||||
xml::Text* t;
|
||||
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
|
||||
group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string()));
|
||||
group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ enum class Abi {
|
||||
kUniversal
|
||||
};
|
||||
|
||||
/** Helper method to convert an ABI to a string representing the path within the APK. */
|
||||
const std::string& AbiToString(Abi abi);
|
||||
|
||||
/**
|
||||
* Represents an individual locale. When a locale is included, it must be
|
||||
* declared from least specific to most specific, as a region does not make
|
||||
@@ -118,7 +121,8 @@ struct GlTexture {
|
||||
* AAPT2 XML configuration binary representation.
|
||||
*/
|
||||
struct Configuration {
|
||||
std::unordered_map<std::string, Artifact> artifacts;
|
||||
// TODO: Support named artifacts?
|
||||
std::vector<Artifact> artifacts;
|
||||
Maybe<std::string> artifact_format;
|
||||
|
||||
Group<Abi> abi_groups;
|
||||
|
||||
@@ -196,7 +196,7 @@ TEST_F(ConfigurationParserTest, ArtifactAction) {
|
||||
|
||||
EXPECT_EQ(1ul, config.artifacts.size());
|
||||
|
||||
auto& artifact = config.artifacts.begin()->second;
|
||||
auto& artifact = config.artifacts.front();
|
||||
EXPECT_EQ("", artifact.name); // TODO: make this fail.
|
||||
EXPECT_EQ("arm", artifact.abi_group.value());
|
||||
EXPECT_EQ("large", artifact.screen_density_group.value());
|
||||
@@ -204,6 +204,21 @@ TEST_F(ConfigurationParserTest, ArtifactAction) {
|
||||
EXPECT_EQ("19", artifact.android_sdk_group.value());
|
||||
EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
|
||||
EXPECT_EQ("low-latency", artifact.device_feature_group.value());
|
||||
|
||||
// Perform a second action to ensure we get 2 artifacts.
|
||||
static constexpr const char* second = R"xml(
|
||||
<artifact
|
||||
abi-group="other"
|
||||
screen-density-group="large"
|
||||
locale-group="europe"
|
||||
android-sdk-group="19"
|
||||
gl-texture-group="dxt1"
|
||||
device-feature-group="low-latency"/>)xml";
|
||||
doc = test::BuildXmlDom(second);
|
||||
|
||||
ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
EXPECT_EQ(2ul, config.artifacts.size());
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
|
||||
|
||||
51
tools/aapt2/filter/AbiFilter.cpp
Normal file
51
tools/aapt2/filter/AbiFilter.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 "AbiFilter.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "io/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) {
|
||||
std::unordered_set<std::string> abi_set;
|
||||
for (auto& abi : abi_list) {
|
||||
abi_set.insert(configuration::AbiToString(abi));
|
||||
}
|
||||
// Make unique by hand as the constructor is private.
|
||||
return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set));
|
||||
}
|
||||
|
||||
bool AbiFilter::Keep(const std::string& path) {
|
||||
// We only care about libraries.
|
||||
if (!util::StartsWith(path, kLibPrefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto abi_end = path.find('/', kLibPrefixLen);
|
||||
if (abi_end == std::string::npos) {
|
||||
// Ignore any files in the top level lib directory.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Strip the lib/ prefix.
|
||||
const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
|
||||
return (abis_.find(path_abi) != abis_.end());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
54
tools/aapt2/filter/AbiFilter.h
Normal file
54
tools/aapt2/filter/AbiFilter.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 AAPT2_ABISPLITTER_H
|
||||
#define AAPT2_ABISPLITTER_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "configuration/ConfigurationParser.h"
|
||||
#include "filter/Filter.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Filters native library paths by ABI. ABIs present in the filter list are kept and all over
|
||||
* libraries are removed. The filter is only applied to native library paths (this under lib/).
|
||||
*/
|
||||
class AbiFilter : public IPathFilter {
|
||||
public:
|
||||
/** Factory method to create a filter from a list of configuration::Abi. */
|
||||
static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list);
|
||||
|
||||
/** Returns true if the path is for a native library in the list of desired ABIs. */
|
||||
bool Keep(const std::string& path) override;
|
||||
|
||||
private:
|
||||
explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) {
|
||||
}
|
||||
|
||||
/** The path prefix to where all native libs end up inside an APK file. */
|
||||
static constexpr const char* kLibPrefix = "lib/";
|
||||
static constexpr size_t kLibPrefixLen = 4;
|
||||
const std::unordered_set<std::string> abis_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT2_ABISPLITTER_H
|
||||
66
tools/aapt2/filter/AbiFilter_test.cpp
Normal file
66
tools/aapt2/filter/AbiFilter_test.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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/AbiFilter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace {
|
||||
|
||||
using ::aapt::configuration::Abi;
|
||||
|
||||
struct TestData {
|
||||
std::string path;
|
||||
bool kept;
|
||||
};
|
||||
|
||||
const TestData kTestData[] = {
|
||||
/* Keep. */
|
||||
{"lib/mips/libnative.so", true},
|
||||
{"not/native/file.txt", true},
|
||||
// Not sure if this is a valid use case.
|
||||
{"lib/listing.txt", true},
|
||||
{"lib/mips/foo/bar/baz.so", true},
|
||||
{"lib/mips/x86/foo.so", true},
|
||||
/* Discard. */
|
||||
{"lib/mips_horse/foo.so", false},
|
||||
{"lib/horse_mips/foo.so", false},
|
||||
{"lib/mips64/armeabi-v7a/foo.so", false},
|
||||
{"lib/mips64/x86_64/x86.so", false},
|
||||
{"lib/x86/libnative.so", false},
|
||||
{"lib/x86/foo/bar/baz.so", false},
|
||||
{"lib/x86/x86/foo.so", false},
|
||||
{"lib/x86_horse/foo.so", false},
|
||||
{"lib/horse_x86/foo.so", false},
|
||||
{"lib/x86/armeabi-v7a/foo.so", false},
|
||||
{"lib/x86_64/x86_64/x86.so", false},
|
||||
};
|
||||
|
||||
class AbiFilterTest : public ::testing::TestWithParam<TestData> {};
|
||||
|
||||
TEST_P(AbiFilterTest, Keep) {
|
||||
auto mips = AbiFilter::FromAbiList({Abi::kMips});
|
||||
const TestData& data = GetParam();
|
||||
EXPECT_EQ(mips->Keep(data.path), data.kept);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(NativePaths, AbiFilterTest, ::testing::ValuesIn(kTestData));
|
||||
|
||||
} // namespace
|
||||
} // namespace aapt
|
||||
77
tools/aapt2/filter/Filter.h
Normal file
77
tools/aapt2/filter/Filter.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 AAPT2_FILTER_H
|
||||
#define AAPT2_FILTER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/** A filter to be applied to a path segment. */
|
||||
class IPathFilter {
|
||||
public:
|
||||
~IPathFilter() = default;
|
||||
|
||||
/** Returns true if the path should be kept. */
|
||||
virtual bool Keep(const std::string& path) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Path filter that keeps anything that matches the provided prefix.
|
||||
*/
|
||||
class PrefixFilter : public IPathFilter {
|
||||
public:
|
||||
explicit PrefixFilter(std::string prefix) : prefix_(std::move(prefix)) {
|
||||
}
|
||||
|
||||
/** Returns true if the provided path matches the prefix. */
|
||||
bool Keep(const std::string& path) override {
|
||||
return util::StartsWith(path, prefix_);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string prefix_;
|
||||
};
|
||||
|
||||
/** Applies a set of IPathFilters to a path and returns true iif all filters keep the path. */
|
||||
class FilterChain : public IPathFilter {
|
||||
public:
|
||||
/** Adds a filter to the list to be applied to each path. */
|
||||
void AddFilter(std::unique_ptr<IPathFilter> filter) {
|
||||
filters_.push_back(std::move(filter));
|
||||
}
|
||||
|
||||
/** Returns true if all filters keep the path. */
|
||||
bool Keep(const std::string& path) override {
|
||||
for (auto& filter : filters_) {
|
||||
if (!filter->Keep(path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<IPathFilter>> filters_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT2_FILTER_H
|
||||
54
tools/aapt2/filter/Filter_test.cpp
Normal file
54
tools/aapt2/filter/Filter_test.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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/Filter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "io/Util.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace {
|
||||
|
||||
TEST(FilterChainTest, EmptyChain) {
|
||||
FilterChain chain;
|
||||
ASSERT_TRUE(chain.Keep("some/random/path"));
|
||||
}
|
||||
|
||||
TEST(FilterChainTest, SingleFilter) {
|
||||
FilterChain chain;
|
||||
chain.AddFilter(util::make_unique<PrefixFilter>("keep/"));
|
||||
|
||||
ASSERT_FALSE(chain.Keep("removed/path"));
|
||||
ASSERT_TRUE(chain.Keep("keep/path/1"));
|
||||
ASSERT_TRUE(chain.Keep("keep/path/2"));
|
||||
}
|
||||
|
||||
TEST(FilterChainTest, MultipleFilters) {
|
||||
FilterChain chain;
|
||||
chain.AddFilter(util::make_unique<PrefixFilter>("keep/"));
|
||||
chain.AddFilter(util::make_unique<PrefixFilter>("keep/really/"));
|
||||
|
||||
ASSERT_FALSE(chain.Keep("removed/path"));
|
||||
ASSERT_FALSE(chain.Keep("/keep/really/wrong/prefix"));
|
||||
ASSERT_FALSE(chain.Keep("keep/maybe/1"));
|
||||
ASSERT_TRUE(chain.Keep("keep/really/1"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace aapt
|
||||
Reference in New Issue
Block a user