WSL+Coursier+VSCode で Scala の開発環境を構築する

![WSL Ubuntu 24.04.2](https://img.shields.io/badge/WSL(Ubuntu)-24.04.2-limegreen) ![Coursier 2.1.25-M4](https://img.shields.io/badge/Coursier-2.1.25.M4-limegreen) ![Scala 3.6.4](https://img.shields.io/badge/Scala-3.6.4-limegreen) ![SBT 1.10.11](https://img.shields.io/badge/SBT-1.10.11-limegreen)

Scala の開発環境を一から構築し直した際の手順を公開しておきます。
採用しているツール群は基本的に公式が推奨しているっぽいものにしています。
※ VSCode 派なので IntelliJ は採用しませんでした。

https://docs.scala-lang.org/ja/getting-started/install-scala.html#

この記事で構築する環境の構成図はこちらです。

```mermaid
flowchart LR
    subgraph Windows
        vscode["`**Visual Studio Code**<br/>(UI だけ)`"]
        remote["`**WSL 拡張機能**`"]
        vscode --> remote
    end

    subgraph WSL["`**WSL2 (Ubuntu)**`"]
        VSCodeServer["`**VSCode Server**`"]
        jdk["`**JDK**`"]
        scala["`**Scala**`"]
        sbt["`**SBT**`"]
    end

    subgraph VSCodeServer["`**VSCode Server**`"]
        scalaext["`**Scala (Metals)**`"]
        scalaext --> jdk
        scalaext --> scala
        scalaext --> sbt
    end

    remote --> VSCodeServer
    
    %% スタイリングの定義
    classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px,font-size:14px
    classDef windows fill:#f0f4f8,stroke:#2d89ef,stroke-width:2px,color:#000,font-weight:bold
    classDef wsl fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#000,font-weight:bold
    classDef server fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#000,font-weight:bold
    classDef component fill:#fff,stroke:#666,stroke-width:1px,rx:5px,color:#000,font-size:16px,font-weight:bold
    
    %% スタイルの適用
    class Windows windows
    class WSL wsl
    class VSCodeServer server
    class vscode,remote,jdk,scala,sbt,scalaext component
```

この構成で示している通り、 Windows 側では VSCode と WSL 拡張機能のみが動作し、実際の開発環境（JDK、Scala、SBT）は WSL 上に構築します。

# 1. WSL2 のインストール

Windows 10 ビルド 19041 以上、または、Windows 11 であれば、以下のコマンドだけで大丈夫です。

```powershell:PowerShell(管理者モード)
# OS のバージョンを確認
$PSVersionTable.OS
#=> Microsoft Windows 10.0.19045

# WSL をインストール
wsl --install

# インストールが完了すれば、wsl コマンドで Ubuntu を起動 & ログインする。
wsl
# 初回はユーザ名とパスワードを設定する。
```

これで Ubuntu ディストリビューションが WSL として利用できます。

それ以外の PC 環境の方は、以下の公式ページをご参照ください。

https://learn.microsoft.com/ja-jp/windows/wsl/install


## 2. WSL で Scala と SBT のインストール

Coursier を使用して必要な開発ツールをセットアップしていきます。Coursier は、Scala 関連の開発ツールに特化したパッケージマネージャのようなツールです。

```bash:Coursier セットアップ（公式から抜粋）
# Coursier の最新バージョンをダウンロード
curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs

# 展開して実行可能ファイル「cs」を作成
chmod +x cs
# Scala、SBT、および関連ツールをインストール
./cs setup
```

最新のインストール手順についてはこちらをご参照ください。

https://get-coursier.io/docs/cli-installation

JDK は既にインストールされていれば追加でインストールされることはありません。セットアップ中には必要なツールが順次インストールされ、PATH への設定も自動的に行われます。

:::note info
JDK がインストールされていない場合、Coursier が自動的に検知し、インストールするか尋ねられます。

```
No JVM found, should we try to install one? [Y/n]
```

JDK を既にインストールしている場合は、このようなメッセージが表示されます。

```
Found a JVM installed under /home/nimzo/.sdkman/candidates/java/current.
```
:::

インストールが完了したら、一度シェルを再起動（もしくは、 `source ~/.profile` を実行）して環境変数の変更を反映させます。その後、以下のコマンドでインストールされたツールのバージョンを確認できます。

```bash:バージョン確認
scala --version
#=> Scala code runner version: 1.5.4
#=> Scala version (default): 3.6.4

sbt --version
#=> sbt runner version: 1.10.11

scala-cli --version
#=> Scala CLI version: 1.7.1
```

# 3. VSCode のセットアップ

VSCode で WSL にて構築した開発環境を使うには、Microsoft 社が提供している WSL 拡張機能をインストールします。

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl

WSL 拡張機能を使うことで、我々は VSCode の UI のみ Windows で実行し、それ以外の開発作業（例えば、コマンドや拡張機能、ターミナル操作、ビルドと実行、デバッグ、など）を WSL としてインストールされた Linux 上で実行することができます。

## 3-1. VSCode で WSL を開く

VSCode で開く前に、先にディレクトリを WSL 側に作っておきます。

```bash
mkdir ~/repos/qiita
cd ~/repos/qiita
```

Windows の VSCode から WSL に接続する方法は２つです。
まず、ターミナル上で wsl に入り、そこで `code` コマンドで VSCode を起動することです。

```bash
pwd
#=> /home/nimzo6689/repos/qiita
code .
```

すると、 VSCode が起動し、ステータスバーの左下を見ると、WSL に接続できていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/35e7ad8a-5818-9720-ecca-1866904dcdcf.png)

