AAPT2: Add support for comments in R.java
Change-Id: Iaa5f3b75bf7de9dbf458fa5c452f7312989f4c4f
This commit is contained in:
@@ -132,6 +132,32 @@ struct Public_header {
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
/**
|
||||
* A structure representing source data for a resource entry.
|
||||
* Appears after an android::ResTable_entry or android::ResTable_map_entry.
|
||||
*
|
||||
* TODO(adamlesinski): This causes some issues when runtime code checks
|
||||
* the size of an android::ResTable_entry. It assumes it is an
|
||||
* android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
|
||||
* which may not be true if this structure is present.
|
||||
*/
|
||||
struct ResTable_entry_source {
|
||||
/**
|
||||
* File path reference.
|
||||
*/
|
||||
android::ResStringPool_ref path;
|
||||
|
||||
/**
|
||||
* Line number this resource was defined on.
|
||||
*/
|
||||
uint32_t line;
|
||||
|
||||
/**
|
||||
* Comment string reference.
|
||||
*/
|
||||
android::ResStringPool_ref comment;
|
||||
};
|
||||
|
||||
struct Public_entry {
|
||||
uint16_t entryId;
|
||||
|
||||
@@ -143,8 +169,7 @@ struct Public_entry {
|
||||
|
||||
uint16_t state;
|
||||
android::ResStringPool_ref key;
|
||||
android::ResStringPool_ref source;
|
||||
uint32_t sourceLine;
|
||||
ResTable_entry_source source;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -173,28 +198,7 @@ struct SymbolTable_entry {
|
||||
* The index into the string pool where the name of this
|
||||
* symbol exists.
|
||||
*/
|
||||
uint32_t stringIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* A structure representing the source of a resourc entry.
|
||||
* Appears after an android::ResTable_entry or android::ResTable_map_entry.
|
||||
*
|
||||
* TODO(adamlesinski): This causes some issues when runtime code checks
|
||||
* the size of an android::ResTable_entry. It assumes it is an
|
||||
* android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
|
||||
* which may not be true if this structure is present.
|
||||
*/
|
||||
struct ResTable_entry_source {
|
||||
/**
|
||||
* Index into the source string pool.
|
||||
*/
|
||||
uint32_t pathIndex;
|
||||
|
||||
/**
|
||||
* Line number this resource was defined on.
|
||||
*/
|
||||
uint32_t line;
|
||||
android::ResStringPool_ref name;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,9 +54,16 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
|
||||
struct FlatEntry {
|
||||
ResourceEntry* entry;
|
||||
Value* value;
|
||||
|
||||
// The entry string pool index to the entry's name.
|
||||
uint32_t entryKey;
|
||||
|
||||
// The source string pool index to the source file path.
|
||||
uint32_t sourcePathKey;
|
||||
uint32_t sourceLine;
|
||||
|
||||
// The source string pool index to the comment.
|
||||
uint32_t commentKey;
|
||||
};
|
||||
|
||||
class SymbolWriter {
|
||||
@@ -318,8 +325,9 @@ private:
|
||||
if (mOptions.useExtendedChunks) {
|
||||
// Write the extra source block. This will be ignored by the Android runtime.
|
||||
ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
|
||||
sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
|
||||
sourceBlock->path.index = util::hostToDevice32(entry->sourcePathKey);
|
||||
sourceBlock->line = util::hostToDevice32(entry->sourceLine);
|
||||
sourceBlock->comment.index = util::hostToDevice32(entry->commentKey);
|
||||
outEntry->size += sizeof(*sourceBlock);
|
||||
}
|
||||
|
||||
@@ -486,12 +494,14 @@ private:
|
||||
publicEntry->entryId = util::hostToDevice32(entry->id.value());
|
||||
publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
|
||||
entry->name).getIndex());
|
||||
publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
|
||||
publicEntry->source.path.index = util::hostToDevice32(mSourcePool->makeRef(
|
||||
util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
|
||||
if (entry->symbolStatus.source.line) {
|
||||
publicEntry->sourceLine = util::hostToDevice32(
|
||||
publicEntry->source.line = util::hostToDevice32(
|
||||
entry->symbolStatus.source.line.value());
|
||||
}
|
||||
publicEntry->source.comment.index = util::hostToDevice32(mSourcePool->makeRef(
|
||||
entry->symbolStatus.comment).getIndex());
|
||||
|
||||
switch (entry->symbolStatus.state) {
|
||||
case SymbolState::kPrivate:
|
||||
@@ -565,13 +575,16 @@ private:
|
||||
lineNumber = value->getSource().line.value();
|
||||
}
|
||||
|
||||
const StringPool::Ref commentRef = mSourcePool->makeRef(value->getComment());
|
||||
|
||||
configToEntryListMap[configValue.config]
|
||||
.push_back(FlatEntry{
|
||||
entry,
|
||||
value,
|
||||
keyIndex,
|
||||
(uint32_t) sourceRef.getIndex(),
|
||||
lineNumber });
|
||||
lineNumber,
|
||||
(uint32_t) commentRef.getIndex() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,7 +693,7 @@ bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
|
||||
// Update the offsets to their final values.
|
||||
if (symbolEntryData) {
|
||||
for (SymbolWriter::Entry& entry : symbolOffsets) {
|
||||
symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
|
||||
symbolEntryData->name.index = util::hostToDevice32(entry.name.getIndex());
|
||||
|
||||
// The symbols were all calculated with the packageBuffer offset. We need to
|
||||
// add the beginning of the output buffer.
|
||||
|
||||
@@ -21,16 +21,10 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
void AnnotationProcessor::appendCommentLine(const StringPiece16& line) {
|
||||
void AnnotationProcessor::appendCommentLine(const std::string& comment) {
|
||||
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()) {
|
||||
@@ -63,14 +57,28 @@ void AnnotationProcessor::appendCommentLine(const StringPiece16& line) {
|
||||
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));
|
||||
line = util::trimWhitespace(line);
|
||||
if (!line.empty()) {
|
||||
appendCommentLine(util::utf16ToUtf8(line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnnotationProcessor::appendComment(const StringPiece& comment) {
|
||||
for (StringPiece line : util::tokenize(comment, '\n')) {
|
||||
line = util::trimWhitespace(line);
|
||||
if (!line.empty()) {
|
||||
appendCommentLine(line.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string AnnotationProcessor::buildComment() {
|
||||
mComment += "\n";
|
||||
mComment += mPrefix;
|
||||
mComment += " */";
|
||||
if (!mComment.empty()) {
|
||||
mComment += "\n";
|
||||
mComment += mPrefix;
|
||||
mComment += " */";
|
||||
}
|
||||
return std::move(mComment);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ public:
|
||||
* we need to collect all the comments.
|
||||
*/
|
||||
void appendComment(const StringPiece16& comment);
|
||||
void appendComment(const StringPiece& comment);
|
||||
|
||||
/**
|
||||
* Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have
|
||||
@@ -85,7 +86,7 @@ private:
|
||||
bool mDeprecated = false;
|
||||
bool mSystemApi = false;
|
||||
|
||||
void appendCommentLine(const StringPiece16& line);
|
||||
void appendCommentLine(const std::string& line);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include "java/AnnotationProcessor.h"
|
||||
#include "java/JavaClassGenerator.h"
|
||||
#include "util/StringPiece.h"
|
||||
@@ -109,13 +111,13 @@ void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGen
|
||||
std::sort(sortedAttributes.begin(), sortedAttributes.end());
|
||||
|
||||
// First we emit the array containing the IDs of each attribute.
|
||||
*out << " "
|
||||
*out << " "
|
||||
<< "public static final int[] " << transform(entryName) << " = {";
|
||||
|
||||
const size_t attrCount = sortedAttributes.size();
|
||||
for (size_t i = 0; i < attrCount; i++) {
|
||||
if (i % kAttribsPerLine == 0) {
|
||||
*out << "\n ";
|
||||
*out << "\n ";
|
||||
}
|
||||
|
||||
*out << sortedAttributes[i].first;
|
||||
@@ -123,11 +125,11 @@ void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGen
|
||||
*out << ", ";
|
||||
}
|
||||
}
|
||||
*out << "\n };\n";
|
||||
*out << "\n };\n";
|
||||
|
||||
// Now we emit the indices into the array.
|
||||
for (size_t i = 0; i < attrCount; i++) {
|
||||
*out << " "
|
||||
*out << " "
|
||||
<< "public static" << finalModifier
|
||||
<< " int " << transform(entryName);
|
||||
|
||||
@@ -141,6 +143,85 @@ void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGen
|
||||
}
|
||||
}
|
||||
|
||||
static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
|
||||
const uint32_t typeMask = attr->typeMask;
|
||||
if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
|
||||
processor->appendComment(
|
||||
"<p>May be a reference to another resource, in the form\n"
|
||||
"\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
|
||||
"attribute in the form\n"
|
||||
"\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_STRING) {
|
||||
processor->appendComment(
|
||||
"<p>May be a string value, using '\\;' to escape characters such as\n"
|
||||
"'\\n' or '\\uxxxx' for a unicode character;");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_INTEGER) {
|
||||
processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
|
||||
processor->appendComment(
|
||||
"<p>May be a boolean value, such as \"<code>true</code>\" or\n"
|
||||
"\"<code>false</code>\".");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_COLOR) {
|
||||
processor->appendComment(
|
||||
"<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
|
||||
"\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
|
||||
"\"<code>#<i>aarrggbb</i></code>\".");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_FLOAT) {
|
||||
processor->appendComment(
|
||||
"<p>May be a floating point value, such as \"<code>1.2</code>\".");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
|
||||
processor->appendComment(
|
||||
"<p>May be a dimension value, which is a floating point number appended with a\n"
|
||||
"unit such as \"<code>14.5sp</code>\".\n"
|
||||
"Available units are: px (pixels), dp (density-independent pixels),\n"
|
||||
"sp (scaled pixels based on preferred font size), in (inches), and\n"
|
||||
"mm (millimeters).");
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_FRACTION) {
|
||||
processor->appendComment(
|
||||
"<p>May be a fractional value, which is a floating point number appended with\n"
|
||||
"either % or %p, such as \"<code>14.5%</code>\".\n"
|
||||
"The % suffix always means a percentage of the base size;\n"
|
||||
"the optional %p suffix provides a size relative to some parent container.");
|
||||
}
|
||||
|
||||
if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
|
||||
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
|
||||
processor->appendComment(
|
||||
"<p>Must be one or more (separated by '|') of the following "
|
||||
"constant values.</p>");
|
||||
} else {
|
||||
processor->appendComment("<p>Must be one of the following constant values.</p>");
|
||||
}
|
||||
|
||||
processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
|
||||
"<colgroup align=\"left\" />\n"
|
||||
"<colgroup align=\"left\" />\n"
|
||||
"<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
|
||||
for (const Attribute::Symbol& symbol : attr->symbols) {
|
||||
std::stringstream line;
|
||||
line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
|
||||
<< "<td>" << std::hex << symbol.value << std::dec << "</td>"
|
||||
<< "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
|
||||
processor->appendComment(line.str());
|
||||
}
|
||||
processor->appendComment("</table>");
|
||||
}
|
||||
}
|
||||
|
||||
bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
|
||||
const ResourceTablePackage* package,
|
||||
const ResourceTableType* type,
|
||||
@@ -186,7 +267,33 @@ bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate
|
||||
generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
|
||||
entry->values.front().value.get()), out);
|
||||
} else {
|
||||
*out << " " << "public static" << finalModifier
|
||||
AnnotationProcessor processor(" ");
|
||||
if (entry->symbolStatus.state != SymbolState::kUndefined) {
|
||||
processor.appendComment(entry->symbolStatus.comment);
|
||||
}
|
||||
|
||||
for (const auto& configValue : entry->values) {
|
||||
processor.appendComment(configValue.value->getComment());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
std::string comment = processor.buildComment();
|
||||
if (!comment.empty()) {
|
||||
*out << comment << "\n";
|
||||
}
|
||||
|
||||
std::string annotations = processor.buildAnnotations();
|
||||
if (!annotations.empty()) {
|
||||
*out << annotations << "\n";
|
||||
}
|
||||
|
||||
*out << " " << "public static" << finalModifier
|
||||
<< " int " << transform(unmangledName) << " = " << id << ";\n";
|
||||
}
|
||||
}
|
||||
@@ -211,11 +318,11 @@ bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
|
||||
} else {
|
||||
typeStr = toString(type->type);
|
||||
}
|
||||
*out << " public static final class " << typeStr << " {\n";
|
||||
*out << " public static final class " << typeStr << " {\n";
|
||||
if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
|
||||
return false;
|
||||
}
|
||||
*out << " }\n";
|
||||
*out << " }\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,4 +198,35 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
|
||||
EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
|
||||
}
|
||||
|
||||
TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId(u"android", 0x01)
|
||||
.addSimple(u"@android:id/foo", ResourceId(0x01010000))
|
||||
.build();
|
||||
test::getValue<Id>(table.get(), u"@android:id/foo")
|
||||
->setComment(std::u16string(u"This is a comment\n@deprecated"));
|
||||
|
||||
JavaClassGenerator generator(table.get(), {});
|
||||
|
||||
std::stringstream out;
|
||||
ASSERT_TRUE(generator.generate(u"android", &out));
|
||||
std::string actual = out.str();
|
||||
|
||||
EXPECT_NE(std::string::npos, actual.find(
|
||||
R"EOF(/**
|
||||
* This is a comment
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public static final int foo = 0x01010000;)EOF"));
|
||||
}
|
||||
|
||||
TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
|
||||
|
||||
}
|
||||
|
||||
TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
|
||||
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -116,7 +116,7 @@ bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbo
|
||||
if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
|
||||
// This offset is a symbol!
|
||||
const StringPiece16 str = util::getString(
|
||||
mSymbolPool, util::deviceToHost32(mSymbolEntries[i].stringIndex));
|
||||
mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
|
||||
|
||||
StringPiece16 typeStr;
|
||||
ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
|
||||
@@ -425,8 +425,14 @@ bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package,
|
||||
Symbol symbol;
|
||||
if (mSourcePool.getError() == NO_ERROR) {
|
||||
symbol.source.path = util::utf16ToUtf8(util::getString(
|
||||
mSourcePool, util::deviceToHost32(entry->source.index)));
|
||||
symbol.source.line = util::deviceToHost32(entry->sourceLine);
|
||||
mSourcePool, util::deviceToHost32(entry->source.path.index)));
|
||||
symbol.source.line = util::deviceToHost32(entry->source.line);
|
||||
}
|
||||
|
||||
StringPiece16 comment = util::getString(mSourcePool,
|
||||
util::deviceToHost32(entry->source.comment.index));
|
||||
if (!comment.empty()) {
|
||||
symbol.comment = comment.toString();
|
||||
}
|
||||
|
||||
switch (util::deviceToHost16(entry->state)) {
|
||||
@@ -560,7 +566,7 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
|
||||
Source source = mSource;
|
||||
if (sourceBlock) {
|
||||
size_t len;
|
||||
const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->pathIndex),
|
||||
const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
|
||||
&len);
|
||||
if (str) {
|
||||
source.path.assign(str, len);
|
||||
@@ -568,6 +574,12 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
|
||||
source.line = util::deviceToHost32(sourceBlock->line);
|
||||
}
|
||||
|
||||
StringPiece16 comment = util::getString(mSourcePool,
|
||||
util::deviceToHost32(sourceBlock->comment.index));
|
||||
if (!comment.empty()) {
|
||||
resourceValue->setComment(comment);
|
||||
}
|
||||
|
||||
resourceValue->setSource(source);
|
||||
if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
|
||||
mContext->getDiagnostics())) {
|
||||
|
||||
@@ -76,6 +76,25 @@ StringPiece16 trimWhitespace(const StringPiece16& str) {
|
||||
return StringPiece16(start, end - start);
|
||||
}
|
||||
|
||||
StringPiece trimWhitespace(const StringPiece& str) {
|
||||
if (str.size() == 0 || str.data() == nullptr) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const char* start = str.data();
|
||||
const char* end = str.data() + str.length();
|
||||
|
||||
while (start != end && isspace(*start)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
while (end != start && isspace(*(end - 1))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return StringPiece(start, end - start);
|
||||
}
|
||||
|
||||
StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
|
||||
const StringPiece16& allowedChars) {
|
||||
const auto endIter = str.end();
|
||||
|
||||
@@ -62,6 +62,8 @@ bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& s
|
||||
*/
|
||||
StringPiece16 trimWhitespace(const StringPiece16& str);
|
||||
|
||||
StringPiece trimWhitespace(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* UTF-16 isspace(). It basically checks for lower range characters that are
|
||||
* whitespace.
|
||||
|
||||
Reference in New Issue
Block a user