KotlinとGradleとSpekとIntelliJIdeaで環境構築

# Introduction

タイトルの通り、IntellijIDEA上でKotlinのGradleを使ったプロジェクトを作成し、Spekでテストをさせようとしたときに環境構築で少し苦労したのでまとめておきます。

なお、使用したバージョンは以下の通りです。

|種類|名前|バージョン|
|---|---|---|
|OS|Ubuntu|18.04(Bionic Beaver)|
|開発環境|Intellij IDEA|ULTIMATE 2018.1|
|開発言語|Kotlin|1.2.50|
|JVM|OpenJDK|10.0.1|
|ビルド|Gradle|4.8|
|テスト環境1|JUnit|5.2|
|テスト環境2|Spek|1.1.5|

# 環境構築

## OS

Ubuntuのインストールです。本題では無いのでささっと書きます。

- Ubuntu18.04 DesktopのISOイメージを使ってインストール(なお、私はVirtualBoxを使っています)
- ネットワークのProxyと/etc/environmentにproxyを設定 (もちろんProxy下にある人のみ)
- `$ sudo apt-get update && sudo apt-get upgrade && sudo apt-get dist-upgrade`
- `$ sudo apt-get install build-essential`
- VirtualBox Guest Additionをインストール(再起動)
- `$ sudo apt-get install language-pack-gnome-ja language-pack-ja gnome-user-docs-ja gnome-getting-started-docs-ja
 fonts-noto-cjk-extra firefox-locale-ja` (日本語パックのインストール)
- 地域と言語設定より、入力ソースを日本語(Mozc)を優先に変更

## JVM

OpenJDKをインストール。なお、執筆時点ではJDK11と言ってもJDK10がインストールされます。

- `$ sudo apt-get install openjdk-11-jdk-headless`

## Intellij IDEA

開発環境にはIntellij IDEAを使っています。Ubuntuを使っているので、折角ですからsnapを使ってインストールします。

- `$ sudo snap install --classic intellij-idea-ultimate`  (EAP版を使いたい人は--edgeオプション付きで)
- `$ intellij-idea-ultimate` で起動後、お気に入りに設定
- 設定のApperance&Behaviour -> System Settings -> HTTP ProxyでProxyを設定 (もちろん、Proxyを使う人だけ)

なお、snapを使ってインストールすると、mavenリポジトリからjarファイルを取得しようとしたときに、エラーが出てしまうようです。

```
the trustAnchors parameter must be non-empty gradle
他、スタックトレースがいっぱい
```

そこで、IntellijIDEAのVMOptionsを上書きします。

- `$ vi ~/.idea64.vmoptions`

```
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Dawt.useSystemAAFontSettings=lcd
-Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine
-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts
-Djavax.net.ssl.trustStorePassword=changeit
```

なお、下の二行以外は、デフォルトのvmoptionsです。

そして、IntellijIDEA起動時に、このファイルを読み込むように環境変数をセットします。

- `$ vi ~/.bashrc`

```
export IDEA_VM_OPTIONS=~/.idea64.vmoptions
```

なお、環境変数をセットするのではなく、以下のファイルに設定を書くという方法もあります。

- `$ vi ~/.IntelliJIdea2018.1/config/idea64.vmoptions`


## Kotlin

Kotlinもsnapを使ってインストールします。

- `$ sudo snap install --classic kotlin`
- `$ kotlin -version` (バージョン確認)

```
Kotlin version 1.2.50-release-103 (JRE 10.0.1+10-Ubuntu-3ubuntu1)
```

また、Intellij IDEAのKotlinプラグインも更新しておきます。

- Setting → Plugins → Kotlinを検索して必要であればUpdate

## Gradle

Intellij IDEAに標準で入っているGradleは、この時点では4.4です。このバージョンでは、Java9以降のバージョン番号が正しく取得できないという不具合があります。折角ですので、最新版のGradleをインストールしてみます。

- `$ wget https://services.gradle.org/distributions/gradle-4.8-bin.zip`
- `$ sudo mkdir /opt/gradle`
- `$ sudo unzip -d /opt/gradle gradle-4.8-bin.zip`

インストールしたgradleへのパスを通します。