もう１つは、Windows で VSCode を起動し、ステータスバーの左下（上の画像で WSL: Ubuntu と表示されている箇所）をクリックし、コマンドパレットで `WSL: Connect to WSL` を選択することです。どちらの方法でも利用できるように覚えておくと便利だと思います。

WSL の接続を終了するときは、同じようにコマンドパレットから `Close Remote Connection` を選択すれば終了できます。

## 3-2. VSCode Scala 拡張機能のインストール

WSL に接続された VSCode ウィンドウで、Scala 開発に必要な拡張機能をインストールします。主に必要なのは「Scala (Metals)」拡張機能です。
Metals は Scala 言語サーバープロトコル（LSP）の実装名になります。

https://marketplace.visualstudio.com/items?itemName=scalameta.metals

# 4. Scala プロジェクトの作成

WSL 環境で Scala プロジェクトを作成します。

# 4-1. sbt new にてテンプレートから生成する

SBT の `new` コマンドを使ってテンプレートからプロジェクトを生成し、そのプロジェクトを code コマンドで開きなおします。

```bash
sbt new scala/scala3.g8
# プロジェクト名は「scaka-demo」とします。
#=>A template to demonstrate a minimal Scala 3 application 
#=>name [Scala 3 Project Template]: scala-demo
#=>Template applied in /home/nimzo/repos/qiita/./scala-demo

# 生成されたプロジェクトを VSCode で開き直す
code --reuse-window scala-demo
```

フォルダを開いた後は、Scala (Metals) がビルドをインポートするか聞かれるため、Import Build をクリックします。すると、Metals のインストールが開始され、Metals が提供する機能が利用可能な状態になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/7e6e02e5-b0c2-4486-991f-80895a4ad7d3.png)

:::note info
ポップアップが表示されない場合は、Activity Bar にある Metails View にて、Import Build をクリックすることでもインポートできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/64c06d20-bed6-48a0-8017-4b41e17c86aa.png)
:::

## 4-2. プロジェクト構造の確認

プロジェクトが開かれると、以下のような構造が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/19fbdfff-ab91-451c-b868-29e53c3d4ff2.png)

Scala プロジェクトの標準的な構造は以下のようになります。

- `build.sbt`: プロジェクトの設定ファイル。依存関係、バージョン、ビルド設定などを定義します
- `project/`: SBT 自体の設定ファイルを含むディレクトリ
  - `build.properties`: SBT のバージョンを指定します
  - `plugins.sbt`: SBT プラグインを設定するファイル（必要に応じて作成）
- `src/`: ソースコードとテストコードを格納するディレクトリ
  - `main/scala/`: アプリケーションのメインソースコード
  - `test/scala/`: テストコード
- `.scalafmt.conf`: コードフォーマットの設定ファイル
※ 作成されていない場合、 `Alt+Shift+F` でフォーマットをかけようとすると、Metals から `.scalafmt.conf` を作成するか聞かれます。

# 5. Scala コードの実行

他の言語と同じように VSCode 内でコードの実行やデバッグができます。

# 5-1. main 関数の実行

`src/main/scala/Main.scala` を開き、デフォルトのコードを確認します。このファイルには基本的な「Hello, world!」プログラムが書かれています。

```scala:Main.scala
@main def hello(): Unit =
  println("Hello world!")
  println(msg)

def msg = "I was compiled by Scala 3. :)"
```

