読者です 読者をやめる 読者になる 読者になる

ねこ元帥の中の人@リウマチ

日々の覚書など。ブログ名はオンラインゲームのアカウント名をいつも「ねこ元帥」にしているから。そして、2013年11月リウマチと診断されたとです。(2012年11月から時々手足に原因不明の痛みが発生、2013年9月以降は足の痛みが常態化、2013年12月30日からMTX服用開始、4月過ぎるころにはわずかの痛みが残る程度まで回復して現在に至る。)

sbteclipseプラグイン使用メモ

  • サブプロジェクトのeclipse設定ファイルだけを作成する。
  • .classpathのクラスパスエントリに記述されるパスを、eclipse変数指定に書き換える。

プロジェクト構成

プラグインを使用したプロジェクトの構成は以下の通り。

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プラグインの読み込み

参考:sbteclipseプラグインのgitリポジトリ

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関数を使用する。

参考: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
  }
}



参考:sbtプロジェクトテンプレート(常に未完)