AAPT2: Compile --zip flag

Added a --zip flag similar to --dir that allows resources to be passed
into "aapt2 compile" using a zip file.

Also refactored Compile.cpp to be easier to mock and test in the future.

Bug: 74574557
Test: aapt2_tests
Change-Id: Idb90cb97e23a219525bdead38220cbf7bc6f3cab
This commit is contained in:
Ryan Mitchell
2018-08-02 16:16:45 -07:00
parent 196a2956b2
commit f3649d6690
15 changed files with 318 additions and 181 deletions

View File

@@ -184,10 +184,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table
std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
while (iterator->HasNext()) {
io::IFile* file = iterator->Next();
std::string path = file->GetSource().path;
// The name of the path has the format "<zip-file-name>@<path-to-file>".
path = path.substr(path.find('@') + 1);
// Skip resources that are not referenced if requested.
if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) {

View File

@@ -31,12 +31,16 @@ namespace aapt {
struct Source {
std::string path;
Maybe<size_t> line;
Maybe<std::string> archive;
Source() = default;
inline Source(const android::StringPiece& path) : path(path.to_string()) { // NOLINT(implicit)
}
inline Source(const android::StringPiece& path, const android::StringPiece& archive)
: path(path.to_string()), archive(archive.to_string()) {}
inline Source(const android::StringPiece& path, size_t line)
: path(path.to_string()), line(line) {}
@@ -45,10 +49,14 @@ struct Source {
}
std::string to_string() const {
if (line) {
return ::android::base::StringPrintf("%s:%zd", path.c_str(), line.value());
std::string s = path;
if (archive) {
s = ::android::base::StringPrintf("%s@%s", archive.value().c_str(), s.c_str());
}
return path;
if (line) {
s = ::android::base::StringPrintf("%s:%zd", s.c_str(), line.value());
}
return s;
}
};

View File

@@ -41,8 +41,10 @@
#include "format/proto/ProtoSerialize.h"
#include "io/BigBufferStream.h"
#include "io/FileStream.h"
#include "io/FileSystem.h"
#include "io/StringStream.h"
#include "io/Util.h"
#include "io/ZipArchive.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
@@ -135,81 +137,20 @@ static std::string BuildIntermediateContainerFilename(const ResourcePathData& da
return name.str();
}
static bool IsHidden(const StringPiece& filename) {
return util::StartsWith(filename, ".");
}
// Walks the res directory structure, looking for resource files.
static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
std::vector<ResourcePathData>* out_path_data) {
const std::string& root_dir = options.res_dir.value();
std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
if (!d) {
context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
<< SystemErrorCodeToString(errno));
return false;
}
while (struct dirent* entry = readdir(d.get())) {
if (IsHidden(entry->d_name)) {
continue;
}
std::string prefix_path = root_dir;
file::AppendPath(&prefix_path, entry->d_name);
if (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
continue;
}
std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
if (!subdir) {
context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
<< SystemErrorCodeToString(errno));
return false;
}
while (struct dirent* leaf_entry = readdir(subdir.get())) {
if (IsHidden(leaf_entry->d_name)) {
continue;
}
std::string full_path = prefix_path;
file::AppendPath(&full_path, leaf_entry->d_name);
std::string err_str;
Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
if (!path_data) {
context->GetDiagnostics()->Error(DiagMessage(full_path) << err_str);
return false;
}
out_path_data->push_back(std::move(path_data.value()));
}
}
// File-system directory enumeration order is platform-dependent. Sort the result to remove any
// inconsistencies between platforms.
std::sort(
out_path_data->begin(), out_path_data->end(),
[](const ResourcePathData& a, const ResourcePathData& b) { return a.source < b.source; });
return true;
}
static bool CompileTable(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data, IArchiveWriter* writer,
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
ResourceTable table;
{
FileInputStream fin(path_data.source.path);
if (fin.HadError()) {
auto fin = file->OpenInputStream();
if (fin->HadError()) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
<< "failed to open file: " << fin.GetError());
<< "failed to open file: " << fin->GetError());
return false;
}
// Parse the values file from XML.
xml::XmlPullParser xml_parser(&fin);
xml::XmlPullParser xml_parser(fin.get());
ResourceParserOptions parser_options;
parser_options.error_on_positional_arguments = !options.legacy_mode;
@@ -222,7 +163,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
parser_options.visibility = options.visibility;
ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
parser_options);
parser_options);
if (!res_parser.Parse(&xml_parser)) {
return false;
}
@@ -408,7 +349,7 @@ static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
}
static bool CompileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data, IArchiveWriter* writer,
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
@@ -416,18 +357,17 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
std::unique_ptr<xml::XmlResource> xmlres;
{
FileInputStream fin(path_data.source.path);
if (fin.HadError()) {
auto fin = file->OpenInputStream();
if (fin->HadError()) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
<< "failed to open file: " << fin.GetError());
<< "failed to open file: " << fin->GetError());
return false;
}
xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
}
if (!xmlres) {
return false;
xmlres = xml::Inflate(fin.get(), context->GetDiagnostics(), path_data.source);
if (!xmlres) {
return false;
}
}
xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
@@ -508,7 +448,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
}
static bool CompilePng(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data, IArchiveWriter* writer,
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
@@ -522,15 +462,17 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
res_file.type = ResourceFile::Type::kPng;
{
std::string content;
if (!android::base::ReadFileToString(path_data.source.path, &content,
true /*follow_symlinks*/)) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
<< "failed to open file: "
<< SystemErrorCodeToString(errno));
auto data = file->OpenAsData();
if (!data) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file ");
return false;
}
// Read the file as a string
char buffer_2[data->size()];
memcpy(&buffer_2, data->data(), data->size());
StringPiece content(buffer_2, data->size());
BigBuffer crunched_png_buffer(4096);
io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
@@ -598,7 +540,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
if (context->IsVerbose()) {
// For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
// This will help catch exotic cases where the new code may generate larger PNGs.
std::stringstream legacy_stream(content);
std::stringstream legacy_stream(content.to_string());
BigBuffer legacy_buffer(4096);
Png png(context->GetDiagnostics());
if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
@@ -612,41 +554,31 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
}
io::BigBufferInputStream buffer_in(&buffer);
if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
context->GetDiagnostics())) {
return false;
}
return true;
return WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
context->GetDiagnostics());
}
static bool CompileFile(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data, IArchiveWriter* writer,
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
}
BigBuffer buffer(256);
ResourceFile res_file;
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
res_file.config = path_data.config;
res_file.source = path_data.source;
res_file.type = ResourceFile::Type::kUnknown;
std::string error_str;
Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
if (!f) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
<< error_str);
auto data = file->OpenAsData();
if (!data) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file ");
return false;
}
io::MmappedData mmapped_in(std::move(f.value()));
if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer,
context->GetDiagnostics())) {
return false;
}
return true;
return WriteHeaderAndDataToWriter(output_path, res_file, data.get(), writer,
context->GetDiagnostics());
}
class CompileContext : public IAaptContext {
@@ -701,6 +633,79 @@ class CompileContext : public IAaptContext {
bool verbose_ = false;
};
int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer,
CompileOptions& options) {
bool error = false;
// Iterate over the input files in a stable, platform-independent manner
auto file_iterator = inputs->Iterator();
while (file_iterator->HasNext()) {
auto file = file_iterator->Next();
std::string path = file->GetSource().path;
// Skip hidden input files
if (file::IsHidden(path)) {
continue;
}
if (!options.res_zip && !IsValidFile(context, path)) {
error = true;
continue;
}
// Extract resource type information from the full path
std::string err_str;
ResourcePathData path_data;
if (auto maybe_path_data = ExtractResourcePathData(path, &err_str)) {
path_data = maybe_path_data.value();
} else {
context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str);
error = true;
continue;
}
// Determine how to compile the file based on its type.
auto compile_func = &CompileFile;
if (path_data.resource_dir == "values" && path_data.extension == "xml") {
compile_func = &CompileTable;
// We use a different extension (not necessary anymore, but avoids altering the existing
// build system logic).
path_data.extension = "arsc";
} else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
if (*type != ResourceType::kRaw) {
if (path_data.extension == "xml") {
compile_func = &CompileXml;
} else if ((!options.no_png_crunch && path_data.extension == "png")
|| path_data.extension == "9.png") {
compile_func = &CompilePng;
}
}
} else {
context->GetDiagnostics()->Error(DiagMessage()
<< "invalid file path '" << path_data.source << "'");
error = true;
continue;
}
// Treat periods as a reserved character that should not be present in a file name
// Legacy support for AAPT which did not reserve periods
if (compile_func != &CompileFile && !options.legacy_mode
&& std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
error = true;
context->GetDiagnostics()->Error(DiagMessage(file->GetSource())
<< "file name cannot contain '.' other than for"
<< " specifying the extension");
continue;
}
const std::string out_path = BuildIntermediateContainerFilename(path_data);
error |= !compile_func(context, options, path_data, file, output_writer, out_path);
}
return error ? 1 : 0;
}
int CompileCommand::Action(const std::vector<std::string>& args) {
CompileContext context(diagnostic_);
context.SetVerbose(options_.verbose);
@@ -720,37 +725,55 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
}
}
std::unique_ptr<io::IFileCollection> file_collection;
std::unique_ptr<IArchiveWriter> archive_writer;
std::vector<ResourcePathData> input_data;
if (options_.res_dir) {
// Collect the resources files to compile
if (options_.res_dir && options_.res_zip) {
context.GetDiagnostics()->Error(DiagMessage()
<< "only one of --dir and --zip can be specified");
return 1;
} else if (options_.res_dir) {
if (!args.empty()) {
// Can't have both files and a resource directory.
context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
Usage(&std::cerr);
return 1;
}
if (!LoadInputFilesFromDir(&context, options_, &input_data)) {
// Load the files from the res directory
std::string err;
file_collection = io::FileCollection::Create(options_.res_dir.value(), &err);
if (!file_collection) {
context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err);
return 1;
}
archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
} else if (options_.res_zip) {
if (!args.empty()) {
context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified");
Usage(&std::cerr);
return 1;
}
// Load a zip file containing a res directory
std::string err;
file_collection = io::ZipFileCollection::Create(options_.res_zip.value(), &err);
if (!file_collection) {
context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err);
return 1;
}
archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
} else {
input_data.reserve(args.size());
auto collection = util::make_unique<io::FileCollection>();
// Collect data from the path for each input file.
for (const std::string& arg : args) {
std::string error_str;
if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
input_data.push_back(std::move(path_data.value()));
} else {
context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
return 1;
}
collection->InsertFile(arg);
}
file_collection = std::move(collection);
archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
}
@@ -758,57 +781,7 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
return 1;
}
bool error = false;
for (ResourcePathData& path_data : input_data) {
if (options_.verbose) {
context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
}
if (!IsValidFile(&context, path_data.source.path)) {
error = true;
continue;
}
// Determine how to compile the file based on its type.
auto compile_func = &CompileFile;
if (path_data.resource_dir == "values" && path_data.extension == "xml") {
compile_func = &CompileTable;
// We use a different extension (not necessary anymore, but avoids altering the existing
// build system logic).
path_data.extension = "arsc";
} else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
if (*type != ResourceType::kRaw) {
if (path_data.extension == "xml") {
compile_func = &CompileXml;
} else if ((!options_.no_png_crunch && path_data.extension == "png")
|| path_data.extension == "9.png") {
compile_func = &CompilePng;
}
}
} else {
context.GetDiagnostics()->Error(DiagMessage()
<< "invalid file path '" << path_data.source << "'");
error = true;
continue;
}
// Treat periods as a reserved character that should not be present in a file name
// Legacy support for AAPT which did not reserve periods
if (compile_func != &CompileFile && !options_.legacy_mode
&& std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
error = true;
context.GetDiagnostics()->Error(DiagMessage() << "resource file '" << path_data.source.path
<< "' name cannot contain '.' other than for"
<< "specifying the extension");
continue;
}
// Compile the file.
const std::string out_path = BuildIntermediateContainerFilename(path_data);
error |= !compile_func(&context, options_, path_data, archive_writer.get(), out_path);
}
return error ? 1 : 0;
return Compile(&context, file_collection.get(), archive_writer.get(), options_);
}
} // namespace aapt