- `$ vi ~/.bashrc`

```
export PATH=$PATH:/opt/gradle/gradle-4.8/bin
```

また、Java9よりprivateメソッドへのReflectionの振る舞いが変わったことから、gradle実行時にgroovyの警告メッセージが出力されてしまいます。

```
$ gradle -v
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/opt/gradle/gradle-4.8/lib/groovy-all-2.4.12.jar) to method java.lang.Object.finalize()
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.reflection.CachedClass
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

------------------------------------------------------------
Gradle 4.8
------------------------------------------------------------

Build time:   2018-06-04 10:39:58 UTC
Revision:     9e1261240e412cbf61a5e3a5ab734f232b2f887d

Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.11 compiled on March 23 2018
JVM:          10.0.1 (Oracle Corporation 10.0.1+10-Ubuntu-3ubuntu1)
OS:           Linux 4.15.0-23-generic amd64
```

これを防ぐために、gradle起動時のオプションを環境設定に指定してします。

- `$ vi ~/.bashrc`

```
export GRADLE_OPTS="--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED"
```

これで警告が出なくなりました。

```
$ gradle -v

------------------------------------------------------------
Gradle 4.8
------------------------------------------------------------

Build time:   2018-06-04 10:39:58 UTC
Revision:     9e1261240e412cbf61a5e3a5ab734f232b2f887d

Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.11 compiled on March 23 2018
JVM:          10.0.1 (Oracle Corporation 10.0.1+10-Ubuntu-3ubuntu1)
OS:           Linux 4.15.0-23-generic amd64
```

# プロジェクト作成

## Create New Project

Intellij IDEAでgradleプロジェクトを作成します。
なお、これ以降、プロジェクト名はhogehogeを使用します。

- Create New Project → Gradle → Kotlin (Java) → Next 
- GroupIdとArtifactIdを入力 → Next
- Use auto-importは環境と好みに合わせてON/OFF
- Use local gradle distributionを選択し、Gradle homeに`/opt/gradle/gradle-4.8`を指定する → Next
- Project nameとlocationを入力 → Finish

## gradle.properties

IntelliJ IDEAのリポジトリ読み込みでもセキュリティに関するvm optionsを追加しましたが、Gradleで使用するvmにも同じオプションを追加する必要があります。

そこで、プロジェクトディレクトリ直下に、`gradle.properties`ファイルを作成して、vm optionsを記載します。

- `$ vi ~/IdeaProjects/hogehoge/gradle.properties` 

```gradle.properties
org.gradle.jvmargs=-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
```

なお、ビルド時にdaemonを使わない場合は、ここに-Dorg.gradle.daemon=falseを追加します。
また、Proxy使用時は、-DproxyHost=PROXY.HOST -DpropxyPort=PROXY.PORTを追加します。

## settings.gradle

最新版のkotlin-gradle-pluginを取得するために、ビルド用のリポジトリにkotlin-devを追加します。
なお、pluginManagementはsettings.gradleの先頭に記述します。

- `$ vi ~/IdeaProjects/hogehoge/settings.gradle`

```settings.gradle
pluginManagement {
    repositories {
        mavenCentral()
        maven {
            url { 'http://dl.bintray.com/kotlin/kotlin-dev' }
        }
    }
}

rootProject.name = 'hogehoge'
```

## ソースディレクトリ

今回は、mainとtestの二つのソースディレクトリを使用することにします(もちろんコマンドではなくUIから作成しても可)。

* `$ mkdir -p ~/IdeaProjects/hogehoge/src/main/kotlin`
* `$ mkdir -p ~/IdeaProjects/hogehoge/src/test/kotlin`

これらをIntellij IDEAの`Mark Directory as` をソースディレクトリなどに指定しても、build.gradleの内容で上書きさせてしまうので注意が必要です。


## build.gradle


初期状態のbuild.gradleは以下の通りになっています。

```build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}

group 'hogehoge'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
```

これに、ソースディレクトリの設定と、Gradle4.7以降deprecatedになったcompileコマンドの変更を行います。
また、ついでにkotlin-stdlib-jdk8のバージョン指定も行いました。

