Gradle実践シリーズその5です〜
Gradleの依存管理にはprovidedCompileという属性があります。
これは、コンパイル時にはクラスパスに含めるが、パッケージ(war・jar等)には含めない、という機能です。
しかしこのprovidedCompile、どうもサブプロジェクト構成で挙動が怪しいのです。
Gradleの環境は以下の通りです。
------------------------------------------------------------ Gradle 1.4 ------------------------------------------------------------ Gradle build time: 2013年1月28日 3時42分46秒 UTC Groovy: 1.8.6 Ant: Apache Ant(TM) version 1.8.4 compiled on May 22 2012 Ivy: 2.2.0 JVM: 1.7.0_21 (Oracle Corporation 23.21-b01) OS: Mac OS X 10.8.4 x86_64
hoge-common
├ hoge-search
└ hoge-web
こんな感じでcommonを共通の親プロジェクトとし、searchとwebが小プロジェクトになります。
この時、commonとwebの両者でservlet-api.jarに依存したコードがあるとします。
すると、commonとwebの両方のクラスパスにservlet-api.jarが無いといけません。
クラスパスが通っていないと、コンパイルに失敗してしまいます。
単純にcommonとwebの両方にservlet-api.jarを以下のように追加してしまうと、クラスパス上にservlet-apiが複数存在する事になり、tomcat起動時にClassCastExceptionが発生してしまいます。
project(':hoge-common') { dependencies { providedCompile group: 'org.apache.tomcat', name: 'tomcat-servlet-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-el-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jasper', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jasper-el', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jsp-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-juli', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-util', version: '7.0.27' } } project(':hoge-web') { dependencies { compile project(':hoge-common') providedCompile group: 'org.apache.tomcat', name: 'tomcat-servlet-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-el-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jasper', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jasper-el', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jsp-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-juli', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-util', version: '7.0.27' } } project(':hoge-search') { dependencies { compile project(':hoge-common') } }
こうするとClassCastExceptionが発生します。
親クラスはjar化されてwebとsearchに自動的にクラスパスに配備されるので、親クラスだけにservlet-api.jarがあればいいのでは?という事でやってみます。
project(':hoge-common') { dependencies { providedCompile group: 'org.apache.tomcat', name: 'tomcat-servlet-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-el-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jasper', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jasper-el', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-jsp-api', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-juli', version: '7.0.27' providedCompile group: 'org.apache.tomcat', name: 'tomcat-util', version: '7.0.27' } } project(':hoge-web') { dependencies { compile project(':hoge-common') } } project(':hoge-search') { dependencies { compile project(':hoge-common') } }
これでコンパイルエラーは出ず、tomcat起動時にClassCastExceptionは発生しません。
これで解決ですね!!!
・・・・
実は全然解決してないのです
何が問題なのかというと、war内にservlet-api.jarが入っているのです。
別の環境でGradle1.6で試しましたが、結果は同じでした。
これではtomcatが起動できません。
localhost:lib tree$ ll ・・・中略・・・ -rw-r--r-- 1 tree staff 6.7K 5 31 01:56 tomcat-api-7.0.27.jar -rw-r--r-- 1 tree staff 45K 5 31 01:56 tomcat-el-api-7.0.27.jar -rw-r--r-- 1 tree staff 577K 5 31 01:56 tomcat-jasper-7.0.27.jar -rw-r--r-- 1 tree staff 120K 5 31 01:56 tomcat-jasper-el-7.0.27.jar -rw-r--r-- 1 tree staff 87K 5 31 01:56 tomcat-jsp-api-7.0.27.jar -rw-r--r-- 1 tree staff 37K 5 31 01:56 tomcat-juli-7.0.27.jar -rw-r--r-- 1 tree staff 173K 5 31 01:56 tomcat-servlet-api-7.0.27.jar -rw-r--r-- 1 tree staff 23K 5 31 01:56 tomcat-util-7.0.27.jar -rw-r--r-- 1 tree staff 34K 6 19 00:48 hoge-base-1.0-SNAPSHOT.jar
providedCompileの仕様を考えると、war内にservlet-api.jarが残るはおかしいのでは?と思います。
このままでは困るので、webのwarから特定のjarを除外する方法を見つけました。
具体的には以下の通りです。
def tomcatJars = [ 'tomcat-servlet-api-' + tomcatVersion + '.jar', 'tomcat-api-' + tomcatVersion + '.jar', 'tomcat-el-api-' + tomcatVersion + '.jar', 'tomcat-jasper-el-' + tomcatVersion + '.jar', 'tomcat-jsp-api-' + tomcatVersion + '.jar', 'tomcat-juli-' + tomcatVersion + '.jar', 'tomcat-util-' + tomcatVersion + '.jar' ] war { def now = new Date().getTime().toString() // jspのtoken置換 from(webAppDir) { include '**/*.jsp' filter(ReplaceTokens, tokens:['TIMESTAMP': now]) } classpath = classpath.filter { File file -> !tomcatJars.contains(file.name) } }
classpathの部分です。
こうすると、配列内のjar以外を対象にクラスパスに追加する事ができます。
さて、再度warタスクを実行し、warを解凍してlibを確認します。
localhost:lib tree$ ll ・・・中略・・・ -rw-r--r-- 1 tree staff 34K 6 19 00:55 hoge-base-1.0-SNAPSHOT.jar
tomcatのjarは全て消えました。これで目標達成です。
具体的に以下の条件を満たすことができています。
- 親・子プロジェクトが共にservlet-api.jarに依存している
- tomcat起動時にClassCastExceptionが発生しないこと
- warタスク実行時のコンパイルエラーが発生しないこと
- war内のlibにtomcatのjarが混入しないこと
話は変わりますが、このprovidedCompileの挙動、なんか変なのです。
例えば以下の構成でwarタスクを実行するとどうなるでしょう。
project(':hoge-common') { } project(':hoge-web') { dependencies { providedCompile project(':hoge-common') } } project(':hoge-search') { dependencies { providedCompile project(':hoge-common') } }
hoge-commonをprovidecCompileで依存させています。
実はこの状態だと、webのwarからjarが全て消えるのです。
この挙動を見ると、やはりサブプロジェクト下のprovidedCompileはちょっとおかしいのでは?という事が予想できます。
javaのビルドをgradleで行う:調査編1:maven・SBTとの違い - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:調査編2:依存から1行で特定jarを除外! - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:調査編3:seasar系の依存の注意点 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:調査編4:Groovy Librariesの罠 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編1:jsとcssのcombineとminify - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編2:静的ファイルの圧縮 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編3:jspの文字列置換(ReplaceTokens) - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編4:あれどうやるの?の疑問と自己解決 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編5:マルチプロジェクト時のprovidedCompileについて - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編6:高速に実用的な実行可能jarを生成する - 文系プログラマによるTIPSブログ