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
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 1dd46ba..2d786dd 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -184,10 +184,7 @@
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()) {
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 0f312d6..92934c3 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -31,12 +31,16 @@
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 @@
}
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;
}
};
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 2ba2cf7..62c19fb 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -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 @@
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 @@
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 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 @@
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 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 @@
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 @@
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 @@
}
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 @@
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 @@
}
}
+ 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 @@
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
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 4151952..c429d5f 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -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 @@
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 @@
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 @@
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 @@
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 @@
Maybe<std::string> visibility_;
};
+int Compile(IAaptContext* context, io::IFileCollection* inputs,
+ IArchiveWriter* output_writer, CompileOptions& options);
}// namespace aapt
#endif //AAPT2_COMPILE_H
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index d21addf..dd5198c 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -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 @@
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 @@
ASSERT_EQ(remove(path5_out.c_str()), 0);
}
-}
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index d57eaa1..86b1f4c 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -105,10 +105,7 @@
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 ||
diff --git a/tools/aapt2/integration-tests/CompileTest/DirInput/res/drawable/image.png b/tools/aapt2/integration-tests/CompileTest/DirInput/res/drawable/image.png
new file mode 100644
index 0000000..1a3731b
--- /dev/null
+++ b/tools/aapt2/integration-tests/CompileTest/DirInput/res/drawable/image.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/CompileTest/DirInput/res/layout/layout.xml b/tools/aapt2/integration-tests/CompileTest/DirInput/res/layout/layout.xml
new file mode 100644
index 0000000..e5835ed
--- /dev/null
+++ b/tools/aapt2/integration-tests/CompileTest/DirInput/res/layout/layout.xml
@@ -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" />
diff --git a/tools/aapt2/integration-tests/CompileTest/DirInput/res/values/values.xml b/tools/aapt2/integration-tests/CompileTest/DirInput/res/values/values.xml
new file mode 100644
index 0000000..62ab652
--- /dev/null
+++ b/tools/aapt2/integration-tests/CompileTest/DirInput/res/values/values.xml
@@ -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>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/CompileTest/ZipInput/res.zip b/tools/aapt2/integration-tests/CompileTest/ZipInput/res.zip
new file mode 100644
index 0000000..00e396d
--- /dev/null
+++ b/tools/aapt2/integration-tests/CompileTest/ZipInput/res.zip
Binary files differ
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 1387d22..16a20f4c 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -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 @@
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();
}
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index 6be8807..fb6bf6e 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -59,6 +59,10 @@
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;
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 269b6c5..8e6d713 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -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::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 @@
if (out_error) *out_error = ErrorCodeString(result);
return {};
}
+
return collection;
}
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 5a8ff09..7cd023b 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -149,6 +149,10 @@
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);
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index b26e4fa..219e1a0 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -70,6 +70,9 @@
// 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);