sbteclipseプラグイン使用メモ
プロジェクト構成
プラグインを使用したプロジェクトの構成は以下の通り。
template-scala-sbt/ ├─lib/ 自動管理しない外部ライブラリ ├─lib_managed/ update タスクの出力 ├─app-main/ サブプロジェクト ├─app-data/ サブプロジェクト ├─project/ │ ├─build.properties sbt設定 │ ├─plugins.sbt プラグイン設定 │ ├─Build.scala ビルド設定 │ └─MyEclipse.scala sbteclipseプラグインのカスタマイズ ├─sbt/ │ └─sbt-launch.jar ├─sbtl sbt起動スクリプト(Unix) └─sbtl.bat sbt起動スクリプト(Windows)
sbteclipseプラグインの読み込み
plugins.sbt に以下の設定を記述する。
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0")
参考サイトのREADMEの説明ではバージョンが2.3.0になっているが、mavenリポジトリには配布されていないようなので2.2.0にした。
最新版を使用する場合は以下の操作を行えばよい。
※コミットログを見ると7/28に2.3.0をリリースしたようだがtagがついていない。
git clone https://github.com/typesafehub/sbteclipse.git cd sbteclipse git co d0ebc2a sbt publish-local
plugins.sbt に以下の設定を記述する。
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.3.0")
sbteclipseプラグインの設定読み込み
Build.scala に以下の設定を記述する。
import sbt._ import Keys._ object AppBuild extends Build { // SETTING: プロジェクト共通設定 import com.typesafe.sbt.SbtSite.site lazy val buildSettings = Seq( ≪省略≫ ) // SETTING: サブプロジェクト共通設定 def subProject(nameString: String, path: File) = Project( id = nameString, base = path, // 別ファイルで定義したsbteclipseプラグインの設定を読み込む // ※数行程度であれば本ファイル内に記述するが、一画面に収まらない // 量になったので、sbteclipseフラグイン用の設定を分割した。 settings = Defaults.defaultSettings ++ buildSettings ++ MyEclipse.eclipseSettings) .settings( // libに置いたjarファイルをサブプロジェクトのクラスパスに追加する // ※サブプロジェクトで共通に使用するライブラリを置く unmanagedBase <<= unmanagedBase in root, // lib_managedに依存ライブラリをコピーする // ※プラグインが出力する.classpathのjarファイルの指定先となる。 // falseにした場合、.classpathのjarファイルのパスはivyキャッシュ内の // jarファイルを指定する。 retrieveManaged := true, libraryDependencies ++= Seq( ≪省略≫ // PROJECT: ルートプロジェクト設定 ≪省略≫ // PROJECT: サブプロジェクト設定 lazy val appMain = subProject("app-main", file("app-main")) dependsOn (appData) lazy val appData = subProject("app-data", file("app-data")) }
sbteclipseプラグインの設定
MyEclipse.scala ファイルに以下の設定を記述する。
import sbt._ import Keys._ object MyEclipse { // Eclipseクラスパス変数 private val eclipseClasspathVar = "PROJECT_HOME" // PLUGIN: sbteclipse設定 import com.typesafe.sbteclipse.plugin.EclipsePlugin._ import EclipseKeys._ lazy val eclipseSettings = Seq( executionEnvironment := Some(EclipseExecutionEnvironment.JavaSE17), // サブプロジェクトのみを対象とする skipParents in ThisBuild := true, // .projectファイルを更新する skipProject := false, // .projectファイルの書き換え // ※書き換えは行わないので「Seq()」を代入する定義でもよい projectTransformerFactories := Seq(ProjectTransformerFactory), projectFlavor := EclipseProjectFlavor.Scala, classpathTransformerFactories := Seq(ClasspathEntryTransformerFactory), // ビルドパスにリソースパスを含める createSrc := EclipseCreateSrc.Default + EclipseCreateSrc.Resource, // ライブラリにソースを含める withSource := true, // ライブラリパスを絶対パスで指定する relativizeLibs := false )
.classpath ファイルのクラスパスエントリ書き換えは、RewriteRule#transform()で処理するが、設定ではRewriteRuleオブジェクトのファクトリを引き渡すようになっているため、ファクトリをobjectとして定義する。
RewriteRuleのサブクラスにディレクトリ情報を渡すため、setting関数を使用する。
import scala.xml.{ Attribute, Elem, MetaData, Node, Null, Text } import scala.xml.transform.RewriteRule import com.typesafe.sbteclipse.core.{ Validation, setting } /** * プラグインが生成する.classpathファイルをカスタマイズするための設定 */ private object ClasspathEntryTransformerFactory extends EclipseTransformerFactory[RewriteRule] { override def createTransformer(ref: ProjectRef, state: State): Validation[RewriteRule] = setting(Keys.baseDirectory in ThisBuild, state) map (baseDir => new ClasspathEntryRewriteRule(baseDir) ) }
RewriteRuleのサブクラスの定義は以下の通り。
xmlのノードオブジェクトを引数としてtransformメソッドが呼ばれる仕組みになっており、戻り値を加工することで追加/変更/削除を行う。
private class ClasspathEntryRewriteRule(baseDir: File) extends RewriteRule { // ビルドツリーのトップディレクトリ private val buildDir = baseDir.getAbsolutePath private val CpEntry = "classpathentry" /** * <classpathentry /> ノードを書き換える */ override def transform(node: Node): Seq[Node] = node match { case Elem(prefix, CpEntry, attributes, scope, child @ _*) if containsBuildDir(attributes) => Elem(prefix, CpEntry, newAttributes(attributes), scope, child: _*) case other => other } /** * ライブラリパスがビルドディレクトリを含むか判定 */ private def containsBuildDir(attributes: MetaData) = attributes("kind") == Text("lib") && (Option(attributes("path").text) map (_ contains buildDir) getOrElse false) /** * ビルドディレクトリをEclipseクラスパス変数に変換 */ private def newAttributes(attributes: MetaData): MetaData = attributes match { case Attribute("kind", Text("lib"), next) => Attribute("kind", Text("var"), newAttributes(next)) case Attribute(key, Text(value), next) => val newValue = value.replace(buildDir, eclipseClasspathVar) Attribute(key, Text(newValue), newAttributes(next)) case other => other } }
.projectファイルは書き換えないので、transformメソッドの処理は引数で受け取ったノードをそのまま返すようにしている。
/** * プラグインが生成する.projectファイルをカスタマイズするための設定 */ private object ProjectTransformerFactory extends EclipseTransformerFactory[RewriteRule] { override def createTransformer(ref: ProjectRef, state: State): Validation[RewriteRule] = setting(Keys.baseDirectory in ThisBuild, state) map (baseDir => new ProjectRewriteRule(baseDir) ) } private class ProjectRewriteRule(baseDir: File) extends RewriteRule { // 何もしない override def transform(node: Node): Seq[Node] = node } }