diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-06-08 11:41:09 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-06-09 11:14:24 -0700 |
commit | a1ad4a812a87642ad259ff4478159e8cc8194680 (patch) | |
tree | eff82221ed22a3be824ddf40823b2db3af002fb1 /tools/aapt2 | |
parent | b5766468538de200d26012d96019db26bccac5d4 (diff) | |
download | frameworks_base-a1ad4a812a87642ad259ff4478159e8cc8194680.zip frameworks_base-a1ad4a812a87642ad259ff4478159e8cc8194680.tar.gz frameworks_base-a1ad4a812a87642ad259ff4478159e8cc8194680.tar.bz2 |
AAPT2: Proguard rules generation added.
Change-Id: Ifbe79516cd9a1ade471e211a259301c63b62ac67
Diffstat (limited to 'tools/aapt2')
-rw-r--r-- | tools/aapt2/Android.mk | 1 | ||||
-rw-r--r-- | tools/aapt2/Main.cpp | 49 | ||||
-rw-r--r-- | tools/aapt2/ProguardRules.cpp | 240 | ||||
-rw-r--r-- | tools/aapt2/ProguardRules.h | 58 | ||||
-rw-r--r-- | tools/aapt2/Source.h | 5 | ||||
-rw-r--r-- | tools/aapt2/Util.cpp | 45 | ||||
-rw-r--r-- | tools/aapt2/Util.h | 17 | ||||
-rw-r--r-- | tools/aapt2/Util_test.cpp | 40 | ||||
-rw-r--r-- | tools/aapt2/data/AndroidManifest.xml | 3 | ||||
-rw-r--r-- | tools/aapt2/data/Makefile | 3 | ||||
-rw-r--r-- | tools/aapt2/data/res/layout/main.xml | 3 |
11 files changed, 458 insertions, 6 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index d311cd9..10f8150 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -40,6 +40,7 @@ sources := \ ManifestParser.cpp \ ManifestValidator.cpp \ Png.cpp \ + ProguardRules.cpp \ ResChunkPullParser.cpp \ Resource.cpp \ ResourceParser.cpp \ diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index de2dafc..41c229d 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -28,6 +28,7 @@ #include "ManifestValidator.h" #include "NameMangler.h" #include "Png.h" +#include "ProguardRules.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceTableResolver.h" @@ -300,6 +301,9 @@ struct AaptOptions { // Directory to in which to generate R.java. Maybe<Source> generateJavaClass; + // File in which to produce proguard rules. + Maybe<Source> generateProguardRules; + // Whether to output verbose details about // compilation. bool verbose = false; @@ -417,7 +421,8 @@ bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, const std::shared_ptr<IResolver>& resolver, const LinkItem& item, - const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) { + const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue, + proguard::KeepSet* keepSet) { SourceLogger logger(item.source); std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger); if (!root) { @@ -435,6 +440,10 @@ bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; } + if (options.generateProguardRules) { + proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet); + } + BigBuffer outBuffer(1024); Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(), item.originalPackage, resolver, @@ -509,7 +518,7 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks, - const android::ResTable& table, ZipFile* outApk) { + const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) { if (options.verbose) { Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; } @@ -557,6 +566,11 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver } } + if (options.generateProguardRules) { + proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(), + keepSet); + } + BigBuffer outBuffer(1024); if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package, resolver, {}, &outBuffer)) { @@ -805,8 +819,10 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT return false; } + proguard::KeepSet keepSet; + android::ResTable binTable; - if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) { + if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) { return false; } @@ -826,7 +842,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT assert(uncompressedData); if (!linkXml(options, outTable, resolver, item, uncompressedData, - entry->getUncompressedLen(), &outApk, &linkQueue)) { + entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) { Logger::error(options.output) << "failed to link '" << item.originalPath << "'." << std::endl; return false; @@ -883,6 +899,26 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT } } + // Generate the Proguard rules file. + if (options.generateProguardRules) { + const Source& outPath = options.generateProguardRules.value(); + + if (options.verbose) { + Logger::note(outPath) << "writing proguard rules." << std::endl; + } + + std::ofstream fout(outPath.path); + if (!fout) { + Logger::error(outPath) << strerror(errno) << std::endl; + return false; + } + + if (!proguard::writeKeepSet(&fout, keepSet)) { + Logger::error(outPath) << "failed to write proguard rules." << std::endl; + return false; + } + } + outTable->getValueStringPool().prune(); outTable->getValueStringPool().sort( [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { @@ -1072,6 +1108,11 @@ static AaptOptions prepareArgs(int argc, char** argv) { options.generateJavaClass = Source{ arg.toString() }; }); + flag::optionalFlag("--proguard", "file in which to output proguard rules", + [&options](const StringPiece& arg) { + options.generateProguardRules = Source{ arg.toString() }; + }); + flag::optionalSwitch("--static-lib", "generate a static Android library", true, &isStaticLib); diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp new file mode 100644 index 0000000..e89fb7c --- /dev/null +++ b/tools/aapt2/ProguardRules.cpp @@ -0,0 +1,240 @@ +/* + * 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 "ProguardRules.h" +#include "Util.h" +#include "XmlDom.h" + +#include <memory> +#include <string> + +namespace aapt { +namespace proguard { + +constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; + +class BaseVisitor : public xml::Visitor { +public: + BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) { + } + + virtual void visit(xml::Text*) override {}; + + virtual void visit(xml::Namespace* node) override { + for (const auto& child : node->children) { + child->accept(this); + } + } + + virtual void visit(xml::Element* node) override { + if (!node->namespaceUri.empty()) { + Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace( + node->namespaceUri); + if (maybePackage) { + // This is a custom view, let's figure out the class name from this. + std::u16string package = maybePackage.value() + u"." + node->name; + if (util::isJavaClassName(package)) { + addClass(node->lineNumber, package); + } + } + } else if (util::isJavaClassName(node->name)) { + addClass(node->lineNumber, node->name); + } + + for (const auto& child: node->children) { + child->accept(this); + } + } + +protected: + void addClass(size_t lineNumber, const std::u16string& className) { + mKeepSet->addClass(mSource.line(lineNumber), className); + } + + void addMethod(size_t lineNumber, const std::u16string& methodName) { + mKeepSet->addMethod(mSource.line(lineNumber), methodName); + } + +private: + Source mSource; + KeepSet* mKeepSet; +}; + +struct LayoutVisitor : public BaseVisitor { + LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { + } + + virtual void visit(xml::Element* node) override { + bool checkClass = false; + bool checkName = false; + if (node->namespaceUri.empty()) { + checkClass = node->name == u"view" || node->name == u"fragment"; + } else if (node->namespaceUri == kSchemaAndroid) { + checkName = node->name == u"fragment"; + } + + for (const auto& attr : node->attributes) { + if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" && + util::isJavaClassName(attr.value)) { + addClass(node->lineNumber, attr.value); + } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" && + util::isJavaClassName(attr.value)) { + addClass(node->lineNumber, attr.value); + } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") { + addMethod(node->lineNumber, attr.value); + } + } + + BaseVisitor::visit(node); + } +}; + +struct XmlResourceVisitor : public BaseVisitor { + XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { + } + + virtual void visit(xml::Element* node) override { + bool checkFragment = false; + if (node->namespaceUri.empty()) { + checkFragment = node->name == u"PreferenceScreen" || node->name == u"header"; + } + + if (checkFragment) { + xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment"); + if (attr && util::isJavaClassName(attr->value)) { + addClass(node->lineNumber, attr->value); + } + } + + BaseVisitor::visit(node); + } +}; + +struct TransitionVisitor : public BaseVisitor { + TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { + } + + virtual void visit(xml::Element* node) override { + bool checkClass = node->namespaceUri.empty() && + (node->name == u"transition" || node->name == u"pathMotion"); + if (checkClass) { + xml::Attribute* attr = node->findAttribute({}, u"class"); + if (attr && util::isJavaClassName(attr->value)) { + addClass(node->lineNumber, attr->value); + } + } + + BaseVisitor::visit(node); + } +}; + +struct ManifestVisitor : public BaseVisitor { + ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) { + } + + virtual void visit(xml::Element* node) override { + if (node->namespaceUri.empty()) { + bool getName = false; + if (node->name == u"manifest") { + xml::Attribute* attr = node->findAttribute({}, u"package"); + if (attr) { + mPackage = attr->value; + } + } else if (node->name == u"application") { + getName = true; + xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent"); + if (attr) { + Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage, + attr->value); + if (result) { + addClass(node->lineNumber, result.value()); + } + } + } else if (node->name == u"activity" || node->name == u"service" || + node->name == u"receiver" || node->name == u"provider" || + node->name == u"instrumentation") { + getName = true; + } + + if (getName) { + xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name"); + if (attr) { + Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage, + attr->value); + if (result) { + addClass(node->lineNumber, result.value()); + } + } + } + } + BaseVisitor::visit(node); + } + + std::u16string mPackage; +}; + +bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) { + ManifestVisitor visitor(source, keepSet); + node->accept(&visitor); + return true; +} + +bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node, + KeepSet* keepSet) { + switch (type) { + case ResourceType::kLayout: { + LayoutVisitor visitor(source, keepSet); + node->accept(&visitor); + break; + } + + case ResourceType::kXml: { + XmlResourceVisitor visitor(source, keepSet); + node->accept(&visitor); + break; + } + + case ResourceType::kTransition: { + TransitionVisitor visitor(source, keepSet); + node->accept(&visitor); + break; + } + + default: + break; + } + return true; +} + +bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) { + for (const auto& entry : keepSet.mKeepSet) { + for (const SourceLine& source : entry.second) { + *out << "// Referenced at " << source << "\n"; + } + *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; + } + + for (const auto& entry : keepSet.mKeepMethodSet) { + for (const SourceLine& source : entry.second) { + *out << "// Referenced at " << source << "\n"; + } + *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; + } + return true; +} + +} // namespace proguard +} // namespace aapt diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h new file mode 100644 index 0000000..bbb3e64 --- /dev/null +++ b/tools/aapt2/ProguardRules.h @@ -0,0 +1,58 @@ +/* + * 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_PROGUARD_RULES_H +#define AAPT_PROGUARD_RULES_H + +#include "Resource.h" +#include "Source.h" +#include "XmlDom.h" + +#include <map> +#include <ostream> +#include <set> +#include <string> + +namespace aapt { +namespace proguard { + +class KeepSet { +public: + inline void addClass(const SourceLine& source, const std::u16string& className) { + mKeepSet[className].insert(source); + } + + inline void addMethod(const SourceLine& source, const std::u16string& methodName) { + mKeepMethodSet[methodName].insert(source); + } + +private: + friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); + + std::map<std::u16string, std::set<SourceLine>> mKeepSet; + std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet; +}; + +bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet); +bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node, + KeepSet* keepSet); + +bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); + +} // namespace proguard +} // namespace aapt + +#endif // AAPT_PROGUARD_RULES_H diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 10c75aa..3606488 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -19,6 +19,7 @@ #include <ostream> #include <string> +#include <tuple> namespace aapt { @@ -80,6 +81,10 @@ inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& s return out << source.path << ":" << source.line << ":" << source.column; } +inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) { + return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line); +} + } // namespace aapt #endif // AAPT_SOURCE_H diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp index 7adaf1e..03ecd1a 100644 --- a/tools/aapt2/Util.cpp +++ b/tools/aapt2/Util.cpp @@ -102,6 +102,51 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16 return endIter; } +bool isJavaClassName(const StringPiece16& str) { + size_t pieces = 0; + for (const StringPiece16& piece : tokenize(str, u'.')) { + pieces++; + if (piece.empty()) { + return false; + } + + // Can't have starting or trailing $ character. + if (piece.data()[0] == u'$' || piece.data()[piece.size() - 1] == u'$') { + return false; + } + + if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) { + return false; + } + } + return pieces >= 2; +} + +Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, + const StringPiece16& className) { + if (className.empty()) { + return {}; + } + + if (util::isJavaClassName(className)) { + return className.toString(); + } + + if (package.empty()) { + return {}; + } + + std::u16string result(package.data(), package.size()); + if (className.data()[0] != u'.') { + result += u'.'; + } + result.append(className.data(), className.size()); + if (!isJavaClassName(result)) { + return {}; + } + return result; +} + static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) { char16_t code = 0; for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) { diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h index 6015d82..9cdb152 100644 --- a/tools/aapt2/Util.h +++ b/tools/aapt2/Util.h @@ -78,6 +78,23 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16 const StringPiece16& allowedChars); /** + * Tests that the string is a valid Java class name. + */ +bool isJavaClassName(const StringPiece16& str); + +/** + * Converts the class name to a fully qualified class name from the given `package`. Ex: + * + * asdf --> package.asdf + * .asdf --> package.asdf + * .a.b --> package.a.b + * asdf.adsf --> asdf.adsf + */ +Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, + const StringPiece16& className); + + +/** * Makes a std::unique_ptr<> with the template parameter inferred by the compiler. * This will be present in C++14 and can be removed then. */ diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp index c16f6bb..0b08d24 100644 --- a/tools/aapt2/Util_test.cpp +++ b/tools/aapt2/Util_test.cpp @@ -93,4 +93,44 @@ TEST(UtilTest, TokenizeInput) { ASSERT_EQ(tokenizer.end(), iter); } +TEST(UtilTest, IsJavaClassName) { + EXPECT_TRUE(util::isJavaClassName(u"android.test.Class")); + EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner")); + EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class")); + EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_")); + EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner")); + EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$")); + EXPECT_FALSE(util::isJavaClassName(u".test.Class")); + EXPECT_FALSE(util::isJavaClassName(u"android")); +} + +TEST(UtilTest, FullyQualifiedClassName) { + Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf"); + ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"android.asdf"); + + res = util::getFullyQualifiedClassName(u"android", u".asdf"); + ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"android.asdf"); + + res = util::getFullyQualifiedClassName(u"android", u".a.b"); + ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"android.a.b"); + + res = util::getFullyQualifiedClassName(u"android", u"a.b"); + ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"a.b"); + + res = util::getFullyQualifiedClassName(u"", u"a.b"); + ASSERT_TRUE(res); + EXPECT_EQ(res.value(), u"a.b"); + + res = util::getFullyQualifiedClassName(u"", u""); + ASSERT_FALSE(res); + + res = util::getFullyQualifiedClassName(u"android", u"./Apple"); + ASSERT_FALSE(res); +} + + } // namespace aapt diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml index c017a0d..8533c28 100644 --- a/tools/aapt2/data/AndroidManifest.xml +++ b/tools/aapt2/data/AndroidManifest.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app"> - <application> + <application + android:name=".Activity"> </application> </manifest> diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile index ce5201b..3387135 100644 --- a/tools/aapt2/data/Makefile +++ b/tools/aapt2/data/Makefile @@ -15,6 +15,7 @@ LOCAL_RESOURCE_DIR := res LOCAL_LIBS := lib/out/package.apk LOCAL_OUT := out LOCAL_GEN := out/gen +LOCAL_PROGUARD := out/proguard.rule ## # AAPT2 custom rules. @@ -57,7 +58,7 @@ $(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) # Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk $(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml - $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) + $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v # R.java: gen/com/android/app/R.java <- out/resources.arsc # No action since R.java is generated when out/resources.arsc is. diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml index 77ccedb..50a51d9 100644 --- a/tools/aapt2/data/res/layout/main.xml +++ b/tools/aapt2/data/res/layout/main.xml @@ -5,11 +5,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + <fragment class="android.test.sample.App$Inner" /> + <variable name="user" type="com.android.User" /> <View xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/me" android:layout_width="1dp" + android:onClick="doClick" android:text="@{user.name}" android:layout_height="match_parent" app:layout_width="@support:bool/allow" |