diff options
Diffstat (limited to 'tools/aapt2/java')
-rw-r--r-- | tools/aapt2/java/AnnotationProcessor.cpp | 73 | ||||
-rw-r--r-- | tools/aapt2/java/AnnotationProcessor.h | 77 | ||||
-rw-r--r-- | tools/aapt2/java/AnnotationProcessor_test.cpp | 35 | ||||
-rw-r--r-- | tools/aapt2/java/ClassDefinition.cpp | 66 | ||||
-rw-r--r-- | tools/aapt2/java/ClassDefinition.h | 62 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator.cpp | 241 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator.h | 25 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator_test.cpp | 227 | ||||
-rw-r--r-- | tools/aapt2/java/ManifestClassGenerator.cpp | 25 | ||||
-rw-r--r-- | tools/aapt2/java/ManifestClassGenerator.h | 3 | ||||
-rw-r--r-- | tools/aapt2/java/ManifestClassGenerator_test.cpp | 25 | ||||
-rw-r--r-- | tools/aapt2/java/ProguardRules.cpp | 187 | ||||
-rw-r--r-- | tools/aapt2/java/ProguardRules.h | 70 | ||||
-rw-r--r-- | tools/aapt2/java/ProguardRules_test.cpp | 137 |
14 files changed, 796 insertions, 457 deletions
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 1f83fa098d74..8d91b0098c1f 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -17,11 +17,13 @@ #include "java/AnnotationProcessor.h" #include <algorithm> +#include <array> #include "text/Unicode.h" #include "text/Utf8Iterator.h" #include "util/Util.h" +using ::aapt::text::Printer; using ::aapt::text::Utf8Iterator; using ::android::StringPiece; @@ -41,30 +43,54 @@ StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment return comment; } -void AnnotationProcessor::AppendCommentLine(std::string& comment) { +struct AnnotationRule { + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + kTestApi = 0x04, + }; + + StringPiece doc_str; + uint32_t bit_mask; + StringPiece annotation; +}; + +static std::array<AnnotationRule, 2> sAnnotationRules = {{ + {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi"}, + {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi"}, +}}; + +void AnnotationProcessor::AppendCommentLine(std::string comment) { static const std::string sDeprecated = "@deprecated"; - static const std::string sSystemApi = "@SystemApi"; + // Treat deprecated specially, since we don't remove it from the source comment. if (comment.find(sDeprecated) != std::string::npos) { - annotation_bit_mask_ |= kDeprecated; + annotation_bit_mask_ |= AnnotationRule::kDeprecated; } - std::string::size_type idx = comment.find(sSystemApi); - if (idx != std::string::npos) { - annotation_bit_mask_ |= kSystemApi; - comment.erase(comment.begin() + idx, - comment.begin() + idx + sSystemApi.size()); + for (const AnnotationRule& rule : sAnnotationRules) { + std::string::size_type idx = comment.find(rule.doc_str.data()); + if (idx != std::string::npos) { + annotation_bit_mask_ |= rule.bit_mask; + comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size()); + } } - if (util::TrimWhitespace(comment).empty()) { + // Check if after removal of annotations the line is empty. + const StringPiece trimmed = util::TrimWhitespace(comment); + if (trimmed.empty()) { return; } + // If there was trimming to do, copy the string. + if (trimmed.size() != comment.size()) { + comment = trimmed.to_string(); + } + if (!has_comments_) { has_comments_ = true; comment_ << "/**"; } - comment_ << "\n * " << std::move(comment); } @@ -73,31 +99,34 @@ void AnnotationProcessor::AppendComment(const StringPiece& comment) { for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - std::string lineCopy = line.to_string(); - AppendCommentLine(lineCopy); + AppendCommentLine(line.to_string()); } } } -void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; } +void AnnotationProcessor::AppendNewLine() { + if (has_comments_) { + comment_ << "\n *"; + } +} -void AnnotationProcessor::WriteToStream(std::ostream* out, - const StringPiece& prefix) const { +void AnnotationProcessor::Print(Printer* printer) const { if (has_comments_) { std::string result = comment_.str(); for (StringPiece line : util::Tokenize(result, '\n')) { - *out << prefix << line << "\n"; + printer->Println(line); } - *out << prefix << " */" - << "\n"; + printer->Println(" */"); } - if (annotation_bit_mask_ & kDeprecated) { - *out << prefix << "@Deprecated\n"; + if (annotation_bit_mask_ & AnnotationRule::kDeprecated) { + printer->Println("@Deprecated"); } - if (annotation_bit_mask_ & kSystemApi) { - *out << prefix << "@android.annotation.SystemApi\n"; + for (const AnnotationRule& rule : sAnnotationRules) { + if (annotation_bit_mask_ & rule.bit_mask) { + printer->Println(rule.annotation); + } } } diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index a06eda0f9c5c..ae7bdb0c3ae2 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -22,66 +22,57 @@ #include "androidfw/StringPiece.h" +#include "text/Printer.h" + namespace aapt { -/** - * Builds a JavaDoc comment from a set of XML comments. - * This will also look for instances of @SystemApi and convert them to - * actual Java annotations. - * - * Example: - * - * Input XML: - * - * <!-- This is meant to be hidden because - * It is system api. Also it is @deprecated - * @SystemApi - * --> - * - * Output JavaDoc: - * - * /\* - * * This is meant to be hidden because - * * It is system api. Also it is @deprecated - * *\/ - * - * Output Annotations: - * - * @Deprecated - * @android.annotation.SystemApi - * - */ +// Builds a JavaDoc comment from a set of XML comments. +// This will also look for instances of @SystemApi and convert them to +// actual Java annotations. +// +// Example: +// +// Input XML: +// +// <!-- This is meant to be hidden because +// It is system api. Also it is @deprecated +// @SystemApi +// --> +// +// Output JavaDoc: +// +// /** +// * This is meant to be hidden because +// * It is system api. Also it is @deprecated +// */ +// +// Output Annotations: +// +// @Deprecated +// @android.annotation.SystemApi class AnnotationProcessor { public: + // Extracts the first sentence of a comment. The algorithm selects the substring starting from + // the beginning of the string, and ending at the first '.' character that is followed by a + // whitespace character. If these requirements are not met, the whole string is returned. static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); - /** - * Adds more comments. Since resources can have various values with different - * configurations, - * we need to collect all the comments. - */ + // Adds more comments. Resources can have value definitions for various configurations, and + // each of the definitions may have comments that need to be processed. void AppendComment(const android::StringPiece& comment); void AppendNewLine(); - /** - * Writes the comments and annotations to the stream, with the given prefix - * before each line. - */ - void WriteToStream(std::ostream* out, const android::StringPiece& prefix) const; + // Writes the comments and annotations to the Printer. + void Print(text::Printer* printer) const; private: - enum : uint32_t { - kDeprecated = 0x01, - kSystemApi = 0x02, - }; - std::stringstream comment_; std::stringstream mAnnotations; bool has_comments_ = false; uint32_t annotation_bit_mask_ = 0; - void AppendCommentLine(std::string& line); + void AppendCommentLine(std::string line); }; } // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 9ccac8888426..69f49c8b97c3 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -16,8 +16,12 @@ #include "java/AnnotationProcessor.h" +#include "io/StringStream.h" #include "test/Test.h" +#include "text/Printer.h" +using ::aapt::io::StringOutputStream; +using ::aapt::text::Printer; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::Not; @@ -33,9 +37,11 @@ TEST(AnnotationProcessorTest, EmitsDeprecated) { AnnotationProcessor processor; processor.AppendComment(comment); - std::stringstream result; - processor.WriteToStream(&result, ""); - std::string annotations = result.str(); + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); EXPECT_THAT(annotations, HasSubstr("@Deprecated")); } @@ -44,15 +50,32 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { AnnotationProcessor processor; processor.AppendComment("@SystemApi This is a system API"); - std::stringstream result; - processor.WriteToStream(&result, ""); - std::string annotations = result.str(); + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi")); EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi"))); EXPECT_THAT(annotations, HasSubstr("This is a system API")); } +TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) { + AnnotationProcessor processor; + processor.AppendComment("@TestApi This is a test API"); + + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); + + EXPECT_THAT(annotations, HasSubstr("@android.annotation.TestApi")); + EXPECT_THAT(annotations, Not(HasSubstr("@TestApi"))); + EXPECT_THAT(annotations, HasSubstr("This is a test API")); +} + TEST(AnnotationProcessor, ExtractsFirstSentence) { EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), Eq("This is the only sentence")); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index c139b73db296..f5f5b05491bb 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -18,33 +18,45 @@ #include "androidfw/StringPiece.h" -using android::StringPiece; +using ::aapt::text::Printer; +using ::android::StringPiece; namespace aapt { -void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - processor_.WriteToStream(out, prefix); +void ClassMember::Print(bool /*final*/, Printer* printer) const { + processor_.Print(printer); } void MethodDefinition::AppendStatement(const StringPiece& statement) { statements_.push_back(statement.to_string()); } -void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final, - std::ostream* out) const { - *out << prefix << signature_ << " {\n"; +void MethodDefinition::Print(bool final, Printer* printer) const { + printer->Print(signature_).Println(" {"); + printer->Indent(); for (const auto& statement : statements_) { - *out << prefix << " " << statement << "\n"; + printer->Println(statement); } - *out << prefix << "}"; + printer->Undent(); + printer->Print("}"); } ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) { Result result = Result::kAdded; auto iter = indexed_members_.find(member->GetName()); if (iter != indexed_members_.end()) { - // Overwrite the entry. - ordered_members_[iter->second].reset(); + // Overwrite the entry. Be careful, as the key in indexed_members_ is actually memory owned + // by the value at ordered_members_[index]. Since overwriting a value for a key doesn't replace + // the key (the initial key inserted into the unordered_map is kept), we must erase and then + // insert a new key, whose memory is being kept around. We do all this to avoid using more + // memory for each key. + size_t index = iter->second; + + // Erase the key + value from the map. + indexed_members_.erase(iter); + + // Now clear the memory that was backing the key (now erased). + ordered_members_[index].reset(); result = Result::kOverridden; } @@ -62,34 +74,32 @@ bool ClassDefinition::empty() const { return true; } -void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final, - std::ostream* out) const { +void ClassDefinition::Print(bool final, Printer* printer) const { if (empty() && !create_if_empty_) { return; } - ClassMember::WriteToStream(prefix, final, out); + ClassMember::Print(final, printer); - *out << prefix << "public "; + printer->Print("public "); if (qualifier_ == ClassQualifier::kStatic) { - *out << "static "; + printer->Print("static "); } - *out << "final class " << name_ << " {\n"; - - std::string new_prefix = prefix.to_string(); - new_prefix.append(kIndent); + printer->Print("final class ").Print(name_).Println(" {"); + printer->Indent(); for (const std::unique_ptr<ClassMember>& member : ordered_members_) { // There can be nullptr members when a member is added to the ClassDefinition // and takes precedence over a previous member with the same name. The overridden member is // set to nullptr. if (member != nullptr) { - member->WriteToStream(new_prefix, final, out); - *out << "\n"; + member->Print(final, printer); + printer->Println(); } } - *out << prefix << "}"; + printer->Undent(); + printer->Print("}"); } constexpr static const char* sWarningHeader = @@ -100,12 +110,12 @@ constexpr static const char* sWarningHeader = " * should not be modified by hand.\n" " */\n\n"; -bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, - const StringPiece& package, bool final, - std::ostream* out) { - *out << sWarningHeader << "package " << package << ";\n\n"; - def->WriteToStream("", final, out); - return bool(*out); +void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package, + bool final, io::OutputStream* out) { + Printer printer(out); + printer.Print(sWarningHeader).Print("package ").Print(package).Println(";"); + printer.Println(); + def->Print(final, &printer); } } // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 28a3489e71a4..fb11266f1761 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -17,7 +17,6 @@ #ifndef AAPT_JAVA_CLASSDEFINITION_H #define AAPT_JAVA_CLASSDEFINITION_H -#include <ostream> #include <string> #include <unordered_map> #include <vector> @@ -27,6 +26,7 @@ #include "Resource.h" #include "java/AnnotationProcessor.h" +#include "text/Printer.h" #include "util/Util.h" namespace aapt { @@ -47,11 +47,10 @@ class ClassMember { virtual const std::string& GetName() const = 0; - // Writes the class member to the out stream. Subclasses should derive this method + // Writes the class member to the Printer. Subclasses should derive this method // to write their own data. Call this base method from the subclass to write out // this member's comments/annotations. - virtual void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const; + virtual void Print(bool final, text::Printer* printer) const; private: AnnotationProcessor processor_; @@ -71,11 +70,16 @@ class PrimitiveMember : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override { - ClassMember::WriteToStream(prefix, final, out); - *out << prefix << "public static " << (final ? "final " : "") << "int " << name_ << "=" << val_ - << ";"; + void Print(bool final, text::Printer* printer) const override { + using std::to_string; + + ClassMember::Print(final, printer); + + printer->Print("public static "); + if (final) { + printer->Print("final "); + } + printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";"); } private: @@ -100,12 +104,14 @@ class PrimitiveMember<std::string> : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override { - ClassMember::WriteToStream(prefix, final, out); + void Print(bool final, text::Printer* printer) const override { + ClassMember::Print(final, printer); - *out << prefix << "public static " << (final ? "final " : "") << "String " - << name_ << "=\"" << val_ << "\";"; + printer->Print("public static "); + if (final) { + printer->Print("final "); + } + printer->Print("String ").Print(name_).Print("=\"").Print(val_).Print("\";"); } private: @@ -136,25 +142,27 @@ class PrimitiveArrayMember : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override { - ClassMember::WriteToStream(prefix, final, out); + void Print(bool final, text::Printer* printer) const override { + ClassMember::Print(final, printer); - *out << prefix << "public static final int[] " << name_ << "={"; + printer->Print("public static final int[] ").Print(name_).Print("={"); + printer->Indent(); const auto begin = elements_.begin(); const auto end = elements_.end(); for (auto current = begin; current != end; ++current) { if (std::distance(begin, current) % kAttribsPerLine == 0) { - *out << "\n" << prefix << kIndent << kIndent; + printer->Println(); } - *out << *current; + printer->Print(to_string(*current)); if (std::distance(current, end) > 1) { - *out << ", "; + printer->Print(", "); } } - *out << "\n" << prefix << kIndent << "};"; + printer->Println(); + printer->Undent(); + printer->Print("};"); } private: @@ -187,8 +195,7 @@ class MethodDefinition : public ClassMember { return false; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override; + void Print(bool final, text::Printer* printer) const override; private: DISALLOW_COPY_AND_ASSIGN(MethodDefinition); @@ -201,8 +208,8 @@ enum class ClassQualifier { kNone, kStatic }; class ClassDefinition : public ClassMember { public: - static bool WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package, - bool final, std::ostream* out); + static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package, + bool final, io::OutputStream* out); ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {} @@ -220,8 +227,7 @@ class ClassDefinition : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override; + void Print(bool final, text::Printer* printer) const override; private: DISALLOW_COPY_AND_ASSIGN(ClassDefinition); diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 44fa0f19a0e5..6b07b1e96261 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -37,8 +37,10 @@ #include "java/ClassDefinition.h" #include "process/SymbolTable.h" -using android::StringPiece; -using android::base::StringPrintf; +using ::aapt::io::OutputStream; +using ::aapt::text::Printer; +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { @@ -61,7 +63,7 @@ static bool IsValidSymbol(const StringPiece& symbol) { // Java symbols can not contain . or -, but those are valid in a resource name. // Replace those with '_'. -static std::string TransformToFieldName(const StringPiece& symbol) { +std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) { std::string output = symbol.to_string(); for (char& c : output) { if (c == '.' || c == '-') { @@ -89,9 +91,9 @@ static std::string TransformNestedAttr(const ResourceNameRef& attr_name, // the package. if (!attr_name.package.empty() && package_name_to_generate != attr_name.package) { - output += "_" + TransformToFieldName(attr_name.package); + output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package); } - output += "_" + TransformToFieldName(attr_name.entry); + output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry); return output; } @@ -189,14 +191,14 @@ JavaClassGenerator::JavaClassGenerator(IAaptContext* context, const JavaClassGeneratorOptions& options) : context_(context), table_(table), options_(options) {} -bool JavaClassGenerator::SkipSymbol(SymbolState state) { +bool JavaClassGenerator::SkipSymbol(Visibility::Level level) { switch (options_.types) { case JavaClassGeneratorOptions::SymbolTypes::kAll: return false; case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate: - return state == SymbolState::kUndefined; + return level == Visibility::Level::kUndefined; case JavaClassGeneratorOptions::SymbolTypes::kPublic: - return state != SymbolState::kPublic; + return level != Visibility::Level::kPublic; } return true; } @@ -230,7 +232,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res const StringPiece& package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, - std::ostream* out_r_txt) { + Printer* r_txt_printer) { const std::string array_field_name = TransformToFieldName(name.entry); std::unique_ptr<ResourceArrayMember> array_def = util::make_unique<ResourceArrayMember>(array_field_name); @@ -270,7 +272,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res // Build the JavaDoc comment for the Styleable array. This has references to child attributes // and what possible values can be used for them. const size_t attr_count = sorted_attributes.size(); - if (attr_count > 0) { + if (out_class_def != nullptr && attr_count > 0) { std::stringstream styleable_comment; if (!styleable.GetComment().empty()) { styleable_comment << styleable.GetComment() << "\n"; @@ -323,8 +325,8 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res array_def->GetCommentBuilder()->AppendComment(styleable_comment.str()); } - if (out_r_txt != nullptr) { - *out_r_txt << "int[] styleable " << array_field_name << " {"; + if (r_txt_printer != nullptr) { + r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {"); } // Add the ResourceIds to the array member. @@ -332,16 +334,16 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0)); array_def->AddElement(id); - if (out_r_txt != nullptr) { + if (r_txt_printer != nullptr) { if (i != 0) { - *out_r_txt << ","; + r_txt_printer->Print(","); } - *out_r_txt << " " << id; + r_txt_printer->Print(" ").Print(id.to_string()); } } - if (out_r_txt != nullptr) { - *out_r_txt << " }\n"; + if (r_txt_printer != nullptr) { + r_txt_printer->Println(" }"); } // Add the Styleable array to the Styleable class. @@ -354,54 +356,56 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res continue; } - StringPiece comment = styleable_attr.attr_ref->GetComment(); - if (styleable_attr.symbol.value().attribute && comment.empty()) { - comment = styleable_attr.symbol.value().attribute->GetComment(); - } + if (out_class_def != nullptr) { + StringPiece comment = styleable_attr.attr_ref->GetComment(); + if (styleable_attr.symbol.value().attribute && comment.empty()) { + comment = styleable_attr.symbol.value().attribute->GetComment(); + } - if (comment.contains("@removed")) { - // Removed attributes are public but hidden from the documentation, so - // don't emit them as part of the class documentation. - continue; - } + if (comment.contains("@removed")) { + // Removed attributes are public but hidden from the documentation, so + // don't emit them as part of the class documentation. + continue; + } - const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); + const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); - StringPiece package_name = attr_name.package; - if (package_name.empty()) { - package_name = context_->GetCompilationPackage(); - } + StringPiece package_name = attr_name.package; + if (package_name.empty()) { + package_name = context_->GetCompilationPackage(); + } - std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>( - sorted_attributes[i].field_name, static_cast<uint32_t>(i)); + std::unique_ptr<IntMember> index_member = + util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i)); + + AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); + + if (!comment.empty()) { + attr_processor->AppendComment("<p>\n@attr description"); + attr_processor->AppendComment(comment); + } else { + std::stringstream default_comment; + default_comment << "<p>This symbol is the offset where the " + << "{@link " << package_name << ".R.attr#" + << TransformToFieldName(attr_name.entry) << "}\n" + << "attribute's value can be found in the " + << "{@link #" << array_field_name << "} array."; + attr_processor->AppendComment(default_comment.str()); + } - AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); + attr_processor->AppendNewLine(); + AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); + attr_processor->AppendNewLine(); + attr_processor->AppendComment( + StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); - if (!comment.empty()) { - attr_processor->AppendComment("<p>\n@attr description"); - attr_processor->AppendComment(comment); - } else { - std::stringstream default_comment; - default_comment << "<p>This symbol is the offset where the " - << "{@link " << package_name << ".R.attr#" - << TransformToFieldName(attr_name.entry) << "}\n" - << "attribute's value can be found in the " - << "{@link #" << array_field_name << "} array."; - attr_processor->AppendComment(default_comment.str()); + out_class_def->AddMember(std::move(index_member)); } - attr_processor->AppendNewLine(); - AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); - attr_processor->AppendNewLine(); - attr_processor->AppendComment( - StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); - - if (out_r_txt != nullptr) { - *out_r_txt << StringPrintf("int styleable %s %d\n", sorted_attributes[i].field_name.data(), - (int)i); + if (r_txt_printer != nullptr) { + r_txt_printer->Println( + StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i)); } - - out_class_def->AddMember(std::move(index_member)); } // If there is a rewrite method to generate, add the statements that rewrite package IDs @@ -422,46 +426,55 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id, const ResourceEntry& entry, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, - std::ostream* out_r_txt) { + text::Printer* r_txt_printer) { ResourceId real_id = id; if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId && id.package_id() > kAppPackageId) { + // Workaround for feature splits using package IDs > 0x7F. + // See b/37498913. real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id()); } const std::string field_name = TransformToFieldName(name.entry); - std::unique_ptr<ResourceMember> resource_member = - util::make_unique<ResourceMember>(field_name, real_id); + if (out_class_def != nullptr) { + std::unique_ptr<ResourceMember> resource_member = + util::make_unique<ResourceMember>(field_name, real_id); - // Build the comments and annotations for this entry. - AnnotationProcessor* processor = resource_member->GetCommentBuilder(); + // Build the comments and annotations for this entry. + AnnotationProcessor* processor = resource_member->GetCommentBuilder(); - // Add the comments from any <public> tags. - if (entry.symbol_status.state != SymbolState::kUndefined) { - processor->AppendComment(entry.symbol_status.comment); - } + // Add the comments from any <public> tags. + if (entry.visibility.level != Visibility::Level::kUndefined) { + processor->AppendComment(entry.visibility.comment); + } - // Add the comments from all configurations of this entry. - for (const auto& config_value : entry.values) { - processor->AppendComment(config_value->value->GetComment()); - } + // Add the comments from all configurations of this entry. + for (const auto& config_value : entry.values) { + processor->AppendComment(config_value->value->GetComment()); + } - // If this is an Attribute, append the format Javadoc. - if (!entry.values.empty()) { - if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { - // We list out the available values for the given attribute. - AddAttributeFormatDoc(processor, attr); + // If this is an Attribute, append the format Javadoc. + if (!entry.values.empty()) { + if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { + // We list out the available values for the given attribute. + AddAttributeFormatDoc(processor, attr); + } } - } - out_class_def->AddMember(std::move(resource_member)); + out_class_def->AddMember(std::move(resource_member)); + } - if (out_r_txt != nullptr) { - *out_r_txt << "int " << name.type << " " << field_name << " " << real_id << "\n"; + if (r_txt_printer != nullptr) { + r_txt_printer->Print("int ") + .Print(to_string(name.type)) + .Print(" ") + .Print(field_name) + .Print(" ") + .Println(real_id.to_string()); } if (out_rewrite_method != nullptr) { - const StringPiece& type_str = ToString(name.type); + const StringPiece& type_str = to_string(name.type); out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);", type_str.data(), field_name.data(), type_str.data(), field_name.data())); @@ -471,7 +484,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name, const StringPiece& package_name_to_generate, const ResourceEntry& entry) { - if (SkipSymbol(entry.symbol_status.state)) { + if (SkipSymbol(entry.visibility.level)) { return {}; } @@ -480,7 +493,7 @@ Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& packa if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) { // The entry name was mangled, and we successfully unmangled it. // Check that we want to emit this symbol. - if (package_name != unmangled_package) { + if (package_name_to_generate != unmangled_package) { // Skip the entry if it doesn't belong to the package we're writing. return {}; } @@ -497,7 +510,7 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate const ResourceTableType& type, ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def, - std::ostream* out_r_txt) { + Printer* r_txt_printer) { for (const auto& entry : type.entries) { const Maybe<std::string> unmangled_name = UnmangleResource(package.name, package_name_to_generate, *entry); @@ -532,18 +545,18 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate static_cast<const Styleable*>(entry->values.front()->value.get()); ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def, - out_rewrite_method_def, out_r_txt); + out_rewrite_method_def, r_txt_printer); } else { ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def, - out_r_txt); + r_txt_printer); } } return true; } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out, - std::ostream* out_r_txt) { - return Generate(package_name_to_generate, package_name_to_generate, out); +bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out, + OutputStream* out_r_txt) { + return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt); } static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations, @@ -556,13 +569,18 @@ static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations } bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, - const StringPiece& out_package_name, std::ostream* out, - std::ostream* out_r_txt) { + const StringPiece& out_package_name, OutputStream* out, + OutputStream* out_r_txt) { ClassDefinition r_class("R", ClassQualifier::kNone, true); std::unique_ptr<MethodDefinition> rewrite_method; + std::unique_ptr<Printer> r_txt_printer; + if (out_r_txt != nullptr) { + r_txt_printer = util::make_unique<Printer>(out_r_txt); + } + // Generate an onResourcesLoaded() callback if requested. - if (options_.rewrite_callback_options) { + if (out != nullptr && options_.rewrite_callback_options) { rewrite_method = util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)"); for (const std::string& package_to_callback : @@ -579,15 +597,18 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, continue; } - // Stay consistent with AAPT and generate an empty type class if the R class - // is public. + // Stay consistent with AAPT and generate an empty type class if the R class is public. const bool force_creation_if_empty = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>( - ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty); + std::unique_ptr<ClassDefinition> class_def; + if (out != nullptr) { + class_def = util::make_unique<ClassDefinition>( + to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty); + } + if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(), - rewrite_method.get(), out_r_txt)) { + rewrite_method.get(), r_txt_printer.get())) { return false; } @@ -596,22 +617,23 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate); if (priv_type) { if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(), - rewrite_method.get(), out_r_txt)) { + rewrite_method.get(), r_txt_printer.get())) { return false; } } } - if (type->type == ResourceType::kStyleable && + if (out != nullptr && type->type == ResourceType::kStyleable && options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { // When generating a public R class, we don't want Styleable to be part // of the API. It is only emitted for documentation purposes. class_def->GetCommentBuilder()->AppendComment("@doconly"); } - AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); - - r_class.AddMember(std::move(class_def)); + if (out != nullptr) { + AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); + r_class.AddMember(std::move(class_def)); + } } } @@ -619,23 +641,10 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, r_class.AddMember(std::move(rewrite_method)); } - AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); - - if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) { - return false; - } - - out->flush(); - - if (out_r_txt != nullptr) { - out_r_txt->flush(); - - if (!*out_r_txt) { - error_ = android::base::SystemErrorCodeToString(errno); - return false; - } + if (out != nullptr) { + AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); + ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out); } - return true; } diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 18746ffc5a0a..853120b3cb98 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -17,15 +17,16 @@ #ifndef AAPT_JAVA_CLASS_GENERATOR_H #define AAPT_JAVA_CLASS_GENERATOR_H -#include <ostream> #include <string> #include "androidfw/StringPiece.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "io/Io.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" +#include "text/Printer.h" namespace aapt { @@ -69,17 +70,19 @@ class JavaClassGenerator { // All symbols technically belong to a single package, but linked libraries will // have their names mangled, denoting that they came from a different package. // We need to generate these symbols in a separate file. Returns true on success. - bool Generate(const android::StringPiece& package_name_to_generate, std::ostream* out, - std::ostream* out_r_txt = nullptr); + bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out, + io::OutputStream* out_r_txt = nullptr); bool Generate(const android::StringPiece& package_name_to_generate, - const android::StringPiece& output_package_name, std::ostream* out, - std::ostream* out_r_txt = nullptr); + const android::StringPiece& output_package_name, io::OutputStream* out, + io::OutputStream* out_r_txt = nullptr); - const std::string& getError() const; + const std::string& GetError() const; + + static std::string TransformToFieldName(const android::StringPiece& symbol); private: - bool SkipSymbol(SymbolState state); + bool SkipSymbol(Visibility::Level state); bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol); // Returns the unmangled resource entry name if the unmangled package is the same as @@ -91,13 +94,13 @@ class JavaClassGenerator { bool ProcessType(const android::StringPiece& package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def, - std::ostream* out_r_txt); + text::Printer* r_txt_printer); // Writes a resource to the R.java file, optionally writing out a rewrite rule for its package // ID if `out_rewrite_method` is not nullptr. void ProcessResource(const ResourceNameRef& name, const ResourceId& id, const ResourceEntry& entry, ClassDefinition* out_class_def, - MethodDefinition* out_rewrite_method, std::ostream* out_r_txt); + MethodDefinition* out_rewrite_method, text::Printer* r_txt_printer); // Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for // its package ID if `out_rewrite_method` is not nullptr. @@ -106,7 +109,7 @@ class JavaClassGenerator { const Styleable& styleable, const android::StringPiece& package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, - std::ostream* out_r_txt); + text::Printer* r_txt_printer); IAaptContext* context_; ResourceTable* table_; @@ -114,7 +117,7 @@ class JavaClassGenerator { std::string error_; }; -inline const std::string& JavaClassGenerator::getError() const { +inline const std::string& JavaClassGenerator::GetError() const { return error_; } diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 84bf04134ad9..e449546f9399 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -16,15 +16,18 @@ #include "java/JavaClassGenerator.h" -#include <sstream> #include <string> +#include "io/StringStream.h" #include "test/Test.h" #include "util/Util.h" -using android::StringPiece; +using ::aapt::io::StringOutputStream; +using ::android::StringPiece; +using ::testing::HasSubstr; using ::testing::Lt; using ::testing::Ne; +using ::testing::Not; namespace aapt { @@ -43,7 +46,8 @@ TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + std::string result; + StringOutputStream out(&result); EXPECT_FALSE(generator.Generate("android", &out)); } @@ -53,35 +57,28 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { .SetPackageId("android", 0x01) .AddSimple("android:id/hey-man", ResourceId(0x01020000)) .AddValue("android:attr/cool.attr", ResourceId(0x01010000), - test::AttributeBuilder(false).Build()) - .AddValue( - "android:styleable/hey.dude", ResourceId(0x01030000), - test::StyleableBuilder() - .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) - .Build()) + test::AttributeBuilder().Build()) + .AddValue("android:styleable/hey.dude", ResourceId(0x01030000), + test::StyleableBuilder() + .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) + .Build()) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + std::string output; + StringOutputStream out(&output); EXPECT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string output = out.str(); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_man=0x01020000;")); - - EXPECT_NE(std::string::npos, - output.find("public static final int[] hey_dude={")); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_dude_cool_attr=0;")); + EXPECT_THAT(output, HasSubstr("public static final int hey_man=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int[] hey_dude={")); + EXPECT_THAT(output, HasSubstr("public static final int hey_dude_cool_attr=0;")); } TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { @@ -94,20 +91,20 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out)); + out.Flush(); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("package com.android.internal;")); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("com_foo$two")); + EXPECT_THAT(output, HasSubstr("package com.android.internal;")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("com_foo$two"))); } TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { @@ -120,18 +117,18 @@ TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final class attr")); - EXPECT_EQ(std::string::npos, - output.find("public static final class ^attr-private")); + EXPECT_THAT(output, HasSubstr("public static final class attr")); + EXPECT_THAT(output, Not(HasSubstr("public static final class ^attr-private"))); } TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { @@ -142,16 +139,13 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { .AddSimple("android:id/one", ResourceId(0x01020000)) .AddSimple("android:id/two", ResourceId(0x01020001)) .AddSimple("android:id/three", ResourceId(0x01020002)) - .SetSymbolState("android:id/one", ResourceId(0x01020000), - SymbolState::kPublic) - .SetSymbolState("android:id/two", ResourceId(0x01020001), - SymbolState::kPrivate) + .SetSymbolState("android:id/one", ResourceId(0x01020000), Visibility::Level::kPublic) + .SetSymbolState("android:id/two", ResourceId(0x01020001), Visibility::Level::kPrivate) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); @@ -159,40 +153,40 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; { JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("three")); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; { JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_EQ(std::string::npos, output.find("three")); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; { JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_NE(std::string::npos, - output.find("public static final int three=0x01020002;")); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, HasSubstr("public static final int three=0x01020002;")); } } @@ -235,10 +229,8 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { test::ResourceTableBuilder() .SetPackageId("android", 0x01) .SetPackageId("com.lib", 0x02) - .AddValue("android:attr/bar", ResourceId(0x01010000), - test::AttributeBuilder(false).Build()) - .AddValue("com.lib:attr/bar", ResourceId(0x02010000), - test::AttributeBuilder(false).Build()) + .AddValue("android:attr/bar", ResourceId(0x01010000), test::AttributeBuilder().Build()) + .AddValue("com.lib:attr/bar", ResourceId(0x02010000), test::AttributeBuilder().Build()) .AddValue("android:styleable/foo", ResourceId(0x01030000), test::StyleableBuilder() .AddItem("android:attr/bar", ResourceId(0x01010000)) @@ -248,18 +240,18 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + std::string output; + StringOutputStream out(&output); EXPECT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int foo_bar=")); - EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_com_lib_bar=")); } TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { @@ -273,35 +265,34 @@ TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + out.Flush(); - const char* expectedText = + const char* expected_text = R"EOF(/** * This is a comment * @deprecated */ @Deprecated public static final int foo=0x01010000;)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expectedText)); + EXPECT_THAT(output, HasSubstr(expected_text)); } TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) { - Attribute attr(false); + Attribute attr; attr.SetComment(StringPiece("This is an attribute")); Styleable styleable; - styleable.entries.push_back( - Reference(test::ParseNameOrDie("android:attr/one"))); + styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); styleable.SetComment(StringPiece("This is a styleable")); std::unique_ptr<ResourceTable> table = @@ -314,21 +305,22 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; options.use_final = false; JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + out.Flush(); - EXPECT_NE(std::string::npos, actual.find("attr name android:one")); - EXPECT_NE(std::string::npos, actual.find("attr description")); - EXPECT_NE(std::string::npos, actual.find(attr.GetComment().data())); - EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data())); + EXPECT_THAT(output, HasSubstr("attr name android:one")); + EXPECT_THAT(output, HasSubstr("attr description")); + EXPECT_THAT(output, HasSubstr(attr.GetComment())); + EXPECT_THAT(output, HasSubstr(styleable.GetComment())); } TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) { @@ -355,9 +347,11 @@ TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) { JavaClassGeneratorOptions options; JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); + out.Flush(); std::string::size_type actionbar_pos = output.find("int[] ActionBar"); ASSERT_THAT(actionbar_pos, Ne(std::string::npos)); @@ -379,7 +373,7 @@ TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) { } TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { - Attribute attr(false); + Attribute attr; attr.SetComment(StringPiece("removed")); std::unique_ptr<ResourceTable> table = @@ -390,33 +384,34 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; options.use_final = false; JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + out.Flush(); - EXPECT_EQ(std::string::npos, actual.find("@attr name android:one")); - EXPECT_EQ(std::string::npos, actual.find("@attr description")); + EXPECT_THAT(output, Not(HasSubstr("@attr name android:one"))); + EXPECT_THAT(output, Not(HasSubstr("@attr description"))); // We should find @removed only in the attribute javadoc and not anywhere else - // (i.e. the class - // javadoc). - const size_t pos = actual.find("removed"); - EXPECT_NE(std::string::npos, pos); - EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1)); + // (i.e. the class javadoc). + const std::string kRemoved("removed"); + ASSERT_THAT(output, HasSubstr(kRemoved)); + std::string after_first_match = output.substr(output.find(kRemoved) + kRemoved.size()); + EXPECT_THAT(after_first_match, Not(HasSubstr(kRemoved))); } TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("android", 0x00) - .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>(false)) + .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>()) .AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>()) .AddValue( "android:style/foo", ResourceId(0x00030000), @@ -430,19 +425,17 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) JavaClassGeneratorOptions options; options.use_final = false; - options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{ - {"com.foo", "com.boo"}, - }; + options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{{"com.foo", "com.boo"}}; JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string actual = out.str(); - - EXPECT_NE(std::string::npos, actual.find("void onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.foo.R.onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.boo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("void onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded")); } } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index 8981e0718be0..c4b36176aa71 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -25,7 +25,7 @@ #include "util/Maybe.h" #include "xml/XmlDom.h" -using android::StringPiece; +using ::android::StringPiece; using ::aapt::text::IsJavaIdentifier; namespace aapt { @@ -50,17 +50,16 @@ static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, const Source return result; } -static bool WriteSymbol(const Source& source, IDiagnostics* diag, - xml::Element* el, ClassDefinition* class_def) { +static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, + ClassDefinition* class_def) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (!attr) { - diag->Error(DiagMessage(source) << "<" << el->name - << "> must define 'android:name'"); + diag->Error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); return false; } - Maybe<StringPiece> result = ExtractJavaIdentifier( - diag, source.WithLine(el->line_number), attr->value); + Maybe<StringPiece> result = + ExtractJavaIdentifier(diag, source.WithLine(el->line_number), attr->value); if (!result) { return false; } @@ -76,8 +75,7 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, return true; } -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, - xml::XmlResource* res) { +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { xml::Element* el = xml::FindRootElement(res->root.get()); if (!el) { diag->Error(DiagMessage(res->file.source) << "no root tag defined"); @@ -85,8 +83,7 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, } if (el->name != "manifest" && !el->namespace_uri.empty()) { - diag->Error(DiagMessage(res->file.source) - << "no <manifest> root tag defined"); + diag->Error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); return {}; } @@ -100,11 +97,9 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, for (xml::Element* child_el : children) { if (child_el->namespace_uri.empty()) { if (child_el->name == "permission") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_class.get()); } else if (child_el->name == "permission-group") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_group_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_group_class.get()); } } } diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index b12202a8d137..3f6645facaa2 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -23,8 +23,7 @@ namespace aapt { -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, - xml::XmlResource* res); +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res); } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 44b6a1ffd5ae..f4e10ab2e584 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -16,8 +16,10 @@ #include "java/ManifestClassGenerator.h" +#include "io/StringStream.h" #include "test/Test.h" +using ::aapt::io::StringOutputStream; using ::testing::HasSubstr; using ::testing::Not; @@ -84,6 +86,8 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @hide @SystemApi --> <permission android:name="android.permission.SECRET" /> + <!-- @TestApi This is a test only permission. --> + <permission android:name="android.permission.TEST_ONLY" /> </manifest>)"); std::string actual; @@ -110,6 +114,13 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @android.annotation.SystemApi public static final String SECRET="android.permission.SECRET";)"; EXPECT_THAT(actual, HasSubstr(expected_secret)); + + const char* expected_test = R"( /** + * This is a test only permission. + */ + @android.annotation.TestApi + public static final String TEST_ONLY="android.permission.TEST_ONLY";)"; + EXPECT_THAT(actual, HasSubstr(expected_test)); } // This is bad but part of public API behaviour so we need to preserve it. @@ -118,13 +129,16 @@ TEST(ManifestClassGeneratorTest, LastSeenPermissionWithSameLeafNameTakesPreceden std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <permission android:name="android.permission.ACCESS_INTERNET" /> - <permission android:name="com.android.aapt.test.ACCESS_INTERNET" /> + <permission android:name="com.android.sample.ACCESS_INTERNET" /> + <permission android:name="com.android.permission.UNRELATED_PERMISSION" /> + <permission android:name="com.android.aapt.test.ACCESS_INTERNET" /> --> </manifest>)"); std::string actual; ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); EXPECT_THAT(actual, HasSubstr("ACCESS_INTERNET=\"com.android.aapt.test.ACCESS_INTERNET\";")); EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";"))); + EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"com.android.sample.ACCESS_INTERNET\";"))); } static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, @@ -135,12 +149,9 @@ static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xm return ::testing::AssertionFailure() << "manifest_class == nullptr"; } - std::stringstream out; - if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out)) { - return ::testing::AssertionFailure() << "failed to write java file"; - } - - *out_str = out.str(); + StringOutputStream out(out_str); + manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out); + out.Flush(); return ::testing::AssertionSuccess(); } diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 10c46101123c..ffcef8966654 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -20,10 +20,18 @@ #include <string> #include "android-base/macros.h" +#include "androidfw/StringPiece.h" +#include "JavaClassGenerator.h" +#include "ResourceUtils.h" +#include "ValueVisitor.h" +#include "text/Printer.h" #include "util/Util.h" #include "xml/XmlDom.h" +using ::aapt::io::OutputStream; +using ::aapt::text::Printer; + namespace aapt { namespace proguard { @@ -31,7 +39,7 @@ class BaseVisitor : public xml::Visitor { public: using xml::Visitor::Visit; - BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) { + BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) { } void Visit(xml::Element* node) override { @@ -52,27 +60,47 @@ class BaseVisitor : public xml::Visitor { for (const auto& child : node->children) { child->Accept(this); } + + for (const auto& attr : node->attributes) { + if (attr.compiled_value) { + auto ref = ValueCast<Reference>(attr.compiled_value.get()); + if (ref) { + AddReference(node->line_number, ref); + } + } + } } protected: - void AddClass(size_t line_number, const std::string& class_name) { - keep_set_->AddClass(Source(source_.path, line_number), class_name); + ResourceFile file_; + KeepSet* keep_set_; + + virtual void AddClass(size_t line_number, const std::string& class_name) { + keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name); } void AddMethod(size_t line_number, const std::string& method_name) { - keep_set_->AddMethod(Source(source_.path, line_number), method_name); + keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name); + } + + void AddReference(size_t line_number, Reference* ref) { + if (ref && ref->name) { + ResourceName ref_name = ref->name.value(); + if (ref_name.package.empty()) { + ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry); + } + keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name); + } } private: DISALLOW_COPY_AND_ASSIGN(BaseVisitor); - Source source_; - KeepSet* keep_set_; }; class LayoutVisitor : public BaseVisitor { public: - LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -110,7 +138,7 @@ class LayoutVisitor : public BaseVisitor { class MenuVisitor : public BaseVisitor { public: - MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -136,7 +164,7 @@ class MenuVisitor : public BaseVisitor { class XmlResourceVisitor : public BaseVisitor { public: - XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -163,7 +191,7 @@ class XmlResourceVisitor : public BaseVisitor { class TransitionVisitor : public BaseVisitor { public: - TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -185,8 +213,9 @@ class TransitionVisitor : public BaseVisitor { class ManifestVisitor : public BaseVisitor { public: - ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only) - : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {} + ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only) + : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) { + } void Visit(xml::Element* node) override { if (node->namespace_uri.empty()) { @@ -241,6 +270,10 @@ class ManifestVisitor : public BaseVisitor { BaseVisitor::Visit(node); } + virtual void AddClass(size_t line_number, const std::string& class_name) override { + keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name); + } + private: DISALLOW_COPY_AND_ASSIGN(ManifestVisitor); @@ -249,9 +282,8 @@ class ManifestVisitor : public BaseVisitor { std::string default_process_; }; -bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set, - bool main_dex_only) { - ManifestVisitor visitor(source, keep_set, main_dex_only); +bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) { + ManifestVisitor visitor(res->file, keep_set, main_dex_only); if (res->root) { res->root->Accept(&visitor); return true; @@ -259,55 +291,150 @@ bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res return false; } -bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) { +bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) { if (!res->root) { return false; } switch (res->file.name.type) { case ResourceType::kLayout: { - LayoutVisitor visitor(source, keep_set); + LayoutVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } case ResourceType::kXml: { - XmlResourceVisitor visitor(source, keep_set); + XmlResourceVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } case ResourceType::kTransition: { - TransitionVisitor visitor(source, keep_set); + TransitionVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } case ResourceType::kMenu: { - MenuVisitor visitor(source, keep_set); + MenuVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } - default: + default: { + BaseVisitor visitor(res->file, keep_set); + res->root->Accept(&visitor); break; + } + } + return true; +} + +void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { + Printer printer(out); + for (const auto& entry : keep_set.manifest_class_set_) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); + } + + for (const auto& entry : keep_set.conditional_class_set_) { + std::set<UsageLocation> locations; + bool can_be_conditional = true; + for (const UsageLocation& location : entry.second) { + can_be_conditional &= CollectLocations(location, keep_set, &locations); + } + + if (keep_set.conditional_keep_rules_ && can_be_conditional) { + for (const UsageLocation& location : locations) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + printer.Print("-if class **.R$layout { int ") + .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) + .Println("; }"); + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); + } + } else { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); + } + printer.Println(); + } + + for (const auto& entry : keep_set.method_set_) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keepclassmembers class * { *** ").Print(entry.first).Println("(...); }"); + printer.Println(); + } +} + +bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, + std::set<UsageLocation>* locations) { + locations->insert(location); + + // TODO: allow for more reference types if we can determine its safe. + if (location.name.type != ResourceType::kLayout) { + return false; + } + + for (const auto& entry : keep_set.reference_set_) { + if (entry.first == location.name) { + for (auto& refLocation : entry.second) { + // Don't get stuck in loops + if (locations->find(refLocation) != locations->end()) { + return false; + } + if (!CollectLocations(refLocation, keep_set, locations)) { + return false; + } + } + } } + return true; } -bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) { - for (const auto& entry : keep_set.keep_set_) { - for (const Source& source : entry.second) { - *out << "# Referenced at " << source << "\n"; +class ReferenceVisitor : public ValueVisitor { + public: + using ValueVisitor::Visit; + + ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set) + : context_(context), from_(from), keep_set_(keep_set) { + } + + void Visit(Reference* reference) override { + if (reference->name) { + ResourceName reference_name = reference->name.value(); + if (reference_name.package.empty()) { + reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type, + reference_name.entry); + } + keep_set_->AddReference({from_, reference->GetSource()}, reference_name); } - *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; } - for (const auto& entry : keep_set.keep_method_set_) { - for (const Source& source : entry.second) { - *out << "# Referenced at " << source << "\n"; + private: + aapt::IAaptContext* context_; + ResourceName from_; + KeepSet* keep_set_; +}; + +bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table, + KeepSet* keep_set) { + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + ResourceName from(pkg->name, type->type, entry->name); + ReferenceVisitor visitor(context, from, keep_set); + config_value->value->Accept(&visitor); + } + } } - *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; } return true; } diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index 3c349bab1217..46827ee7cf93 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -22,37 +22,83 @@ #include <set> #include <string> +#include "androidfw/StringPiece.h" + #include "Resource.h" +#include "ResourceTable.h" #include "Source.h" +#include "ValueVisitor.h" +#include "io/Io.h" +#include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" namespace aapt { namespace proguard { +struct UsageLocation { + ResourceName name; + Source source; +}; + class KeepSet { public: - inline void AddClass(const Source& source, const std::string& class_name) { - keep_set_[class_name].insert(source); + KeepSet() = default; + + KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) { } - inline void AddMethod(const Source& source, const std::string& method_name) { - keep_method_set_[method_name].insert(source); + inline void AddManifestClass(const UsageLocation& file, const std::string& class_name) { + manifest_class_set_[class_name].insert(file); + } + + inline void AddConditionalClass(const UsageLocation& file, const std::string& class_name) { + conditional_class_set_[class_name].insert(file); + } + + inline void AddMethod(const UsageLocation& file, const std::string& method_name) { + method_set_[method_name].insert(file); + } + + inline void AddReference(const UsageLocation& file, const ResourceName& resource_name) { + reference_set_[resource_name].insert(file); } private: - friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set); + friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out); + + friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, + std::set<UsageLocation>* locations); - std::map<std::string, std::set<Source>> keep_set_; - std::map<std::string, std::set<Source>> keep_method_set_; + bool conditional_keep_rules_ = false; + std::map<std::string, std::set<UsageLocation>> manifest_class_set_; + std::map<std::string, std::set<UsageLocation>> method_set_; + std::map<std::string, std::set<UsageLocation>> conditional_class_set_; + std::map<ResourceName, std::set<UsageLocation>> reference_set_; }; -bool CollectProguardRulesForManifest(const Source& source, - xml::XmlResource* res, KeepSet* keep_set, +bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only = false); -bool CollectProguardRules(const Source& source, xml::XmlResource* res, - KeepSet* keep_set); -bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set); +bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set); + +bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set); + +void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out); + +bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, + std::set<UsageLocation>* locations); + +// +// UsageLocation implementation. +// + +inline bool operator==(const UsageLocation& lhs, const UsageLocation& rhs) { + return lhs.name == rhs.name; +} + +inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) { + return lhs.name.compare(rhs.name); +} } // namespace proguard } // namespace aapt diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index 900b07339715..37d1a5fbaeb8 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -15,14 +15,25 @@ */ #include "java/ProguardRules.h" +#include "link/Linkers.h" +#include "io/StringStream.h" #include "test/Test.h" +using ::aapt::io::StringOutputStream; using ::testing::HasSubstr; using ::testing::Not; namespace aapt { +std::string GetKeepSetString(const proguard::KeepSet& set) { + std::string out; + StringOutputStream sout(&out); + proguard::WriteKeepSet(set, &sout); + sout.Flush(); + return out; +} + TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( @@ -31,12 +42,10 @@ TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); } @@ -47,12 +56,10 @@ TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); } @@ -65,16 +72,110 @@ TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); } +TEST(ProguardRulesTest, CustomViewRulesAreEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) { + std::unique_ptr<xml::XmlResource> bar_layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + bar_layout->file.name = test::ParseNameOrDie("com.foo:layout/bar"); + + ResourceTable table; + StdErrDiagnostics errDiagnostics; + table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "", + util::make_unique<FileReference>(), &errDiagnostics); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.foo") + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(&table)) + .Build(); + + std::unique_ptr<xml::XmlResource> foo_layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <include layout="@layout/bar" /> + </View>)"); + foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo"); + + XmlReferenceLinker xml_linker; + ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get())); + ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get())); + + proguard::KeepSet set = proguard::KeepSet(true); + ASSERT_TRUE(proguard::CollectProguardRules(bar_layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(foo_layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("int foo")); + EXPECT_THAT(actual, HasSubstr("int bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set = proguard::KeepSet(true); + set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); + EXPECT_THAT(actual, HasSubstr("int foo")); + EXPECT_THAT(actual, HasSubstr("int bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set = proguard::KeepSet(true); + set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, Not(HasSubstr("-if"))); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); +} + TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( @@ -83,12 +184,10 @@ TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("bar_method")); } @@ -104,12 +203,10 @@ TEST(ProguardRulesTest, MenuRulesAreEmitted) { menu->file.name = test::ParseNameOrDie("menu/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(menu.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("on_click")); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); |