```build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}

group 'hogehoge'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.50"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
```

ひとまずプロジェクト作成用の準備完了です。


# ビルド

## サンプルソース

今回は、メインのソースディレクトリ下にpackage hoge.hogehogeを作成し、パッケージ下にHello.ktというソースを作成することにします。

```
src
|-- main
  |-- kotlin
    |-- hoge.hogehoge
      |  Hello.kt
```

```Hello.kt
package hoge.hogehoge

data class Hello(val who: String) {
    override fun toString():String = "Hello, $who!"
}
```

## Gradleビルド

IntelliJ IDEAで、Gradle用ビルドコマンドを追加します。

* Runメニュー → Edit Configurations...
* Add New Configuration(+ボタン)で、Gradleを選択
* Gradle projectに対象となるプロジェクト(hogehoge)を選択します
* Tasksにbuildを入力します

これで、メニュー等からGradleビルドを行うことができるようになります。


# テスト(JUnit)

まず、JUnit5の設定を行います。

## テストソース

以下の箇所にJUnit5用のテストソースを作成します。

```
src
|-- test
  |-- kotlin
    |-- hoge.hogehoge
      |  HelloTest.kt
```

```HelloTest.kt
package hoge.hogehoge

import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

class HelloTest {
    @Test fun helloWorld() {
        val hw = Hello("World")
        Assertions.assertEquals(hw.toString(), "Hello, World?") // わざと失敗
    }
}
```

## build.gradle

続いて、JUnit用にbuild.gradleを修正します。
dependenciesにJUnit用のライブラリを追加したのと、testブロックを追加しました。

```build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}

group 'test8'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.50"

    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5:1.2.50'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.2.0'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.2.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.2.0'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

test {
    useJUnitPlatform()
}
```

## Gradleテストビルド

IntelliJ IDEAで、Gradle用テストビルドコマンドを追加します。
上記でbuildコマンドを作成したのとほぼ同じで、違いはタスクがbuildからtestになっただけです。

* Runメニュー → Edit Configurations...
* Add New Configuration(+ボタン)で、Gradleを選択
* Gradle projectに対象となるプロジェクト(hogehoge)を選択します
* Tasksにtestを入力します



# テスト(Spek)

続いて、テスト用にSpekの設定を行います。

## テストソース

以下の箇所にSpek用のテストソースを作成します。

```
src
|-- test
  |-- kotlin
    |-- hoge.hogehoge
      |  HelloSpec.kt
```

```HelloSpec.kt
package hoge.hogehoge

import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.describe
import org.jetbrains.spek.api.dsl.it
import org.jetbrains.spek.api.dsl.on
import org.junit.jupiter.api.Assertions

object HelloSpec : Spek({
    describe("Hello, World!") {
        val hw = Hello("World")

        on("world") {
            it("World is hello") {
                Assertions.assertEquals(hw.toString(), "Hello, World?") // わざとERROR
            }
        }
    }
})
```

## build.gradle

続いて、Spek用にbuild.gradleを修正します。
dependenciesにSpek用のライブラリを追加したのと、そのリポジトリを追加しました。

```build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}

group 'test8'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()

    maven {
        url {
            'http://dl.bintray.com/jetbrains/spek'
        }
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.50"

    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5:1.2.50'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.2.0'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.2.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.2.0'

    testImplementation 'org.jetbrains.spek:spek-api:1.1.5'
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.2.50'
    testRuntimeOnly 'org.jetbrains.spek:spek-junit-platform-engine:1.1.5'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

test {
    useJUnitPlatform()
}
```

## Gradleテストビルド

テスト実行は、先ほどJUnit用に作成したものをそのまま使用できます。


# これでようやく

スタートラインだな。。。


# 変更点

2018/7/3

* 各build.gradleからsourceSetsの設定を削除しました(指定してあったパスsrc/main/kotlinは標準なので明示的に指定する必要が無いため)
* 各build.gradleのruntimeOnly 'org.jetbrains.kotlin:kotlin-reflect:1.2.50'を、implementation ～に変更しました (ビルド時に警告がでるため)
* gradle.propertiesに-Dorg.gradle.daemonと-DproxyHost(Port)の説明を追加