diff options
author | 2017-05-17 19:28:38 -0700 | |
---|---|---|
committer | 2017-05-24 15:14:29 -0700 | |
commit | c744ae8aca97edfb2422598ea620e8219449fa9b (patch) | |
tree | 4e7c29d6b47c464dd5aa0af204d86a7340b0084f /tools | |
parent | 0418b72fd2c91f32c42e3a406df2a866888fc766 (diff) |
AAPT2: Implement attribute compat versioning
This change defines some hardcoded rules to degrade
attributes in newer SDKs to specific older attributes.
An attribute with a degrade rule will generate a new XML for the API
in which the attribute resulting from the degradation was introduced.
Since API 22 (Lollipop MR1), attributes are correctly ignored and do
not need to be versioned. In XML files defined for APIs 22+, the
original and degraded attributes coexist in the same XML file.
One such example is paddingHorizontal, introduced in API 26.
paddingHorizontal degrades to paddingLeft and paddingRight, which
were both introduced in API 1.
Bug: 35763493
Test: make aapt2_tests
Change-Id: I4aa8755a9ee2c0cc5afdc55c3d30093fd3a47f3d
Diffstat (limited to 'tools')
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; }; /** |