CLOVER🍀

That was when it all began.

pom.xmlから、Ensimeの設定ファイルを作る

このところ、Spring Bootなどで遊んでいると、Scalaを使っているにも関わらずMavenを使うような事態になります。
※極めて、個人の趣向です

で、自分はScalaコードを書く時はEmacs+Ensime+sbtなのですが、Mavenにしてしまうとこの組み合わせが使えません。

これはどうしたものかと調べたところ、下記のようなものが見つかりました。

Maven support dropped?
https://groups.google.com/forum/#!topic/ensime/uxzWOj_T964

ここに載っているPythonのコードを動かせばいいんですね!

と思ったら、出力される設定ファイルの形が全然違っていて、今では動かないようです…。これは困りました(普通、困らない)。

ところで、よくよく見ると、このPythonコードはmaven-dependency-pluginの結果を使っているだけでみたいです。だったら、同じようなことをすればいいんじゃない?ということで、書いてみました。

Perlで。

書いたスクリプトは、こちら。

#!/usr/bin/perl

use strict;
use warnings;

my $sbt_build_file = '___gen_ensime.sbt';

my $scala_version = '2.11.6';

if (@ARGV ge 1) {
    $scala_version = $ARGV[0];
}

my @maven_dependency_tree = split /\r?\n/, `mvn dependency:tree`;

my $line_index = 0;

for (my $i  = 0; $i < @maven_dependency_tree; $i++) {
    if ($maven_dependency_tree[$i] =~ /^\[INFO\] --- maven-dependency-plugin:([^ ]+):tree .+/) {
        $line_index = $i + 1;
        last;
    }
}

my $project_group;
my $project_name;
my $project_version;

if ($maven_dependency_tree[$line_index] =~ /^\[INFO\] ([^:]+):([^:]+):[^:]+:([^:]+)$/) {
    ($project_group, $project_name, $project_version) = ($1, $2, $3);
}

$line_index++;

my @dependencies = ();

for (my $i = $line_index; $i < @maven_dependency_tree; $i++) {
    if ($maven_dependency_tree[$i] =~ /^\[INFO\] -+/) {
        last;
    }

    if ($maven_dependency_tree[$i] =~ /^\[INFO\] [+\\]- ([^:]+):([^:]+):[^:]+:([^:]+):([^:]+)/) {
        my ($group, $name, $version, $scope) = ($1, $2, $3, $4);
        push(@dependencies, "\"$group\" % \"$name\" % \"$version\" % \"$scope\"");
    }
}

open my $fh, '>', $sbt_build_file or die "Can't open file:$!";

my $library_dependencies_flat = join(',', @dependencies);

print $fh <<BUILD_FILE;
name := "$project_name"

organization := "$project_group"

scalaVersion := "$scala_version"

updateOptions := updateOptions.value.withCachedResolution(true)

scalacOptions ++= Seq("-Xlint", "-unchecked", "-deprecation", "-feature")

libraryDependencies ++= Seq(
    $library_dependencies_flat
)
BUILD_FILE

close $fh;

system('sbt gen-ensime');

unlink $sbt_build_file;

これを、gen_ensime.plという名前にでもして保存します。

やっていることは単純で、

  • 「mvn dependency:tree」の結果から、簡単なsbtの設定ファイルを作る(名前は「___gen_ensime.sbt」)
  • 作ったsbtの設定ファイルを使って、「sbt gen-ensime」する
  • 作ったsbtの設定ファイルを削除する

という感じで、自分でEnsimeの設定ファイルを書くのではなくEnsime自身に作ってもらうことにしました。

ちなみに、Scalaのバージョンだけは引数で指定できる謎仕様。

よって、このPerlスクリプトを動かすための前提は以下になります。

  • Mavenがインストールされていること(mvnコマンドが実行できること)
  • sbtがインストールされていること(sbtコマンドが実行できること)
  • Ensimeのsbtプラグインが、グローバルに適用されていること(いきなり「sbt gen-ensime」ができること)
  • Mavenプロジェクトで構成されていること
  • sbtのプロジェクトではないこと

では、試してみましょう。

こんなpom.xmlを用意します。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.littlewings</groupId>
  <artifactId>gen-ensime</artifactId>
  <packaging>jar</packaging>
  <version>0.0.1-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search-orm</artifactId>
      <version>5.1.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.3.8.Final</version>
    </dependency>

    <dependency>
      <groupId>net.databinder.dispatch</groupId>
      <artifactId>dispatch-jsoup_2.11</artifactId>
      <version>0.11.2</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>2.11.6</version>
    </dependency>

    <dependency>
      <groupId>org.scalatest</groupId>
      <artifactId>scalatest_2.11</artifactId>
      <version>2.2.4</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>1.2.2.RELEASE</version>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <version>3.2.0</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scalaVersion>2.11.6</scalaVersion>
          <args>
            <arg>-Xlint</arg>
            <arg>-unchecked</arg>
            <arg>-deprecation</arg>
            <arg>-feature</arg>
          </args>
          <recompileMode>incremental</recompileMode>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

依存関係に、特に意味はありません。

ここで、先ほどのスクリプトを実行。

$ perl gen_ensime.pl

すると、このような.ensimeファイルが生成されます。

