Java(Maven)プロジェクトのビルドをCircleCI+Orbsで自動化

# はじめに
Java(Maven)での開発・ビルドをCircleCIで自動化をさらっと説明したようなサンプルはありそうで見当たらなかったので、やってみました。

# 前提
GitHubやCircleCIのアカウントがある、VSCodeやgitがインストール、設定されているといったあたりについては本記事では説明しません。
なお、GitHubのアカウント設定とCircleCIのアカウント設定に関しては、以前書いた記事がありますので、こちらに挙げておきます。

- [GitHubにサインアップ(Sign up) - 新規ユーザ登録](https://qiita.com/mfunaki/items/e01762475967d4e05a1f)
- [CircleCIにサインアップ(Sign up) - 新規ユーザ登録](https://qiita.com/mfunaki/items/2032970cfc31f85178e4)

# 自動化対象のJava(Maven)プロジェクト
私が書いた

- [VSCodeでJava(Maven)プロジェクトを作成してDockerコンテナ上で開発する]
(https://qiita.com/mfunaki/items/dc09764cb08bb787c261)

で取り上げたプロジェクトのビルドをCircleCIを使って自動化することにします。

# その1: とりあえずクイックに自動化する
ウェブブラウザを使い、CircleCIの[ホームページ](https://circleci.com/ja/)右上の`アプリへ移動`をクリックするか、直接[https://app.circleci.com/](https://app.circleci.com/)をアクセスします。
Projectという画面で、GitHub上に存在していても、CircleCIのプロジェクトとして登録されていないリポジトリにはSet Up Projectボタンが表示されているので、今回作成中のプロジェクト(CCI-SMP-Java-Maven)横のSet Up Projectボタンをクリックします。
![Project](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/74444ab2-0676-e96c-8766-0707a13a68fd.png)
すると、おすすめの設定ファイル(config.yml)テンプレートが表示されるので、今回は **Maven (Java)** を選択し、**Commit and Run**ボタンをクリックするのではなく、**↓ボタン(ダウンロード)をクリック**します。
![config.yml](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/95890ad4-6438-cfe8-64ce-c0a05d998d1a.png)
ウェブブラウザがconfig.ymlの保存先を聞いてくるようであれば、ローカルにあるCCI-SMP-Java-Mavenリポジトリに`.circleci`ディレクトリを作成し、その中に`config.yml`を格納します。聞いてこないようであれば、ダウンロードフォルダに自動的に保存されていると思われるので、ローカルにあるCCI-SMP-Java-Mavenリポジトリに`.circleci`ディレクトリを作成し、その中にダウンロードフォルダ内の`config.yml`ファイルを、コピーまたは移動するようにします。
Visual Studio Code上で`config.yml`ファイルの変更通知(U)が表示されたら、追加、コミット＆プッシュしておきます。
![config.yml](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/0e3cf93c-510c-c534-2e5e-3dffb8c6db07.png)
追加、コミット＆プッシュが終わったら、再びウェブブラウザに戻り、**Use Existing Config**ボタンをクリックします。Have you added a config.yml file?と確認されますが、**Start Building**ボタンをクリックすればOKです。
![Have you added a config.yml file?](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/cce1d8b9-ba08-3fdd-c43e-94cb3b06e939.png)
すると、CI/CDプロセスが開始するものの、Install Dependenciesのところで The specified user setting file does not exist: /home/circleci/project/pom.xml ということで、Mavenが使う`pom.xml`ファイルが見つからないと怒られていることがわかります。
![Install Dependencies](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/6ebbc7be-536a-6ed6-df35-5712f5f36bf0.png)
Visual Studio Codeのエクスプローラーを見ると、`pom.xml`ファイルは存在するものの、トップレベルのディレクトリにではなく、`my-app`ディレクトリ内に存在しています。
したがって、この場所を指定してあげれば良さそうです。
![config.yml](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/a430c2c2-e374-986f-daaf-3a19f0ae13f3.png)
先ほど追加した`config.yml`ファイルの4行目を見ると、maven Orbを使っていることがわかるので、こちら([https://circleci.com/developer/ja/orbs](https://circleci.com/developer/ja/orbs))にあるCircleCI Orbsの一覧からmaven Orbを検索します。
![CircleCI Developer](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/5beeaef2-22a7-8f87-5556-cc336fa5d5e9.png)
[circleci/maven@1.0.3](https://circleci.com/developer/ja/orbs/orb/circleci/maven)(バージョン番号は新バージョンのリリースに伴い異なることがあります)をクリックすると、maven Orbのマニュアルが表示されるので、maven/testジョブの中でパラメータ**app_src_directory**を指定すれば、うまくいくように思われます。
![app_src_directory](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/1d6a0686-c81a-4a84-c87a-7d38e2f7764a.png)
合わせて、4行目のcircleci/mavenのバージョンも1.0.3にしておきます。

```yaml:config.yml
version: 2.1

orbs:
  maven: circleci/maven@1.0.3

workflows:
  maven_test:
    jobs:
      - maven/test: # checkout, build, test, and upload test results
          app_src_directory: my-app
```
こちらを更新、コミット＆プッシュすれば、うまくいきました！ CCI-SMP-Java-Mavenプロジェクトから、maven_testワークフローを選び、さらにmaven/testジョブを選ぶとこのように表示されます。
![maven/test](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/ad77ae71-f387-2e13-186e-d119e20abc4c.png)
一見すると、すべてがグリーンでうまくいっているように見えますが、うまくいっていないと思える点が(少なくとも)２つあります。

1. 生成された`/home/circleci/project/my-app/target/my-app-1.0-SNAPSHOT.jar`ファイルにアクセスする方法がない。
1. テスト結果を見ることができない。**Uploading test results**の中を見ると、テスト結果を保存できずにエラーになっています。
![Uploading test results](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/260700cc-a85d-8f7e-9c5a-17c1e2564a64.png)

その2では、単にビルドを(グリーンで)通すだけでなく、生成された`*.jar`ファイルが取得でき、テスト結果のレポートが見られるようにします。

# その2: テスト結果とビルド結果(*.jar)を受け取る
## テスト結果
まずはテスト結果ですが、テスト結果の格納先を正しく指定することができればうまくいきそうです。[Maven Orbのマニュアル](https://circleci.com/developer/ja/orbs/orb/circleci/maven)中ほどの**Usage Examples**のところには、**custom_test_results_path**がIf your tests results are not in the default (target/surefire-reports) directory then you could add a custom directory.とドンピシャなことが書かれているので、`config.yml`中での`maven/test`のパラメータに`app_src_directory`に加えて`test_results_path`の記述を追加することにします。

```config.yml
version: 2.1

orbs:
  maven: circleci/maven@1.0.3

workflows:
  maven_test:
    jobs:
      - maven/test: # checkout, build, test, and upload test results
          app_src_directory: my-app
          test_results_path: my-app/target/surefire-reports
```
この`config.yml`を更新、コミット、プッシュしてCircleCI側の画面を見ると、TESTSタブに緑字に白文字の**1**が追加され、**Uploading test results**の中の実行結果もArchiving the following test resultsと出力されています。
![Uploading test results](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/228ce8c8-47f6-6949-0312-c4c8058eeb74.png)
TESTSタブをクリックすると、こんな感じでテスト結果が表示されます。
![Good Job](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/fd0950f5-916a-eb44-21a8-130bf15d4248.png)

## ビルド結果
Maven Orbを使うことで、テスト結果の格納は容易になるものの(`test_results_path`)、アーティファクトの保存(`store_artifacts`)との併用ができないため、Maven Orbを使わない形で`config.yml`を記述します。そうは言っても、Maven Orbのソースコードは[こちら](https://circleci.com/developer/ja/orbs/orb/circleci/maven#orb-source)で公開されているため、参考にできるのは非常に助かります。

```config.yml
version: 2
jobs:
  build:
    docker:
      - image: cimg/openjdk:15.0.1
    steps:
      - checkout
      - run:
          command: find . -name 'pom.xml' | sort | xargs cat > /tmp/maven_cache_seed
          working_directory: ~/project/my-app
      - restore_cache:
          key: circleci-java-maven-{{ checksum "/tmp/maven_cache_seed" }}
      - run: 
          command: mvn package
          working_directory: ~/project/my-app          
      - save_cache:
          paths:
            - ~/.m2/repository
          key: circleci-java-maven-{{ checksum "/tmp/maven_cache_seed" }}
      - store_test_results:
          path: ~/project/my-app/target/surefire-reports
      - store_artifacts:
          path: ~/project/my-app/target/my-app-1.0-SNAPSHOT.jar
```

では、いくつかの部分に分けて説明していきます。

### Dockerコンテナイメージの指定
前回の記事では、マイクロソフト謹製のイメージ(mcr.microsoft.com/vscode/devcontainers/java:0-15)を使っていましたが、ここではCircleCIでの利用に最適化されたCircleCI謹製のコンビニエンスイメージ(cimg/openjdk:15.0.1)を使用しています。なお、コンビニエンスイメージの一覧は[こちら](https://circleci.com/developer/ja/images)で見ることができます。

```config.yml
  build:
    docker:
      - image: cimg/openjdk:15.0.1
```

### キャッシュ用のシード値の生成
前記事のビルド過程でたくさんのライブラリがダウンロードされるのを見ました。ダウンロードされるライブラリは`pom.xml`にライブラリ名やバージョンと合わせて指定されており、`.m2/repository`ディレクトリに保存されています。
別の言い方をすれば、`pom.xml`ファイルの内容が変更されていなければ、`.m2/repository`の内容を使いまわせば、ダウンロードの負担が軽減されるわけです。ここでは、`pom.xml`ファイルの(`cat`で出力した)内容を`/tmp/maven_cache_seed`に取得しています。

```config.yml
      - run:
          command: find . -name 'pom.xml' | sort | xargs cat > /tmp/maven_cache_seed
          working_directory: ~/project/my-app
      - restore_cache:
          key: circleci-java-maven-{{ checksum "/tmp/maven_cache_seed" }}
(中略)
      - save_cache:
          paths:
            - ~/.m2/repository
          key: circleci-java-maven-{{ checksum "/tmp/maven_cache_seed" }}
```

こんな回りくどいやり方をしなくても、**restore_cache**や**save_cache**のkeyに `circleci-java-maven-{{ checksum "~/project/my-app/pom.xml" }}`を渡せば良いようにも思われますが、ここで実装されているやり方であれば、`pom.xml`が存在しない場合に、この**restore_cache**ステップでエラーになってくれる(後続のステップが実行されない)ので、このようなやり方をしているかと思います(やり方の出典はMaven Orbのソースコード)。

### mvn packageの実行
これはシンプルです。念の為に説明を加えると、`working_directory`の中で(正確には`config.yml`の中で)`$CIRCLE_WORKING_DIRECTORY`環境変数は使えません。詳しくは、[ドキュメント](https://circleci.com/docs/ja/2.0/configuration-reference/#jobs)の`working_directory`の説明を参照ください(私はこれでハマってしまいましたが、SSHログインした先で文字通り"$CIRCLE_WORKING_DIRECTORY"が作られているのを見て、すぐに気づくことができました！)。

```config.yml
      - run: 
          command: mvn package
          working_directory: ~/project/my-app          
```

### テスト結果の保存
こちらもシンプルです。

```config.yml
      - store_test_results:
          path: ~/project/my-app/target/surefire-reports
```

### 生成されたJARファイルをアーティファクトに保存
こちらもシンプルです。

```config.yml
      - store_artifacts:
          path: ~/project/my-app/target/my-app-1.0-SNAPSHOT.jar
```
こうやってアーティファクトに保存されたJARファイルは、CircleCIのジョブ画面のARTIFACTSタブから取得可能です。
![ARTIFACTS](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19248/4b727be4-2bd2-6761-39d0-7de12b3b2453.png)

# おわりに
説明が冗長だったり不足していてわかりづらかった点もあるかと思いますが、一通りやってみれば、Java(Maven)プロジェクトであれば、新規でも既存でもCircleCIの上にのせて自動化できるのではないか？という肌感覚を持ってもらえたのではないでしょうか？

ほかの言語(やビルドツール)に関しても、こんな感じでご紹介させていただきたいと考えております。それではまたお会いしましょう！