個人プロジェクトにて、先日リリースされた Go 1.11 の Modules (vgo) を使ってみました。
移行自体はスムーズにできたのですが、CircleCI でのキャッシュのやり方がそこそこ重要かも?と思ったので記事を書きました。
dep から Modules への移行
まずは dep で管理していた依存パッケージを Modules に移行します。
移行は簡単で、以下のコマンドを実行するだけです。
$ export GO111MODULE=on
$ go mod init
$ go mod download # go.sum を生成するため
これによって go.mod と go.sum が生成されるためこれらを git の管理下に入れれば OK です。
$ ls
go.mod  go.sum  Gopkg.lock  Gopkg.toml  main.go
後は dep 用のファイルを削除しましょう。
$ rm -f Gopkg.*
その他の詳しい使い方は今回は割愛します。
CircleCI で Modules を使う
さて、本題の CircleCI 内で Modules を使う場合についてです。
CircleCI の公式イメージで Go 1.11 がインストールされたイメージが公開されているのでこれを使います。
$ docker run -t circleci/golang:1.11.0 go version
go version go1.11 linux/amd64
.circleci/config.yml で circleci/golang:1.11.0 を使い、$GO111MODULE に on を指定すれば良いです。
version: 2
jobs:
  build:
    docker:
      - image: circleci/golang:1.11.0
        environment:
          GO111MODULE: "on"
    steps:
      - checkout
      - run:
          name: Run tests
          command: go test ./...
これで $ go test ./... 実行時に自動で依存パッケージをダウンロードしてきてくれます。
Modules でダウンロードした依存パッケージをキャッシュする
ただし、上記の設定だけだと毎回依存パッケージをダウンロードしてくるので CI に時間がかかります。
そのため、CircleCI のキャッシュを使います。
version: 2
jobs:
  build:
    docker:
      - image: circleci/golang:1.11.0
        environment:
          GO111MODULE: "on"
    steps:
      - checkout
      - restore_cache:
          name: Restore go modules cache
          keys:
            - mod-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "go.mod" }}
      - run:
          name: Vendoring
          command: go mod download
      - save_cache:
          name: Save go modules cache
          key: mod-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "go.mod" }}
          paths:
            - /go/pkg/mod/cache
      - run:
          name: Run tests
          command: go test ./...
Modules でダウンロードした依存パッケージはプロジェクトのディレクトリ内を見てもどこにもありません。
おもむろに $GOPATH 内をあさってみたところ、$GOPATH/pkg/mod/cache の中にダウンロードしてきた依存パッケージがありました。
これをキャッシュすれば良いです。
また、go test ./... で暗黙的にダウンロードする前に go mod download で明示的に依存パッケージをダウンロードするようにしてみました。
Modules のベストプラクティスに乗っかる
公式の Wiki にリリースする前の準備として、ベストプラクティスが載ってました。
これによると、
- go mod tidyを実行して不要なパッケージの prune と必要なパッケージのダウンロードを行う
- go test allを実行して依存パッケージも含めた全パッケージのテストをする
- go mod verifyを実行してダウンロードした依存パッケージが本当に正しいものか検証する
のが良いようです。
(ちなみにこれらは将来的に go release コマンドによって自動化されるかもとのこと)
これに習って CircleCI の設定を書くと、以下のようになります。
version: 2
jobs:
  build:
    docker: &docker
      - image: circleci/golang:1.11.0
        environment:
          GO111MODULE: "on"
    steps:
      - checkout
      - restore_cache: &restore_cache
          name: Restore go modules cache
          keys:
            - mod-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "go.mod" }}
      - run: &vendoring
          name: Vendoring
          command: go mod download
      - save_cache: &save_cache
          name: Save go modules cache
          key: mod-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "go.mod" }}
          paths:
            - /go/pkg/mod/cache
      - run:
          name: Run tests
          command: go test ./...
  deploy:
    docker: *docker
    steps:
      - checkout
      - restore_cache: *restore_cache
      - run: *vendoring
      - save_cache: *save_cache
      - run:
          name: Add missing and remove unused modules
          command: go mod tidy
      - run:
          name: Verify dependencies have expected content
          command: go mod verify
      - run:
          name: Run all tests
          command: go test all
      - deploy:
          name: Release
          command: curl -sL https://git.io/goreleaser | bash
workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build:
          # ref: https://circleci.com/docs/2.0/workflows/#git-tag-job-execution
          filters:
            tags:
              only: /.*/
      - deploy:
          requires:
            - build
          filters:
            tags:
              only: /.*/
            branches:
              ignore: /.*/
リリースに goreleaser を使っているのと、git のタグを打った時にリリースするというフローにしたため上記のようになっています。
$ go mod tidy, $ go mod verify, $ go test all はリリース前だけやれば良いかと思います (特に $ go test all は毎回やってると時間がかかるので)。