SBTでクラスパスをカスタマイズする

SBTではMavenと同じように依存関係を定義したライブラリはスコープに応じて自動的にクラスパスを通してくれます。ただし、Mavenリポジトリで提供されていないライブラリを使用する場合や、より詳細にクラスパスの制御を行いたい場合(たとえばコンパイル時にはクラスパスに含めたいけど実行時には含めたくないといった場合)は直接クラスパスをカスタマイズすることもできます。
たとえばScalateはSLF4Jに依存しているため、そのままパッケージングしてしまうとwarファイルのWEB-INF/libにSLF4JのJARファイルが入りますが、JBossにデプロイする場合、JBoss本体で利用しているSLF4Jのバージョンと違うため起動時にエラーログが出力されてしまいます(無視すれば動くことは動きます)。
アプリケーションのコンパイルだけであればSLF4JのJARファイルは不要なので、

override def ivyXML =
  <dependencies>
    <exclude module="slf4j-api"/>
  </dependencies>

こんな感じでSLF4Jをexcludesしてやればよい、と思いきや、ScalateのプリコンパイルをSBTで行う場合、プリコンパイル処理がSLF4Jを必要とします。SBTは内部でいくつかのクラスパスを使い分けているのですが、プリコンパイル処理はrunClasspath、warへのパッケージングにはwebappClasspathが利用されます。そしてwebappClasspathは基本的にはrunClasspathと同じクラスパスになります。
この問題を解決するには以下のようにする必要があります。

  • runClasspathにはSLF4JのJARファイルを含める
  • webappClasspathにはSLF4JのJARファイルは含めない

仕方ないのでプロジェクト内の lib_unmanaged/slf4j-api-1.6.1.jar にSLF4JのJARを配置した上でSBTのプロジェクト定義を以下のようにしました。

val slf4jApi: PathFinder = "lib_unmanaged" / "slf4j-api-1.6.1.jar"
override def runClasspath = super.runClasspath +++ slf4jApi
override def webappClasspath = super.webappClasspath --- slf4jApi

runClasspathにSLF4JのJARファイルを追加した上で、webappClasspathからは削除しています。このJARファイルだけはMavenリポジトリとは別管理になってしまいますが、プリコンパイル自体が特殊な処理なのでまあ仕方ないかなと。
ちなみにPathFinderは

val otherlibs: PathFinder = "lib" ** "*.jar"

みたいな感じでワイルドカードも使えるみたいです。このあたりの自由度の高さとカスタマイズの容易さはSBTのメリットと言えそうです。