name: API Proto CI on: pull_request: paths: - "esphome/components/api/api.proto" - "esphome/components/api/api_pb2.cpp" - "esphome/components/api/api_pb2.h" - "esphome/components/api/api_pb2_service.cpp" - "esphome/components/api/api_pb2_service.h" - "script/api_protobuf/api_protobuf.py" - ".github/workflows/ci-api-proto.yml" permissions: contents: read # actions/checkout for the PR head pull-requests: write # pulls.createReview / listReviews / dismissReview when generated proto files are stale jobs: check: name: Check generated files runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Set up uv # ``--system`` (below) installs into the setup-python interpreter; # no venv is created or restored by this workflow. uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true # Pull-request-only workflow: a save could never be shared and # would only consume quota. save-cache: "false" # Pin uv version so the action does not have to fetch the # manifest from raw.githubusercontent.com on every cache # miss; that fetch flakes on Windows runners. version: "0.11.15" - name: Install apt dependencies # PR-only workflow, so nothing on dev could seed a shared apt cache # entry; the cached apt action would save one copy per PR. Plain apt # with every call bounded: the apt.conf.d timeouts make a dead # mirror fail over in seconds, and timeout runs under sudo so it can # kill apt-get itself. Install without update first: image lists are # fresh, and the index refresh is what a congested mirror makes slow. timeout-minutes: 15 run: | sudo tee /etc/apt/apt.conf.d/99ci-acquire-timeouts >/dev/null <<'EOF' Acquire::Retries "1"; Acquire::http::Timeout "15"; Acquire::https::Timeout "15"; EOF # Common path: the image's package lists are fresh enough. if sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 90 \ apt-get install -y protobuf-compiler; then protoc --version exit 0 fi # Rescue path: refresh the lists once with a generous bound; the # apt config already fails a stalled mirror over quickly. sudo DEBIAN_FRONTEND=noninteractive timeout -k 10 30 \ dpkg --configure -a || true sudo timeout -k 15 300 apt-get update sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 300 \ apt-get install -y protobuf-compiler protoc --version - name: Install python dependencies run: uv pip install --system aioesphomeapi -c requirements.txt -r requirements_dev.txt - name: Generate files run: script/api_protobuf/api_protobuf.py - name: Check for changes run: | if ! git diff --quiet; then echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY echo "You have altered the generated proto files but they do not match what is expected." | tee -a $GITHUB_STEP_SUMMARY echo "Please run 'script/api_protobuf/api_protobuf.py' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY exit 1 fi - if: failure() name: Review PR uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | await github.rest.pulls.createReview({ pull_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, event: 'REQUEST_CHANGES', body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.' }) - if: failure() name: Show changes run: git diff - if: failure() name: Archive artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: generated-proto-files path: | esphome/components/api/api_pb2.* esphome/components/api/api_pb2_service.* - if: success() name: Dismiss review uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | let reviews = await github.rest.pulls.listReviews({ pull_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo }); for (let review of reviews.data) { if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') { await github.rest.pulls.dismissReview({ pull_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, review_id: review.id, message: 'Files now match the expected proto files.' }); } }