diff --git a/.api-linter.yaml b/.api-linter.yaml new file mode 100644 index 000000000..5417291f0 --- /dev/null +++ b/.api-linter.yaml @@ -0,0 +1,35 @@ +# Only AIP-140's reserved-word check is enabled. api-linter's default rule set +# encodes Google's whole API style guide, which these protos do not follow — it +# reports hundreds of problems per file. This one rule is about generated code +# being usable at all: a field whose name is a keyword in Java, JavaScript or +# Python 3 cannot be addressed by name in that language. +- included_paths: ["**"] + disabled_rules: ["core"] + enabled_rules: ["core::0140::reserved-words"] + +# Fields that predate the check. Suppressed here rather than with the in-proto +# `(-- api-linter: ... --)` comment, which protoc copies into the generated code +# of every language. +# +# livekit_rtc.proto TrickleRequest.final +# livekit_models.proto TranscriptionSegment.final +# On the wire since 2021; renaming breaks every SDK. `final` is reserved in +# Java alone, where the generator mangles it. +# +# agent/livekit_agent_session.proto FunctionCall.arguments +# Not a keyword in Python, and JavaScript restricts `arguments` only inside +# a strict-mode function body, not as a property name. +# +# rpc/io.proto SIPCall.from +# Names the SIP From header it carries. rpc/ is generated for Go only, so +# the Python keyword never reaches a stub — renaming is a prerequisite if +# that ever changes. +# +# The Python side of these files stays covered: the stub check in CI runs the +# generator itself, and no suppression reaches it. +- included_paths: + - "livekit_rtc.proto" + - "livekit_models.proto" + - "agent/livekit_agent_session.proto" + - "rpc/io.proto" + disabled_rules: ["core::0140::reserved-words"] diff --git a/.github/workflows/buildtest.yaml b/.github/workflows/buildtest.yaml index 87fe86b7d..04033cdbd 100644 --- a/.github/workflows/buildtest.yaml +++ b/.github/workflows/buildtest.yaml @@ -75,3 +75,56 @@ jobs: ~/.cache/go-build ~/go/pkg/mod key: ${{ steps.go-cache.outputs.cache-primary-key }} + + # A field name that is a keyword in a target language cannot be addressed by + # name in that language. protoc's Python stub generator silently degrades: + # `from` cost python-sdks a red mypy gate days later, on an unrelated PR. + proto-lint: + runs-on: ubuntu-latest + # setup-protoc uses the token only to raise its release-download rate limit. + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: "go.mod" + + - name: Install Protoc + uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0 + with: + version: "35.x" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install api-linter + run: go install github.com/googleapis/api-linter/cmd/api-linter@v1.72.0 + + # Run from protobufs/: api-linter resolves the files it lints against the + # working directory, and uses -I only for their imports. + - name: Check for reserved words in field names + run: | + set -euo pipefail + go mod download github.com/livekit/psrpc + psrpc=$(go list -m -f '{{.Dir}}' github.com/livekit/psrpc) + test -d "$psrpc/protoc-gen-psrpc/options" + psrpc="$psrpc/protoc-gen-psrpc/options" + cd protobufs + api-linter --config ../.api-linter.yaml --set-exit-status \ + -I . -I "$psrpc" \ + $(find . -name '*.proto' | sed 's|^\./||') + + # The reserved-word rule matches a list; this asks the generator. A stub + # falls back to an untyped **kwargs whenever protoc cannot emit a keyword + # argument for a field, whatever the reason. + - name: Check the Python stubs name every field + run: | + set -euo pipefail + out=$(mktemp -d) + protoc -I=protobufs --pyi_out="$out" \ + protobufs/livekit_*.proto protobufs/agent/*.proto protobufs/logger/*.proto + if grep -rn '\*\*kwargs' "$out"; then + echo "::error::a field name above is unaddressable in Python; rename it" >&2 + exit 1 + fi