generate_pb.sh 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. #!/usr/bin/env bash
  2. # Regenerate the vendored Python protobuf + ConnectRPC bindings under
  3. # src/chattolib/_pb.
  4. #
  5. # Fetches the .proto sources for a pinned Chatto ref (default: the version
  6. # recorded in pyproject.toml, as a `v<version>` tag) and runs protoc with
  7. # the built-in Python generator and the connect-python plugin.
  8. #
  9. # ALWAYS pin to a released tag. Fetching from `main` risks shipping
  10. # unreleased wire changes that the deployed Chatto server does not speak
  11. # — chattolib 0.4.19 shipped that way and broke realtime for every
  12. # downstream client. Override the pin explicitly if you know what you are
  13. # doing:
  14. #
  15. # CHATTO_REF=v0.4.19 ./scripts/generate_pb.sh
  16. # CHATTO_REF=main ./scripts/generate_pb.sh # unreleased
  17. #
  18. # Requirements:
  19. # - protoc on PATH
  20. # - protoc-gen-connect-python on PATH (install via
  21. # `pip install protoc-gen-connect-python`, which chattolib pins under
  22. # the `[realtime]` extra)
  23. # - curl on PATH
  24. #
  25. # Run from the repo root: ./scripts/generate_pb.sh
  26. set -euo pipefail
  27. if ! command -v protoc >/dev/null; then
  28. echo "protoc not found on PATH" >&2
  29. exit 1
  30. fi
  31. repo_root=$(cd "$(dirname "$0")/.." && pwd)
  32. cd "$repo_root"
  33. # Default the Chatto ref to `v<base-version-in-pyproject.toml>`. Strip any
  34. # post/pre-release suffix — those are chattolib-only fixes against the same
  35. # server release. Callers can override with CHATTO_REF.
  36. default_version=$(sed -nE 's/^version *= *"([^"]+)".*/\1/p' pyproject.toml | head -1)
  37. default_base=${default_version%%.post*}
  38. default_base=${default_base%%.dev*}
  39. default_base=${default_base%%a*}
  40. default_base=${default_base%%b*}
  41. default_base=${default_base%%rc*}
  42. default_ref="v${default_base}"
  43. chatto_ref=${CHATTO_REF:-$default_ref}
  44. echo "Fetching proto sources from chattocorp/chatto@${chatto_ref}"
  45. mkdir -p proto/chatto/{api,admin,auth,discovery,realtime}/v1 proto/buf/validate
  46. chatto_base="https://raw.githubusercontent.com/chattocorp/chatto/${chatto_ref}/proto/chatto"
  47. fetch() {
  48. local rel=$1
  49. curl -sfL "$chatto_base/${rel#chatto/}" -o "proto/$rel"
  50. echo " $rel"
  51. }
  52. echo "Fetching proto sources ..."
  53. # chatto.api.v1
  54. for f in \
  55. account.proto attachments.proto asset_uploads.proto common.proto \
  56. external_identities.proto link_previews.proto member_directory.proto \
  57. messages.proto message_types.proto \
  58. notifications.proto pagination.proto permissions.proto presence.proto \
  59. push_notifications.proto reactions.proto read_state.proto \
  60. room_directory.proto rooms.proto room_timeline.proto roles.proto \
  61. server.proto server_state.proto threads.proto users.proto \
  62. user_status.proto viewer.proto voice_calls.proto
  63. do
  64. fetch "chatto/api/v1/$f"
  65. done
  66. # chatto.admin.v1
  67. for f in \
  68. diagnostics.proto event_log.proto members.proto permissions.proto \
  69. roles.proto room_layout.proto server.proto
  70. do
  71. fetch "chatto/admin/v1/$f"
  72. done
  73. # chatto.auth.v1
  74. fetch chatto/auth/v1/external_identity_auth.proto
  75. # chatto.discovery.v1
  76. fetch chatto/discovery/v1/server.proto
  77. # chatto.realtime.v1
  78. fetch chatto/realtime/v1/realtime.proto
  79. # buf.validate — sourced from bufbuild/protovalidate
  80. curl -sfL \
  81. "https://raw.githubusercontent.com/bufbuild/protovalidate/main/proto/protovalidate/buf/validate/validate.proto" \
  82. -o proto/buf/validate/validate.proto
  83. echo " buf/validate/validate.proto"
  84. echo "Regenerating Python bindings ..."
  85. rm -rf src/chattolib/_pb/chatto src/chattolib/_pb/buf
  86. mkdir -p src/chattolib/_pb
  87. # Collect every input .proto so protoc's plugins visit each service.
  88. mapfile -t proto_files < <(find proto -type f -name '*.proto' | sort)
  89. protoc --proto_path=proto \
  90. --python_out=src/chattolib/_pb \
  91. "${proto_files[@]}"
  92. # Only run the connect-python plugin when it is available. The realtime
  93. # WebSocket path only needs the pb2 files; every request/response service is
  94. # strongly typed via the connect stubs.
  95. if command -v protoc-gen-connect-python >/dev/null; then
  96. protoc --proto_path=proto \
  97. --connect-python_out=src/chattolib/_pb \
  98. "${proto_files[@]}"
  99. else
  100. echo "protoc-gen-connect-python not on PATH; skipped connect stubs" >&2
  101. fi
  102. # Add __init__.py at every level so the generated modules form a package.
  103. find src/chattolib/_pb \( -path '*/chatto*' -o -path '*/buf*' \) -type d \
  104. | while read -r dir; do touch "$dir/__init__.py"; done
  105. echo "Done."