diff options
Diffstat (limited to 'tools/aapt2/cmd')
-rw-r--r-- | tools/aapt2/cmd/Compile.cpp | 18 | ||||
-rw-r--r-- | tools/aapt2/cmd/Compile_test.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/cmd/Convert.cpp | 6 | ||||
-rw-r--r-- | tools/aapt2/cmd/Diff.cpp | 6 | ||||
-rw-r--r-- | tools/aapt2/cmd/Dump.cpp | 17 | ||||
-rw-r--r-- | tools/aapt2/cmd/Dump.h | 12 | ||||
-rw-r--r-- | tools/aapt2/cmd/Link.cpp | 199 | ||||
-rw-r--r-- | tools/aapt2/cmd/Link.h | 31 | ||||
-rw-r--r-- | tools/aapt2/cmd/Link_test.cpp | 244 | ||||
-rw-r--r-- | tools/aapt2/cmd/Optimize.cpp | 56 | ||||
-rw-r--r-- | tools/aapt2/cmd/Optimize.h | 17 | ||||
-rw-r--r-- | tools/aapt2/cmd/Optimize_test.cpp | 68 | ||||
-rw-r--r-- | tools/aapt2/cmd/Util.cpp | 4 | ||||
-rw-r--r-- | tools/aapt2/cmd/Util_test.cpp | 22 |
14 files changed, 594 insertions, 108 deletions
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 21719705838d..32686538c10d 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -630,6 +630,12 @@ class CompileContext : public IAaptContext { return 0; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "No Split Name Dependencies be needed in compile phase"; + static std::set<std::string> empty; + return empty; + } + private: DISALLOW_COPY_AND_ASSIGN(CompileContext); @@ -735,7 +741,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) { } 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) { @@ -756,8 +761,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) { 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"); @@ -772,8 +775,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) { 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>(); @@ -786,7 +787,14 @@ int CompileCommand::Action(const std::vector<std::string>& args) { } file_collection = std::move(collection); + } + + std::unique_ptr<IArchiveWriter> archive_writer; + file::FileType output_file_type = file::GetFileType(options_.output_path); + if (output_file_type == file::FileType::kDirectory) { archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path); + } else { + archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path); } if (!archive_writer) { diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index 5f637bd8d582..fb786a31360e 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -200,7 +200,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name, const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name); const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk"); - CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent)); + ctf->WriteFile(source_file, sTranslatableXmlContent); CHECK(file::mkdirs(compiled_files_dir.data())); ASSERT_EQ(CompileCommand(&diag).Execute({ diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 0cf86ccdd59f..22bcd8589ce9 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -243,6 +243,12 @@ class Context : public IAaptContext { return 0u; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + bool verbose_ = false; std::string package_; diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index 262f4fc4e394..d56994e3ae24 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -65,6 +65,12 @@ class DiffContext : public IAaptContext { return 0; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + private: std::string empty_; StdErrDiagnostics diagnostics_; diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index a23a6a46cf0f..3982d12f6036 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -118,6 +118,12 @@ class DumpContext : public IAaptContext { return 0; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + private: StdErrDiagnostics diagnostics_; bool verbose_ = false; @@ -388,6 +394,17 @@ int DumpXmlTreeCommand::Dump(LoadedApk* apk) { return 0; } +int DumpOverlayableCommand::Dump(LoadedApk* apk) { + ResourceTable* table = apk->GetResourceTable(); + if (!table) { + GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + return 1; + } + + Debug::DumpOverlayable(*table, GetPrinter()); + return 0; +} + const char DumpBadgerCommand::kBadgerData[2925] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h index 7ded9bcf8470..cd51f7a7718c 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -240,6 +240,16 @@ class DumpXmlTreeCommand : public DumpApkCommand { std::vector<std::string> files_; }; +class DumpOverlayableCommand : public DumpApkCommand { + public: + explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag) + : DumpApkCommand("overlayable", printer, diag) { + SetDescription("Print the <overlayable> resources of an APK."); + } + + int Dump(LoadedApk* apk) override; +}; + /** The default dump command. Performs no action because a subcommand is required. */ class DumpCommand : public Command { public: @@ -255,8 +265,8 @@ class DumpCommand : public Command { AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_)); + AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true); - // TODO(b/120609160): Add aapt2 overlayable dump command } int Action(const std::vector<std::string>& args) override { diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 85414fb8a4e5..72cb41a1b172 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -140,6 +140,14 @@ class LinkContext : public IAaptContext { min_sdk_version_ = minSdk; } + const std::set<std::string>& GetSplitNameDependencies() override { + return split_name_dependencies_; + } + + void SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) { + split_name_dependencies_ = split_name_dependencies; + } + private: DISALLOW_COPY_AND_ASSIGN(LinkContext); @@ -151,6 +159,7 @@ class LinkContext : public IAaptContext { SymbolTable symbols_; bool verbose_ = false; int min_sdk_version_ = 0; + std::set<std::string> split_name_dependencies_; }; // A custom delegate that generates compatible pre-O IDs for use with feature splits. @@ -269,6 +278,7 @@ struct ResourceFileFlattenerOptions { bool keep_raw_values = false; bool do_not_compress_anything = false; bool update_proguard_spec = false; + bool do_not_fail_on_missing_resources = false; OutputFormat output_format = OutputFormat::kApk; std::unordered_set<std::string> extensions_to_not_compress; Maybe<std::regex> regex_to_not_compress; @@ -297,6 +307,25 @@ struct R { }; }; +template <typename T> +uint32_t GetCompressionFlags(const StringPiece& str, T options) { + if (options.do_not_compress_anything) { + return 0; + } + + if (options.regex_to_not_compress + && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) { + return 0; + } + + for (const std::string& extension : options.extensions_to_not_compress) { + if (util::EndsWith(str, extension)) { + return 0; + } + } + return ArchiveEntry::kCompress; +} + class ResourceFileFlattener { public: ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context, @@ -321,8 +350,6 @@ class ResourceFileFlattener { std::string dst_path; }; - uint32_t GetCompressionFlags(const StringPiece& str); - std::vector<std::unique_ptr<xml::XmlResource>> LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op); @@ -381,26 +408,6 @@ ResourceFileFlattener::ResourceFileFlattener(const ResourceFileFlattenerOptions& } } -// TODO(rtmitchell): turn this function into a variable that points to a method that retrieves the -// compression flag -uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) { - if (options_.do_not_compress_anything) { - return 0; - } - - if (options_.regex_to_not_compress - && std::regex_search(str.to_string(), options_.regex_to_not_compress.value())) { - return 0; - } - - for (const std::string& extension : options_.extensions_to_not_compress) { - if (util::EndsWith(str, extension)) { - return 0; - } - } - return ArchiveEntry::kCompress; -} - static bool IsTransitionElement(const std::string& name) { return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" || name == "changeImageTransform" || name == "changeTransform" || @@ -438,7 +445,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer xml::StripAndroidStudioAttributes(doc->root.get()); XmlReferenceLinker xml_linker; - if (!xml_linker.Consume(context_, doc)) { + if (!options_.do_not_fail_on_missing_resources && !xml_linker.Consume(context_, doc)) { return {}; } @@ -640,7 +647,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv } } else { error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, - GetCompressionFlags(file_op.dst_path), archive_writer); + GetCompressionFlags(file_op.dst_path, options_), + archive_writer); } } } @@ -887,7 +895,7 @@ class Linker { // android:versionCode from the framework AndroidManifest.xml. ExtractCompileSdkVersions(asset_source->GetAssetManager()); } - } else if (asset_source->IsPackageDynamic(entry.first)) { + } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) { final_table_.included_packages_[entry.first] = entry.second; } } @@ -965,6 +973,17 @@ class Linker { app_info.min_sdk_version = ResourceUtils::ParseSdkVersion(min_sdk->value); } } + + for (const xml::Element* child_el : manifest_el->GetChildElements()) { + if (child_el->namespace_uri.empty() && child_el->name == "uses-split") { + if (const xml::Attribute* split_name = + child_el->FindAttribute(xml::kSchemaAndroid, "name")) { + if (!split_name->value.empty()) { + app_info.split_name_dependencies.insert(split_name->value); + } + } + } + } return app_info; } @@ -1065,7 +1084,8 @@ class Linker { case OutputFormat::kProto: { pb::ResourceTable pb_table; - SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics()); + SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics(), + options_.proto_table_flattener_options); return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, ArchiveEntry::kCompress, writer); } break; @@ -1278,7 +1298,8 @@ class Linker { return false; } - proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules); + proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules, + options_.no_proguard_location_reference); fout.Flush(); if (fout.HadError()) { @@ -1548,16 +1569,7 @@ class Linker { } for (auto& entry : merged_assets) { - uint32_t compression_flags = ArchiveEntry::kCompress; - std::string extension = file::GetExtension(entry.first).to_string(); - - if (options_.do_not_compress_anything - || options_.extensions_to_not_compress.count(extension) > 0 - || (options_.regex_to_not_compress - && std::regex_search(extension, options_.regex_to_not_compress.value()))) { - compression_flags = 0u; - } - + uint32_t compression_flags = GetCompressionFlags(entry.first, options_); if (!io::CopyFileToArchive(context_, entry.second.get(), entry.first, compression_flags, writer)) { return false; @@ -1566,6 +1578,93 @@ class Linker { return true; } + void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) { + xml::Element* application = manifest->root->FindChild("", "application"); + if (!application) { + return; + } + + xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon"); + xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon"); + if (!icon || !round_icon) { + return; + } + + // Find the icon resource defined within the application. + auto icon_reference = ValueCast<Reference>(icon->compiled_value.get()); + if (!icon_reference || !icon_reference->name) { + return; + } + auto package = table->FindPackageById(icon_reference->id.value().package_id()); + if (!package) { + return; + } + auto type = package->FindType(icon_reference->name.value().type); + if (!type) { + return; + } + auto icon_entry = type->FindEntry(icon_reference->name.value().entry); + if (!icon_entry) { + return; + } + + int icon_max_sdk = 0; + for (auto& config_value : icon_entry->values) { + icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion) + ? config_value->config.sdkVersion : icon_max_sdk; + } + if (icon_max_sdk < SDK_O) { + // Adaptive icons must be versioned with v26 qualifiers, so this is not an adaptive icon. + return; + } + + // Find the roundIcon resource defined within the application. + auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get()); + if (!round_icon_reference || !round_icon_reference->name) { + return; + } + package = table->FindPackageById(round_icon_reference->id.value().package_id()); + if (!package) { + return; + } + type = package->FindType(round_icon_reference->name.value().type); + if (!type) { + return; + } + auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry); + if (!round_icon_entry) { + return; + } + + int round_icon_max_sdk = 0; + for (auto& config_value : round_icon_entry->values) { + round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion) + ? config_value->config.sdkVersion : round_icon_max_sdk; + } + if (round_icon_max_sdk >= SDK_O) { + // The developer explicitly used a v26 compatible drawable as the roundIcon, meaning we should + // not generate an alias to the icon drawable. + return; + } + + // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon. + for (auto& config_value : icon_entry->values) { + if (config_value->config.sdkVersion < SDK_O) { + continue; + } + + context_->GetDiagnostics()->Note(DiagMessage() << "generating " + << round_icon_reference->name.value() + << " with config \"" << config_value->config + << "\" for round icon compatibility"); + + auto value = icon_reference->Clone(&table->string_pool); + auto round_config_value = round_icon_entry->FindOrCreateValue( + config_value->config, config_value->product); + round_config_value->value.reset(value); + } + } + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, @@ -1579,6 +1678,14 @@ class Linker { return false; } + // When a developer specifies an adaptive application icon, and a non-adaptive round application + // icon, create an alias from the round icon to the regular icon for v26 APIs and up. We do this + // because certain devices prefer android:roundIcon over android:icon regardless of the API + // levels of the drawables set for either. This auto-aliasing behaviour allows an app to prefer + // the android:roundIcon on API 25 devices, and prefer the adaptive icon on API 26 devices. + // See (b/34829129) + AliasAdaptiveIcon(manifest, table); + ResourceFileFlattenerOptions file_flattener_options; file_flattener_options.keep_raw_values = keep_raw_values; file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything; @@ -1591,9 +1698,9 @@ class Linker { file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); file_flattener_options.output_format = options_.output_format; + file_flattener_options.do_not_fail_on_missing_resources = options_.merge_only; ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); - if (!file_flattener.Flatten(table, writer)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources"); return false; @@ -1660,10 +1767,9 @@ class Linker { return 1; } - // First extract the package name without modifying it (via --rename-manifest-package). + // First extract the Package name without modifying it (via --rename-manifest-package). if (Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(manifest_xml.get(), context_->GetDiagnostics())) { - // Extract the package name from the manifest ignoring the value of --rename-manifest-package. const AppInfo& app_info = maybe_app_info.value(); context_->SetCompilationPackage(app_info.package); } @@ -1699,6 +1805,7 @@ class Linker { context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0)); context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()}); + context_->SetSplitNameDependencies(app_info_.split_name_dependencies); // Override the package ID when it is "android". if (context_->GetCompilationPackage() == "android") { @@ -1714,6 +1821,8 @@ class Linker { TableMergerOptions table_merger_options; table_merger_options.auto_add_overlay = options_.auto_add_overlay; + table_merger_options.override_styles_instead_of_overlaying = + options_.override_styles_instead_of_overlaying; table_merger_options.strict_visibility = options_.strict_visibility; table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options); @@ -1828,7 +1937,7 @@ class Linker { } ReferenceLinker linker; - if (!linker.Consume(context_, &final_table_)) { + if (!options_.merge_only && !linker.Consume(context_, &final_table_)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed linking references"); return 1; } @@ -1980,7 +2089,7 @@ class Linker { manifest_xml->file.name.package = context_->GetCompilationPackage(); XmlReferenceLinker manifest_linker; - if (manifest_linker.Consume(context_, manifest_xml.get())) { + if (options_.merge_only || manifest_linker.Consume(context_, manifest_xml.get())) { if (options_.generate_proguard_rules_path && !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) { error = true; @@ -2114,6 +2223,12 @@ int LinkCommand::Action(const std::vector<std::string>& args) { return 1; } + if (options_.merge_only && !static_lib_) { + context.GetDiagnostics()->Error( + DiagMessage() << "the --merge-only flag can be only used when building a static library"); + return 1; + } + // The default build type. context.SetPackageType(PackageType::kApp); context.SetPackageId(kAppPackageId); diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index e62e0a6b9f62..852b1244cd6e 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -24,6 +24,7 @@ #include "Resource.h" #include "split/TableSplitter.h" #include "format/binary/TableFlattener.h" +#include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" #include "trace/TraceBuffer.h" @@ -42,6 +43,7 @@ struct LinkOptions { std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; + bool override_styles_instead_of_overlaying = false; OutputFormat output_format = OutputFormat::kApk; Maybe<std::string> rename_resources_package; @@ -55,6 +57,7 @@ struct LinkOptions { bool generate_conditional_proguard_rules = false; bool generate_minimal_proguard_rules = false; bool generate_non_final_ids = false; + bool no_proguard_location_reference = false; std::vector<std::string> javadoc_annotations; Maybe<std::string> private_symbols; @@ -71,6 +74,7 @@ struct LinkOptions { // Static lib options. bool no_static_lib_packages = false; + bool merge_only = false; // AndroidManifest.xml massaging options. ManifestFixerOptions manifest_fixer_options; @@ -80,6 +84,7 @@ struct LinkOptions { // Flattening options. TableFlattenerOptions table_flattener_options; + SerializeTableOptions proto_table_flattener_options; bool keep_raw_values = false; // Split APK options. @@ -212,6 +217,9 @@ class LinkCommand : public Command { "Generates R.java without the final modifier. This is implied when\n" "--static-lib is specified.", &options_.generate_non_final_ids); + AddOptionalSwitch("--no-proguard-location-reference", + "Keep proguard rules files from having a reference to the source file", + &options_.no_proguard_location_reference); AddOptionalFlag("--stable-ids", "File containing a list of name to ID mapping.", &stable_id_file_path_); AddOptionalFlag("--emit-ids", @@ -243,6 +251,10 @@ class LinkCommand : public Command { "Allows the addition of new resources in overlays without\n" "<add-resource> tags.", &options_.auto_add_overlay); + AddOptionalSwitch("--override-styles-instead-of-overlaying", + "Causes styles defined in -R resources to replace previous definitions\n" + "instead of merging into them\n", + &options_.override_styles_instead_of_overlaying); AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.", &options_.manifest_fixer_options.rename_manifest_package); AddOptionalFlag("--rename-resources-package", "Renames the package in resources table", @@ -255,7 +267,7 @@ class LinkCommand : public Command { "Changes the name of the target package for overlay. Most useful\n" "when used in conjunction with --rename-manifest-package.", &options_.manifest_fixer_options.rename_overlay_target_package); - AddOptionalFlagList("-0", "File extensions not to compress.", + AddOptionalFlagList("-0", "File suffix not to compress.", &options_.extensions_to_not_compress); AddOptionalSwitch("--no-compress", "Do not compress any resources.", &options_.do_not_compress_anything); @@ -263,8 +275,8 @@ class LinkCommand : public Command { &options_.keep_raw_values); AddOptionalFlag("--no-compress-regex", "Do not compress extensions matching the regular expression. Remember to\n" - " use the '$' symbol for end of line. Uses a non case-sensitive\n" - " ECMAScript regular expression grammar.", + "use the '$' symbol for end of line. Uses a case-sensitive ECMAScript" + "regular expression grammar.", &no_compress_regex); AddOptionalSwitch("--warn-manifest-validation", "Treat manifest validation errors as warnings.", @@ -284,9 +296,18 @@ class LinkCommand : public Command { AddOptionalSwitch("--strict-visibility", "Do not allow overlays with different visibility levels.", &options_.strict_visibility); + AddOptionalSwitch("--exclude-sources", + "Do not serialize source file information when generating resources in\n" + "Protobuf format.", + &options_.proto_table_flattener_options.exclude_sources); + AddOptionalFlag("--trace-folder", + "Generate systrace json trace fragment to specified folder.", + &trace_folder_); + AddOptionalSwitch("--merge-only", + "Only merge the resources, without verifying resource references. This flag\n" + "should only be used together with the --static-lib flag.", + &options_.merge_only); AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_); - AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.", - &trace_folder_); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 9ea93f638aff..062dd8eac975 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "AppInfo.h" #include "Link.h" #include "LoadedApk.h" @@ -43,10 +44,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); - std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); - AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has not been assigned @@ -71,10 +70,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); - std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); - AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has been set to the correct string pool entry @@ -83,4 +80,241 @@ TEST_F(LinkTest, KeepRawXmlStrings) { EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); } -} // namespace aapt
\ No newline at end of file +TEST_F(LinkTest, NoCompressAssets) { + StdErrDiagnostics diag; + std::string content(500, 'a'); + WriteFile(GetTestPath("assets/testtxt"), content); + WriteFile(GetTestPath("assets/testtxt2"), content); + WriteFile(GetTestPath("assets/test.txt"), content); + WriteFile(GetTestPath("assets/test.hello.txt"), content); + WriteFile(GetTestPath("assets/test.hello.xml"), content); + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(), + "-o", out_apk, + "-0", ".txt", + "-0", "txt2", + "-0", ".hello.txt", + "-0", "hello.xml", + "-A", GetTestPath("assets") + }; + + ASSERT_TRUE(Link(link_args, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + io::IFileCollection* zip = apk->GetFileCollection(); + ASSERT_THAT(zip, Ne(nullptr)); + + auto file = zip->FindFile("assets/testtxt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_TRUE(file->WasCompressed()); + + file = zip->FindFile("assets/testtxt2"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("assets/test.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("assets/test.hello.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("assets/test.hello.xml"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); +} + +TEST_F(LinkTest, NoCompressResources) { + StdErrDiagnostics diag; + std::string content(500, 'a'); + const std::string compiled_files_dir = GetTestPath("compiled"); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/testtxt"), content, compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test1.hello.txt"), content, compiled_files_dir, + &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test2.goodbye.xml"), content, compiled_files_dir, + &diag)); + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(), + "-o", out_apk, + "-0", ".txt", + "-0", ".hello.txt", + "-0", "goodbye.xml", + }; + + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + io::IFileCollection* zip = apk->GetFileCollection(); + ASSERT_THAT(zip, Ne(nullptr)); + + auto file = zip->FindFile("res/raw/testtxt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_TRUE(file->WasCompressed()); + + file = zip->FindFile("res/raw/test.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("res/raw/test1.hello.hello.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("res/raw/test2.goodbye.goodbye.xml"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); +} + +TEST_F(LinkTest, OverlayStyles) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + const std::string override_files_dir = GetTestPath("compiled-override"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:textColor">#123</item> + </style> + </resources>)", + compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:background">#456</item> + </style> + </resources>)", + override_files_dir, &diag)); + + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(kDefaultPackageName), + "-o", out_apk, + }; + const auto override_files = file::FindFiles(override_files_dir, &diag); + for (const auto &override_file : override_files.value()) { + link_args.push_back("-R"); + link_args.push_back(file::BuildPath({override_files_dir, override_file})); + } + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + const Style* actual_style = test::GetValue<Style>( + apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); + ASSERT_NE(actual_style, nullptr); + ASSERT_EQ(actual_style->entries.size(), 2); + EXPECT_EQ(actual_style->entries[0].key.id, 0x01010098); // android:textColor + EXPECT_EQ(actual_style->entries[1].key.id, 0x010100d4); // android:background +} + +TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + const std::string override_files_dir = GetTestPath("compiled-override"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:textColor">#123</item> + </style> + </resources>)", + compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:background">#456</item> + </style> + </resources>)", + override_files_dir, &diag)); + + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(kDefaultPackageName), + "--override-styles-instead-of-overlaying", + "-o", out_apk, + }; + const auto override_files = file::FindFiles(override_files_dir, &diag); + for (const auto &override_file : override_files.value()) { + link_args.push_back("-R"); + link_args.push_back(file::BuildPath({override_files_dir, override_file})); + } + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + const Style* actual_style = test::GetValue<Style>( + apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); + ASSERT_NE(actual_style, nullptr); + ASSERT_EQ(actual_style->entries.size(), 1); + EXPECT_EQ(actual_style->entries[0].key.id, 0x010100d4); // android:background +} + +TEST_F(LinkTest, AppInfoWithUsesSplit) { + StdErrDiagnostics diag; + const std::string base_files_dir = GetTestPath("base"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string name="bar">bar</string> + </resources>)", + base_files_dir, &diag)); + const std::string base_apk = GetTestPath("base.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest("com.aapt2.app"), + "-o", base_apk, + }; + ASSERT_TRUE(Link(link_args, base_files_dir, &diag)); + + const std::string feature_manifest = GetTestPath("feature_manifest.xml"); + WriteFile(feature_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app" split="feature1"> + </manifest>)")); + const std::string feature_files_dir = GetTestPath("feature"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string name="foo">foo</string> + </resources>)", + feature_files_dir, &diag)); + const std::string feature_apk = GetTestPath("feature.apk"); + const std::string feature_package_id = "0x80"; + link_args = { + "--manifest", feature_manifest, + "-I", base_apk, + "--package-id", feature_package_id, + "-o", feature_apk, + }; + ASSERT_TRUE(Link(link_args, feature_files_dir, &diag)); + + const std::string feature2_manifest = GetTestPath("feature2_manifest.xml"); + WriteFile(feature2_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app" split="feature2"> + <uses-split android:name="feature1"/> + </manifest>)")); + const std::string feature2_files_dir = GetTestPath("feature2"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string-array name="string_array"> + <item>@string/bar</item> + <item>@string/foo</item> + </string-array> + </resources>)", + feature2_files_dir, &diag)); + const std::string feature2_apk = GetTestPath("feature2.apk"); + const std::string feature2_package_id = "0x81"; + link_args = { + "--manifest", feature2_manifest, + "-I", base_apk, + "-I", feature_apk, + "--package-id", feature2_package_id, + "-o", feature2_apk, + }; + ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag)); +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 2e6af18c1948..e36668e5a043 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -53,9 +53,9 @@ using ::android::ConfigDescription; using ::android::ResTable_config; using ::android::StringPiece; using ::android::base::ReadFileToString; -using ::android::base::WriteStringToFile; using ::android::base::StringAppendF; using ::android::base::StringPrintf; +using ::android::base::WriteStringToFile; namespace aapt { @@ -108,6 +108,12 @@ class OptimizeContext : public IAaptContext { return sdk_version_; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + private: DISALLOW_COPY_AND_ASSIGN(OptimizeContext); @@ -294,29 +300,7 @@ class Optimizer { OptimizeContext* context_; }; -bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context, - OptimizeOptions* options) { - std::string contents; - if (!ReadFileToString(path, &contents, true)) { - context->GetDiagnostics()->Error(DiagMessage() - << "failed to parse whitelist from config file: " << path); - return false; - } - for (StringPiece resource_name : util::Tokenize(contents, ',')) { - options->table_flattener_options.whitelisted_resources.insert( - resource_name.to_string()); - } - return true; -} - -bool ExtractConfig(const std::string& path, OptimizeContext* context, - OptimizeOptions* options) { - std::string content; - if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { - context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist"); - return false; - } - +bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) { size_t line_no = 0; for (StringPiece line : util::Tokenize(content, '\n')) { line_no++; @@ -345,15 +329,24 @@ bool ExtractConfig(const std::string& path, OptimizeContext* context, for (StringPiece directive : util::Tokenize(directives, ',')) { if (directive == "remove") { options->resources_blacklist.insert(resource_name.ToResourceName()); - } else if (directive == "no_obfuscate") { - options->table_flattener_options.whitelisted_resources.insert( - resource_name.entry.to_string()); + } else if (directive == "no_collapse" || directive == "no_obfuscate") { + options->table_flattener_options.name_collapse_exemptions.insert( + resource_name.ToResourceName()); } } } return true; } +bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) { + std::string content; + if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { + context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file"); + return false; + } + return ParseConfig(content, context, options); +} + bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, OptimizeOptions* out_options) { const xml::XmlResource* manifest = apk->GetManifest(); @@ -461,15 +454,6 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { } } - if (options_.table_flattener_options.collapse_key_stringpool) { - if (whitelist_path_) { - std::string& path = whitelist_path_.value(); - if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) { - return 1; - } - } - } - if (resources_config_path_) { std::string& path = resources_config_path_.value(); if (!ExtractConfig(path, &context, &options_)) { diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index 7f4a3ed85364..5070ccc8afbf 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -57,7 +57,7 @@ struct OptimizeOptions { std::unordered_set<std::string> kept_artifacts; // Whether or not to shorten resource paths in the APK. - bool shorten_resource_paths; + bool shorten_resource_paths = false; // Path to the output map of original resource paths to shortened paths. Maybe<std::string> shortened_paths_map_path; @@ -78,10 +78,6 @@ class OptimizeCommand : public Command { "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities_); - AddOptionalFlag("--whitelist-path", - "Path to the whitelist.cfg file containing whitelisted resources \n" - "whose names should not be altered in final resource tables.", - &whitelist_path_); AddOptionalFlag("--resources-config-path", "Path to the resources.cfg file containing the list of resources and \n" "directives to each resource. \n" @@ -104,11 +100,13 @@ class OptimizeCommand : public Command { "Enables encoding sparse entries using a binary search tree.\n" "This decreases APK size at the cost of resource retrieval performance.", &options_.table_flattener_options.use_sparse_entries); - AddOptionalSwitch("--enable-resource-obfuscation", - "Enables obfuscation of key string pool to single value", + AddOptionalSwitch("--collapse-resource-names", + "Collapses resource names to a single value in the key string pool. Resources can \n" + "be exempted using the \"no_collapse\" directive in a file specified by " + "--resources-config-path.", &options_.table_flattener_options.collapse_key_stringpool); - AddOptionalSwitch("--enable-resource-path-shortening", - "Enables shortening of the path of the resources inside the APK.", + AddOptionalSwitch("--shorten-resource-paths", + "Shortens the paths of resources inside the APK.", &options_.shorten_resource_paths); AddOptionalFlag("--resource-path-shortening-map", "Path to output the map of old resource paths to shortened paths.", @@ -125,7 +123,6 @@ class OptimizeCommand : public Command { const std::string &file_path); Maybe<std::string> config_path_; - Maybe<std::string> whitelist_path_; Maybe<std::string> resources_config_path_; Maybe<std::string> target_densities_; std::vector<std::string> configs_; diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp new file mode 100644 index 000000000000..ac681e85b3d6 --- /dev/null +++ b/tools/aapt2/cmd/Optimize_test.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 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 "Optimize.h" + +#include "AppInfo.h" +#include "Diagnostics.h" +#include "LoadedApk.h" +#include "Resource.h" +#include "test/Test.h" + +using testing::Contains; +using testing::Eq; + +namespace aapt { + +bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*); + +using OptimizeTest = CommandTestFixture; + +TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) { + const std::string& content = R"( +string/foo#no_collapse +dimen/bar#no_collapse +)"; + aapt::test::Context context; + OptimizeOptions options; + ParseConfig(content, &context, &options); + + const std::set<ResourceName>& name_collapse_exemptions = + options.table_flattener_options.name_collapse_exemptions; + + ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); +} + +TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) { + const std::string& content = R"( +string/foo#no_obfuscate +dimen/bar#no_obfuscate +)"; + aapt::test::Context context; + OptimizeOptions options; + ParseConfig(content, &context, &options); + + const std::set<ResourceName>& name_collapse_exemptions = + options.table_flattener_options.name_collapse_exemptions; + + ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index e2c65ba74271..7214f1a68d2c 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -436,9 +436,9 @@ void SetLongVersionCode(xml::Element* manifest, uint64_t version) { } std::regex GetRegularExpression(const std::string &input) { - // Standard ECMAScript grammar plus case insensitive. + // Standard ECMAScript grammar. std::regex case_insensitive( - input, std::regex_constants::icase | std::regex_constants::ECMAScript); + input, std::regex_constants::ECMAScript); return case_insensitive; } diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index 7e492610b658..ac1f981d753c 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -383,7 +383,7 @@ TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { EXPECT_NE(*adjusted_contraints[1].configs.begin(), ConfigDescription::DefaultConfig()); } -TEST(UtilTest, RegularExperssions) { +TEST (UtilTest, RegularExperssionsSimple) { std::string valid(".bc$"); std::regex expression = GetRegularExpression(valid); EXPECT_TRUE(std::regex_search("file.abc", expression)); @@ -391,4 +391,24 @@ TEST(UtilTest, RegularExperssions) { EXPECT_FALSE(std::regex_search("abc.zip", expression)); } +TEST (UtilTest, RegularExpressionComplex) { + std::string valid("\\.(d|D)(e|E)(x|X)$"); + std::regex expression = GetRegularExpression(valid); + EXPECT_TRUE(std::regex_search("file.dex", expression)); + EXPECT_TRUE(std::regex_search("file.DEX", expression)); + EXPECT_TRUE(std::regex_search("file.dEx", expression)); + EXPECT_FALSE(std::regex_search("file.dexx", expression)); + EXPECT_FALSE(std::regex_search("dex.file", expression)); + EXPECT_FALSE(std::regex_search("file.adex", expression)); +} + +TEST (UtilTest, RegularExpressionNonEnglish) { + std::string valid("\\.(k|K)(o|O)(ń|Ń)(c|C)(ó|Ó)(w|W)(k|K)(a|A)$"); + std::regex expression = GetRegularExpression(valid); + EXPECT_TRUE(std::regex_search("file.końcówka", expression)); + EXPECT_TRUE(std::regex_search("file.KOŃCÓWKA", expression)); + EXPECT_TRUE(std::regex_search("file.kOńcÓwkA", expression)); + EXPECT_FALSE(std::regex_search("file.koncowka", expression)); +} + } // namespace aapt |