sbt 1.2.xのライブラリ依存性解決が遅いんじゃ！

## はじめに

`Apache Spark`フレームワークを使用してビックデータ処理をScalaで記述しています。
処理をデプロイするためのjarファイルをGitLab CI/CDで作成しています。
しかし、毎回ライブラリ依存性解決が遅すぎる！
そのときの解決策です。

## 環境

|項目|バージョン|
|---|---|
|バージョン管理システム|GitLab|
|GitLab CI/CD Runner|Docker Executor|
|Scala|2.11|
|sbt|1.2|

## なぜ依存関係の解決が遅いのか

sbt 1.2までは依存関係の解決に`Apache Ivy`をしており、これが原因らしいです。
sbt 1.3以降は`Coursier`がデフォルトで使用されるようになり、改善されています。

## Dockerイメージ

GitLab CI/CDのRunnerとしてDocker Executorを使用しているので、Dockerイメージを作成が必要です。  
[公式ドキュメント](https://get-coursier.io/docs/sbt-coursier)の記載されている通り、そのDockerイメージに`Coursier`をグローバルプラグインとして追加します。

### Dockerfile

[scala-sbt](https://hub.docker.com/r/hseeberger/scala-sbt)を参考に作成しています！

```dockerfile:Dockerfile
#
# Scala and sbt Dockerfile
#
# https://github.com/hseeberger/scala-sbt
#

# Pull base image
ARG BASE_IMAGE_TAG
FROM openjdk:${BASE_IMAGE_TAG:-8u312-jdk-bullseye}

# Env variables
ARG SCALA_VERSION
ENV SCALA_VERSION ${SCALA_VERSION:-2.13.8}
ARG SBT_VERSION
ENV SBT_VERSION ${SBT_VERSION:-1.6.2}
ARG COURSIER_VERSION
ENV COURSIER_VERSION ${COURSIER_VERSION:-2.0.0-RC6-8}
ARG USER_ID
ENV USER_ID ${USER_ID:-1001}
ARG GROUP_ID 
ENV GROUP_ID ${GROUP_ID:-1001}

# Install sbt
RUN \
  curl -fsL "https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz" | tar xfz - -C /usr/share && \
  chown -R root:root /usr/share/sbt && \
  chmod -R 755 /usr/share/sbt && \
  ln -s /usr/share/sbt/bin/sbt /usr/local/bin/sbt

# Install Scala
RUN \
  case $SCALA_VERSION in \
    "3"*) URL=https://github.com/lampepfl/dotty/releases/download/$SCALA_VERSION/scala3-$SCALA_VERSION.tar.gz SCALA_DIR=/usr/share/scala3-$SCALA_VERSION ;; \
    *) URL=https://downloads.typesafe.com/scala/$SCALA_VERSION/scala-$SCALA_VERSION.tgz SCALA_DIR=/usr/share/scala-$SCALA_VERSION ;; \
  esac && \
  curl -fsL $URL | tar xfz - -C /usr/share && \
  mv $SCALA_DIR /usr/share/scala && \
  chown -R root:root /usr/share/scala && \
  chmod -R 755 /usr/share/scala && \
  ln -s /usr/share/scala/bin/* /usr/local/bin && \
  case $SCALA_VERSION in \
    "3"*) echo "@main def main = println(util.Properties.versionMsg)" > test.scala ;; \
    *) echo "println(util.Properties.versionMsg)" > test.scala ;; \
  esac && \
  scala -nocompdaemon test.scala && rm test.scala

# Install rpm for sbt-native-packager
# see https://github.com/hseeberger/scala-sbt/pull/114
RUN apt-get update && \
  apt-get install rpm bc -y && \
  rm -rf /var/lib/apt/lists/*

# Add and use user sbtuser
RUN groupadd --gid $GROUP_ID sbtuser && useradd -m --gid $GROUP_ID --uid $USER_ID sbtuser --shell /bin/bash
USER sbtuser

# Switch working directory
WORKDIR /home/sbtuser

# Prepare sbt (warm cache)
RUN \
  sbt sbtVersion && \
  mkdir -p project && \
  echo "scalaVersion := \"${SCALA_VERSION}\"" > build.sbt && \
  echo "sbt.version=${SBT_VERSION}" > project/build.properties && \
  echo "// force sbt compiler-bridge download" > project/Dependencies.scala && \
  echo "case object Temp" > Temp.scala && \
  if [ "$(echo "$(echo ${SBT_VERSION} | cut -c 1-3) < 1.3" | bc)" -eq 1 ]; then \
    mkdir -p .sbt/1.0/plugins; \
    echo "addSbtPlugin(\"io.get-coursier\" % \"sbt-coursier\" % \"${COURSIER_VERSION}\")" > .sbt/1.0/plugins/build.sbt \
  fi && \
  sbt compile && \
  rm -r project && rm build.sbt && rm Temp.scala && rm -r target

# Link everything into root as well
# This allows users of this container to choose, whether they want to run the container as sbtuser (non-root) or as root
USER root
RUN \
  ln -s /home/sbtuser/.cache /root/.cache && \
  ln -s /home/sbtuser/.sbt /root/.sbt && \
  if [ -d "/home/sbtuser/.ivy2" ]; then ln -s /home/sbtuser/.ivy2 /root/.ivy2; fi

# Switch working directory back to root
## Users wanting to use this container as non-root should combine the two following arguments
## -u sbtuser
## -w /home/sbtuser
WORKDIR /root  

CMD sbt
```

### 使い方

sbt 1.2.8, scala 2.11.12のイメージを作成する場合は下記のコマンドを実行します。

```bash
docker build --build-arg SBT_VERSION="1.2.8" --build-arg SCALA_VERSION="2.13.1" .
```

### 変更点

* 変数の追加

Dockerイメージのビルドで使用する引数とデフォルトの値を設定します。

```dockerfile:Dockerfile(一部抜粋)
ARG COURSIER_VERSION
ENV COURSIER_VERSION ${COURSIER_VERSION:-2.0.0-RC6-8}
```

* 小数比較のパッケージをインストール

バージョンの比較を行うために`bc`をインストールします。

```dockerfile:Dockerfile(一部抜粋)
  apt-get install rpm bc -y && \
```

* `sbt-coursier`をグローバルプラグインに追加

sbtのバージョンが1.3未満を条件にグローバルプラグインとして`sbt-coursier`を設定します。

```dockerfile:Dockerfile(一部抜粋)
  if [ "$(echo "$(echo ${SBT_VERSION} | cut -c 1-3) < 1.3" | bc)" -eq 1 ]; then \
    mkdir -p .sbt/1.0/plugins; \
    echo "addSbtPlugin(\"io.get-coursier\" % \"sbt-coursier\" % \"${COURSIER_VERSION}\")" > .sbt/1.0/plugins/build.sbt \
  fi && \
```

## 終わりに

依存解決に30分以上要していましたが、`Coursier`を追加することで**5分**で終わるようになりました！
そもそもはsbtをバージョンアップしろって話ですよね。。。