GradleではProGuardを使ったことがなかったので、やり方をメモ。
注意: この記事はAndroidについてではありません。GradleでProGuardというとAndroid向けの記事ばっかり出てきますが、これは違います。
build.gradle
buildscript {
dependencies {
classpath(
'net.sf.proguard:proguard-gradle:6.0.3'
)
}
}
// 中略...
jar {
manifest {
attributes 'Main-Class': 'rip.deadcode.Main' // 自分のMainクラスに合わせ修正
}
// Fat Jarを作る
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
}
task proguard(type: proguard.gradle.ProGuardTask, dependsOn: jar) {
def javaHome = System.getProperty('java.home')
// Shrink対象のJAR
injars jar.archivePath
libraryjars files(
"${javaHome}/lib/rt.jar", // Java SEランタイム
"${javaHome}/lib/jce.jar" // cryptoモジュール
)
// Fat JARを使わない場合、依存ライブラリーをlibraryjarsに追加する
// libraryjars configurations.compile.files
// 出力先 お好みでどうぞ
outjars("${jar.destinationDir}/proguarded.jar")
// Shrinkしないクラス
keep("public class ${jar.manifest.attributes['Main-Class']} { public static void main(java.lang.String[]); }")
dontwarn("ch.qos.logback.**")
dontwarn('afu.org.checkerframework.**')
dontwarn("org.checkerframework.**")
dontwarn('org.slf4j.**')
}
// assemble実行時にProGuard
assemble.dependsOn(proguard)
ポイント
- buildscriptのクラスパスに
net.sf.proguard:proguard-gradle:6.0.3
を追加する。公式のサンプルではローカルのJARを参照していたが、それはさすがに……。これが公式のJARなのかは調べきれなかったので、不安な方は各自のリポジトリで。 -
proguard.gradle.ProGuardTask
で実行できる。ほとんどの場合シュリンクする対象はプロジェクトのJARなので、ここではjarタスクに依存させている。 -
injars
にjar.archivePath
を指定して、jarタスクが生成したJARを対象にする。 -
libraryjars
にはJDKのJARを追加する必要がある。ここではJAVA_HOME
からJARのパスを取得しているが、ビルド時のJVMとは別のJVMで実行する場合、そちらを参照させる必要がある。 - 今回はライブラリー等も一つのJARにまとめている。依存ライブラリーをビルドするJARに含めない場合、依存ライブラリーのJARも
libraryjars
に指定する必要がある。とはいえライブラリーを作る場合は特にシュリンクの必要はないわけで……。 - 出力先を指定する
outjars
はお好みで。例では入力のJARと同じ個所に出力する。 -
keep
でシュリンクの対象にしないクラスを指定する。たいていの場合main
メソッドが対象になる。そのほかリフレクションで不具合が出る場合なども、ここで指定する。 -
dontwarn
で、クラスがクラスパス上で見つからない場合の警告を抑制する。普通は起こらないかと思いきや、SLF4Jなど、「クラスパスに〇〇がいる場合」の動作があるようなライブラリーは結構あったりする。 -
assemble
タスクに今回作ったタスクを依存させ、ビルド時にProGuardが実行されるようにする。 - ソース
AWS LambdaでProGuard
コールドスタート時の速度改善のためにもできる限り不必要なコードは取り除きたいところですが、いろいろと設定が必要になります。
keep("public class rip.deadcode.bot.Application { *; }") // RequestHandlerを実装しているメソッド
keep("public interface com.amazonaws.services.lambda.runtime.RequestHandler { *; }")
keep("class com.amazonaws.** { *; }")
keep("class com.fasterxml.** { *; }")
keepattributes("Signature,*Annotation*")
-
keep
にいろいろ指定する必要あり-
RequestHandler
を実装しているメソッド。エントリーポイントとなるため必要。 -
RequestHandler
インターフェース自体。名前が変わると正しくラムダ関数として認識されない模様。 -
org.apache.commons.logging
。ハンドラーに渡されるコンテキストがもつロガーとの間でコンフリクトする。 -
com.amazonaws.**
とcom.fasterxml.**
。シリアライズ関係で不具合が起きる。もっとスコープは縮められるとおもうが、デバッグ困難なエラーが多く断念。識者の調査が待たれる。実際このせいでファイルサイズがそこそこ大きくなってしまい悲しい。
-
-
dontwarn
にorg.apache.commons.logging.**
とorg.joda.time.**
を追加。Joda TimeはJava 8に移行してほしいところだが、AWSほどの巨大サービスにもなるとそうもいかないんだろうなぁと。
RequestStreamHandler
なら今回の多くの部分が問題にならないと思われるので、横着せずそちらを使うべきかもしれない。