diff --git a/.github/actions/check_artifact_size/action.yaml b/.github/actions/check_artifact_size/action.yaml new file mode 100644 index 000000000000..f7395bea675f --- /dev/null +++ b/.github/actions/check_artifact_size/action.yaml @@ -0,0 +1,140 @@ +name: Check Artifact Size +description: Check if the increase in artifact size exceeds the threshold, and if so, apply a label to the pull request. +inputs: + workflow: + description: "Workflow to check artifact binary size for." + required: true + name: + description: "Name of the uploaded artifact, artifact is a zip file that can contain more than one binary" + required: true + path: + description: "Path to the newly created binary artifacts being checked." + required: true + thresholds: + description: "Thresholds is a JSON-formatted string that specifies the maximum permissible percentage increase in the size of each respective binary artifact." + required: true + token: + description: "Github token needed for downloading artifacts." + required: true +runs: + using: "composite" + steps: + - name: 'Download artifact from main branch' + id: download-artifact + uses: actions/github-script@v6 + with: + github-token: ${{inputs.token}} + script: | + const fs = require('fs'); + const path = require('path'); + + // Get the latest successful workflow run on the main branch. + const workflowRuns = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: '${{ inputs.workflow }}.yaml', + branch: 'main', + status: 'success', + per_page: 1 + }); + + const latestRun = workflowRuns.data.workflow_runs[0].id; + + // Get the artifact uploaded on the latest successful workflow run on the main branch. + const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: latestRun + }); + + const matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == '${{ inputs.name }}'; + }); + + if (matchArtifacts.length == 1) { + console.log(`Found the latest uploaded artifact ${{ inputs.name }} on the main branch.`); + + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifacts[0].id, + archive_format: 'zip', + }); + + const downloadDir = path.join(process.env.GITHUB_WORKSPACE, 'artifact_tmp'); + fs.mkdirSync(downloadDir); + fs.writeFileSync(path.join(downloadDir, `${{ inputs.name }}.zip`), Buffer.from(download.data)); + + core.setOutput("downloadDir", downloadDir); + } else { + core.setFailed(`Expected one artifact with name ${{ inputs.name }}. Found ${matchArtifacts.length}.`); + } + + - name: 'Unzip artifact from main branch' + id: unzip-downloaded-artifact + shell: bash + run: | + unzip "${{ steps.download-artifact.outputs.downloadDir }}/${{ inputs.name }}.zip" -d "${{ steps.download-artifact.outputs.downloadDir }}" + + - name: 'Check new artifact size against main branch' + id: check-artifact-size + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + const fileSizeThresholds = JSON.parse('${{ inputs.thresholds }}'); + + for (let file in fileSizeThresholds) { + console.log(`Checking file size of ${file}.`); + + const downloadFilePath = path.join('${{ steps.download-artifact.outputs.downloadDir }}', file); + if (!fs.existsSync(downloadFilePath)) { + console.error(`File ${file} was not uploaded to the main branch.`); + continue; + } + + const filePath = path.join(process.env.GITHUB_WORKSPACE, '${{ inputs.path }}', file); + if (!fs.existsSync(filePath)) { + console.error(`File ${filePath} was not created in the current workflow run.`); + continue; + } + + const oldStats = fs.statSync(downloadFilePath); + const oldSize = oldStats.size; + const newStats = fs.statSync(filePath); + const newSize = newStats.size; + + console.log(`Latest uploaded artifact size on the main branch is ${oldSize / 1024}kB, new artifact size generated in this PR is ${newSize / 1024}kB.`); + + const deltaSize = newSize - oldSize; + const deltaThreshold = (Math.abs(deltaSize) / oldSize * 100).toFixed(4); + + if (deltaSize < 0) { + console.log(`Artifact size is decreased by ${Math.abs(deltaSize)} (${deltaThreshold}%).`); + } else { + console.log(`Artifact size is increased by ${deltaSize} (${deltaThreshold}%).`); + if (deltaThreshold > fileSizeThresholds[file]) { + const threshold = (fileSizeThresholds[file] * 100).toFixed(4); + console.error(`Artifact size increase exceeds threshold ${threshold}%.`); + core.setOutput("addLabel", true); + } + } + } + - name: 'Remove downloaded artifact' + id: remove-downloaded-artifact + shell: bash + run: rm -r "${{ steps.download-artifact.outputs.downloadDir }}" + - name: 'Add label for artifact size increase violation' + id: add-label + if: | + steps.check-artifact-size.outputs.addLabel && + github.event.pull_request.merged == true && + github.event.pull_request.merge_commit_sha != null + shell: bash + run: | + curl -s -X POST -H "Authorization: token ${{ inputs.token }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -d '["artifact size increase violation"]' \ + "https://api.github.com/repos/${{ github.event.repository.full_name }}/issues/${{ github.event.number }}/labels" diff --git a/.github/workflows/evergreen.yaml b/.github/workflows/evergreen.yaml index b9ca62af2a67..f299a90a87f4 100644 --- a/.github/workflows/evergreen.yaml +++ b/.github/workflows/evergreen.yaml @@ -31,7 +31,8 @@ jobs: platform: evergreen-x64 nightly: ${{ github.event.inputs.nightly }} run_api_leak_detector: true - keep_artifacts: libcobalt.* + keep_artifacts: install/lib/libcobalt.* + artifact_size_increase_thresholds: '{"install/lib/libcobalt.so": 0.02, "install/lib/libcobalt.lz4": 0.02}' evergreen-arm-hardfp: uses: ./.github/workflows/main.yaml permissions: @@ -41,7 +42,8 @@ jobs: platform: evergreen-arm-hardfp nightly: ${{ github.event.inputs.nightly }} run_api_leak_detector: true - keep_artifacts: libcobalt.* + keep_artifacts: install/lib/libcobalt.* + artifact_size_increase_thresholds: '{"install/lib/libcobalt.so": 0.02, "install/lib/libcobalt.lz4": 0.02}' evergreen-arm-softfp: uses: ./.github/workflows/main.yaml permissions: @@ -51,7 +53,8 @@ jobs: platform: evergreen-arm-softfp nightly: ${{ github.event.inputs.nightly }} run_api_leak_detector: true - keep_artifacts: libcobalt.* + keep_artifacts: install/lib/libcobalt.* + artifact_size_increase_thresholds: '{"install/lib/libcobalt.so": 0.02, "install/lib/libcobalt.lz4": 0.02}' evergreen-arm64: uses: ./.github/workflows/main.yaml permissions: @@ -61,4 +64,5 @@ jobs: platform: evergreen-arm64 nightly: ${{ github.event.inputs.nightly }} run_api_leak_detector: true - keep_artifacts: libcobalt.* + keep_artifacts: install/lib/libcobalt.* + artifact_size_increase_thresholds: '{"install/lib/libcobalt.so": 0.02, "install/lib/libcobalt.lz4": 0.02}' diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6f118069f965..2ff636b76ebe 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -30,10 +30,15 @@ on: type: boolean default: false keep_artifacts: - description: 'Which artifacts to keep for releases' + description: 'Which artifacts to keep for releases.' required: false type: string default: '' + artifact_size_increase_thresholds: + description: 'Threshold for artifact binary size increase.' + required: false + type: string + default: "" # Global env vars. env: @@ -257,6 +262,16 @@ jobs: uses: ./.github/actions/gn - name: Build Cobalt uses: ./.github/actions/build + - name: 'Check Artifact Size' + uses: ./.github/actions/check_artifact_size + if: ${{ inputs.artifact_size_increase_thresholds }} + continue-on-error: true # Ignore this step if check artifact size failed. + with: + workflow: ${{ github.workflow }} + name: ${{ matrix.platform }}-${{ matrix.config }} + path: out/${{ matrix.target_platform }}_${{ matrix.config }} + thresholds: ${{ inputs.artifact_size_increase_thresholds }} + token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload Artifact' uses: actions/upload-artifact@v4 if: ${{ inputs.keep_artifacts }}