-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add size change report #9860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Add size change report #9860
Changes from all commits
bbbdaf0
1602d47
948c28a
e30294b
8fa5b4f
c77b10a
5a8ce60
723209b
29bce48
5716aeb
9c79b45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,8 +15,7 @@ on: | |
| - "**.md" | ||
| - version.properties | ||
|
|
||
| # Note: This is different from "pull_request". Need to specify ref when doing checkouts. | ||
| pull_request_target: | ||
| pull_request: | ||
| branches: | ||
| - master | ||
| - develop | ||
|
|
@@ -88,8 +87,6 @@ jobs: | |
| - uses: actions/checkout@v6 | ||
| with: | ||
| submodules: recursive | ||
| ref: ${{github.event.pull_request.head.ref}} | ||
| repository: ${{github.event.pull_request.head.repo.full_name}} | ||
| - name: Check ${{ matrix.check.board }} | ||
| uses: meshtastic/gh-action-firmware@main | ||
| with: | ||
|
|
@@ -173,9 +170,6 @@ jobs: | |
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| ref: ${{github.event.pull_request.head.ref}} | ||
| repository: ${{github.event.pull_request.head.repo.full_name}} | ||
|
|
||
| - uses: actions/download-artifact@v8 | ||
| with: | ||
|
|
@@ -238,47 +232,169 @@ jobs: | |
| description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| shame: | ||
| firmware-size-report: | ||
| if: github.repository == 'meshtastic/firmware' | ||
| continue-on-error: true | ||
| permissions: | ||
| contents: read | ||
| actions: read | ||
| runs-on: ubuntu-latest | ||
| needs: [build] | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| if: github.event_name == 'pull_request_target' | ||
| with: | ||
| filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head) | ||
| fetch-depth: 0 | ||
| - name: Download the current manifests | ||
|
|
||
|
Comment on lines
244
to
+245
|
||
| - name: Download current manifests | ||
| uses: actions/download-artifact@v8 | ||
| with: | ||
| path: ./manifests-new/ | ||
| path: ./manifests/ | ||
| pattern: manifest-* | ||
| merge-multiple: true | ||
| - name: Upload combined manifests for later commit and global stats crunching. | ||
|
|
||
| - name: Collect current firmware sizes | ||
| run: python3 bin/collect_sizes.py ./manifests/ ./current-sizes.json | ||
|
|
||
| - name: Upload size report artifact | ||
| uses: actions/upload-artifact@v7 | ||
| id: upload-manifest | ||
| with: | ||
| name: manifests-${{ github.sha }} | ||
| name: firmware-sizes-${{ github.sha }} | ||
| overwrite: true | ||
| path: manifests-new/*.mt.json | ||
| - name: Find the merge base | ||
| if: github.event_name == 'pull_request_target' | ||
| run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV | ||
| path: ./current-sizes.json | ||
| retention-days: 90 | ||
|
|
||
| - name: Download baseline sizes from develop | ||
| if: github.event_name == 'pull_request' | ||
| continue-on-error: true | ||
| id: baseline-develop | ||
| env: | ||
| base: ${{ github.base_ref }} | ||
| head: ${{ github.sha }} | ||
| # Currently broken (for-loop through EVERY artifact -- rate limiting) | ||
| # - name: Download the old manifests | ||
| # if: github.event_name == 'pull_request_target' | ||
| # run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/ | ||
| # env: | ||
| # GH_TOKEN: ${{ github.token }} | ||
| # merge_base: ${{ env.MERGE_BASE }} | ||
| # repo: ${{ github.repository }} | ||
| # - name: Do scan and post comment | ||
| # if: github.event_name == 'pull_request_target' | ||
| # run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/ | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| # Find the latest successful CI run on develop and get its firmware-sizes artifact | ||
| RUN_ID=$(gh run list -R "${{ github.repository }}" \ | ||
| --workflow CI --branch develop --status success \ | ||
| --limit 1 --json databaseId --jq '.[0].databaseId // empty') | ||
| if [ -n "$RUN_ID" ]; then | ||
|
Comment on lines
+272
to
+275
|
||
| ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \ | ||
| --jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | .name' | head -1) | ||
| if [ -n "$ARTIFACT_NAME" ]; then | ||
| gh run download "$RUN_ID" -R "${{ github.repository }}" \ | ||
| --name "$ARTIFACT_NAME" --dir ./baseline-develop/ | ||
| cp ./baseline-develop/current-sizes.json ./develop-sizes.json | ||
| echo "found=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| else | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Download baseline sizes from master | ||
| if: github.event_name == 'pull_request' | ||
| continue-on-error: true | ||
| id: baseline-master | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| RUN_ID=$(gh run list -R "${{ github.repository }}" \ | ||
| --workflow CI --branch master --status success \ | ||
| --limit 1 --json databaseId --jq '.[0].databaseId // empty') | ||
| if [ -n "$RUN_ID" ]; then | ||
| ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \ | ||
| --jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | .name' | head -1) | ||
| if [ -n "$ARTIFACT_NAME" ]; then | ||
| gh run download "$RUN_ID" -R "${{ github.repository }}" \ | ||
| --name "$ARTIFACT_NAME" --dir ./baseline-master/ | ||
| cp ./baseline-master/current-sizes.json ./master-sizes.json | ||
| echo "found=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| else | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Generate size comparison report | ||
| if: github.event_name == 'pull_request' | ||
| id: report | ||
| run: | | ||
| ARGS="./current-sizes.json" | ||
| if [ -f ./develop-sizes.json ]; then | ||
| ARGS="$ARGS --baseline develop:./develop-sizes.json" | ||
| fi | ||
| if [ -f ./master-sizes.json ]; then | ||
| ARGS="$ARGS --baseline master:./master-sizes.json" | ||
| fi | ||
| REPORT=$(python3 bin/size_report.py $ARGS) | ||
| if [ -z "$REPORT" ]; then | ||
| echo "has_report=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "has_report=true" >> "$GITHUB_OUTPUT" | ||
| { | ||
| echo '<!-- firmware-size-report -->' | ||
| echo '# Firmware Size Report' | ||
| echo '' | ||
| echo "$REPORT" | ||
| echo '' | ||
| echo '---' | ||
| echo "*Updated for ${{ github.sha }}*" | ||
| } > ./size-report.md | ||
| cat ./size-report.md >> "$GITHUB_STEP_SUMMARY" | ||
| fi | ||
|
|
||
| - name: Upload size report | ||
| if: github.event_name == 'pull_request' && steps.report.outputs.has_report == 'true' | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: size-report | ||
| path: ./size-report.md | ||
| retention-days: 5 | ||
|
|
||
| firmware-size-comment: | ||
| if: github.event_name == 'pull_request' && github.repository == 'meshtastic/firmware' | ||
| continue-on-error: true | ||
| permissions: | ||
| pull-requests: write | ||
| runs-on: ubuntu-latest | ||
| needs: [firmware-size-report] | ||
| steps: | ||
| - name: Download size report | ||
| id: download | ||
| uses: actions/download-artifact@v8 | ||
| continue-on-error: true | ||
| with: | ||
| name: size-report | ||
|
|
||
| - name: Post or update PR comment | ||
| if: steps.download.outcome == 'success' | ||
| uses: actions/github-script@v8 | ||
|
Comment on lines
+366
to
+368
|
||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| const marker = '<!-- firmware-size-report -->'; | ||
| const body = fs.readFileSync('./size-report.md', 'utf8'); | ||
| const prNumber = context.payload.pull_request.number; | ||
|
|
||
| const { data: comments } = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| }); | ||
|
|
||
| const existing = comments.find(c => c.body.includes(marker)); | ||
| if (existing) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: existing.id, | ||
| body, | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| body, | ||
| }); | ||
| } | ||
|
|
||
| release-artifacts: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| """Collect firmware binary sizes from manifest (.mt.json) files into a single report.""" | ||
|
|
||
| import json | ||
| import os | ||
| import sys | ||
|
|
||
|
|
||
| def collect_sizes(manifest_dir): | ||
| """Scan manifest_dir for .mt.json files and return {board: size_bytes} dict.""" | ||
| sizes = {} | ||
| for fname in sorted(os.listdir(manifest_dir)): | ||
| if not fname.endswith(".mt.json"): | ||
| continue | ||
| path = os.path.join(manifest_dir, fname) | ||
| with open(path) as f: | ||
| data = json.load(f) | ||
| board = data.get("platformioTarget", fname.replace(".mt.json", "")) | ||
| # Find the main firmware .bin size (largest .bin, excluding OTA/littlefs/bleota) | ||
| bin_size = None | ||
| for entry in data.get("files", []): | ||
| name = entry.get("name", "") | ||
| if name.startswith("firmware-") and name.endswith(".bin"): | ||
| bin_size = entry["bytes"] | ||
| break | ||
| # Fallback: any .bin that isn't ota/littlefs/bleota | ||
| if bin_size is None: | ||
| for entry in data.get("files", []): | ||
| name = entry.get("name", "") | ||
| if name.endswith(".bin") and not any( | ||
| x in name for x in ["littlefs", "bleota", "ota"] | ||
| ): | ||
| bin_size = entry["bytes"] | ||
| break | ||
| if bin_size is not None: | ||
| sizes[board] = bin_size | ||
| return sizes | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| if len(sys.argv) != 3: | ||
| print(f"Usage: {sys.argv[0]} <manifest_dir> <output.json>", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| manifest_dir = sys.argv[1] | ||
| output_path = sys.argv[2] | ||
|
|
||
| sizes = collect_sizes(manifest_dir) | ||
| with open(output_path, "w") as f: | ||
| json.dump(sizes, f, indent=2, sort_keys=True) | ||
|
|
||
| print(f"Collected sizes for {len(sizes)} targets -> {output_path}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This job runs on
pull_request_targetand checks out/runs code from the PR branch while grantingpull-requests: write(andactions: read). That combination allows untrusted PR code to use the elevatedGITHUB_TOKENto modify PR metadata/comments (and potentially exfiltrate token-scoped data). Consider generating the report using code from the base branch (checkoutgithub.event.pull_request.base.shaor default branch) and only using PR artifacts as inputs, or split into two jobs so the commenting job never executes PR-provided code.