View File

@@ -18,7 +18,8 @@
#define AAPT2_COMPILE_H
#include "androidfw/StringPiece.h"
#include "format/Archive.h"
#include "process/IResourceTableConsumer.h"
#include "Command.h"
#include "Diagnostics.h"
#include "ResourceTable.h"
@@ -28,6 +29,7 @@ namespace aapt {
struct CompileOptions {
std::string output_path;
Maybe<std::string> res_dir;
Maybe<std::string> res_zip;
Maybe<std::string> generate_text_symbols_path;
Maybe<Visibility::Level> visibility;
bool pseudolocalize = false;
@@ -36,6 +38,7 @@ struct CompileOptions {
bool verbose = false;
};
/** Parses flags and compiles resources to be used in linking. */
class CompileCommand : public Command {
public:
explicit CompileCommand(IDiagnostics* diagnostic) : Command("compile", "c"),
@@ -43,6 +46,8 @@ class CompileCommand : public Command {
SetDescription("Compiles resources to be linked into an apk.");
AddRequiredFlag("-o", "Output path", &options_.output_path);
AddOptionalFlag("--dir", "Directory to scan for resources", &options_.res_dir);
AddOptionalFlag("--zip", "Zip file containing the res directory to scan for resources",
&options_.res_zip);
AddOptionalFlag("--output-text-symbols",
"Generates a text file containing the resource symbols in the\n"
"specified file", &options_.generate_text_symbols_path);
@@ -51,10 +56,10 @@ class CompileCommand : public Command {
AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options_.legacy_mode);
AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
AddOptionalFlag("--visibility",
"Sets the visibility of the compiled resources to the specified\n"
"level. Accepted levels: public, private, default", &visibility_);
AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
}
int Action(const std::vector<std::string>& args) override;
@@ -65,6 +70,8 @@ class CompileCommand : public Command {
Maybe<std::string> visibility_;
};
int Compile(IAaptContext* context, io::IFileCollection* inputs,
IArchiveWriter* output_writer, CompileOptions& options);
}// namespace aapt
#endif //AAPT2_COMPILE_H

View File

@@ -18,6 +18,7 @@
#include "android-base/file.h"
#include "io/StringStream.h"
#include "io/ZipArchive.h"
#include "java/AnnotationProcessor.h"
#include "test/Test.h"
@@ -29,7 +30,6 @@ int TestCompile(const std::string& path, const std::string& outDir, bool legacy,
args.push_back(path);
args.push_back("-o");
args.push_back(outDir);
args.push_back("-v");
if (legacy) {
args.push_back("--legacy");
}
@@ -94,4 +94,56 @@ TEST(CompilerTest, MultiplePeriods) {
ASSERT_EQ(remove(path5_out.c_str()), 0);
}
}
TEST(CompilerTest, DirInput) {
StdErrDiagnostics diag;
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const std::string kResDir = android::base::Dirname(android::base::GetExecutablePath())
+ "/integration-tests/CompileTest/DirInput/res";
const std::string kOutputFlata = android::base::Dirname(android::base::GetExecutablePath())
+ "/integration-tests/CompileTest/DirInput/compiled.flata";
remove(kOutputFlata.c_str());
std::vector<android::StringPiece> args;
args.push_back("--dir");
args.push_back(kResDir);
args.push_back("-o");
args.push_back(kOutputFlata);
ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0);
// Check for the presence of the compiled files
std::string err;
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err);
ASSERT_NE(zip, nullptr) << err;
ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr);
ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr);
ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr);
ASSERT_EQ(remove(kOutputFlata.c_str()), 0);
}
TEST(CompilerTest, ZipInput) {
StdErrDiagnostics diag;
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const std::string kResZip = android::base::Dirname(android::base::GetExecutablePath())
+ "/integration-tests/CompileTest/ZipInput/res.zip";
const std::string kOutputFlata = android::base::Dirname(android::base::GetExecutablePath())
+ "/integration-tests/CompileTest/ZipInput/compiled.flata";
remove(kOutputFlata.c_str());
std::vector<android::StringPiece> args;
args.push_back("--zip");
args.push_back(kResZip);
args.push_back("-o");
args.push_back(kOutputFlata);
ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0);
// Check for the presence of the compiled files
std::string err;
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err);
ASSERT_NE(zip, nullptr) << err;
ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr);
ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr);
ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr);
ASSERT_EQ(remove(kOutputFlata.c_str()), 0);
}
} // namespace aapt

