Loading tools/aapt2/Android.mk +3 −1 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ sources := \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ xml/XmlActionExecutor.cpp \ xml/XmlDom.cpp \ xml/XmlPullParser.cpp \ xml/XmlUtil.cpp Loading Loading @@ -107,6 +108,7 @@ testSources := \ SdkConstants_test.cpp \ StringPool_test.cpp \ ValueVisitor_test.cpp \ xml/XmlActionExecutor_test.cpp \ xml/XmlDom_test.cpp \ xml/XmlPullParser_test.cpp \ xml/XmlUtil_test.cpp Loading Loading @@ -140,7 +142,7 @@ else endif cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti cppFlags := -std=c++14 -Wno-missing-field-initializers -fno-exceptions -fno-rtti protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST) # ========================================================== Loading tools/aapt2/Diagnostics.h +72 −21 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ #include "util/StringPiece.h" #include "util/Util.h" #include <android-base/macros.h> #include <iostream> #include <sstream> #include <string> Loading @@ -46,7 +47,11 @@ public: DiagMessage(const Source& src) : mSource(src) { } template <typename T> DiagMessage& operator<<(const T& value) { DiagMessage(size_t line) : mSource(Source().withLine(line)) { } template <typename T> DiagMessage& operator<<(const T& value) { mMessage << value; return *this; } Loading @@ -59,36 +64,82 @@ public: struct IDiagnostics { virtual ~IDiagnostics() = default; virtual void error(const DiagMessage& message) = 0; virtual void warn(const DiagMessage& message) = 0; virtual void note(const DiagMessage& message) = 0; enum class Level { Note, Warn, Error }; struct StdErrDiagnostics : public IDiagnostics { size_t mNumErrors = 0; virtual void log(Level level, DiagMessageActual& actualMsg) = 0; void emit(const DiagMessage& msg, const char* tag) { DiagMessageActual actual = msg.build(); if (!actual.source.path.empty()) { std::cerr << actual.source << ": "; virtual void error(const DiagMessage& message) { DiagMessageActual actual = message.build(); log(Level::Error, actual); } std::cerr << tag << actual.message << "." << std::endl; virtual void warn(const DiagMessage& message) { DiagMessageActual actual = message.build(); log(Level::Warn, actual); } void error(const DiagMessage& msg) override { if (mNumErrors < 20) { emit(msg, "error: "); virtual void note(const DiagMessage& message) { DiagMessageActual actual = message.build(); log(Level::Note, actual); } }; class StdErrDiagnostics : public IDiagnostics { public: StdErrDiagnostics() = default; void log(Level level, DiagMessageActual& actualMsg) override { const char* tag; switch (level) { case Level::Error: mNumErrors++; if (mNumErrors > 20) { return; } tag = "error"; break; case Level::Warn: tag = "warn"; break; void warn(const DiagMessage& msg) override { emit(msg, "warn: "); case Level::Note: tag = "note"; break; } void note(const DiagMessage& msg) override { emit(msg, "note: "); if (!actualMsg.source.path.empty()) { std::cerr << actualMsg.source << ": "; } std::cerr << tag << ": " << actualMsg.message << "." << std::endl; } private: size_t mNumErrors = 0; DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); }; class SourcePathDiagnostics : public IDiagnostics { public: SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) { } void log(Level level, DiagMessageActual& actualMsg) override { actualMsg.source.path = mSource.path; mDiag->log(level, actualMsg); } private: Source mSource; IDiagnostics* mDiag; DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); }; } // namespace aapt Loading tools/aapt2/ResourceTable_test.cpp +16 −26 Original line number Diff line number Diff line Loading @@ -28,33 +28,23 @@ namespace aapt { struct ResourceTableTest : public ::testing::Test { struct EmptyDiagnostics : public IDiagnostics { void error(const DiagMessage& msg) override {} void warn(const DiagMessage& msg) override {} void note(const DiagMessage& msg) override {} }; EmptyDiagnostics mDiagnostics; }; TEST_F(ResourceTableTest, FailToAddResourceWithBadName) { TEST(ResourceTableTest, FailToAddResourceWithBadName) { ResourceTable table; EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey,there"), ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey:there"), ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), &mDiagnostics)); test::getDiagnostics())); } TEST_F(ResourceTableTest, AddOneResource) { TEST(ResourceTableTest, AddOneResource) { ResourceTable table; EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), Loading @@ -62,12 +52,12 @@ TEST_F(ResourceTableTest, AddOneResource) { "", test::ValueBuilder<Id>() .setSource("test/path/file.xml", 23u).build(), &mDiagnostics)); test::getDiagnostics())); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); } TEST_F(ResourceTableTest, AddMultipleResources) { TEST(ResourceTableTest, AddMultipleResources) { ResourceTable table; ConfigDescription config; Loading @@ -79,21 +69,21 @@ TEST_F(ResourceTableTest, AddMultipleResources) { config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:attr/id"), config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), Loading @@ -102,7 +92,7 @@ TEST_F(ResourceTableTest, AddMultipleResources) { test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) .setSource("test/path/file.xml", 20u) .build(), &mDiagnostics)); test::getDiagnostics())); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width")); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); Loading @@ -111,37 +101,37 @@ TEST_F(ResourceTableTest, AddMultipleResources) { languageConfig)); } TEST_F(ResourceTableTest, OverrideWeakResourceValue) { TEST(ResourceTableTest, OverrideWeakResourceValue) { ResourceTable table; ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, "", util::make_unique<Attribute>(true), &mDiagnostics)); "", util::make_unique<Attribute>(true), test::getDiagnostics())); Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_TRUE(attr->isWeak()); ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, "", util::make_unique<Attribute>(false), &mDiagnostics)); "", util::make_unique<Attribute>(false), test::getDiagnostics())); attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_FALSE(attr->isWeak()); } TEST_F(ResourceTableTest, ProductVaryingValues) { TEST(ResourceTableTest, ProductVaryingValues) { ResourceTable table; EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), test::parseConfigOrDie("land"), "tablet", util::make_unique<Id>(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), test::parseConfigOrDie("land"), "phone", util::make_unique<Id>(), &mDiagnostics)); test::getDiagnostics())); EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", test::parseConfigOrDie("land"), Loading tools/aapt2/link/ManifestFixer.cpp +188 −118 Original line number Diff line number Diff line Loading @@ -17,67 +17,198 @@ #include "ResourceUtils.h" #include "link/ManifestFixer.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" namespace aapt { static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) { xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); /** * This is how PackageManager builds class names from AndroidManifest.xml entries. */ static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr, SourcePathDiagnostics* diag) { std::u16string className = attr->value; if (className.find(u'.') == std::u16string::npos) { // There is no '.', so add one to the beginning. className = u"."; className += attr->value; } // We allow unqualified class names (ie: .HelloActivity) // Since we don't know the package name, we can just make a fake one here and // the test will be identical as long as the real package name is valid too. Maybe<std::u16string> fullyQualifiedClassName = util::getFullyQualifiedClassName(u"a", className); StringPiece16 qualifiedClassName = fullyQualifiedClassName ? fullyQualifiedClassName.value() : className; if (!util::isJavaClassName(qualifiedClassName)) { diag->error(DiagMessage(el->lineNumber) << "attribute 'android:name' in <" << el->name << "> tag must be a valid Java class name"); return false; } return true; } static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { return nameIsJavaClassName(el, attr, diag); } return true; } static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { return nameIsJavaClassName(el, attr, diag); } diag->error(DiagMessage(el->lineNumber) << "<" << el->name << "> is missing attribute 'android:name'"); return false; } static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { xml::Attribute* attr = el->findAttribute({}, u"package"); if (!attr) { context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) << "missing 'package' attribute"); diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute"); return false; } else if (ResourceUtils::isReference(attr->value)) { context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) << "value for attribute 'package' must not be a " "reference"); diag->error(DiagMessage(el->lineNumber) << "attribute 'package' in <manifest> tag must not be a reference"); return false; } else if (!util::isJavaPackageName(attr->value)) { context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) << "invalid package name '" << attr->value << "'"); } else { diag->error(DiagMessage(el->lineNumber) << "attribute 'package' in <manifest> tag is not a valid Java package name: '" << attr->value << "'"); return false; } return true; } bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) { // First verify some options. if (mOptions.renameManifestPackage) { if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) { diag->error(DiagMessage() << "invalid manifest package override '" << mOptions.renameManifestPackage.value() << "'"); return false; } static bool includeVersionName(IAaptContext* context, const Source& source, const StringPiece16& versionName, xml::Element* manifestEl) { if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName")) { return true; } manifestEl->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionName", versionName.toString() }); return true; if (mOptions.renameInstrumentationTargetPackage) { if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) { diag->error(DiagMessage() << "invalid instrumentation target package override '" << mOptions.renameInstrumentationTargetPackage.value() << "'"); return false; } } static bool includeVersionCode(IAaptContext* context, const Source& source, const StringPiece16& versionCode, xml::Element* manifestEl) { if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode")) { return true; // Common intent-filter actions. xml::XmlNodeAction intentFilterAction; intentFilterAction[u"action"]; intentFilterAction[u"category"]; intentFilterAction[u"data"]; // Common meta-data actions. xml::XmlNodeAction metaDataAction; // Manifest actions. xml::XmlNodeAction& manifestAction = (*executor)[u"manifest"]; manifestAction.action(verifyManifest); manifestAction.action([&](xml::Element* el) -> bool { if (mOptions.versionNameDefault) { if (el->findAttribute(xml::kSchemaAndroid, u"versionName") == nullptr) { el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionName", mOptions.versionNameDefault.value() }); } } manifestEl->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionCode", versionCode.toString() }); return true; if (mOptions.versionCodeDefault) { if (el->findAttribute(xml::kSchemaAndroid, u"versionCode") == nullptr) { el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionCode", mOptions.versionCodeDefault.value() }); } } return true; }); static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el, const ManifestFixerOptions& options) { if (options.minSdkVersionDefault && // Uses-sdk actions. manifestAction[u"uses-sdk"].action([&](xml::Element* el) -> bool { if (mOptions.minSdkVersionDefault && el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) { // There was no minSdkVersion defined and we have a default to assign. el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() }); xml::kSchemaAndroid, u"minSdkVersion", mOptions.minSdkVersionDefault.value() }); } if (options.targetSdkVersionDefault && if (mOptions.targetSdkVersionDefault && el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) { // There was no targetSdkVersion defined and we have a default to assign. el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"targetSdkVersion", options.targetSdkVersionDefault.value() }); mOptions.targetSdkVersionDefault.value() }); } return true; }); // Instrumentation actions. manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool { if (!mOptions.renameInstrumentationTargetPackage) { return true; } if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"targetPackage")) { attr->value = mOptions.renameInstrumentationTargetPackage.value(); } return true; }); manifestAction[u"uses-permission"]; manifestAction[u"permission"]; manifestAction[u"permission-tree"]; manifestAction[u"permission-group"]; manifestAction[u"uses-configuration"]; manifestAction[u"uses-feature"]; manifestAction[u"uses-library"]; manifestAction[u"supports-screens"]; manifestAction[u"compatible-screens"]; manifestAction[u"supports-gl-texture"]; // Application actions. xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"]; applicationAction.action(optionalNameIsJavaClassName); // Activity actions. applicationAction[u"activity"].action(requiredNameIsJavaClassName); applicationAction[u"activity"][u"intent-filter"] = intentFilterAction; applicationAction[u"activity"][u"meta-data"] = metaDataAction; // Activity alias actions. applicationAction[u"activity-alias"][u"intent-filter"] = intentFilterAction; applicationAction[u"activity-alias"][u"meta-data"] = metaDataAction; // Service actions. applicationAction[u"service"].action(requiredNameIsJavaClassName); applicationAction[u"service"][u"intent-filter"] = intentFilterAction; applicationAction[u"service"][u"meta-data"] = metaDataAction; // Receiver actions. applicationAction[u"receiver"].action(requiredNameIsJavaClassName); applicationAction[u"receiver"][u"intent-filter"] = intentFilterAction; applicationAction[u"receiver"][u"meta-data"] = metaDataAction; // Provider actions. applicationAction[u"provider"].action(requiredNameIsJavaClassName); applicationAction[u"provider"][u"grant-uri-permissions"]; applicationAction[u"provider"][u"meta-data"] = metaDataAction; applicationAction[u"provider"][u"path-permissions"]; return true; } class FullyQualifiedClassNameVisitor : public xml::Visitor { Loading @@ -103,14 +234,7 @@ private: StringPiece16 mPackage; }; static bool renameManifestPackage(IAaptContext* context, const Source& source, const StringPiece16& packageOverride, xml::Element* manifestEl) { if (!util::isJavaPackageName(packageOverride)) { context->getDiagnostics()->error(DiagMessage() << "invalid manifest package override '" << packageOverride << "'"); return false; } static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) { xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); // We've already verified that the manifest element is present, with a package name specified. Loading @@ -124,32 +248,6 @@ static bool renameManifestPackage(IAaptContext* context, const Source& source, return true; } static bool renameInstrumentationTargetPackage(IAaptContext* context, const Source& source, const StringPiece16& packageOverride, xml::Element* manifestEl) { if (!util::isJavaPackageName(packageOverride)) { context->getDiagnostics()->error(DiagMessage() << "invalid instrumentation target package override '" << packageOverride << "'"); return false; } xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation"); if (!instrumentationEl) { // No error if there is no work to be done. return true; } xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage"); if (!attr) { // No error if there is no work to be done. return true; } attr->value = packageOverride.toString(); return true; } bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { xml::Element* root = xml::findRootElement(doc->root.get()); if (!root || !root->namespaceUri.empty() || root->name != u"manifest") { Loading @@ -158,59 +256,31 @@ bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { return false; } if (!verifyManifest(context, doc->file.source, root)) { return false; if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) && root->findChild({}, u"uses-sdk") == nullptr) { // Auto insert a <uses-sdk> element. std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); usesSdk->name = u"uses-sdk"; root->addChild(std::move(usesSdk)); } if (mOptions.versionCodeDefault) { if (!includeVersionCode(context, doc->file.source, mOptions.versionCodeDefault.value(), root)) { xml::XmlActionExecutor executor; if (!buildRules(&executor, context->getDiagnostics())) { return false; } } if (mOptions.versionNameDefault) { if (!includeVersionName(context, doc->file.source, mOptions.versionNameDefault.value(), root)) { if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(), doc)) { return false; } } if (mOptions.renameManifestPackage) { // Rename manifest package. if (!renameManifestPackage(context, doc->file.source, mOptions.renameManifestPackage.value(), root)) { // Rename manifest package outside of the XmlActionExecutor. // We need to extract the old package name and FullyQualify all class names. if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) { return false; } } if (mOptions.renameInstrumentationTargetPackage) { if (!renameInstrumentationTargetPackage(context, doc->file.source, mOptions.renameInstrumentationTargetPackage.value(), root)) { return false; } } bool foundUsesSdk = false; for (xml::Element* el : root->getChildElements()) { if (!el->namespaceUri.empty()) { continue; } if (el->name == u"uses-sdk") { foundUsesSdk = true; fixUsesSdk(context, doc->file.source, el, mOptions); } } if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) { std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); usesSdk->name = u"uses-sdk"; fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions); root->addChild(std::move(usesSdk)); } return true; } Loading tools/aapt2/link/ManifestFixer.h +8 −3 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include "process/IResourceTableConsumer.h" #include "util/Maybe.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" #include <string> Loading @@ -38,13 +39,17 @@ struct ManifestFixerOptions { * Verifies that the manifest is correctly formed and inserts defaults * where specified with ManifestFixerOptions. */ struct ManifestFixer : public IXmlResourceConsumer { ManifestFixerOptions mOptions; class ManifestFixer : public IXmlResourceConsumer { public: ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { } bool consume(IAaptContext* context, xml::XmlResource* doc) override; private: bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); ManifestFixerOptions mOptions; }; } // namespace aapt Loading Loading
tools/aapt2/Android.mk +3 −1 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ sources := \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ xml/XmlActionExecutor.cpp \ xml/XmlDom.cpp \ xml/XmlPullParser.cpp \ xml/XmlUtil.cpp Loading Loading @@ -107,6 +108,7 @@ testSources := \ SdkConstants_test.cpp \ StringPool_test.cpp \ ValueVisitor_test.cpp \ xml/XmlActionExecutor_test.cpp \ xml/XmlDom_test.cpp \ xml/XmlPullParser_test.cpp \ xml/XmlUtil_test.cpp Loading Loading @@ -140,7 +142,7 @@ else endif cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti cppFlags := -std=c++14 -Wno-missing-field-initializers -fno-exceptions -fno-rtti protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST) # ========================================================== Loading
tools/aapt2/Diagnostics.h +72 −21 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ #include "util/StringPiece.h" #include "util/Util.h" #include <android-base/macros.h> #include <iostream> #include <sstream> #include <string> Loading @@ -46,7 +47,11 @@ public: DiagMessage(const Source& src) : mSource(src) { } template <typename T> DiagMessage& operator<<(const T& value) { DiagMessage(size_t line) : mSource(Source().withLine(line)) { } template <typename T> DiagMessage& operator<<(const T& value) { mMessage << value; return *this; } Loading @@ -59,36 +64,82 @@ public: struct IDiagnostics { virtual ~IDiagnostics() = default; virtual void error(const DiagMessage& message) = 0; virtual void warn(const DiagMessage& message) = 0; virtual void note(const DiagMessage& message) = 0; enum class Level { Note, Warn, Error }; struct StdErrDiagnostics : public IDiagnostics { size_t mNumErrors = 0; virtual void log(Level level, DiagMessageActual& actualMsg) = 0; void emit(const DiagMessage& msg, const char* tag) { DiagMessageActual actual = msg.build(); if (!actual.source.path.empty()) { std::cerr << actual.source << ": "; virtual void error(const DiagMessage& message) { DiagMessageActual actual = message.build(); log(Level::Error, actual); } std::cerr << tag << actual.message << "." << std::endl; virtual void warn(const DiagMessage& message) { DiagMessageActual actual = message.build(); log(Level::Warn, actual); } void error(const DiagMessage& msg) override { if (mNumErrors < 20) { emit(msg, "error: "); virtual void note(const DiagMessage& message) { DiagMessageActual actual = message.build(); log(Level::Note, actual); } }; class StdErrDiagnostics : public IDiagnostics { public: StdErrDiagnostics() = default; void log(Level level, DiagMessageActual& actualMsg) override { const char* tag; switch (level) { case Level::Error: mNumErrors++; if (mNumErrors > 20) { return; } tag = "error"; break; case Level::Warn: tag = "warn"; break; void warn(const DiagMessage& msg) override { emit(msg, "warn: "); case Level::Note: tag = "note"; break; } void note(const DiagMessage& msg) override { emit(msg, "note: "); if (!actualMsg.source.path.empty()) { std::cerr << actualMsg.source << ": "; } std::cerr << tag << ": " << actualMsg.message << "." << std::endl; } private: size_t mNumErrors = 0; DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); }; class SourcePathDiagnostics : public IDiagnostics { public: SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) { } void log(Level level, DiagMessageActual& actualMsg) override { actualMsg.source.path = mSource.path; mDiag->log(level, actualMsg); } private: Source mSource; IDiagnostics* mDiag; DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); }; } // namespace aapt Loading
tools/aapt2/ResourceTable_test.cpp +16 −26 Original line number Diff line number Diff line Loading @@ -28,33 +28,23 @@ namespace aapt { struct ResourceTableTest : public ::testing::Test { struct EmptyDiagnostics : public IDiagnostics { void error(const DiagMessage& msg) override {} void warn(const DiagMessage& msg) override {} void note(const DiagMessage& msg) override {} }; EmptyDiagnostics mDiagnostics; }; TEST_F(ResourceTableTest, FailToAddResourceWithBadName) { TEST(ResourceTableTest, FailToAddResourceWithBadName) { ResourceTable table; EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey,there"), ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey:there"), ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), &mDiagnostics)); test::getDiagnostics())); } TEST_F(ResourceTableTest, AddOneResource) { TEST(ResourceTableTest, AddOneResource) { ResourceTable table; EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), Loading @@ -62,12 +52,12 @@ TEST_F(ResourceTableTest, AddOneResource) { "", test::ValueBuilder<Id>() .setSource("test/path/file.xml", 23u).build(), &mDiagnostics)); test::getDiagnostics())); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); } TEST_F(ResourceTableTest, AddMultipleResources) { TEST(ResourceTableTest, AddMultipleResources) { ResourceTable table; ConfigDescription config; Loading @@ -79,21 +69,21 @@ TEST_F(ResourceTableTest, AddMultipleResources) { config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:attr/id"), config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), Loading @@ -102,7 +92,7 @@ TEST_F(ResourceTableTest, AddMultipleResources) { test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) .setSource("test/path/file.xml", 20u) .build(), &mDiagnostics)); test::getDiagnostics())); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width")); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); Loading @@ -111,37 +101,37 @@ TEST_F(ResourceTableTest, AddMultipleResources) { languageConfig)); } TEST_F(ResourceTableTest, OverrideWeakResourceValue) { TEST(ResourceTableTest, OverrideWeakResourceValue) { ResourceTable table; ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, "", util::make_unique<Attribute>(true), &mDiagnostics)); "", util::make_unique<Attribute>(true), test::getDiagnostics())); Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_TRUE(attr->isWeak()); ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, "", util::make_unique<Attribute>(false), &mDiagnostics)); "", util::make_unique<Attribute>(false), test::getDiagnostics())); attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_FALSE(attr->isWeak()); } TEST_F(ResourceTableTest, ProductVaryingValues) { TEST(ResourceTableTest, ProductVaryingValues) { ResourceTable table; EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), test::parseConfigOrDie("land"), "tablet", util::make_unique<Id>(), &mDiagnostics)); test::getDiagnostics())); EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), test::parseConfigOrDie("land"), "phone", util::make_unique<Id>(), &mDiagnostics)); test::getDiagnostics())); EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", test::parseConfigOrDie("land"), Loading
tools/aapt2/link/ManifestFixer.cpp +188 −118 Original line number Diff line number Diff line Loading @@ -17,67 +17,198 @@ #include "ResourceUtils.h" #include "link/ManifestFixer.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" namespace aapt { static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) { xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); /** * This is how PackageManager builds class names from AndroidManifest.xml entries. */ static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr, SourcePathDiagnostics* diag) { std::u16string className = attr->value; if (className.find(u'.') == std::u16string::npos) { // There is no '.', so add one to the beginning. className = u"."; className += attr->value; } // We allow unqualified class names (ie: .HelloActivity) // Since we don't know the package name, we can just make a fake one here and // the test will be identical as long as the real package name is valid too. Maybe<std::u16string> fullyQualifiedClassName = util::getFullyQualifiedClassName(u"a", className); StringPiece16 qualifiedClassName = fullyQualifiedClassName ? fullyQualifiedClassName.value() : className; if (!util::isJavaClassName(qualifiedClassName)) { diag->error(DiagMessage(el->lineNumber) << "attribute 'android:name' in <" << el->name << "> tag must be a valid Java class name"); return false; } return true; } static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { return nameIsJavaClassName(el, attr, diag); } return true; } static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { return nameIsJavaClassName(el, attr, diag); } diag->error(DiagMessage(el->lineNumber) << "<" << el->name << "> is missing attribute 'android:name'"); return false; } static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { xml::Attribute* attr = el->findAttribute({}, u"package"); if (!attr) { context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) << "missing 'package' attribute"); diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute"); return false; } else if (ResourceUtils::isReference(attr->value)) { context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) << "value for attribute 'package' must not be a " "reference"); diag->error(DiagMessage(el->lineNumber) << "attribute 'package' in <manifest> tag must not be a reference"); return false; } else if (!util::isJavaPackageName(attr->value)) { context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) << "invalid package name '" << attr->value << "'"); } else { diag->error(DiagMessage(el->lineNumber) << "attribute 'package' in <manifest> tag is not a valid Java package name: '" << attr->value << "'"); return false; } return true; } bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) { // First verify some options. if (mOptions.renameManifestPackage) { if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) { diag->error(DiagMessage() << "invalid manifest package override '" << mOptions.renameManifestPackage.value() << "'"); return false; } static bool includeVersionName(IAaptContext* context, const Source& source, const StringPiece16& versionName, xml::Element* manifestEl) { if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName")) { return true; } manifestEl->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionName", versionName.toString() }); return true; if (mOptions.renameInstrumentationTargetPackage) { if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) { diag->error(DiagMessage() << "invalid instrumentation target package override '" << mOptions.renameInstrumentationTargetPackage.value() << "'"); return false; } } static bool includeVersionCode(IAaptContext* context, const Source& source, const StringPiece16& versionCode, xml::Element* manifestEl) { if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode")) { return true; // Common intent-filter actions. xml::XmlNodeAction intentFilterAction; intentFilterAction[u"action"]; intentFilterAction[u"category"]; intentFilterAction[u"data"]; // Common meta-data actions. xml::XmlNodeAction metaDataAction; // Manifest actions. xml::XmlNodeAction& manifestAction = (*executor)[u"manifest"]; manifestAction.action(verifyManifest); manifestAction.action([&](xml::Element* el) -> bool { if (mOptions.versionNameDefault) { if (el->findAttribute(xml::kSchemaAndroid, u"versionName") == nullptr) { el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionName", mOptions.versionNameDefault.value() }); } } manifestEl->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionCode", versionCode.toString() }); return true; if (mOptions.versionCodeDefault) { if (el->findAttribute(xml::kSchemaAndroid, u"versionCode") == nullptr) { el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"versionCode", mOptions.versionCodeDefault.value() }); } } return true; }); static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el, const ManifestFixerOptions& options) { if (options.minSdkVersionDefault && // Uses-sdk actions. manifestAction[u"uses-sdk"].action([&](xml::Element* el) -> bool { if (mOptions.minSdkVersionDefault && el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) { // There was no minSdkVersion defined and we have a default to assign. el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() }); xml::kSchemaAndroid, u"minSdkVersion", mOptions.minSdkVersionDefault.value() }); } if (options.targetSdkVersionDefault && if (mOptions.targetSdkVersionDefault && el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) { // There was no targetSdkVersion defined and we have a default to assign. el->attributes.push_back(xml::Attribute{ xml::kSchemaAndroid, u"targetSdkVersion", options.targetSdkVersionDefault.value() }); mOptions.targetSdkVersionDefault.value() }); } return true; }); // Instrumentation actions. manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool { if (!mOptions.renameInstrumentationTargetPackage) { return true; } if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"targetPackage")) { attr->value = mOptions.renameInstrumentationTargetPackage.value(); } return true; }); manifestAction[u"uses-permission"]; manifestAction[u"permission"]; manifestAction[u"permission-tree"]; manifestAction[u"permission-group"]; manifestAction[u"uses-configuration"]; manifestAction[u"uses-feature"]; manifestAction[u"uses-library"]; manifestAction[u"supports-screens"]; manifestAction[u"compatible-screens"]; manifestAction[u"supports-gl-texture"]; // Application actions. xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"]; applicationAction.action(optionalNameIsJavaClassName); // Activity actions. applicationAction[u"activity"].action(requiredNameIsJavaClassName); applicationAction[u"activity"][u"intent-filter"] = intentFilterAction; applicationAction[u"activity"][u"meta-data"] = metaDataAction; // Activity alias actions. applicationAction[u"activity-alias"][u"intent-filter"] = intentFilterAction; applicationAction[u"activity-alias"][u"meta-data"] = metaDataAction; // Service actions. applicationAction[u"service"].action(requiredNameIsJavaClassName); applicationAction[u"service"][u"intent-filter"] = intentFilterAction; applicationAction[u"service"][u"meta-data"] = metaDataAction; // Receiver actions. applicationAction[u"receiver"].action(requiredNameIsJavaClassName); applicationAction[u"receiver"][u"intent-filter"] = intentFilterAction; applicationAction[u"receiver"][u"meta-data"] = metaDataAction; // Provider actions. applicationAction[u"provider"].action(requiredNameIsJavaClassName); applicationAction[u"provider"][u"grant-uri-permissions"]; applicationAction[u"provider"][u"meta-data"] = metaDataAction; applicationAction[u"provider"][u"path-permissions"]; return true; } class FullyQualifiedClassNameVisitor : public xml::Visitor { Loading @@ -103,14 +234,7 @@ private: StringPiece16 mPackage; }; static bool renameManifestPackage(IAaptContext* context, const Source& source, const StringPiece16& packageOverride, xml::Element* manifestEl) { if (!util::isJavaPackageName(packageOverride)) { context->getDiagnostics()->error(DiagMessage() << "invalid manifest package override '" << packageOverride << "'"); return false; } static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) { xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); // We've already verified that the manifest element is present, with a package name specified. Loading @@ -124,32 +248,6 @@ static bool renameManifestPackage(IAaptContext* context, const Source& source, return true; } static bool renameInstrumentationTargetPackage(IAaptContext* context, const Source& source, const StringPiece16& packageOverride, xml::Element* manifestEl) { if (!util::isJavaPackageName(packageOverride)) { context->getDiagnostics()->error(DiagMessage() << "invalid instrumentation target package override '" << packageOverride << "'"); return false; } xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation"); if (!instrumentationEl) { // No error if there is no work to be done. return true; } xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage"); if (!attr) { // No error if there is no work to be done. return true; } attr->value = packageOverride.toString(); return true; } bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { xml::Element* root = xml::findRootElement(doc->root.get()); if (!root || !root->namespaceUri.empty() || root->name != u"manifest") { Loading @@ -158,59 +256,31 @@ bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { return false; } if (!verifyManifest(context, doc->file.source, root)) { return false; if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) && root->findChild({}, u"uses-sdk") == nullptr) { // Auto insert a <uses-sdk> element. std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); usesSdk->name = u"uses-sdk"; root->addChild(std::move(usesSdk)); } if (mOptions.versionCodeDefault) { if (!includeVersionCode(context, doc->file.source, mOptions.versionCodeDefault.value(), root)) { xml::XmlActionExecutor executor; if (!buildRules(&executor, context->getDiagnostics())) { return false; } } if (mOptions.versionNameDefault) { if (!includeVersionName(context, doc->file.source, mOptions.versionNameDefault.value(), root)) { if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(), doc)) { return false; } } if (mOptions.renameManifestPackage) { // Rename manifest package. if (!renameManifestPackage(context, doc->file.source, mOptions.renameManifestPackage.value(), root)) { // Rename manifest package outside of the XmlActionExecutor. // We need to extract the old package name and FullyQualify all class names. if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) { return false; } } if (mOptions.renameInstrumentationTargetPackage) { if (!renameInstrumentationTargetPackage(context, doc->file.source, mOptions.renameInstrumentationTargetPackage.value(), root)) { return false; } } bool foundUsesSdk = false; for (xml::Element* el : root->getChildElements()) { if (!el->namespaceUri.empty()) { continue; } if (el->name == u"uses-sdk") { foundUsesSdk = true; fixUsesSdk(context, doc->file.source, el, mOptions); } } if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) { std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); usesSdk->name = u"uses-sdk"; fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions); root->addChild(std::move(usesSdk)); } return true; } Loading
tools/aapt2/link/ManifestFixer.h +8 −3 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include "process/IResourceTableConsumer.h" #include "util/Maybe.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" #include <string> Loading @@ -38,13 +39,17 @@ struct ManifestFixerOptions { * Verifies that the manifest is correctly formed and inserts defaults * where specified with ManifestFixerOptions. */ struct ManifestFixer : public IXmlResourceConsumer { ManifestFixerOptions mOptions; class ManifestFixer : public IXmlResourceConsumer { public: ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { } bool consume(IAaptContext* context, xml::XmlResource* doc) override; private: bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); ManifestFixerOptions mOptions; }; } // namespace aapt Loading