LoginSignup
8
2

More than 3 years have passed since last update.

gRPCのコード生成をCircleCIで自動化する

Last updated at Posted at 2018-09-28

はじめに

gRPCではクライアントとサーバーのインターフェースを一致させる事ができますが
クライアントとスタブのコード生成がずれると通信できなくなります。
また各言語向けのprotocol buffer compilerを環境構築するのは面倒です。
そこで諸々の環境が揃ったDockerイメージのnamely/protoc-allとCircleCIを組合せてコード生成を自動化します。

構成

以下の図とブランチ構成でコード生成の自動化を行います。

plantuml.png

  • masterブランチ
    • .circleci/config.yml
    • docker-compose.yml
    • protos/echo.proto
  • Go用のgenerated/goブランチ
  • C#用のgenerated/csharpブランチ

また実際の試したものは以下のリポジトリに置いています。

https://github.com/shiena/grpc-gen-circleci
https://github.com/shiena/grpc-gen-circleci/tree/generated/go
https://github.com/shiena/grpc-gen-circleci/tree/generated/csharp

各ファイルについて

docker-compose.yml

docker-compose.yml
version: "3"
services:
    lint:
        image: namely/protoc-all:1.16_0
        volumes:
            - .:/defs
        entrypoint: "sh -c"
        command: '"protoc -I/usr/local/include -I. --lint_out=. ./protos/*.proto"'
    go:
        image: namely/protoc-all:1.16_0
        volumes:
            - .:/defs
        command: "-d ./protos -o ./pb-go --with-docs markdown,readme.md --with-gateway -l go"
    csharp:
        image: namely/protoc-all:1.16_0
        volumes:
            - .:/defs
        command: "-d ./protos -o ./pb-csharp --with-docs markdown,readme.md -l csharp"

docker-compose.ymlではlintとGoのコード生成とC#のコード生成を例にサービスを定義します。

  • lint
    • *.protoをワイルドカード展開したいので敢えてentrypointとcommandを設定しています。
  • go
    • クライアント、スタブと一緒にgrpc-gateway用のコード(--with-gateway)とドキュメント(--with-docs)も一緒に生成します。
  • csharp
    • こちらもクライアント、スタブと一緒にドキュメント(--with-docs)を生成します。

またentrypointに設定されているコマンドは以下のようなオプションがあります。

gen-proto generates grpc and protobuf @ Namely

Usage: gen-proto -f my-service.proto -l go

options:
 -h, --help           Show help
 -f FILE              The proto source file to generate
 -d DIR               Scans the given directory for all proto files
 -l LANGUAGE          The language to generate (go ruby csharp java python objc gogo php node)
 -o DIRECTORY         The output directory for generated files. Will be automatically created.
 -i includes          Extra includes
 --lint CHECKS        Enable linting protoc-lint (CHECKS are optional - see https://github.com/ckaznocha/protoc-gen-lint#optional-checks)
 --with-gateway       Generate grpc-gateway files (experimental).
 --with-docs FORMAT   Generate documentation (FORMAT is optional - see https://github.com/pseudomuto/protoc-gen-doc#invoking-the-plugin)
 --go-source-relative Make go import paths 'source_relative' - see https://github.com/golang/protobuf#parameters

.circleci/config.yml

.circleci/config.yml
version: 2

references:
  defaults: &defaults
    working_directory: ~/grpc-gen
    machine: true

jobs:
  lint:
    <<: *defaults
    steps:
      - checkout
      - run:
          name: Lint
          command: docker-compose run --rm lint
      - persist_to_workspace:
          root: .
          paths:
            - .

  build:
    <<: *defaults
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Clean up
          command: rm -rf pb-go pb-csharp
      - run:
          name: Generate gRPC for go
          command: docker-compose run --rm go
      - run:
          name: Generate gRPC for csharp
          command: docker-compose run --rm csharp
      - persist_to_workspace:
          root: .
          paths:
            - .

  push:
    <<: *defaults
    steps:
      - attach_workspace:
          at: .
      - run:
          name: git config
          command: |
            git config user.email "shiena.jp@gmail.com"
            git config user.name "Generator Bot"
            git remote add upstream https://${GH_TOKEN}@github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}.git
      - run:
          name: git commit go-src
          command: |
            if [ `git branch -r --list origin/generated/go | wc -l` -eq 1 ]; then
              echo "checkout generated/go"
              git fetch origin
              git worktree add -b generated/go ../go-src origin/generated/go
              git -C ../go-src rm `git -C ../go-src ls-files`
            else
              echo "create generated/go"
              git worktree add --detach ../go-src
              git -C ../go-src checkout --orphan generated/go
              git -C ../go-src rm --cached -r .
              git -C ../go-src clean -d -f
            fi
            cp -a pb-go/* ../go-src/
            git -C ../go-src add -A
            git -C ../go-src status
            result=0
            git -C ../go-src commit -m "AUTO GENERATED [ci skip]" || result=$?
            if [ $result -eq 0 ]; then
              git -C ../go-src push upstream generated/go 2> /dev/null
            fi
      - run:
          name: git commit csharp-src
          command: |
            if [ `git branch -r --list origin/generated/csharp | wc -l` -eq 1 ]; then
              echo "checkout generated/csharp"
              git fetch origin
              git worktree add -b generated/csharp ../csharp-src origin/generated/csharp
              git -C ../csharp-src rm `git -C ../csharp-src ls-files`
            else
              echo "create generated/csharp"
              git worktree add --detach ../csharp-src
              git -C ../csharp-src checkout --orphan generated/csharp
              git -C ../csharp-src rm --cached -r .
              git -C ../csharp-src clean -d -f
            fi
            cp -a pb-csharp/* ../csharp-src/
            git -C ../csharp-src add -A
            git -C ../csharp-src status
            result=0
            git -C ../csharp-src commit -m "AUTO GENERATED [ci skip]" || result=$?
            if [ $result -eq 0 ]; then
              git -C ../csharp-src push upstream generated/csharp 2> /dev/null
            fi

workflows:
  version: 2
  build_and_push:
    jobs:
      - lint
      - build:
          requires:
            - lint
          filters:
            branches:
              only:
                - master
      - push:
          requires:
            - lint
            - build
          filters:
            branches:
              only:
                - master

CircleCIでは以下の3つのジョブを作っています。

  • protoの文法をチェックするlintジョブ
  • クライアントとスタブとドキュメントを生成するbuildジョブ
  • 各言語毎のブランチに生成物をcommit -> pushするpushジョブ

この中で一番大きなpushジョブは次の処理を行います。

  1. git configで必須項目を設定する。${GH_TOKEN}はGitHubのPersonal access tokenを設定する
  2. commit対象のbranchをgit worktreeでcheckoutする
  3. protoのファイル名変更や削除も反映するために一旦全部削除する
  4. 生成したファイルをgit commitする。このブランチはCircleCIのジョブを動かしたくないのでコメントに[ci skip]を入れる
  5. 差分があればgit pushする。ここでエラーになるとPersonal access tokenが出力されてしまうので標準エラーを/dev/nullに捨てる

以上の設定でそれぞれのブランチに生成されたコードがpushされるのでsubmoduleで取り込むことができます。

gRPCのバージョン

namely/protoc-allはgRPCの1.14.xブランチをcloneしているのでどのコミットでビルドされたものか分かりません。
そこでv1.15.1のリリースタグをcloneしてビルドしたDockerイメージを作りました

最新版はv1.16.xブランチからビルドされています。

参考リンク

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2