(
 :root-dir "/xxxxx/gen-ensime"
 :cache-dir "/xxxxx/gen-ensime/.ensime_cache"
 :name "gen-ensime"
 :java-home "/usr/lib/jvm/java-8-oracle"
 :java-flags ("-Xms512M" "-Xmx1536M" "-Xss1M" "-XX:+CMSClassUnloadingEnabled" "-XX:MaxMetaspaceSize=384M")
 :reference-source-roots ("/usr/lib/jvm/java-8-oracle/src.zip")
 :scala-version "2.11.6"
 :compiler-args ("-Xlint" "-unchecked" "-deprecation" "-feature")
 
 :subprojects ((
   :name "gen-ensime"
   :module-name "gen-ensime"
   :source-roots ("/xxxxx/gen-ensime/src/main/scala" "/xxxxx/gen-ensime/src/main/java" "/xxxxx/gen-ensime/src/test/scala" "/xxxxx/gen-ensime/src/test/java")
   :target "/xxxxx/gen-ensime/target/scala-2.11/classes"
   :test-targets ("/xxxxx/gen-ensime/target/scala-2.11/test-classes")
   :depends-on-modules nil
   :compile-deps ("/xxxxx/.ivy2/cache/org.hibernate.common/hibernate-commons-annotations/jars/hibernate-commons-annotations-4.0.5.Final.jar" "/xxxxx/.ivy2/cache/org.apache.lucene/lucene-analyzers-common/jars/lucene-analyzers-common-4.10.4.jar" "/xxxxx/.ivy2/cache/org.hibernate/hibernate-core/jars/hibernate-core-4.3.8.Final.jar" "/xxxxx/.ivy2/cache/xml-apis/xml-apis/jars/xml-apis-1.0.b2.jar" "/xxxxx/.ivy2/cache/org.hibernate/hibernate-search-engine/jars/hibernate-search-engine-5.1.0.Final.jar" "/xxxxx/.ivy2/cache/org.javassist/javassist/bundles/javassist-3.18.1-GA.jar" "/xxxxx/.ivy2/cache/org.jboss/jandex/jars/jandex-1.1.0.Final.jar" "/xxxxx/.ivy2/cache/org.apache.lucene/lucene-facet/jars/lucene-facet-4.10.4.jar" "/xxxxx/.ivy2/cache/org.hibernate.javax.persistence/hibernate-jpa-2.1-api/jars/hibernate-jpa-2.1-api-1.0.0.Final.jar" "/xxxxx/.ivy2/cache/org.apache.lucene/lucene-queries/jars/lucene-queries-4.10.4.jar" "/xxxxx/.ivy2/cache/org.jboss.spec.javax.transaction/jboss-transaction-api_1.2_spec/jars/jboss-transaction-api_1.2_spec-1.0.0.Final.jar" "/xxxxx/.ivy2/cache/dom4j/dom4j/jars/dom4j-1.6.1.jar" "/xxxxx/.ivy2/cache/org.hibernate/hibernate-entitymanager/jars/hibernate-entitymanager-4.3.8.Final.jar" "/xxxxx/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.6.jar" "/xxxxx/.ivy2/cache/antlr/antlr/jars/antlr-2.7.7.jar" "/xxxxx/.ivy2/cache/org.jboss.logging/jboss-logging/jars/jboss-logging-3.1.4.GA.jar" "/xxxxx/.ivy2/cache/org.apache.lucene/lucene-core/jars/lucene-core-4.10.4.jar" "/xxxxx/.ivy2/cache/org.hibernate/hibernate-search-orm/jars/hibernate-search-orm-5.1.0.Final.jar" "/xxxxx/.ivy2/cache/org.jboss.logging/jboss-logging-annotations/jars/jboss-logging-annotations-1.2.0.Beta1.jar")
   :runtime-deps nil
   :test-deps ("/xxxxx/.ivy2/cache/org.scala-lang.modules/scala-xml_2.11/bundles/scala-xml_2.11-1.0.2.jar" "/xxxxx/.ivy2/cache/junit/junit/jars/junit-4.12.jar" "/xxxxx/.ivy2/cache/org.hamcrest/hamcrest-core/jars/hamcrest-core-1.3.jar" "/xxxxx/.ivy2/cache/org.scalatest/scalatest_2.11/bundles/scalatest_2.11-2.2.4.jar" "/xxxxx/.ivy2/cache/org.scala-lang/scala-reflect/jars/scala-reflect-2.11.2.jar")
   :doc-jars ("/xxxxx/gen-ensime/target/scala-2.11/gen-ensime_2.11-0.1-SNAPSHOT-javadoc.jar")
   :reference-source-roots nil))
 
)

よーく見ると、「provided」に指定したDispatchの依存関係がありません…。別にsbtの設定ファイルを作って確認してみましたが、providedは対象にされないみたい…?まあ、いいか…。

なお、スクリプト実行後に削除されますが、一時的に生成されるsbtの設定ファイルはこのような形になります。

name := "gen-ensime"

organization := "org.littlewings"

scalaVersion := "2.11.6"

updateOptions := updateOptions.value.withCachedResolution(true)

scalacOptions ++= Seq("-Xlint", "-unchecked", "-deprecation", "-feature")

libraryDependencies ++= Seq(
    "org.hibernate" % "hibernate-search-orm" % "5.1.0.Final" % "compile","org.hibernate" % "hibernate-entitymanager" % "4.3.8.Final" % "compile","net.databinder.dispatch" % "dispatch-jsoup_2.11" % "0.11.2" % "provided","org.scala-lang" % "scala-library" % "2.11.6" % "compile","org.scalatest" % "scalatest_2.11" % "2.2.4" % "test","junit" % "junit" % "4.12" % "test"
)

nameやorganizationは、pom.xmlと同じになります。

これで、MavenでもEnsimeが使えます!

Gradleに移行したら、どうしよ…。