View File

@@ -105,10 +105,7 @@ bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer
std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
while (iterator->HasNext()) {
io::IFile* file = iterator->Next();
std::string path = file->GetSource().path;
// The name of the path has the format "<zip-file-name>@<path-to-file>".
path = path.substr(path.find('@') + 1);
// Manifest, resource table and resources have already been taken care of.
if (path == kAndroidManifestPath ||

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<resources>
</resources>

View File

@@ -16,6 +16,9 @@
#include "io/FileSystem.h"
#include <dirent.h>
#include "android-base/errors.h"
#include "androidfw/StringPiece.h"
#include "utils/FileMap.h"
@@ -26,6 +29,7 @@
#include "util/Util.h"
using ::android::StringPiece;
using ::android::base::SystemErrorCodeToString;
namespace aapt {
namespace io {
@@ -64,6 +68,50 @@ IFile* FileCollectionIterator::Next() {
return result;
}
std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root,
std::string* outError) {
std::unique_ptr<FileCollection> collection =
std::unique_ptr<FileCollection>(new FileCollection());
std::unique_ptr<DIR, decltype(closedir) *> d(opendir(root.data()), closedir);
if (!d) {
*outError = "failed to open directory: " + SystemErrorCodeToString(errno);
return nullptr;
}
while (struct dirent *entry = readdir(d.get())) {
std::string prefix_path = root.to_string();
file::AppendPath(&prefix_path, entry->d_name);
// The directory to iterate over looking for files
if (file::GetFileType(prefix_path) != file::FileType::kDirectory
|| file::IsHidden(prefix_path)) {
continue;
}
std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
if (!subdir) {
*outError = "failed to open directory: " + SystemErrorCodeToString(errno);
return nullptr;
}
while (struct dirent* leaf_entry = readdir(subdir.get())) {
std::string full_path = prefix_path;
file::AppendPath(&full_path, leaf_entry->d_name);
// Do not add folders to the file collection
if (file::GetFileType(full_path) == file::FileType::kDirectory
|| file::IsHidden(full_path)) {
continue;
}
collection->InsertFile(full_path);
}
}
return collection;
}
IFile* FileCollection::InsertFile(const StringPiece& path) {
return (files_[path.to_string()] = util::make_unique<RegularFile>(Source(path))).get();
}

View File

@@ -59,6 +59,10 @@ class FileCollection : public IFileCollection {
public:
FileCollection() = default;
/** Creates a file collection containing all files contained in the specified root directory. */
static std::unique_ptr<FileCollection> Create(const android::StringPiece& path,
std::string* outError);
// Adds a file located at path. Returns the IFile representation of that file.
IFile* InsertFile(const android::StringPiece& path);
IFile* FindFile(const android::StringPiece& path) override;

View File

@@ -20,6 +20,7 @@
#include "ziparchive/zip_archive.h"
#include "Source.h"
#include "util/Files.h"
#include "util/Util.h"
using ::android::StringPiece;
@@ -121,9 +122,14 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
std::string zip_entry_path =
std::string(reinterpret_cast<const char*>(zip_entry_name.name),
zip_entry_name.name_length);
std::string nested_path = path.to_string() + "@" + zip_entry_path;
std::unique_ptr<IFile> file =
util::make_unique<ZipFile>(collection->handle_, zip_data, Source(nested_path));
// Do not add folders to the file collection
if (util::EndsWith(zip_entry_path, "/")) {
continue;
}
std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data,
Source(zip_entry_path, path.to_string()));
collection->files_by_name_[zip_entry_path] = file.get();
collection->files_.push_back(std::move(file));
}
@@ -132,6 +138,7 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
if (out_error) *out_error = ErrorCodeString(result);
return {};
}
return collection;
}

View File

@@ -149,6 +149,10 @@ StringPiece GetExtension(const StringPiece& path) {
return {};
}
bool IsHidden(const android::StringPiece& path) {
return util::StartsWith(GetFilename(path), ".");
}
void AppendPath(std::string* base, StringPiece part) {
CHECK(base != nullptr);
const bool base_has_trailing_sep = (!base->empty() && *(base->end() - 1) == sDirSep);

View File

@@ -70,6 +70,9 @@ android::StringPiece GetFilename(const android::StringPiece& path);
// of the path.
android::StringPiece GetExtension(const android::StringPiece& path);
// Returns whether or not the name of the file or directory is a hidden file name
bool IsHidden(const android::StringPiece& path);
// Converts a package name (com.android.app) to a path: com/android/app
std::string PackageToPath(const android::StringPiece& package);