これは Scala 3 の新しい構文を使用した、シンプルな Hello World プログラムです。`@main` アノテーションは、この関数がプログラムのエントリーポイントであることを示しています。

Metals が完全に読み込まれると、ファイル内に「run」というコードレンズが表示されます。これはファイル内の実行可能な関数（この場合は `@main` でアノテーションされた関数）の上部に表示されるリンクです。これをクリックすると、プログラムが実行されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/83d8cb77-0163-447c-abaf-8950d37faea2.png)

あるいは、VSCode の統合ターミナル（Ctrl+Shift+\`）を開き、 `sbt run` を実行することもできます。

## 5-2. デバッグ機能の使用

VSCode で Scala コードをデバッグするには、まず `src/main/scala/Main.scala` にブレークポイントを設定し、F5 でデバッグ実行を開始します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/c04032a5-ca8e-4ed6-92ff-c24af34418c3.png)

Main.scala のステップ実行であれば、 `.vscode/launch.json` を別途作成しなくてもできますが、今後は用途によって作成が必要になることはあります。

# 6. MUnit によるテスト

MUnit は Scala 用の簡素なテストフレームワークで、明確なエラー表示が特徴です。 

https://scalameta.org/munit/

まず、`build.sbt` ファイルに MUnit の依存関係を追加します。ただ、 `sbt new scala/scala3.g8` で生成した場合、 MUnit はデフォルトで追加されているので、確認だけで大丈夫です。

```scala:build.sbt（既存ファイルそのまま）
libraryDependencies += "org.scalameta" %% "munit" % "1.0.0" % Test
```

この行はテスト用の依存関係として MUnit を追加しています。最後の `% Test` はこの依存関係がテスト時のみ必要であることを示しています。

次に、テスト対象となるクラスを作成します。`src/main/scala` ディレクトリに `Calculator.scala` ファイルを作成し、以下のコードを記述します。

```scala:Calculator.scala
object Calculator {
  def divide(dividend: Int, divisor: Int): Option[Int] = {
    if (divisor == 0) None
    else Some(dividend / divisor)
  }
}
```

このクラスは単純な割り算処理と、ゼロ除算に対する関数型エラーハンドリング（Option型を使用）を提供しています。

では、この機能に対するテストを作成していきます。
`src/test/scala` ディレクトリに `CalculatorSuite.scala` ファイルを作成し、以下のテストコードを記述します。

```scala:CalculatorSuite.scala
import munit.FunSuite

class CalculatorTest extends FunSuite {
  
  test("整数同士で割り切れる場合、正しい商を返す") {
    val result = Calculator.divide(12, 3)
    assertEquals(result, Some(4))
  }
  
  test("整数同士で割り切れない場合、整数部分の商を返す") {
    val result = Calculator.divide(12, 5)
    assertEquals(result, Some(2))
  }
  
  test("0で割る場合、Noneを返す") {
    val result = Calculator.divide(12, 0)
    assertEquals(result, None)
  }
}
```

テストを実行するには、Activity Bar の Testing View にて実行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/2999fbb9-10db-4c6e-97b0-ece876633a64.png)

統合ターミナルで `sbt test` を実行しても同じ結果にはなります。

また、テストファイルを開くと、テストメソッドの行番号の横に実行ボタンが表示されるので、そこから実行もできますし、右クリックして Debug Test を選択するとデバッグしたりすることもできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/68b0b44f-c9ae-4f69-8ace-090cdfb9a3db.png)

# 7. JAR ファイルのビルド

アプリケーションを配布するために、実行可能な JAR ファイル（far JAR）を作成します。

まず、`build.sbt` ファイルを以下のように修正して、アプリケーションのエントリーポイントを指定します。

```diff_scala:build.sbt
val scala3Version = "3.6.4"

lazy val root = project
  .in(file("."))
  .settings(
    name := "scala-demo",
    version := "0.1.0-SNAPSHOT",
    
    scalaVersion := scala3Version,

-    libraryDependencies += "org.scalameta" %% "munit" % "1.0.0" % Test
+   libraryDependencies += "org.scalameta" %% "munit" % "1.0.0" % Test,
    
+    Compile / mainClass := Some("hello"),
+    assembly / mainClass := Some("hello"),
+
+    assembly / assemblyJarName := s"${name.value}-${version.value}.jar"
  )
