diff options
author | 2015-10-21 14:42:43 -0700 | |
---|---|---|
committer | 2015-10-30 13:51:11 -0700 | |
commit | ca5638fd85098c3d0a699492751043545f75553a (patch) | |
tree | 9ede79caee33fc201b2d5a2da994a77fa25a7c2c | |
parent | 030cfd3da697d51bf1bffd076881c12acedd851e (diff) |
AAPT2: Support generating Manifest.java
This includes comments from AndroidManifest.xml.
Change-Id: I412d9ecb12bad20a49a683d6b3bea4a0be1235ae
22 files changed, 624 insertions, 68 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 275476cb2081..a41d2d72b754 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -45,9 +45,11 @@ sources := \ ConfigDescription.cpp \ Debug.cpp \ Flags.cpp \ - JavaClassGenerator.cpp \ + java/AnnotationProcessor.cpp \ + java/JavaClassGenerator.cpp \ + java/ManifestClassGenerator.cpp \ + java/ProguardRules.cpp \ Locale.cpp \ - ProguardRules.cpp \ Resource.cpp \ ResourceParser.cpp \ ResourceTable.cpp \ @@ -76,7 +78,8 @@ testSources := \ util/StringPiece_test.cpp \ util/Util_test.cpp \ ConfigDescription_test.cpp \ - JavaClassGenerator_test.cpp \ + java/JavaClassGenerator_test.cpp \ + java/ManifestClassGenerator_test.cpp \ Locale_test.cpp \ Resource_test.cpp \ ResourceParser_test.cpp \ diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 1962f582ae7c..34dc1d5508e3 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -36,7 +36,6 @@ StringPiece16 toString(ResourceType type) { case ResourceType::kFraction: return u"fraction"; case ResourceType::kId: return u"id"; case ResourceType::kInteger: return u"integer"; - case ResourceType::kIntegerArray: return u"integer-array"; case ResourceType::kInterpolator: return u"interpolator"; case ResourceType::kLayout: return u"layout"; case ResourceType::kMenu: return u"menu"; @@ -65,7 +64,6 @@ static const std::map<StringPiece16, ResourceType> sResourceTypeMap { { u"fraction", ResourceType::kFraction }, { u"id", ResourceType::kId }, { u"integer", ResourceType::kInteger }, - { u"integer-array", ResourceType::kIntegerArray }, { u"interpolator", ResourceType::kInterpolator }, { u"layout", ResourceType::kLayout }, { u"menu", ResourceType::kMenu }, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 7ef18973d89b..a7afbb5e4018 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -47,7 +47,6 @@ enum class ResourceType { kFraction, kId, kInteger, - kIntegerArray, kInterpolator, kLayout, kMenu, diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 44710ebc9dc4..0c7a4d578be5 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -308,6 +308,9 @@ bool ResourceParser::parseResources(XmlPullParser* parser) { } else if (elementName == u"dimen") { parsedResource.name.type = ResourceType::kDimen; result = parsePrimitive(parser, &parsedResource); + } else if (elementName == u"fraction") { + parsedResource.name.type = ResourceType::kFraction; + result = parsePrimitive(parser, &parsedResource); } else if (elementName == u"style") { parsedResource.name.type = ResourceType::kStyle; result = parseStyle(parser, &parsedResource); @@ -321,7 +324,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) { parsedResource.name.type = ResourceType::kArray; result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING); } else if (elementName == u"integer-array") { - parsedResource.name.type = ResourceType::kIntegerArray; + parsedResource.name.type = ResourceType::kArray; result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER); } else if (elementName == u"declare-styleable") { parsedResource.name.type = ResourceType::kStyleable; @@ -464,6 +467,8 @@ bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outRe typeMask |= android::ResTable_map::TYPE_INTEGER; break; + case ResourceType::kFraction: + // fallthrough case ResourceType::kDimen: typeMask |= android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FLOAT @@ -576,6 +581,12 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { return mask; } +/** + * Returns true if the element is <skip> or <eat-comment> and can be safely ignored. + */ +static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) { + return ns.empty() && (name == u"skip" || name == u"eat-comment"); +} bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) { outResource->source = mSource.withLine(parser->getLineNumber()); @@ -613,25 +624,30 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes bool error = false; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { - // Skip comments and text. + if (parser->getEvent() == XmlPullParser::Event::kComment) { + comment = util::trimWhitespace(parser->getComment()).toString(); + continue; + } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Skip text. continue; } + const Source itemSource = mSource.withLine(parser->getLineNumber()); const std::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); - if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) { + if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) { if (elementName == u"enum") { if (typeMask & android::ResTable_map::TYPE_FLAGS) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + mDiag->error(DiagMessage(itemSource) << "can not define an <enum>; already defined a <flag>"); error = true; continue; } typeMask |= android::ResTable_map::TYPE_ENUM; + } else if (elementName == u"flag") { if (typeMask & android::ResTable_map::TYPE_ENUM) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + mDiag->error(DiagMessage(itemSource) << "can not define a <flag>; already defined an <enum>"); error = true; continue; @@ -642,21 +658,22 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { ParsedResource childResource; childResource.name = s.value().symbol.name.value(); - childResource.source = mSource.withLine(parser->getLineNumber()); + childResource.source = itemSource; childResource.value = util::make_unique<Id>(); outResource->childResources.push_back(std::move(childResource)); + + s.value().symbol.setComment(std::move(comment)); + s.value().symbol.setSource(itemSource); items.push_back(std::move(s.value())); } else { error = true; } - } else if (elementName == u"skip" || elementName == u"eat-comment") { - comment = u""; - - } else { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << ":" << elementName << ">"); + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); error = true; } + + comment = {}; } if (error) { @@ -716,11 +733,10 @@ static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) { p++; } - return ResourceName{ package.toString(), ResourceType::kAttr, - name.empty() ? str.toString() : name.toString() }; + return ResourceName(package.toString(), ResourceType::kAttr, + name.empty() ? str.toString() : name.toString()); } - bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) { const Source source = mSource.withLine(parser->getLineNumber()); @@ -783,7 +799,6 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour } bool error = false; - std::u16string comment; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != XmlPullParser::Event::kStartElement) { @@ -796,11 +811,7 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour if (elementNamespace == u"" && elementName == u"item") { error |= !parseStyleItem(parser, style.get()); - } else if (elementNamespace.empty() && - (elementName == u"skip" || elementName == u"eat-comment")) { - comment = u""; - - } else { + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << ":" << elementName << ">"); error = true; @@ -820,7 +831,6 @@ bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResour const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Array> array = util::make_unique<Array>(); - std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { @@ -839,13 +849,10 @@ bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResour error = true; continue; } + item->setSource(itemSource); array->items.emplace_back(std::move(item)); - } else if (elementNamespace.empty() && - (elementName == u"skip" || elementName == u"eat-comment")) { - comment = u""; - - } else { + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) << "unknown tag <" << elementNamespace << ":" << elementName << ">"); error = true; @@ -864,7 +871,6 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { @@ -873,13 +879,14 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou continue; } + const Source itemSource = mSource.withLine(parser->getLineNumber()); const std::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == u"item") { const auto endAttrIter = parser->endAttributes(); auto attrIter = parser->findAttribute(u"", u"quantity"); if (attrIter == endAttrIter || attrIter->value.empty()) { - mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute " + mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute " << "'quantity'"); error = true; continue; @@ -900,7 +907,7 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou } else if (trimmedQuantity == u"other") { index = Plural::Other; } else { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + mDiag->error(DiagMessage(itemSource) << "<item> in <plural> has invalid value '" << trimmedQuantity << "' for attribute 'quantity'"); error = true; @@ -908,7 +915,7 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou } if (plural->values[index]) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + mDiag->error(DiagMessage(itemSource) << "duplicate quantity '" << trimmedQuantity << "'"); error = true; continue; @@ -918,11 +925,10 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou kNoRawString))) { error = true; } - } else if (elementNamespace.empty() && - (elementName == u"skip" || elementName == u"eat-comment")) { - comment = u""; - } else { - mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":" + plural->values[index]->setSource(itemSource); + + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" << elementName << ">"); error = true; } @@ -944,43 +950,52 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource bool error = false; const size_t depth = parser->getDepth(); while (XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != XmlPullParser::Event::kStartElement) { - // Ignore text and comments. + if (parser->getEvent() == XmlPullParser::Event::kComment) { + comment = util::trimWhitespace(parser->getComment()).toString(); + continue; + } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Ignore text. continue; } + const Source itemSource = mSource.withLine(parser->getLineNumber()); const std::u16string& elementNamespace = parser->getElementNamespace(); const std::u16string& elementName = parser->getElementName(); if (elementNamespace.empty() && elementName == u"attr") { const auto endAttrIter = parser->endAttributes(); auto attrIter = parser->findAttribute(u"", u"name"); if (attrIter == endAttrIter || attrIter->value.empty()) { - mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute"); + mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute"); error = true; continue; } + // Create the ParsedResource that will add the attribute to the table. ParsedResource childResource; childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value); - childResource.source = mSource.withLine(parser->getLineNumber()); + childResource.source = itemSource; + childResource.comment = std::move(comment); if (!parseAttrImpl(parser, &childResource, true)) { error = true; continue; } - styleable->entries.push_back(Reference(childResource.name)); - outResource->childResources.push_back(std::move(childResource)); + // Create the reference to this attribute. + Reference childRef(childResource.name); + childRef.setComment(childResource.comment); + childRef.setSource(itemSource); + styleable->entries.push_back(std::move(childRef)); - } else if (elementNamespace.empty() && - (elementName == u"skip" || elementName == u"eat-comment")) { - comment = u""; + outResource->childResources.push_back(std::move(childResource)); - } else { - mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":" + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" << elementName << ">"); error = true; } + + comment = {}; } if (error) { diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index af6bf67c4a3b..2f5daae80fd7 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -414,6 +414,34 @@ TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { EXPECT_EQ(value->getComment(), u"One"); } +TEST_F(ResourceParserTest, ParseNestedComments) { + // We only care about declare-styleable and enum/flag attributes because comments + // from those end up in R.java + std::string input = R"EOF( + <declare-styleable name="foo"> + <!-- The name of the bar --> + <attr name="barName" format="string|reference" /> + </declare-styleable> + + <attr name="foo"> + <!-- The very first --> + <enum name="one" value="1" /> + </attr>)EOF"; + ASSERT_TRUE(testParse(input)); + + Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); + ASSERT_NE(nullptr, styleable); + ASSERT_EQ(1u, styleable->entries.size()); + + EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment()); + + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); + ASSERT_NE(nullptr, attr); + ASSERT_EQ(1u, attr->symbols.size()); + + EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment()); +} + /* * Declaring an ID as public should not require a separate definition * (as an ID has no value). diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index d957999f492b..48dc521d843c 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -69,10 +69,6 @@ TEST(ResourceTypeTest, ParseResourceTypes) { ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kInteger); - type = parseResourceType(u"integer-array"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kIntegerArray); - type = parseResourceType(u"interpolator"); ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kInterpolator); diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp index d948775cc623..b769c7620658 100644 --- a/tools/aapt2/XmlDom.cpp +++ b/tools/aapt2/XmlDom.cpp @@ -125,7 +125,7 @@ static void XMLCALL endElementHandler(void* userData, const char* name) { Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); assert(!stack->nodeStack.empty()); - stack->nodeStack.top()->comment = std::move(stack->pendingComment); + //stack->nodeStack.top()->comment = std::move(stack->pendingComment); stack->nodeStack.pop(); } @@ -194,7 +194,7 @@ std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const XML_ParserFree(parser); if (stack.root) { - return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root)); + return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root)); } return {}; } @@ -317,6 +317,22 @@ std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnost return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); } +Element* findRootElement(Node* node) { + if (!node) { + return nullptr; + } + + Element* el = nullptr; + while ((el = nodeCast<Element>(node)) == nullptr) { + if (node->children.empty()) { + return nullptr; + } + // We are looking for the first element, and namespaces can only have one child. + node = node->children.front().get(); + } + return el; +} + void Node::addChild(std::unique_ptr<Node> child) { child->parent = this; children.push_back(std::move(child)); diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h index c095f085f1fa..b1987f1fd245 100644 --- a/tools/aapt2/XmlDom.h +++ b/tools/aapt2/XmlDom.h @@ -132,6 +132,8 @@ std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, const Source& source); +Element* findRootElement(Node* node); + /** * A visitor interface for the different XML Node subtypes. This will not traverse into * children. Use Visitor for that. diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp new file mode 100644 index 000000000000..16440bcf8ad7 --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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 "java/AnnotationProcessor.h" +#include "util/Util.h" + +#include <algorithm> + +namespace aapt { + +void AnnotationProcessor::appendCommentLine(const StringPiece16& line) { + static const std::string sDeprecated = "@deprecated"; + static const std::string sSystemApi = "@SystemApi"; + + if (line.empty()) { + return; + } + + std::string comment = util::utf16ToUtf8(line); + + if (comment.find(sDeprecated) != std::string::npos && !mDeprecated) { + mDeprecated = true; + if (!mAnnotations.empty()) { + mAnnotations += "\n"; + } + mAnnotations += mPrefix; + mAnnotations += "@Deprecated"; + } + + if (comment.find(sSystemApi) != std::string::npos && !mSystemApi) { + mSystemApi = true; + if (!mAnnotations.empty()) { + mAnnotations += "\n"; + } + mAnnotations += mPrefix; + mAnnotations += "@android.annotations.SystemApi"; + } + + if (mComment.empty()) { + mComment += mPrefix; + mComment += "/**"; + } + + mComment += "\n"; + mComment += mPrefix; + mComment += " * "; + mComment += std::move(comment); +} + +void AnnotationProcessor::appendComment(const StringPiece16& comment) { + // We need to process line by line to clean-up whitespace and append prefixes. + for (StringPiece16 line : util::tokenize(comment, u'\n')) { + appendCommentLine(util::trimWhitespace(line)); + } +} + +std::string AnnotationProcessor::buildComment() { + mComment += "\n"; + mComment += mPrefix; + mComment += " */"; + return std::move(mComment); +} + +std::string AnnotationProcessor::buildAnnotations() { + return std::move(mAnnotations); +} + +} // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h new file mode 100644 index 000000000000..b47210939d06 --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 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_JAVA_ANNOTATIONPROCESSOR_H +#define AAPT_JAVA_ANNOTATIONPROCESSOR_H + +#include "util/StringPiece.h" + +#include <string> + +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 + * * @SystemApi + * *\/ + * + * Output Annotations: + * + * @Deprecated + * @android.annotation.SystemApi + * + */ +class AnnotationProcessor { +public: + /** + * Creates an AnnotationProcessor with a given prefix for each line generated. + * This is usually a set of spaces for indentation. + */ + AnnotationProcessor(const StringPiece& prefix) : mPrefix(prefix.toString()) { + } + + /** + * Adds more comments. Since resources can have various values with different configurations, + * we need to collect all the comments. + */ + void appendComment(const StringPiece16& comment); + + /** + * Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have + * undefined results. + */ + std::string buildComment(); + + /** + * Finishes the annotation and moves it to the caller. Subsequent calls to buildAnnotations() + * have undefined results. + */ + std::string buildAnnotations(); + +private: + std::string mPrefix; + std::string mComment; + std::string mAnnotations; + bool mDeprecated = false; + bool mSystemApi = false; + + void appendCommentLine(const StringPiece16& line); +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */ diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index cdf1b6ad600b..0175489cf6ea 100644 --- a/tools/aapt2/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -14,11 +14,12 @@ * limitations under the License. */ -#include "JavaClassGenerator.h" #include "NameMangler.h" #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "java/AnnotationProcessor.h" +#include "java/JavaClassGenerator.h" #include "util/StringPiece.h" #include <algorithm> diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index e53a765ae0d8..e53a765ae0d8 100644 --- a/tools/aapt2/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index cc5e98150ae3..3625f9c340ed 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "JavaClassGenerator.h" +#include "java/JavaClassGenerator.h" #include "util/Util.h" #include "test/Builders.h" diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp new file mode 100644 index 000000000000..c12da6465a2a --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 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 "Source.h" +#include "XmlDom.h" + +#include "java/AnnotationProcessor.h" +#include "java/ManifestClassGenerator.h" +#include "util/Maybe.h" + +#include <algorithm> + +namespace aapt { + +constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; + +static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source, + const StringPiece16& value) { + const StringPiece16 sep = u"."; + auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end()); + + StringPiece16 result; + if (iter != value.end()) { + result.assign(iter + sep.size(), value.end() - (iter + sep.size())); + } else { + result = value; + } + + if (result.empty()) { + diag->error(DiagMessage(source) << "empty symbol"); + return {}; + } + + iter = util::findNonAlphaNumericAndNotInSet(result, u"_"); + if (iter != result.end()) { + diag->error(DiagMessage(source) + << "invalid character '" << StringPiece16(iter, 1) + << "' in '" << result << "'"); + return {}; + } + + if (*result.begin() >= u'0' && *result.begin() <= u'9') { + diag->error(DiagMessage(source) << "symbol can not start with a digit"); + return {}; + } + + return result; +} + +static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el, + std::ostream* out) { + xml::Attribute* attr = el->findAttribute(kSchemaAndroid, u"name"); + if (!attr) { + diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); + return false; + } + + Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber), + attr->value); + if (!result) { + return false; + } + + *out << "\n"; + + if (!util::trimWhitespace(el->comment).empty()) { + AnnotationProcessor processor(" "); + processor.appendComment(el->comment); + *out << processor.buildComment() << "\n"; + std::string annotations = processor.buildAnnotations(); + if (!annotations.empty()) { + *out << annotations << "\n"; + } + } + *out << " public static final String " << result.value() << "=\"" << attr->value << "\";\n"; + return true; +} + +bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package, + XmlResource* res, std::ostream* out) { + xml::Element* el = xml::findRootElement(res->root.get()); + if (!el) { + return false; + } + + if (el->name != u"manifest" && !el->namespaceUri.empty()) { + diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); + return false; + } + + *out << "package " << package << ";\n\n" + << "public class Manifest {\n"; + + bool error = false; + std::vector<xml::Element*> children = el->getChildElements(); + + + // First write out permissions. + *out << " public static class permission {\n"; + for (xml::Element* childEl : children) { + if (childEl->namespaceUri.empty() && childEl->name == u"permission") { + error |= !writeSymbol(diag, res->file.source, childEl, out); + } + } + *out << " }\n"; + + // Next write out permission groups. + *out << " public static class permission_group {\n"; + for (xml::Element* childEl : children) { + if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") { + error |= !writeSymbol(diag, res->file.source, childEl, out); + } + } + *out << " }\n"; + + *out << "}\n"; + return !error; +} + +} // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h new file mode 100644 index 000000000000..0f0998f8e2ba --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 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_JAVA_MANIFESTCLASSGENERATOR_H +#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H + +#include "Diagnostics.h" +#include "process/IResourceTableConsumer.h" +#include "util/StringPiece.h" + +#include <iostream> + +namespace aapt { + +struct ManifestClassGenerator { + bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res, + std::ostream* out); +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */ diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp new file mode 100644 index 000000000000..1b5bc0586f43 --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 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 "java/ManifestClassGenerator.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <permission android:name="android.DO_DANGEROUS_THINGS" /> + <permission android:name="com.test.sample.permission.HUH" /> + <permission-group android:name="foo.bar.PERMISSION" /> + </manifest>)EOF"); + + std::stringstream out; + ManifestClassGenerator generator; + ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out)); + + std::string actual = out.str(); + + const size_t permissionClassPos = actual.find("public static class permission {"); + const size_t permissionGroupClassPos = actual.find("public static class permission_group {"); + ASSERT_NE(std::string::npos, permissionClassPos); + ASSERT_NE(std::string::npos, permissionGroupClassPos); + + // + // Make sure these permissions are in the permission class. + // + + size_t pos = actual.find("public static final String ACCESS_INTERNET=" + "\"android.permission.ACCESS_INTERNET\";"); + EXPECT_GT(pos, permissionClassPos); + EXPECT_LT(pos, permissionGroupClassPos); + + pos = actual.find("public static final String DO_DANGEROUS_THINGS=" + "\"android.DO_DANGEROUS_THINGS\";"); + EXPECT_GT(pos, permissionClassPos); + EXPECT_LT(pos, permissionGroupClassPos); + + pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";"); + EXPECT_GT(pos, permissionClassPos); + EXPECT_LT(pos, permissionGroupClassPos); + + // + // Make sure these permissions are in the permission_group class + // + + pos = actual.find("public static final String PERMISSION=" + "\"foo.bar.PERMISSION\";"); + EXPECT_GT(pos, permissionGroupClassPos); + EXPECT_LT(pos, std::string::npos); +} + +TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Required to access the internet. + Added in API 1. --> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <!-- @deprecated This permission is for playing outside. --> + <permission android:name="android.permission.PLAY_OUTSIDE" /> + <!-- This is a private permission for system only! + @hide + @SystemApi --> + <permission android:name="android.permission.SECRET" /> + </manifest>)EOF"); + + std::stringstream out; + ManifestClassGenerator generator; + ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out)); + + std::string actual = out.str(); + + EXPECT_NE(std::string::npos, actual.find( +R"EOF( /** + * Required to access the internet. + * Added in API 1. + */ + public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF")); + + EXPECT_NE(std::string::npos, actual.find( +R"EOF( /** + * @deprecated This permission is for playing outside. + */ + @Deprecated + public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF")); + + EXPECT_NE(std::string::npos, actual.find( +R"EOF( /** + * This is a private permission for system only! + * @hide + * @SystemApi + */ + @android.annotations.SystemApi + public static final String SECRET="android.permission.SECRET";)EOF")); +} + +} // namespace aapt diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 7f4dc91eae9f..7683f27f40b3 100644 --- a/tools/aapt2/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ -#include "ProguardRules.h" #include "XmlDom.h" +#include "java/ProguardRules.h" #include "util/Util.h" #include <memory> diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index be61eb9095c2..be61eb9095c2 100644 --- a/tools/aapt2/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index ad701deac16f..77918acb2996 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -17,15 +17,16 @@ #include "AppInfo.h" #include "Debug.h" #include "Flags.h" -#include "JavaClassGenerator.h" #include "NameMangler.h" -#include "ProguardRules.h" #include "XmlDom.h" #include "compile/IdAssigner.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" +#include "java/JavaClassGenerator.h" +#include "java/ManifestClassGenerator.h" +#include "java/ProguardRules.h" #include "link/Linkers.h" #include "link/TableMerger.h" #include "process/IResourceTableConsumer.h" @@ -354,6 +355,36 @@ struct LinkCommand { return true; } + bool writeManifestJavaFile(XmlResource* manifestXml) { + if (!mOptions.generateJavaClassPath) { + return true; + } + + std::string outPath = mOptions.generateJavaClassPath.value(); + file::appendPath(&outPath, + file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage()))); + file::mkdirs(outPath); + file::appendPath(&outPath, "Manifest.java"); + + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + + ManifestClassGenerator generator; + if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(), + manifestXml, &fout)) { + return false; + } + + if (!fout) { + mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + return true; + } + bool writeProguardFile(const proguard::KeepSet& keepSet) { if (!mOptions.generateProguardRulesPath) { return true; @@ -548,6 +579,12 @@ struct LinkCommand { error = true; } + if (mOptions.generateJavaClassPath) { + if (!writeManifestJavaFile(manifestXml.get())) { + error = true; + } + } + if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, archiveWriter.get())) { error = true; diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 1b510e756855..89cd9725227a 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -19,8 +19,8 @@ #include "ResourceTable.h" #include "ResourceValues.h" -#include "util/Util.h" #include "XmlDom.h" +#include "util/Util.h" #include "test/Common.h" diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 314c1e84f44f..30c60918d4e8 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -678,8 +678,6 @@ std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef // fallthrough case ResourceType::kAttr: return parseAttr(name, config, map); - case ResourceType::kIntegerArray: - // fallthrough case ResourceType::kArray: return parseArray(name, config, map); case ResourceType::kStyleable: diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h index 8cbdeae5e892..31deb452b53c 100644 --- a/tools/aapt2/util/StringPiece.h +++ b/tools/aapt2/util/StringPiece.h @@ -36,6 +36,7 @@ template <typename TChar> class BasicStringPiece { public: using const_iterator = const TChar*; + using difference_type = size_t; BasicStringPiece(); BasicStringPiece(const BasicStringPiece<TChar>& str); |