diff options
Diffstat (limited to 'tools/aapt2/cmd/Compile.cpp')
-rw-r--r-- | tools/aapt2/cmd/Compile.cpp | 411 |
1 files changed, 191 insertions, 220 deletions
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 411ad747faa3..fc9514a691d2 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -14,8 +14,9 @@ * limitations under the License. */ -#include <dirent.h> +#include "Compile.h" +#include <dirent.h> #include <string> #include "android-base/errors.h" @@ -27,9 +28,9 @@ #include "google/protobuf/io/zero_copy_stream_impl_lite.h" #include "Diagnostics.h" -#include "Flags.h" #include "ResourceParser.h" #include "ResourceTable.h" +#include "cmd/Util.h" #include "compile/IdAssigner.h" #include "compile/InlineXmlFormatParser.h" #include "compile/Png.h" @@ -40,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" @@ -71,9 +74,9 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep, std::string* out_error) { - std::vector<std::string> parts = util::Split(path, file::sDirSep); + std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; return {}; @@ -121,16 +124,6 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, extension.to_string(), config_str.to_string(), config}; } -struct CompileOptions { - std::string output_path; - Maybe<std::string> res_dir; - Maybe<std::string> generate_text_symbols_path; - bool pseudolocalize = false; - bool no_png_crunch = false; - bool legacy_mode = false; - bool verbose = false; -}; - static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) { std::stringstream name; name << data.resource_dir; @@ -145,81 +138,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; @@ -227,8 +159,12 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // If the filename includes donottranslate, then the default translatable is false. parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos; + // If visibility was forced, we need to use it when creating a new resource and also error if + // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags. + 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; } @@ -294,36 +230,48 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, Printer r_txt_printer(&fout_text); for (const auto& package : table.packages) { - for (const auto& type : package->types) { - for (const auto& entry : type->entries) { - // Check access modifiers. - switch(entry->visibility.level) { - case Visibility::Level::kUndefined : - r_txt_printer.Print("default "); - break; - case Visibility::Level::kPublic : - r_txt_printer.Print("public "); - break; - case Visibility::Level::kPrivate : - r_txt_printer.Print("private "); - } + // Only print resources defined locally, e.g. don't write android attributes. + if (package->name.empty()) { + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + // Check access modifiers. + switch (entry->visibility.level) { + case Visibility::Level::kUndefined : + r_txt_printer.Print("default "); + break; + case Visibility::Level::kPublic : + r_txt_printer.Print("public "); + break; + case Visibility::Level::kPrivate : + r_txt_printer.Print("private "); + } - if (type->type != ResourceType::kStyleable) { - r_txt_printer.Print("int "); - r_txt_printer.Print(to_string(type->type)); - r_txt_printer.Print(" "); - r_txt_printer.Println(entry->name); - } else { - r_txt_printer.Print("int[] styleable "); - r_txt_printer.Println(entry->name); - - if (!entry->values.empty()) { - auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get()); - for (const auto& attr : styleable->entries) { - r_txt_printer.Print("default int styleable "); - r_txt_printer.Print(entry->name); - r_txt_printer.Print("_"); - r_txt_printer.Println(attr.name.value().entry); + if (type->type != ResourceType::kStyleable) { + r_txt_printer.Print("int "); + r_txt_printer.Print(to_string(type->type)); + r_txt_printer.Print(" "); + r_txt_printer.Println(entry->name); + } else { + r_txt_printer.Print("int[] styleable "); + r_txt_printer.Println(entry->name); + + if (!entry->values.empty()) { + auto styleable = + static_cast<const Styleable*>(entry->values.front()->value.get()); + for (const auto& attr : styleable->entries) { + // The visibility of the children under the styleable does not matter as they are + // nested under their parent and use its visibility. + r_txt_printer.Print("default int styleable "); + r_txt_printer.Print(entry->name); + // If the package name is present, also include it in the mangled name (e.g. + // "android") + if (!attr.name.value().package.empty()) { + r_txt_printer.Print("_"); + r_txt_printer.Print(MakePackageSafeName(attr.name.value().package)); + } + r_txt_printer.Print("_"); + r_txt_printer.Println(attr.name.value().entry); + } } } } @@ -402,7 +350,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"); @@ -410,18 +358,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); @@ -502,7 +449,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"); @@ -516,15 +463,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); @@ -592,7 +541,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, {})) { @@ -606,41 +555,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 { @@ -695,79 +634,33 @@ class CompileContext : public IAaptContext { bool verbose_ = false; }; -// Entry point for compilation phase. Parses arguments and dispatches to the correct steps. -int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { - CompileContext context(diagnostics); - CompileOptions options; - - bool verbose = false; - Flags flags = - Flags() - .RequiredFlag("-o", "Output path", &options.output_path) - .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir) - .OptionalFlag("--output-text-symbols", - "Generates a text file containing the resource symbols in the\n" - "specified file", - &options.generate_text_symbols_path) - .OptionalSwitch("--pseudo-localize", - "Generate resources for pseudo-locales " - "(en-XA and ar-XB)", - &options.pseudolocalize) - .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch) - .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings", - &options.legacy_mode) - .OptionalSwitch("-v", "Enables verbose logging", &verbose); - if (!flags.Parse("aapt2 compile", args, &std::cerr)) { - return 1; - } - - context.SetVerbose(verbose); - - std::unique_ptr<IArchiveWriter> archive_writer; - - std::vector<ResourcePathData> input_data; - if (options.res_dir) { - if (!flags.GetArgs().empty()) { - // Can't have both files and a resource directory. - context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); - flags.Usage("aapt2 compile", &std::cerr); - return 1; - } - - if (!LoadInputFilesFromDir(&context, options, &input_data)) { - return 1; - } - - archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path); +int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer, + CompileOptions& options) { + bool error = false; - } else { - input_data.reserve(flags.GetArgs().size()); + // 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; - // Collect data from the path for each input file. - for (const std::string& arg : flags.GetArgs()) { - 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; - } + // Skip hidden input files + if (file::IsHidden(path)) { + continue; } - archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path); - } - - if (!archive_writer) { - return 1; - } - - bool error = false; - for (ResourcePathData& path_data : input_data) { - if (options.verbose) { - context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing"); + if (!options.res_zip && !IsValidFile(context, path)) { + error = true; + continue; } - if (!IsValidFile(&context, path_data.source.path)) { + // Extract resource type information from the full path + std::string err_str; + ResourcePathData path_data; + if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) { + path_data = maybe_path_data.value(); + } else { + context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); error = true; continue; } @@ -785,13 +678,13 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { if (path_data.extension == "xml") { compile_func = &CompileXml; } else if ((!options.no_png_crunch && path_data.extension == "png") - || path_data.extension == "9.png") { + || path_data.extension == "9.png") { compile_func = &CompilePng; } } } else { - context.GetDiagnostics()->Error(DiagMessage() - << "invalid file path '" << path_data.source << "'"); + context->GetDiagnostics()->Error(DiagMessage() + << "invalid file path '" << path_data.source << "'"); error = true; continue; } @@ -801,17 +694,95 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { 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"); + context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "file 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); + 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); + + if (visibility_) { + if (visibility_.value() == "public") { + options_.visibility = Visibility::Level::kPublic; + } else if (visibility_.value() == "private") { + options_.visibility = Visibility::Level::kPrivate; + } else if (visibility_.value() == "default") { + options_.visibility = Visibility::Level::kUndefined; + } else { + context.GetDiagnostics()->Error( + DiagMessage() << "Unrecognized visibility level passes to --visibility: '" + << visibility_.value() << "'. Accepted levels: public, private, default"); + return 1; + } + } + + std::unique_ptr<io::IFileCollection> file_collection; + std::unique_ptr<IArchiveWriter> archive_writer; + + // 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()) { + context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); + Usage(&std::cerr); + return 1; + } + + // 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 { + auto collection = util::make_unique<io::FileCollection>(); + + // Collect data from the path for each input file. + for (const std::string& arg : args) { + collection->InsertFile(arg); + } + + file_collection = std::move(collection); + archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path); + } + + if (!archive_writer) { + return 1; + } + + return Compile(&context, file_collection.get(), archive_writer.get(), options_); +} + } // namespace aapt |