Docker で PHP 用の gRPC スタブを生成する

gRPC のスタブコードを生成するには protoc と protoc プラグインgrpc_php_plugin が必要です。 protocmac でも容易に導入できるのですが、 grpc_php_plugin は自前でビルドする必要がある(=ビルド環境を整える必要がある)ため、ローカル環境を汚さないよう Docker を用いてビルドおよびスタブの生成を行いました。 この記事はその備忘録です。

生成するスタブの例として、Open Match の gRPC クライアントを生成します。

Docker で protoc および grpc_php_plugin を準備する

今回は alpine をベースにします。 結論から言うと、以下の Dockerfile で作れます。

FROM alpine:3.12

RUN apk add --no-cache --allow-untrusted \
    libtool \
    libstdc++ \
    linux-headers \
    zlib-dev \
    protoc \
    && apk add --virtual=.build-deps --no-cache --allow-untrusted \
    gcc \
    g++ \
    git \
    make \
    automake \
    autoconf \
    && git clone --recursive -b v1.34.1 https://github.com/grpc/grpc \
    && cd grpc \
    && make grpc_php_plugin \
    && mv bins/opt/grpc_php_plugin /usr/local/bin \
    && apk del .build-deps \
    && apk del *-dev \
    && cd .. \
    && rm -rf grpc \
    && mkdir -p /project

WORKDIR /project

実行するときに便利なので docker-compose.yml も用意しておきます。

version: "3"
services:
  protoc:
    build:
      context: .
    volumes:
      - .:/project

ちなみに

grpc_php_plugin は v1.35 以降でビルドの方法が変わっているようです。 可能であればビルドの方法を調べて書き換えるほうがいいかも。

.proto の依存を解決する

スタブを生成する環境は整ったので、実際に .proto ファイルからスタブを生成しましょう。

そのためにはまず、 .proto ファイルを取得する必要があります。 Open Match の API を定義する .proto ファイルは GitHub から入手することができます。 また、Open Match の frontend サービスの API を定義する frontend.protoAPI のリクエスト・レスポンスを表現する message.protoサードパーティ.proto に依存しています。 スタブを生成するためにはこれらを取得・管理する必要がありますが、バージョンなどの違いを考慮するとなかなか面倒な作業です。

.proto の管理は protodep というツールを導入するといい感じにできるので、今回はこれを導入します。

protodep は Go で書かれたツールなので、 go install でインストールできます。

$ go install github.com/stormcat24/protodep@0.1.3

インストールできたら、プロジェクトルートに protodep.toml を用意しましょう。

proto_outdir = "./third_party"

[[dependencies]]
  target = "github.com/googleapis/googleapis/google/api"
  revision = "aba342359b6743353195ca53f944fe71e6fb6cd4"
  path = "google/api"

[[dependencies]]
  target = "github.com/googleapis/googleapis/google/rpc"
  revision = "aba342359b6743353195ca53f944fe71e6fb6cd4"
  path = "google/rpc"

[[dependencies]]
  target = "github.com/protocolbuffers/protobuf/src/google/protobuf"
  revision = "v3.13.0"
  path = "google/protobuf"

[[dependencies]]
  target = "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options"
  revision = "v1.14.3"
  path = "protoc-gen-swagger/options"

[[dependencies]]
  target = "github.com/googleforgames/open-match/api"
  revision = "v1.0.0"
  path = "open-match/api"

protodep を用いて依存を解決します。

$ mkdir third_party
$ protodep up --use-https

これで third_party ディレクトリに依存をダウンロードすることができました。

補足

mac に protoc をインストールした場合、 protodep.toml に定義した依存のうち、3つめの github.com/protocolbuffers/protobuf 配下の .proto ファイルは /usr/local/include に入ってきていたのですが、 alpine では入ってきませんでした。 そのため、今回はこれも protodep で管理しています。

スタブコードを生成する

用意したビルド環境と定義ファイルを用いて、スタブを生成しましょう。 以下のコマンドで src/Openmatch ディレクトリにスタブを生成できます。

$ docker-compose run -w /project/third_party/open-match protoc protoc \
    -I .:.. \
    --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin \
    --php_out=../../src/Openmatch \
    --grpc_out=../../src/Openmatch \
    api/messages.proto api/frontend.proto

このとき、生成されたコードの namespace は .proto ファイルのパッケージ名から生成されます。 生成されたコードを autoload するためには PSR-4 の設定か namespace の変更のいずれかが必要です。

PSR-4 の設定は composer.json に記述できます。 composer.json の autoload -> psr-4 に namespace とディレクトリの対応を記述してください。

{
    ...
    "autoload": {
        "psr-4": {
            "Project\\Namespace" : "src",
            "GPBMetadata\\Api\\": "src/ThirdParty/GPBMetadata/Api",
            "GPBMetadata\\ProtocGenSwagger\\": "src/ThirdParty/GPBMetadata/ProtocGenSwagger",
            "Openmatch\\": "src/ThirdParty/Openmatch"
        }
    }
}

namespace を変更する場合は、生成対象の .proto ファイル(今回では messages.protofrontend.proto )に option を追加しましょう。 追加する位置はもともと存在した option の直下あたりでいいのではないかと思います。

// 先頭のコメントは省略

syntax = "proto3";
package openmatch;
option go_package = "open-match.dev/open-match/pkg/pb";
option csharp_namespace = "OpenMatch";
option php_namespace = "Path\\To\\Openmatch\\Extension"; // ←ココ
option php_metadata_namespace = "Path\\To\\Openmatch\\GPBMetadata"; // ←ココも

// 後略