OSDN Git Service

AAPT2: Proguard rules generation added.
authorAdam Lesinski <adamlesinski@google.com>
Mon, 8 Jun 2015 18:41:09 +0000 (11:41 -0700)
committerAdam Lesinski <adamlesinski@google.com>
Tue, 9 Jun 2015 18:14:24 +0000 (11:14 -0700)
Change-Id: Ifbe79516cd9a1ade471e211a259301c63b62ac67

tools/aapt2/Android.mk
tools/aapt2/Main.cpp
tools/aapt2/ProguardRules.cpp [new file with mode: 0644]
tools/aapt2/ProguardRules.h [new file with mode: 0644]
tools/aapt2/Source.h
tools/aapt2/Util.cpp
tools/aapt2/Util.h
tools/aapt2/Util_test.cpp
tools/aapt2/data/AndroidManifest.xml
tools/aapt2/data/Makefile
tools/aapt2/data/res/layout/main.xml

index d311cd9..10f8150 100644 (file)
@@ -40,6 +40,7 @@ sources := \
        ManifestParser.cpp \
        ManifestValidator.cpp \
        Png.cpp \
+       ProguardRules.cpp \
        ResChunkPullParser.cpp \
        Resource.cpp \
        ResourceParser.cpp \
index de2dafc..41c229d 100644 (file)
@@ -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 (file)
index 0000000..e89fb7c
--- /dev/null
@@ -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 (file)
index 0000000..bbb3e64
--- /dev/null
@@ -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
index 10c75aa..3606488 100644 (file)
@@ -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
index 7adaf1e..03ecd1a 100644 (file)
@@ -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)++) {
index 6015d82..9cdb152 100644 (file)
@@ -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.
  */
index c16f6bb..0b08d24 100644 (file)
@@ -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
index c017a0d..8533c28 100644 (file)
@@ -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>
index ce5201b..3387135 100644 (file)
@@ -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.
index 77ccedb..50a51d9 100644 (file)
@@ -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"