diff options
36 files changed, 1176 insertions, 336 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index b460258248af..bf189492b082 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -99,6 +99,7 @@ cc_library_host_static { "link/PrivateAttributeMover.cpp", "link/ReferenceLinker.cpp", "link/TableMerger.cpp", + "link/XmlCompatVersioner.cpp", "link/XmlNamespaceRemover.cpp", "link/XmlReferenceLinker.cpp", "optimize/ResourceDeduper.cpp", diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index 56e2e95df6cb..e2456c7f98b2 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -37,6 +37,7 @@ struct Debug { const ResourceName& target_style); static void DumpHex(const void* data, size_t len); static void DumpXml(xml::XmlResource* doc); + static std::string ToString(xml::XmlResource* doc); }; } // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index e45d1420a902..1d2e3a4f2df0 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -27,7 +27,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "15"; +static const char* sMinorVersion = "16"; int PrintVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 493f238b033d..0a74c1a0f77d 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -388,13 +388,20 @@ template <> struct hash<aapt::ResourceName> { size_t operator()(const aapt::ResourceName& name) const { android::hash_t h = 0; - h = android::JenkinsHashMix(h, hash<string>()(name.package)); + h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.package))); h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type)); - h = android::JenkinsHashMix(h, hash<string>()(name.entry)); + h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.entry))); return static_cast<size_t>(h); } }; +template <> +struct hash<aapt::ResourceId> { + size_t operator()(const aapt::ResourceId& id) const { + return id.id; + } +}; + } // namespace std #endif // AAPT_RESOURCE_H diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 0cb8c67705f9..34bd2b4361ca 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -333,6 +333,12 @@ void BinaryPrimitive::Print(std::ostream* out) const { } } +Attribute::Attribute() + : type_mask(0u), + min_int(std::numeric_limits<int32_t>::min()), + max_int(std::numeric_limits<int32_t>::max()) { +} + Attribute::Attribute(bool w, uint32_t t) : type_mask(t), min_int(std::numeric_limits<int32_t>::min()), diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index c71c738892da..06f949f9555c 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -18,6 +18,7 @@ #define AAPT_RESOURCE_VALUES_H #include <array> +#include <limits> #include <ostream> #include <vector> @@ -251,6 +252,7 @@ struct Attribute : public BaseValue<Attribute> { int32_t max_int; std::vector<Symbol> symbols; + Attribute(); explicit Attribute(bool w, uint32_t t = 0u); bool Equals(const Value* value) const override; diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index e8067143dc75..041cb4fa96cd 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -26,9 +26,9 @@ using android::StringPiece; namespace aapt { static const char* sDevelopmentSdkCodeName = "O"; -static int sDevelopmentSdkLevel = 26; +static ApiVersion sDevelopmentSdkLevel = 26; -static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { +static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = { {0x021c, 1}, {0x021d, 2}, {0x0269, SDK_CUPCAKE}, @@ -48,26 +48,29 @@ static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { {0x03f1, SDK_KITKAT}, {0x03f6, SDK_KITKAT_WATCH}, {0x04ce, SDK_LOLLIPOP}, + {0x04d8, SDK_LOLLIPOP_MR1}, + {0x04f1, SDK_MARSHMALLOW}, + {0x0527, SDK_NOUGAT}, + {0x0530, SDK_NOUGAT_MR1}, + {0x0568, SDK_O}, }; -static bool less_entry_id(const std::pair<uint16_t, size_t>& p, - uint16_t entryId) { +static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) { return p.first < entryId; } -size_t FindAttributeSdkLevel(const ResourceId& id) { - if (id.package_id() != 0x01 && id.type_id() != 0x01) { +ApiVersion FindAttributeSdkLevel(const ResourceId& id) { + if (id.package_id() != 0x01 || id.type_id() != 0x01) { return 0; } - auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), - id.entry_id(), less_entry_id); + auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entry_id(), less_entry_id); if (iter == sAttrIdMap.end()) { return SDK_LOLLIPOP_MR1; } return iter->second; } -static const std::unordered_map<std::string, size_t> sAttrMap = { +static const std::unordered_map<std::string, ApiVersion> sAttrMap = { {"marqueeRepeatLimit", 2}, {"windowNoDisplay", 3}, {"backgroundDimEnabled", 3}, @@ -729,7 +732,7 @@ static const std::unordered_map<std::string, size_t> sAttrMap = { {"windowActivityTransitions", 21}, {"colorEdgeEffect", 21}}; -size_t FindAttributeSdkLevel(const ResourceName& name) { +ApiVersion FindAttributeSdkLevel(const ResourceName& name) { if (name.package != "android" && name.type != ResourceType::kAttr) { return 0; } @@ -741,9 +744,8 @@ size_t FindAttributeSdkLevel(const ResourceName& name) { return SDK_LOLLIPOP_MR1; } -std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion() { - return std::make_pair(StringPiece(sDevelopmentSdkCodeName), - sDevelopmentSdkLevel); +std::pair<StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion() { + return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel); } } // namespace aapt diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index c2ee2524c5e8..e3745e8ce0da 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -25,7 +25,9 @@ namespace aapt { -enum : int { +using ApiVersion = int; + +enum : ApiVersion { SDK_CUPCAKE = 3, SDK_DONUT = 4, SDK_ECLAIR = 5, @@ -49,12 +51,12 @@ enum : int { SDK_MARSHMALLOW = 23, SDK_NOUGAT = 24, SDK_NOUGAT_MR1 = 25, - SDK_O = 26, // STOPSHIP Replace with real version + SDK_O = 26, }; -size_t FindAttributeSdkLevel(const ResourceId& id); -size_t FindAttributeSdkLevel(const ResourceName& name); -std::pair<android::StringPiece, int> GetDevelopmentSdkCodeNameAndVersion(); +ApiVersion FindAttributeSdkLevel(const ResourceId& id); +ApiVersion FindAttributeSdkLevel(const ResourceName& name); +std::pair<android::StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion(); } // namespace aapt diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp index 716d922fb5a0..61f4d71b7fb2 100644 --- a/tools/aapt2/SdkConstants_test.cpp +++ b/tools/aapt2/SdkConstants_test.cpp @@ -21,18 +21,11 @@ namespace aapt { TEST(SdkConstantsTest, FirstAttributeIsSdk1) { - EXPECT_EQ(1u, FindAttributeSdkLevel(ResourceId(0x01010000))); + EXPECT_EQ(1, FindAttributeSdkLevel(ResourceId(0x01010000))); } -TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) { - EXPECT_EQ(SDK_LOLLIPOP, FindAttributeSdkLevel(ResourceId(0x010103f7))); - EXPECT_EQ(SDK_LOLLIPOP, FindAttributeSdkLevel(ResourceId(0x010104ce))); - - EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104cf))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d8))); - - EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d9))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x0101ffff))); +TEST(SdkConstantsTest, NonFrameworkAttributeIsSdk0) { + EXPECT_EQ(0, FindAttributeSdkLevel(ResourceId(0x7f010345))); } } // namespace aapt diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 8accfa84c0a6..24a687cd4d86 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -50,6 +50,7 @@ #include "link/ManifestFixer.h" #include "link/ReferenceLinker.h" #include "link/TableMerger.h" +#include "link/XmlCompatVersioner.h" #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "process/IResourceTableConsumer.h" @@ -247,25 +248,20 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { IAaptContext* context_; }; -static bool FlattenXml(xml::XmlResource* xml_res, const StringPiece& path, - Maybe<size_t> max_sdk_level, bool keep_raw_values, IArchiveWriter* writer, - IAaptContext* context) { +static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path, + bool keep_raw_values, IArchiveWriter* writer) { BigBuffer buffer(1024); XmlFlattenerOptions options = {}; options.keep_raw_values = keep_raw_values; - options.max_sdk_level = max_sdk_level; XmlFlattener flattener(&buffer, options); if (!flattener.Consume(context, xml_res)) { return false; } if (context->IsVerbose()) { - DiagMessage msg; - msg << "writing " << path << " to archive"; - if (max_sdk_level) { - msg << " maxSdkLevel=" << max_sdk_level.value() << " keepRawValues=" << keep_raw_values; - } - context->GetDiagnostics()->Note(msg); + context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values=" + << (keep_raw_values ? "true" : "false") + << ")"); } io::BigBufferInputStream input_stream(&buffer); @@ -311,12 +307,33 @@ struct ResourceFileFlattenerOptions { std::unordered_set<std::string> extensions_to_not_compress; }; +// A sampling of public framework resource IDs. +struct R { + struct attr { + enum : uint32_t { + paddingLeft = 0x010100d6u, + paddingRight = 0x010100d8u, + paddingHorizontal = 0x0101053du, + + paddingTop = 0x010100d7u, + paddingBottom = 0x010100d9u, + paddingVertical = 0x0101053eu, + + layout_marginLeft = 0x010100f7u, + layout_marginRight = 0x010100f9u, + layout_marginHorizontal = 0x0101053bu, + + layout_marginTop = 0x010100f8u, + layout_marginBottom = 0x010100fau, + layout_marginVertical = 0x0101053cu, + }; + }; +}; + class ResourceFileFlattener { public: ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context, - proguard::KeepSet* keep_set) - : options_(options), context_(context), keep_set_(keep_set) { - } + proguard::KeepSet* keep_set); bool Flatten(ResourceTable* table, IArchiveWriter* archive_writer); @@ -325,7 +342,7 @@ class ResourceFileFlattener { ConfigDescription config; // The entry this file came from. - const ResourceEntry* entry; + ResourceEntry* entry; // The file to copy as-is. io::IFile* file_to_copy; @@ -335,19 +352,72 @@ class ResourceFileFlattener { // The destination to write this file to. std::string dst_path; - bool skip_version = false; }; uint32_t GetCompressionFlags(const StringPiece& str); - bool LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op, - std::queue<FileOperation>* out_file_op_queue); + std::vector<std::unique_ptr<xml::XmlResource>> LinkAndVersionXmlFile(ResourceTable* table, + FileOperation* file_op); ResourceFileFlattenerOptions options_; IAaptContext* context_; proguard::KeepSet* keep_set_; + XmlCompatVersioner::Rules rules_; }; +ResourceFileFlattener::ResourceFileFlattener(const ResourceFileFlattenerOptions& options, + IAaptContext* context, proguard::KeepSet* keep_set) + : options_(options), context_(context), keep_set_(keep_set) { + SymbolTable* symm = context_->GetExternalSymbols(); + + // Build up the rules for degrading newer attributes to older ones. + // NOTE(adamlesinski): These rules are hardcoded right now, but they should be + // generated from the attribute definitions themselves (b/62028956). + if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingHorizontal)) { + std::vector<ReplacementAttr> replacements{ + {"paddingLeft", R::attr::paddingLeft, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + {"paddingRight", R::attr::paddingRight, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + }; + rules_[R::attr::paddingHorizontal] = + util::make_unique<DegradeToManyRule>(std::move(replacements)); + } + + if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingVertical)) { + std::vector<ReplacementAttr> replacements{ + {"paddingTop", R::attr::paddingTop, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + {"paddingBottom", R::attr::paddingBottom, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + }; + rules_[R::attr::paddingVertical] = + util::make_unique<DegradeToManyRule>(std::move(replacements)); + } + + if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginHorizontal)) { + std::vector<ReplacementAttr> replacements{ + {"layout_marginLeft", R::attr::layout_marginLeft, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + {"layout_marginRight", R::attr::layout_marginRight, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + }; + rules_[R::attr::layout_marginHorizontal] = + util::make_unique<DegradeToManyRule>(std::move(replacements)); + } + + if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginVertical)) { + std::vector<ReplacementAttr> replacements{ + {"layout_marginTop", R::attr::layout_marginTop, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + {"layout_marginBottom", R::attr::layout_marginBottom, + Attribute(false, android::ResTable_map::TYPE_DIMENSION)}, + }; + rules_[R::attr::layout_marginVertical] = + util::make_unique<DegradeToManyRule>(std::move(replacements)); + } +} + uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) { if (options_.do_not_compress_anything) { return 0; @@ -369,8 +439,19 @@ static bool IsTransitionElement(const std::string& name) { name == "transitionManager"; } -bool ResourceFileFlattener::LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op, - std::queue<FileOperation>* out_file_op_queue) { +static bool IsVectorElement(const std::string& name) { + return name == "vector" || name == "animated-vector"; +} + +template <typename T> +std::vector<T> make_singleton_vec(T&& val) { + std::vector<T> vec; + vec.emplace_back(std::forward<T>(val)); + return vec; +} + +std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVersionXmlFile( + ResourceTable* table, FileOperation* file_op) { xml::XmlResource* doc = file_op->xml_to_flatten.get(); const Source& src = doc->file.source; @@ -380,107 +461,60 @@ bool ResourceFileFlattener::LinkAndVersionXmlFile(ResourceTable* table, FileOper XmlReferenceLinker xml_linker; if (!xml_linker.Consume(context_, doc)) { - return false; + return {}; } if (options_.update_proguard_spec && !proguard::CollectProguardRules(src, doc, keep_set_)) { - return false; + return {}; } if (options_.no_xml_namespaces) { XmlNamespaceRemover namespace_remover; if (!namespace_remover.Consume(context_, doc)) { - return false; + return {}; } } - if (!options_.no_auto_version) { - if (options_.no_version_vectors) { - // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::FindRootElement(doc); - if (el && el->namespace_uri.empty()) { - if (el->name == "vector" || el->name == "animated-vector") { - // We are NOT going to version this file. - file_op->skip_version = true; - return true; - } - } - } - if (options_.no_version_transitions) { - // Skip this if it is a transition resource. - xml::Element* el = xml::FindRootElement(doc); - if (el && el->namespace_uri.empty()) { - if (IsTransitionElement(el->name)) { - // We are NOT going to version this file. - file_op->skip_version = true; - return true; - } - } - } - - const ConfigDescription& config = file_op->config; - - // Find the first SDK level used that is higher than this defined config and - // not superseded by a lower or equal SDK level resource. - const int min_sdk_version = context_->GetMinSdkVersion(); - for (int sdk_level : xml_linker.sdk_levels()) { - if (sdk_level > min_sdk_version && sdk_level > config.sdkVersion) { - if (!ShouldGenerateVersionedResource(file_op->entry, config, sdk_level)) { - // If we shouldn't generate a versioned resource, stop checking. - break; - } - - ResourceFile versioned_file_desc = doc->file; - versioned_file_desc.config.sdkVersion = (uint16_t)sdk_level; - - FileOperation new_file_op; - new_file_op.xml_to_flatten = util::make_unique<xml::XmlResource>( - versioned_file_desc, StringPool{}, doc->root->Clone()); - new_file_op.config = versioned_file_desc.config; - new_file_op.entry = file_op->entry; - new_file_op.dst_path = - ResourceUtils::BuildResourceFileName(versioned_file_desc, context_->GetNameMangler()); - - if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage(versioned_file_desc.source) - << "auto-versioning resource from config '" << config - << "' -> '" << versioned_file_desc.config << "'"); - } - - bool added = table->AddFileReferenceAllowMangled( - versioned_file_desc.name, versioned_file_desc.config, versioned_file_desc.source, - new_file_op.dst_path, nullptr, context_->GetDiagnostics()); - if (!added) { - return false; - } + if (options_.no_auto_version) { + return make_singleton_vec(std::move(file_op->xml_to_flatten)); + } - out_file_op_queue->push(std::move(new_file_op)); - break; + if (options_.no_version_vectors || options_.no_version_transitions) { + // Skip this if it is a vector or animated-vector. + xml::Element* el = xml::FindRootElement(doc); + if (el && el->namespace_uri.empty()) { + if ((options_.no_version_vectors && IsVectorElement(el->name)) || + (options_.no_version_transitions && IsTransitionElement(el->name))) { + return make_singleton_vec(std::move(file_op->xml_to_flatten)); } } } - return true; + + const ConfigDescription& config = file_op->config; + ResourceEntry* entry = file_op->entry; + + XmlCompatVersioner xml_compat_versioner(&rules_); + const util::Range<ApiVersion> api_range{config.sdkVersion, + FindNextApiVersionForConfig(entry, config)}; + return xml_compat_versioner.Process(context_, doc, api_range); } -/** - * Do not insert or remove any resources while executing in this function. It - * will - * corrupt the iteration order. - */ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archive_writer) { bool error = false; std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files; for (auto& pkg : table->packages) { for (auto& type : pkg->types) { - // Sort by config and name, so that we get better locality in the zip - // file. + // Sort by config and name, so that we get better locality in the zip file. config_sorted_files.clear(); std::queue<FileOperation> file_operations; // Populate the queue with all files in the ResourceTable. for (auto& entry : type->entries) { for (auto& config_value : entry->values) { + // WARNING! Do not insert or remove any resources while executing in this scope. It will + // corrupt the iteration order. + FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); if (!file_ref) { continue; @@ -497,6 +531,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.entry = entry.get(); file_op.dst_path = *file_ref->path; file_op.config = config_value->config; + file_op.file_to_copy = file; const StringPiece src_path = file->GetSource().path; if (type->type != ResourceType::kRaw && @@ -518,68 +553,51 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.xml_to_flatten->file.config = config_value->config; file_op.xml_to_flatten->file.source = file_ref->GetSource(); file_op.xml_to_flatten->file.name = ResourceName(pkg->name, type->type, entry->name); - - // Enqueue the XML files to be processed. - file_operations.push(std::move(file_op)); - } else { - file_op.file_to_copy = file; - - // NOTE(adamlesinski): Explicitly construct a StringPiece here, or - // else we end up copying the string in the std::make_pair() method, - // then creating a StringPiece from the copy, which would cause us - // to end up referencing garbage in the map. - const StringPiece entry_name(entry->name); - config_sorted_files[std::make_pair(config_value->config, entry_name)] = - std::move(file_op); } - } - } - - // Now process the XML queue - for (; !file_operations.empty(); file_operations.pop()) { - FileOperation& file_op = file_operations.front(); - if (!LinkAndVersionXmlFile(table, &file_op, &file_operations)) { - error = true; - continue; + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or + // else we end up copying the string in the std::make_pair() method, + // then creating a StringPiece from the copy, which would cause us + // to end up referencing garbage in the map. + const StringPiece entry_name(entry->name); + config_sorted_files[std::make_pair(config_value->config, entry_name)] = + std::move(file_op); } - - // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else - // we end up copying the string in the std::make_pair() method, then - // creating a StringPiece from the copy, which would cause us to end up - // referencing garbage in the map. - const StringPiece entry_name(file_op.entry->name); - config_sorted_files[std::make_pair(file_op.config, entry_name)] = std::move(file_op); - } - - if (error) { - return false; } // Now flatten the sorted values. for (auto& map_entry : config_sorted_files) { const ConfigDescription& config = map_entry.first.first; - const FileOperation& file_op = map_entry.second; + FileOperation& file_op = map_entry.second; if (file_op.xml_to_flatten) { - Maybe<size_t> max_sdk_level; - if (!options_.no_auto_version && !file_op.skip_version) { - max_sdk_level = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u), - context_->GetMinSdkVersion()); - } + std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = + LinkAndVersionXmlFile(table, &file_op); + for (std::unique_ptr<xml::XmlResource>& doc : versioned_docs) { + std::string dst_path = file_op.dst_path; + if (doc->file.config != file_op.config) { + // Only add the new versioned configurations. + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage(doc->file.source) + << "auto-versioning resource from config '" + << config << "' -> '" << doc->file.config << "'"); + } - bool result = FlattenXml(file_op.xml_to_flatten.get(), file_op.dst_path, max_sdk_level, - options_.keep_raw_values, archive_writer, context_); - if (!result) { - error = true; + dst_path = + ResourceUtils::BuildResourceFileName(doc->file, context_->GetNameMangler()); + bool result = table->AddFileReferenceAllowMangled(doc->file.name, doc->file.config, + doc->file.source, dst_path, nullptr, + context_->GetDiagnostics()); + if (!result) { + return false; + } + } + error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values, + archive_writer); } } else { - bool result = - io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, - GetCompressionFlags(file_op.dst_path), archive_writer); - if (!result) { - error = true; - } + error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, + GetCompressionFlags(file_op.dst_path), archive_writer); } } } @@ -1358,8 +1376,7 @@ class LinkCommand { bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib; - bool result = - FlattenXml(manifest, "AndroidManifest.xml", {}, keep_raw_values, writer, context_); + bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values, writer); if (!result) { return false; } diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 14d426061e56..8741b7b678ec 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -131,7 +131,7 @@ std::vector<SplitConstraints> AdjustSplitConstraintsForMinSdk( } static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) { - return xml::AaptAttribute{id, Attribute(true)}; + return xml::AaptAttribute(Attribute(), id); } std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index e98d2d758df4..0711749d0378 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -194,16 +194,9 @@ class XmlFlattenerVisitor : public xml::Visitor { // Filter the attributes. for (xml::Attribute& attr : node->attributes) { - if (options_.max_sdk_level && attr.compiled_attribute && attr.compiled_attribute.value().id) { - size_t sdk_level = FindAttributeSdkLevel(attr.compiled_attribute.value().id.value()); - if (sdk_level > options_.max_sdk_level.value()) { - continue; - } - } - if (attr.namespace_uri == xml::kSchemaTools) { - continue; + if (attr.namespace_uri != xml::kSchemaTools) { + filtered_attrs_.push_back(&attr); } - filtered_attrs_.push_back(&attr); } if (filtered_attrs_.empty()) { diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h index f5129fd40e99..87557f29f3be 100644 --- a/tools/aapt2/flatten/XmlFlattener.h +++ b/tools/aapt2/flatten/XmlFlattener.h @@ -30,11 +30,6 @@ struct XmlFlattenerOptions { * Keep attribute raw string values along with typed values. */ bool keep_raw_values = false; - - /** - * If set, the max SDK level of attribute to flatten. All others are ignored. - */ - Maybe<size_t> max_sdk_level; }; class XmlFlattener : public IXmlResourceConsumer { diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index cfa89bb4b09f..f1e903f2151e 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -149,31 +149,6 @@ TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next()); } -TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:paddingStart="1dp" - android:colorAccent="#ffffff"/>)EOF"); - - XmlReferenceLinker linker; - ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - ASSERT_TRUE(linker.sdk_levels().count(17) == 1); - ASSERT_TRUE(linker.sdk_levels().count(21) == 1); - - android::ResXMLTree tree; - XmlFlattenerOptions options; - options.max_sdk_level = 17; - ASSERT_TRUE(Flatten(doc.get(), &tree, options)); - - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } - - ASSERT_EQ(1u, tree.getAttributeCount()); - EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); -} - TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( <View xmlns:tools="http://schemas.android.com/tools" @@ -234,13 +209,11 @@ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { } const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), - 0); + EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); } TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { - std::unique_ptr<xml::XmlResource> doc = - test::BuildXmlDom("<View package=\"\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); @@ -251,8 +224,7 @@ TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { } const StringPiece16 kPackage = u"package"; - ssize_t idx = - tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); + ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); ASSERT_GE(idx, 0); size_t len; diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.mk b/tools/aapt2/integration-tests/AutoVersionTest/Android.mk new file mode 100644 index 000000000000..012728f1c18f --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.mk @@ -0,0 +1,23 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_PACKAGE_NAME := AaptAutoVersionTest +LOCAL_MODULE_TAGS := tests +include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml new file mode 100644 index 000000000000..e66d709943a4 --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.autoversiontest"> + + <uses-sdk android:minSdkVersion="7" /> +</manifest> diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout-v21/layout3.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout-v21/layout3.xml new file mode 100644 index 000000000000..bfc5445b1e1b --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout-v21/layout3.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:paddingVertical="24dp" /> + diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout.xml new file mode 100644 index 000000000000..091a8ce2d7ed --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout.xml @@ -0,0 +1,35 @@ +<?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:autoStart="true" + android:expandableListViewWhiteStyle="@empty" + android:screenSize="large" + android:subtitle="Hello" + android:resizeMode="none" + android:largestWidthLimitDp="999" + android:uiOptions="none" + android:parentActivityName="Hello" + android:paddingStart="999dp" + android:requiredForAllUsers="true" + android:category="Hello" + android:isGame="true" + android:colorPrimary="#ffffff" + android:revisionCode="999" + android:autoVerify="true" + android:use32bitAbi="true" + android:shortcutId="@+id/id" /> + diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout2.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout2.xml new file mode 100644 index 000000000000..339337a2c953 --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout2.xml @@ -0,0 +1,47 @@ +<?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" + class="foo"> + <View + android:paddingHorizontal="24dp" /> + <View + android:paddingHorizontal="24dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" /> + <View + android:paddingHorizontal="24dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" /> + <View + android:paddingVertical="24dp" /> + <View + android:paddingVertical="24dp" + android:paddingTop="16dp" + android:paddingBottom="16dp" /> + <View + android:layout_marginHorizontal="24dp" /> + <View + android:layout_marginHorizontal="24dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" /> + <View + android:layout_marginVertical="24dp" /> + <View + android:layout_marginVertical="24dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" /> +</View> diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout3.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout3.xml new file mode 100644 index 000000000000..8025ce10aed0 --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout3.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:paddingHorizontal="24dp" /> + diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/values-v21/styles.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/values-v21/styles.xml new file mode 100644 index 000000000000..ff13faab01a3 --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/res/values-v21/styles.xml @@ -0,0 +1,28 @@ +<?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. +--> + +<resources> + <style name="Style2"> + <!-- API 7 --> + <item name="android:autoStart">false</item> + + <!-- API 17 --> + <item name="android:paddingStart">0dp</item> + + <!-- API 21 --> + <item name="android:colorPrimary">#00ff00</item> + </style> +</resources> diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/values/styles.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/values/styles.xml new file mode 100644 index 000000000000..c4f09846ed4b --- /dev/null +++ b/tools/aapt2/integration-tests/AutoVersionTest/res/values/styles.xml @@ -0,0 +1,84 @@ +<?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. +--> + +<resources> + <style name="Style1"> + <!-- API 7 --> + <item name="android:autoStart">true</item> + + <!-- API 8 --> + <item name="android:expandableListViewWhiteStyle">@null</item> + + <!-- API 9 --> + <item name="android:screenSize">large</item> + + <!-- API 11 --> + <item name="android:subtitle">Hello</item> + + <!-- API 12 --> + <item name="android:resizeMode">none</item> + + <!-- API 13 --> + <item name="android:largestWidthLimitDp">999</item> + + <!-- API 14 --> + <item name="android:uiOptions">none</item> + + <!-- API 16 --> + <item name="android:parentActivityName">Hello</item> + + <!-- API 17 --> + <item name="android:paddingStart">999dp</item> + + <!-- API 18 --> + <item name="android:requiredForAllUsers">true</item> + + <!-- API 19 --> + <item name="android:category">Hello</item> + + <!-- API 20 --> + <item name="android:isGame">true</item> + + <!-- API 21 --> + <item name="android:colorPrimary">#ffffff</item> + + <!-- API 22 --> + <item name="android:revisionCode">999</item> + + <!-- API 23 --> + <item name="android:autoVerify">true</item> + + <!-- API 24 --> + <item name="android:use32bitAbi">true</item> + + <!-- API 25 --> + <item name="android:shortcutId">@+id/id</item> + + <!-- API 26 --> + <item name="android:paddingHorizontal">999dp</item> + </style> + + <style name="Style2"> + <!-- API 7 --> + <item name="android:autoStart">true</item> + + <!-- API 17 --> + <item name="android:paddingStart">999dp</item> + + <!-- API 21 --> + <item name="android:colorPrimary">#ffffff</item> + </style> +</resources> diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index 77471ea5d0da..f80c6e9b34d3 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -27,13 +27,15 @@ namespace aapt { -bool ShouldGenerateVersionedResource(const ResourceEntry* entry, - const ConfigDescription& config, - const int sdk_version_to_generate) { - // We assume the caller is trying to generate a version greater than the - // current configuration. +bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, + const ApiVersion sdk_version_to_generate) { + // We assume the caller is trying to generate a version greater than the current configuration. CHECK(sdk_version_to_generate > config.sdkVersion); + return sdk_version_to_generate < FindNextApiVersionForConfig(entry, config); +} +ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, + const ConfigDescription& config) { const auto end_iter = entry->values.end(); auto iter = entry->values.begin(); for (; iter != end_iter; ++iter) { @@ -46,26 +48,23 @@ bool ShouldGenerateVersionedResource(const ResourceEntry* entry, CHECK(iter != entry->values.end()); ++iter; - // The next configuration either only varies in sdkVersion, or it is - // completely different - // and therefore incompatible. If it is incompatible, we must generate the - // versioned resource. + // The next configuration either only varies in sdkVersion, or it is completely different + // and therefore incompatible. If it is incompatible, we must generate the versioned resource. - // NOTE: The ordering of configurations takes sdkVersion as higher precedence - // than other + // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other // qualifiers, so we need to iterate through the entire list to be sure there // are no higher sdk level versions of this resource. ConfigDescription temp_config(config); for (; iter != end_iter; ++iter) { temp_config.sdkVersion = (*iter)->config.sdkVersion; if (temp_config == (*iter)->config) { - // The two configs are the same, check the sdk version. - return sdk_version_to_generate < (*iter)->config.sdkVersion; + // The two configs are the same, return the sdkVersion. + return (*iter)->config.sdkVersion; } } - // No match was found, so we should generate the versioned resource. - return true; + // Didn't find another config with a different sdk version, so return the highest possible value. + return std::numeric_limits<ApiVersion>::max(); } bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { @@ -86,7 +85,7 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { } if (Style* style = ValueCast<Style>(config_value->value.get())) { - Maybe<size_t> min_sdk_stripped; + Maybe<ApiVersion> min_sdk_stripped; std::vector<Style::Entry> stripped; auto iter = style->entries.begin(); @@ -95,17 +94,14 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { // Find the SDK level that is higher than the configuration // allows. - const size_t sdk_level = - FindAttributeSdkLevel(iter->key.id.value()); - if (sdk_level > - std::max<size_t>(config_value->config.sdkVersion, 1)) { + const ApiVersion sdk_level = FindAttributeSdkLevel(iter->key.id.value()); + if (sdk_level > std::max<ApiVersion>(config_value->config.sdkVersion, 1)) { // Record that we are about to strip this. stripped.emplace_back(std::move(*iter)); // We use the smallest SDK level to generate the new style. if (min_sdk_stripped) { - min_sdk_stripped = - std::min(min_sdk_stripped.value(), sdk_level); + min_sdk_stripped = std::min(min_sdk_stripped.value(), sdk_level); } else { min_sdk_stripped = sdk_level; } @@ -126,10 +122,9 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { min_sdk_stripped.value())) { // Let's create a new Style for this versioned resource. ConfigDescription new_config(config_value->config); - new_config.sdkVersion = min_sdk_stripped.value(); + new_config.sdkVersion = static_cast<uint16_t>(min_sdk_stripped.value()); - std::unique_ptr<Style> new_style( - style->Clone(&table->string_pool)); + std::unique_ptr<Style> new_style(style->Clone(&table->string_pool)); new_style->SetComment(style->GetComment()); new_style->SetSource(style->GetSource()); @@ -140,8 +135,7 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { std::make_move_iterator(stripped.end())); // Insert the new Resource into the correct place. - entry->FindOrCreateValue(new_config, {})->value = - std::move(new_style); + entry->FindOrCreateValue(new_config, {})->value = std::move(new_style); } } } diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index d00fa736f28b..5527f9092c87 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -23,6 +23,7 @@ #include "android-base/macros.h" #include "Resource.h" +#include "SdkConstants.h" #include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" @@ -44,9 +45,12 @@ struct CallSite { * Determines whether a versioned resource should be created. If a versioned * resource already exists, it takes precedence. */ -bool ShouldGenerateVersionedResource(const ResourceEntry* entry, - const ConfigDescription& config, - const int sdk_version_to_generate); +bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, + const ApiVersion sdk_version_to_generate); + +// Finds the next largest ApiVersion of the config which is identical to the given config except +// for sdkVersion. +ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, const ConfigDescription& config); class AutoVersioner : public IResourceTableConsumer { public: @@ -105,11 +109,10 @@ class ResourceConfigValue; class ProductFilter : public IResourceTableConsumer { public: - using ResourceConfigValueIter = - std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; + using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; - explicit ProductFilter(std::unordered_set<std::string> products) - : products_(products) {} + explicit ProductFilter(std::unordered_set<std::string> products) : products_(products) { + } ResourceConfigValueIter SelectProductToKeep( const ResourceNameRef& name, const ResourceConfigValueIter begin, @@ -118,19 +121,9 @@ class ProductFilter : public IResourceTableConsumer { bool Consume(IAaptContext* context, ResourceTable* table) override; private: - std::unordered_set<std::string> products_; - DISALLOW_COPY_AND_ASSIGN(ProductFilter); -}; - -class XmlAutoVersioner : public IXmlResourceConsumer { - public: - XmlAutoVersioner() = default; - bool Consume(IAaptContext* context, xml::XmlResource* resource) override; - - private: - DISALLOW_COPY_AND_ASSIGN(XmlAutoVersioner); + std::unordered_set<std::string> products_; }; /** @@ -143,8 +136,7 @@ class XmlAutoVersioner : public IXmlResourceConsumer { */ class XmlNamespaceRemover : public IXmlResourceConsumer { public: - explicit XmlNamespaceRemover(bool keep_uris = false) - : keep_uris_(keep_uris){}; + explicit XmlNamespaceRemover(bool keep_uris = false) : keep_uris_(keep_uris){}; bool Consume(IAaptContext* context, xml::XmlResource* resource) override; @@ -165,17 +157,8 @@ class XmlReferenceLinker : public IXmlResourceConsumer { bool Consume(IAaptContext* context, xml::XmlResource* resource) override; - /** - * Once the XmlResource has been consumed, this returns the various SDK levels - * in which - * framework attributes used within the XML document were defined. - */ - inline const std::set<int>& sdk_levels() const { return sdk_levels_found_; } - private: DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker); - - std::set<int> sdk_levels_found_; }; } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 833ae69a0317..18498e3e3c67 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -274,7 +274,7 @@ Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& if (out_error) *out_error = "is not an attribute"; return {}; } - return xml::AaptAttribute{symbol->id, *symbol->attribute}; + return xml::AaptAttribute(*symbol->attribute, symbol->id); } void ReferenceLinker::WriteResourceName(DiagMessage* out_msg, diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp new file mode 100644 index 000000000000..f1f4e3b7f05f --- /dev/null +++ b/tools/aapt2/link/XmlCompatVersioner.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/XmlCompatVersioner.h" + +#include <algorithm> + +#include "util/Util.h" + +namespace aapt { + +static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string_pool) { + xml::Attribute dst{src.namespace_uri, src.name, src.value, src.compiled_attribute}; + if (src.compiled_value != nullptr) { + dst.compiled_value.reset(src.compiled_value->Clone(out_string_pool)); + } + return dst; +} + +// Returns false if the attribute is not copied because an existing attribute takes precedence +// (came from a rule). +static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::Element* dst_el, + StringPool* out_string_pool) { + xml::Attribute* dst_attr = dst_el->FindAttribute(src_attr.namespace_uri, src_attr.name); + if (dst_attr != nullptr) { + if (generated) { + // Generated attributes always take precedence. + dst_attr->value = src_attr.value; + dst_attr->compiled_attribute = src_attr.compiled_attribute; + if (src_attr.compiled_value != nullptr) { + dst_attr->compiled_value.reset(src_attr.compiled_value->Clone(out_string_pool)); + } + return true; + } + return false; + } + dst_el->attributes.push_back(CopyAttr(src_attr, out_string_pool)); + return true; +} + +void XmlCompatVersioner::ProcessRule(const xml::Element& src_el, const xml::Attribute& src_attr, + const ApiVersion& src_attr_version, const IDegradeRule* rule, + const util::Range<ApiVersion>& api_range, bool generated, + xml::Element* dst_el, + std::set<ApiVersion>* out_apis_referenced, + StringPool* out_string_pool) { + if (src_attr_version <= api_range.start) { + // The API is compatible, so don't check the rule and just copy. + if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) { + // TODO(adamlesinski): Log a warning that an attribute was overridden? + } + return; + } + + if (api_range.start >= SDK_LOLLIPOP_MR1) { + // Since LOLLIPOP MR1, the framework can handle silently ignoring unknown public attributes, + // so we don't need to erase/version them. + // Copy. + if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) { + // TODO(adamlesinski): Log a warning that an attribute was overridden? + } + } else { + // We are going to erase this attribute from this XML resource version, but check if + // we even need to move it anywhere. A developer may have effectively overwritten it with + // a similarly versioned XML resource. + if (src_attr_version < api_range.end) { + // There is room for another versioned XML resource between this XML resource and the next + // versioned XML resource defined by the developer. + out_apis_referenced->insert(std::min<ApiVersion>(src_attr_version, SDK_LOLLIPOP_MR1)); + } + } + + if (rule != nullptr) { + for (const DegradeResult& result : rule->Degrade(src_el, src_attr, out_string_pool)) { + const ResourceId attr_resid = result.attr.compiled_attribute.value().id.value(); + const ApiVersion attr_version = FindAttributeSdkLevel(attr_resid); + + auto iter = rules_->find(attr_resid); + ProcessRule(src_el, result.attr, attr_version, + iter != rules_->end() ? iter->second.get() : nullptr, api_range, + true /*generated*/, dst_el, out_apis_referenced, out_string_pool); + } + } +} + +XmlCompatVersioner::XmlCompatVersioner(const Rules* rules) : rules_(rules) { +} + +std::unique_ptr<xml::XmlResource> XmlCompatVersioner::ProcessDoc( + ApiVersion target_api, ApiVersion max_api, xml::XmlResource* doc, + std::set<ApiVersion>* out_apis_referenced) { + const util::Range<ApiVersion> api_range{target_api, max_api}; + + std::unique_ptr<xml::XmlResource> cloned_doc = util::make_unique<xml::XmlResource>(doc->file); + cloned_doc->file.config.sdkVersion = static_cast<uint16_t>(target_api); + + cloned_doc->root = doc->root->Clone([&](const xml::Element& el, xml::Element* out_el) { + for (const auto& attr : el.attributes) { + if (!attr.compiled_attribute) { + // Just copy if this isn't a compiled attribute. + out_el->attributes.push_back(CopyAttr(attr, &cloned_doc->string_pool)); + continue; + } + + const ResourceId attr_resid = attr.compiled_attribute.value().id.value(); + const ApiVersion attr_version = FindAttributeSdkLevel(attr_resid); + + auto rule = rules_->find(attr_resid); + ProcessRule(el, attr, attr_version, rule != rules_->end() ? rule->second.get() : nullptr, + api_range, false /*generated*/, out_el, out_apis_referenced, + &cloned_doc->string_pool); + } + }); + return cloned_doc; +} + +std::vector<std::unique_ptr<xml::XmlResource>> XmlCompatVersioner::Process( + IAaptContext* context, xml::XmlResource* doc, util::Range<ApiVersion> api_range) { + // Adjust the API range so that it falls after this document and after minSdkVersion. + api_range.start = std::max(api_range.start, context->GetMinSdkVersion()); + api_range.start = std::max(api_range.start, static_cast<ApiVersion>(doc->file.config.sdkVersion)); + + std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs; + std::set<ApiVersion> apis_referenced; + versioned_docs.push_back(ProcessDoc(api_range.start, api_range.end, doc, &apis_referenced)); + + // Adjust the sdkVersion of the first XML document back to its original (this only really + // makes a difference if the sdk version was below the minSdk to start). + versioned_docs.back()->file.config.sdkVersion = doc->file.config.sdkVersion; + + // Iterate from smallest to largest API version. + for (ApiVersion api : apis_referenced) { + std::set<ApiVersion> dummy; + versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &dummy)); + } + return versioned_docs; +} + +DegradeToManyRule::DegradeToManyRule(std::vector<ReplacementAttr> attrs) + : attrs_(std::move(attrs)) { +} + +static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>& src, + StringPool* out_string_pool) { + if (src == nullptr) { + return {}; + } + return std::unique_ptr<Item>(src->Clone(out_string_pool)); +} + +std::vector<DegradeResult> DegradeToManyRule::Degrade(const xml::Element& src_el, + const xml::Attribute& src_attr, + StringPool* out_string_pool) const { + std::vector<DegradeResult> result; + result.reserve(attrs_.size()); + for (const ReplacementAttr& attr : attrs_) { + result.push_back( + DegradeResult{xml::Attribute{xml::kSchemaAndroid, attr.name, src_attr.value, + xml::AaptAttribute{attr.attr, attr.id}, + CloneIfNotNull(src_attr.compiled_value, out_string_pool)}, + FindAttributeSdkLevel(attr.id)}); + } + return result; +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlCompatVersioner.h b/tools/aapt2/link/XmlCompatVersioner.h new file mode 100644 index 000000000000..099e23c9e479 --- /dev/null +++ b/tools/aapt2/link/XmlCompatVersioner.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_LINKER_XMLCOMPATVERSIONER_H +#define AAPT_LINKER_XMLCOMPATVERSIONER_H + +#include <set> +#include <unordered_map> +#include <vector> + +#include "android-base/macros.h" + +#include "Resource.h" +#include "SdkConstants.h" +#include "process/IResourceTableConsumer.h" +#include "util/Util.h" +#include "xml/XmlDom.h" + +namespace aapt { + +class IDegradeRule; + +struct DegradeResult { + xml::Attribute attr; + ApiVersion attr_api_version; +}; + +class IDegradeRule { + public: + IDegradeRule() = default; + virtual ~IDegradeRule() = default; + + virtual std::vector<DegradeResult> Degrade(const xml::Element& src_el, + const xml::Attribute& src_attr, + StringPool* out_string_pool) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(IDegradeRule); +}; + +class XmlCompatVersioner { + public: + using Rules = std::unordered_map<ResourceId, std::unique_ptr<IDegradeRule>>; + + XmlCompatVersioner(const Rules* rules); + + std::vector<std::unique_ptr<xml::XmlResource>> Process(IAaptContext* context, + xml::XmlResource* doc, + util::Range<ApiVersion> api_range); + + private: + DISALLOW_COPY_AND_ASSIGN(XmlCompatVersioner); + + std::unique_ptr<xml::XmlResource> ProcessDoc(ApiVersion target_api, ApiVersion max_api, + xml::XmlResource* doc, + std::set<ApiVersion>* out_apis_referenced); + void ProcessRule(const xml::Element& src_el, const xml::Attribute& src_attr, + const ApiVersion& src_attr_version, const IDegradeRule* rule, + const util::Range<ApiVersion>& api_range, bool generated, xml::Element* dst_el, + std::set<ApiVersion>* out_apis_referenced, StringPool* out_string_pool); + + const Rules* rules_; +}; + +struct ReplacementAttr { + std::string name; + ResourceId id; + Attribute attr; +}; + +class DegradeToManyRule : public IDegradeRule { + public: + DegradeToManyRule(std::vector<ReplacementAttr> attrs); + virtual ~DegradeToManyRule() = default; + + std::vector<DegradeResult> Degrade(const xml::Element& src_el, const xml::Attribute& src_attr, + StringPool* out_string_pool) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(DegradeToManyRule); + + std::vector<ReplacementAttr> attrs_; +}; + +} // namespace aapt + +#endif // AAPT_LINKER_XMLCOMPATVERSIONER_H diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp new file mode 100644 index 000000000000..ce6605c6ad97 --- /dev/null +++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/XmlCompatVersioner.h" + +#include "Linkers.h" +#include "test/Test.h" + +namespace aapt { + +constexpr auto TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION; +constexpr auto TYPE_STRING = android::ResTable_map::TYPE_STRING; + +struct R { + struct attr { + enum : uint32_t { + paddingLeft = 0x010100d6u, // (API 1) + paddingRight = 0x010100d8u, // (API 1) + progressBarPadding = 0x01010319u, // (API 11) + paddingStart = 0x010103b3u, // (API 17) + paddingHorizontal = 0x0101053du, // (API 26) + }; + }; +}; + +class XmlCompatVersionerTest : public ::testing::Test { + public: + void SetUp() override { + context_ = + test::ContextBuilder() + .SetCompilationPackage("com.app") + .SetPackageId(0x7f) + .SetPackageType(PackageType::kApp) + .SetMinSdkVersion(SDK_GINGERBREAD) + .AddSymbolSource( + test::StaticSymbolSourceBuilder() + .AddPublicSymbol("android:attr/paddingLeft", R::attr::paddingLeft, + util::make_unique<Attribute>(false, TYPE_DIMENSION)) + .AddPublicSymbol("android:attr/paddingRight", R::attr::paddingRight, + util::make_unique<Attribute>(false, TYPE_DIMENSION)) + .AddPublicSymbol("android:attr/progressBarPadding", R::attr::progressBarPadding, + util::make_unique<Attribute>(false, TYPE_DIMENSION)) + .AddPublicSymbol("android:attr/paddingStart", R::attr::paddingStart, + util::make_unique<Attribute>(false, TYPE_DIMENSION)) + .AddPublicSymbol("android:attr/paddingHorizontal", R::attr::paddingHorizontal, + util::make_unique<Attribute>(false, TYPE_DIMENSION)) + .AddSymbol("com.app:attr/foo", ResourceId(0x7f010000), + util::make_unique<Attribute>(false, TYPE_STRING)) + .Build()) + .Build(); + } + + protected: + std::unique_ptr<IAaptContext> context_; +}; + +TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) { + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:paddingHorizontal="24dp" + app:foo="16dp" + foo="bar"/>)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + XmlCompatVersioner::Rules rules; + const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1}; + + XmlCompatVersioner versioner(&rules); + std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = + versioner.Process(context_.get(), doc.get(), api_range); + ASSERT_EQ(2u, versioned_docs.size()); + + xml::Element* el; + + // Source XML file's sdkVersion == 0, so the first one must also have the same sdkVersion. + EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[0].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(2u, el->attributes.size()); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); + EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); + + EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[1].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(3u, el->attributes.size()); + EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); + EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); +} + +TEST_F(XmlCompatVersionerTest, SingleRule) { + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:paddingHorizontal="24dp" + app:foo="16dp" + foo="bar"/>)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + XmlCompatVersioner::Rules rules; + rules[R::attr::paddingHorizontal] = + util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>( + {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)}, + ReplacementAttr{"paddingRight", R::attr::paddingRight, + Attribute(false, TYPE_DIMENSION)}})); + + const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1}; + + XmlCompatVersioner versioner(&rules); + std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = + versioner.Process(context_.get(), doc.get(), api_range); + ASSERT_EQ(2u, versioned_docs.size()); + + xml::Element* el; + + EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[0].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(4u, el->attributes.size()); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); + EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); + + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[1].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(5u, el->attributes.size()); + EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); + EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); +} + +TEST_F(XmlCompatVersionerTest, ChainedRule) { + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingHorizontal="24dp" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + XmlCompatVersioner::Rules rules; + rules[R::attr::progressBarPadding] = + util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>( + {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)}, + ReplacementAttr{"paddingRight", R::attr::paddingRight, + Attribute(false, TYPE_DIMENSION)}})); + rules[R::attr::paddingHorizontal] = + util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>({ReplacementAttr{ + "progressBarPadding", R::attr::progressBarPadding, Attribute(false, TYPE_DIMENSION)}})); + + const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1}; + + XmlCompatVersioner versioner(&rules); + std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = + versioner.Process(context_.get(), doc.get(), api_range); + ASSERT_EQ(3u, versioned_docs.size()); + + xml::Element* el; + + EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[0].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(2u, el->attributes.size()); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + EXPECT_EQ(static_cast<uint16_t>(SDK_HONEYCOMB), versioned_docs[1]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[1].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(1u, el->attributes.size()); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft")); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight")); + + attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[2]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[2].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(2u, el->attributes.size()); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft")); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight")); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(attr->compiled_attribute); +} + +TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) { + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingHorizontal="24dp" + android:paddingLeft="16dp" + android:paddingRight="16dp"/>)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + Item* padding_horizontal_value = xml::FindRootElement(doc.get()) + ->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal") + ->compiled_value.get(); + ASSERT_NE(nullptr, padding_horizontal_value); + + XmlCompatVersioner::Rules rules; + rules[R::attr::paddingHorizontal] = + util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>( + {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)}, + ReplacementAttr{"paddingRight", R::attr::paddingRight, + Attribute(false, TYPE_DIMENSION)}})); + + const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1}; + + XmlCompatVersioner versioner(&rules); + std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = + versioner.Process(context_.get(), doc.get(), api_range); + ASSERT_EQ(2u, versioned_docs.size()); + + xml::Element* el; + + EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[0].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(2u, el->attributes.size()); + EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_TRUE(attr->compiled_attribute); + + EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); + el = xml::FindRootElement(versioned_docs[1].get()); + ASSERT_NE(nullptr, el); + EXPECT_EQ(3u, el->attributes.size()); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_TRUE(attr->compiled_attribute); + + attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); + ASSERT_NE(nullptr, attr); + ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_TRUE(attr->compiled_attribute); +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 94bdccd5ab64..721fc26399bc 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -72,13 +72,13 @@ class XmlVisitor : public xml::PackageAwareVisitor { using xml::PackageAwareVisitor::Visit; XmlVisitor(const Source& source, const CallSite& callsite, IAaptContext* context, - SymbolTable* symbols, std::set<int>* sdk_levels_found) + SymbolTable* symbols) : source_(source), callsite_(callsite), context_(context), symbols_(symbols), - sdk_levels_found_(sdk_levels_found), - reference_visitor_(callsite, context, symbols, this) {} + reference_visitor_(callsite, context, symbols, this) { + } void Visit(xml::Element* el) override { // The default Attribute allows everything except enums or flags. @@ -118,22 +118,12 @@ class XmlVisitor : public xml::PackageAwareVisitor { continue; } - // Find this compiled attribute's SDK level. - const xml::AaptAttribute& aapt_attr = attr.compiled_attribute.value(); - if (aapt_attr.id) { - // Record all SDK levels from which the attributes were defined. - const size_t sdk_level = FindAttributeSdkLevel(aapt_attr.id.value()); - if (sdk_level > 1) { - sdk_levels_found_->insert(sdk_level); - } - } - attribute = &aapt_attr.attribute; + attribute = &attr.compiled_attribute.value().attribute; } attr.compiled_value = ResourceUtils::TryParseItemForAttribute(attr.value, attribute); if (attr.compiled_value) { - // With a compiledValue, we must resolve the reference and assign it an - // ID. + // With a compiledValue, we must resolve the reference and assign it an ID. attr.compiled_value->SetSource(source); attr.compiled_value->Accept(&reference_visitor_); } else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) { @@ -164,7 +154,6 @@ class XmlVisitor : public xml::PackageAwareVisitor { IAaptContext* context_; SymbolTable* symbols_; - std::set<int>* sdk_levels_found_; ReferenceVisitor reference_visitor_; bool error_ = false; }; @@ -172,10 +161,8 @@ class XmlVisitor : public xml::PackageAwareVisitor { } // namespace bool XmlReferenceLinker::Consume(IAaptContext* context, xml::XmlResource* resource) { - sdk_levels_found_.clear(); const CallSite callsite = {resource->file.name}; - XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols(), - &sdk_levels_found_); + XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols()); if (resource->root) { resource->root->Accept(&visitor); return !visitor.HasError(); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index 66ecc15730ed..de81e73f613e 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -160,16 +160,6 @@ TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); } -TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:colorAccent="#ffffff" />)EOF"); - - XmlReferenceLinker linker; - ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - EXPECT_TRUE(linker.sdk_levels().count(21) == 1); -} - TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 1a648bf080b5..882a85b1c1bb 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -237,14 +237,12 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( } // We found a resource. - std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(); - s->id = id; + std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(id); // Check to see if it is an attribute. for (size_t i = 0; i < (size_t)count; i++) { if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { - s->attribute = std::make_shared<Attribute>(false); - s->attribute->type_mask = entry[i].map.value.data; + s->attribute = std::make_shared<Attribute>(false, entry[i].map.value.data); break; } } diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index bd252d245dc8..4a2181e0b344 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -53,16 +53,12 @@ class NameMangler; class SymbolTable { public: struct Symbol { - Symbol() : Symbol(Maybe<ResourceId>{}) {} + Symbol() = default; - explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {} - - Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) - : Symbol(i, attr, false) {} - - Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, - bool pub) - : id(i), attribute(attr), is_public(pub) {} + explicit Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr = {}, + bool pub = false) + : id(i), attribute(attr), is_public(pub) { + } Symbol(const Symbol&) = default; Symbol(Symbol&&) = default; diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index e92565f3e76e..7e28ae7b6f78 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,11 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.16 +### `aapt2 link ...` +- Versioning of XML files is more intelligent, using a small set of rules to degrade + specific newer attributes to backwards compatible versions of them. + Ex: `android:paddingHorizontal` degrades to `android:paddingLeft` and `android:paddingRight`. + ## Version 2.15 ### `aapt2 compile ...` - Add `--no-crunch` option to avoid processing PNGs during the compile phase. Note that this diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 30b9af61d896..386f74b0301a 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -44,6 +44,12 @@ namespace aapt { namespace util { +template <typename T> +struct Range { + T start; + T end; +}; + std::vector<std::string> Split(const android::StringPiece& str, char sep); std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 98f5f1d06df9..885ab3e33fed 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -356,17 +356,16 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root)); } -std::unique_ptr<Node> Namespace::Clone() { +std::unique_ptr<Node> Namespace::Clone(const ElementCloneFunc& el_cloner) { auto ns = util::make_unique<Namespace>(); ns->comment = comment; ns->line_number = line_number; ns->column_number = column_number; ns->namespace_prefix = namespace_prefix; ns->namespace_uri = namespace_uri; - ns->children.reserve(children.size()); for (const std::unique_ptr<xml::Node>& child : children) { - ns->AppendChild(child->Clone()); + ns->AppendChild(child->Clone(el_cloner)); } return std::move(ns); } @@ -412,6 +411,15 @@ Attribute* Element::FindAttribute(const StringPiece& ns, return nullptr; } +const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const { + for (const auto& attr : attributes) { + if (ns == attr.namespace_uri && name == attr.name) { + return &attr; + } + } + return nullptr; +} + Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) { return FindChildWithAttribute(ns, name, {}, {}, {}); } @@ -464,29 +472,23 @@ std::vector<Element*> Element::GetChildElements() { return elements; } -std::unique_ptr<Node> Element::Clone() { +std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) { auto el = util::make_unique<Element>(); el->comment = comment; el->line_number = line_number; el->column_number = column_number; el->name = name; el->namespace_uri = namespace_uri; - el->attributes.reserve(attributes.size()); - for (xml::Attribute& attr : attributes) { - // Don't copy compiled values or attributes. - el->attributes.push_back( - xml::Attribute{attr.namespace_uri, attr.name, attr.value}); - } - + el_cloner(*this, el.get()); el->children.reserve(children.size()); for (const std::unique_ptr<xml::Node>& child : children) { - el->AppendChild(child->Clone()); + el->AppendChild(child->Clone(el_cloner)); } return std::move(el); } -std::unique_ptr<Node> Text::Clone() { +std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) { auto t = util::make_unique<Text>(); t->comment = comment; t->line_number = line_number; diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 6950c307abf3..2dc99d693148 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -35,6 +35,8 @@ namespace xml { class RawVisitor; +class Element; + /** * Base class for all XML nodes. */ @@ -51,7 +53,11 @@ class Node { void AppendChild(std::unique_ptr<Node> child); void InsertChild(size_t index, std::unique_ptr<Node> child); virtual void Accept(RawVisitor* visitor) = 0; - virtual std::unique_ptr<Node> Clone() = 0; + + using ElementCloneFunc = std::function<void(const Element&, Element*)>; + + // Clones the Node subtree, using the given function to decide how to clone an Element. + virtual std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) = 0; }; /** @@ -72,12 +78,16 @@ class Namespace : public BaseNode<Namespace> { std::string namespace_prefix; std::string namespace_uri; - std::unique_ptr<Node> Clone() override; + std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; }; struct AaptAttribute { - Maybe<ResourceId> id; + explicit AaptAttribute(const ::aapt::Attribute& attr, const Maybe<ResourceId>& resid = {}) + : attribute(attr), id(resid) { + } + aapt::Attribute attribute; + Maybe<ResourceId> id; }; /** @@ -102,6 +112,8 @@ class Element : public BaseNode<Element> { std::vector<Attribute> attributes; Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name); + const Attribute* FindAttribute(const android::StringPiece& ns, + const android::StringPiece& name) const; xml::Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); xml::Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name, @@ -109,7 +121,7 @@ class Element : public BaseNode<Element> { const android::StringPiece& attr_name, const android::StringPiece& attr_value); std::vector<xml::Element*> GetChildElements(); - std::unique_ptr<Node> Clone() override; + std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; }; /** @@ -119,7 +131,7 @@ class Text : public BaseNode<Text> { public: std::string text; - std::unique_ptr<Node> Clone() override; + std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; }; /** |