```

この修正では、コンパイル時とアセンブリ時のメインクラスを「hello」に設定しています。これは `@main def hello` で定義したエントリーポイントに対応します。

次に、プロジェクトの `project/plugins.sbt` ファイルを作成し、そこに sbt-assembly プラグインを追加します。

```scala:project/plugins.sbt
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.5")
```

sbt-assembly プラグインは、依存ライブラリを含む実行可能な「fat JAR」を作成するためのものです。これにより、依存関係の問題なく JAR ファイルを実行できるようになります。

設定が完了したら、 `sbt assembly` で JAR ファイルをビルドできます。

ビルドが完了すると、`target/scala-3.6.4/scala-demo-0.1.0-SNAPSHOT.jar` (Scala のバージョン名は異なる場合があります) が生成されます。

生成された JAR ファイルは以下のように実行できます。

```bash:JARの実行
java -jar target/scala-3.6.4/scala-demo-0.1.0-SNAPSHOT.jar
```

この実行によって「Hello, world!」が表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/6a0cca62-9c61-4a86-a427-f30c4f8d0f53.png)

コマンドからもわかる通り、この jar ファイルは Java がインストールされている環境であれば、同じコマンドで実行可能です。

# 8. Scala CLI の導入

Coursier でセットアップをすると、自動で Scala CLI という REPL やスクリプティング支援をしてくれるツールが導入されるので、そちらも紹介します。

詳しい使い方はこちら。

https://blog.3qe.us/entry/2023/12/20/011354

## 8-1. REPL

`scala-cli repl` コマンドで REPL が起動します。

外部ライブラリを使いたい場合は、 `scala-cli repl --dep com.lihaoyi::requests:0.9.0` のように指定することで使用できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/57344a5a-deec-4634-a42c-3fdcced4cde8.png)

もしくは、あとで紹介する `//> using dep "com.lihaoyi::requests:0.9.0"` のような依存性情報を記載した sc ファイルを `scala-cli repl demo.sc` のように指定するのでも OK です。こちらの方法の場合、 import も書いておけば、 REPL でいちいち import を実行する必要がないので便利です。

## 8-2. スクリプティング

VS Code 用の設定ファイルを生成するために、以下のコマンドを先に実行しておきます。
このコマンドを実行することで、スクリプトファイルにおいてもエディタ内に表示された `Run | Debug` からコードの実行やデバッグができるようになります。

```bash
scala-cli setup-ide .
```

試しに、 HTTP の GET リクエストを送信するスクリプトを書いてみます。

```scala:demo.sc
#!/usr/bin/env -S scala-cli shebang --quiet

//> using dep "com.lihaoyi::requests:0.9.0"

import requests._

val getResponse =
  requests.get("https://httpbin.org/get", params = Map("name" -> "scala"))
println(s"GET response: ${getResponse.statusCode}")
```

あとはファイルの実行権限を付ければ、そのファイル単体で実行できます。
※ 単純に `scala-cli demo.sc` でも可です。

```bash
chmod u+x demo.sc  # 実行権限を付与
./demo.sc         # スクリプトを実行
```

実行すると以下のような出力が表示されます。（`scala-cli demo.sc` で実行してしまっていますが、 `./demo.sc` の結果も一緒です）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/8e6766ac-6b3c-44d6-932f-ab30ce26bb40.png)

# 9. Scala Worksheet でインタラクティブにコードを実行をする

Scala Worksheet は VSCode の Metals 拡張機能の一部で、コードを書きながらすぐに結果を確認できるインタラクティブな開発環境を提供します。REPL に似ていますが、ファイルとして保存でき、リアルタイムに結果が表示される点に違いがあります。

Scala Worksheet ファイルを作成するには、`.worksheet.sc` 拡張子を持つファイルを作成します。たとえば、以下のような FizzBuzz アルゴリズムを試してみます。

```scala:fizzbuzz.worksheet.sc
def fizzbuzz(n: Int): String = (n % 3, n % 5) match {
  case (0, 0) => "FizzBuzz"
  case (0, _) => "Fizz"
  case (_, 0) => "Buzz"
  case _ => n.toString
}

fizzbuzz(1)
fizzbuzz(3)
fizzbuzz(5)
fizzbuzz(15)
```

Worksheet ファイルで書くと、自動的に式の結果を計算し、右側にコメントとして表示してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/9b385c68-e6b2-4b7d-9f7f-1f482b7fa1dd.png)

また、ファイル先頭に表示される `Copy Worksheet Output` をクリックすると、実行結果とともにクリップボードにコードがコピーされるため、記事投稿やコードレビューなどで実装について説明するときに活用できそうです。