diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml index 74cbbeabbed..7e69fed49ca 100644 --- a/.github/workflows/stats.yml +++ b/.github/workflows/stats.yml @@ -3,272 +3,282 @@ name: Stats Checks & Reports on: - workflow_dispatch: - schedule: - - cron: "30 02 * * *" + workflow_dispatch: + schedule: + - cron: "30 02 * * *" permissions: - pull-requests: write + pull-requests: write jobs: - find_open_pull_requests: - name: Find open PRs - runs-on: ubuntu-20.04 - outputs: - matrix: ${{ steps.compute-pull-request-matrix.outputs.matrix }} - env: - GH_TOKEN: ${{ github.token }} - steps: - - uses: actions/checkout@v4 + find_open_pull_requests: + name: Find open PRs + runs-on: ubuntu-20.04 + outputs: + matrix: ${{ steps.compute-pull-request-matrix.outputs.matrix }} + env: + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 - - name: Compute PR matrix - id: compute-pull-request-matrix - # Remove spaces to ensure the matrix output is on one line. Reference: - # https://stackoverflow.com/a/3232433. - run: | - CURRENT_OPEN_PR_INFO="$(gh pr list --json number,baseRefName,headRefName,headRepository,headRepositoryOwner | tr -d '[:space:]')" - echo "matrix={\"prInfo\": $CURRENT_OPEN_PR_INFO}" >> "$GITHUB_OUTPUT" + - name: Compute PR matrix + id: compute-pull-request-matrix + # Remove spaces to ensure the matrix output is on one line. Reference: + # https://stackoverflow.com/a/3232433. + run: | + CURRENT_OPEN_PR_INFO="$(gh pr list --json number,baseRefName,headRefName,headRepository,headRepositoryOwner | tr -d '[:space:]')" + echo "matrix={\"prInfo\": $CURRENT_OPEN_PR_INFO}" >> "$GITHUB_OUTPUT" - build_stats: - name: Build Stats - needs: find_open_pull_requests - runs-on: ubuntu-20.04 - # Reduce parallelization due to high build times, and allow individual PRs to fail. - strategy: - fail-fast: false - max-parallel: 5 - matrix: ${{ fromJson(needs.find_open_pull_requests.outputs.matrix) }} - env: - ENABLE_CACHING: false - CACHE_DIRECTORY: ~/.bazel_cache - steps: - - name: Compute PR head owner/repo reference - env: - PR_HEAD_REPO: ${{ matrix.prInfo.headRepository.name }} - PR_HEAD_REPO_OWNER: ${{ matrix.prInfo.headRepositoryOwner.login }} - run: | - echo "PR_HEAD=$PR_HEAD_REPO_OWNER/$PR_HEAD_REPO" >> "$GITHUB_ENV" - - name: Print PR information for this run - env: - PR_BASE_REF_NAME: ${{ matrix.prInfo.baseRefName }} - PR_HEAD_REF_NAME: ${{ matrix.prInfo.headRefName }} - PR_NUMBER: ${{ matrix.prInfo.number }} - run: | - echo "PR $PR_NUMBER is merging into $PR_BASE_REF_NAME from https://github.com/$PR_HEAD branch $PR_HEAD_REF_NAME." - - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - - name: Set up Bazel - uses: abhinavsingh/setup-bazel@v3 - with: - version: 6.5.0 - - # For reference on this & the later cache actions, see: - # https://github.com/actions/cache/issues/239#issuecomment-606950711 & - # https://github.com/actions/cache/issues/109#issuecomment-558771281. Note that these work - # with Bazel since Bazel can share the most recent cache from an unrelated build and still - # benefit from incremental build performance (assuming that actions/cache aggressively removes - # older caches due to the 5GB cache limit size & Bazel's large cache size). - - uses: actions/cache@v2 - id: cache - with: - path: ${{ env.CACHE_DIRECTORY }} - key: ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary- - ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel- - - # This check is needed to ensure that Bazel's unbounded cache growth doesn't result in a - # situation where the cache never updates (e.g. due to exceeding GitHub's cache size limit) - # thereby only ever using the last successful cache version. This solution will result in a - # few slower CI actions around the time cache is detected to be too large, but it should - # incrementally improve thereafter. - - name: Ensure cache size - env: - BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} - run: | - # See https://stackoverflow.com/a/27485157 for reference. - EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" - CACHE_SIZE_MB=$(du -smc $EXPANDED_BAZEL_CACHE_PATH | grep total | cut -f1) - echo "Total size of Bazel cache (rounded up to MBs): $CACHE_SIZE_MB" - # Use a 4.5GB threshold since actions/cache compresses the results, and Bazel caches seem - # to only increase by a few hundred megabytes across changes for unrelated branches. This - # is also a reasonable upper-bound (local tests as of 2021-03-31 suggest that a full build - # of the codebase (e.g. //...) from scratch only requires a ~2.1GB uncompressed/~900MB - # compressed cache). - if [[ "$CACHE_SIZE_MB" -gt 4500 ]]; then - echo "Cache exceeds cut-off; resetting it (will result in a slow build)" - rm -rf $EXPANDED_BAZEL_CACHE_PATH - fi + build_stats: + name: Build Stats + needs: find_open_pull_requests + runs-on: ubuntu-20.04 + # Reduce parallelization due to high build times, and allow individual PRs to fail. + strategy: + fail-fast: false + max-parallel: 5 + matrix: ${{ fromJson(needs.find_open_pull_requests.outputs.matrix) }} + env: + ENABLE_CACHING: false + CACHE_DIRECTORY: ~/.bazel_cache + steps: + - name: Find the latest APK & AAB Analysis Comment + uses: actions/github-script@v6 + id: find_latest_apk_aab_comment + with: + script: | + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ matrix.prInfo.number }}, + }); - - name: Configure Bazel to use a local cache - env: - BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} - run: | - EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" - echo "Using $EXPANDED_BAZEL_CACHE_PATH as Bazel's cache path" - echo "build --disk_cache=$EXPANDED_BAZEL_CACHE_PATH" >> $HOME/.bazelrc - shell: bash + for (let i = comments.length - 1; i >= 0; i--) { + if (comments[i].body.includes("# APK & AAB differences analysis")) { + const latestComment = comments[i]; + const commentBody = latestComment.body; + const commentDate = latestComment.created_at; - # This checks out the actual true develop branch separately to ensure that the stats check is - # run from the latest develop rather than the base branch (which might be different for - # chained PRs). - - name: Check out develop repository - uses: actions/checkout@v4 - with: - path: develop + require('fs').writeFileSync('latest_aab_comment_body.log', commentBody, 'utf8'); + core.setOutput("latest_aab_comment_created_at", commentDate); + + return + } + } - - name: Set up build environment - uses: ./develop/.github/actions/set-up-android-bazel-build-environment + - name: Track Commits following the latest APK & AAB Analysis Comment + uses: actions/github-script@v6 + id: track_commits + with: + script: | + const commits = await github.paginate(github.rest.pulls.listCommits, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: ${{ matrix.prInfo.number }}, + }); + + const latestCommentDate = "${{ steps.find_latest_apk_aab_comment.outputs.latest_aab_comment_created_at }}"; + const recentCommits = commits.filter(commit => { + const commitDate = new Date(commit.commit.committer.date); + return commitDate > new Date("${{ steps.find_latest_apk_aab_comment.outputs.latest_aab_comment_created_at }}"); + }); + + const recentCommitsCount = recentCommits.length; + if(recentCommitsCount > 0 || !latestCommentDate) { + core.setOutput("new_commits", "true"); + } else { + console.log("No new commits since the last APK & AAB report; Skipping redundant analysis."); + core.setOutput("new_commits", "false"); + } + + - name: Compute PR head owner/repo reference + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + PR_HEAD_REPO: ${{ matrix.prInfo.headRepository.name }} + PR_HEAD_REPO_OWNER: ${{ matrix.prInfo.headRepositoryOwner.login }} + run: | + echo "PR_HEAD=$PR_HEAD_REPO_OWNER/$PR_HEAD_REPO" >> "$GITHUB_ENV" + + - name: Print PR information for this run + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + PR_BASE_REF_NAME: ${{ matrix.prInfo.baseRefName }} + PR_HEAD_REF_NAME: ${{ matrix.prInfo.headRefName }} + PR_NUMBER: ${{ matrix.prInfo.number }} + run: | + echo "PR $PR_NUMBER is merging into $PR_BASE_REF_NAME from https://github.com/$PR_HEAD branch $PR_HEAD_REF_NAME." - - name: Check Bazel environment - run: | - cd develop - bazel info + - name: Set up JDK 11 + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + uses: actions/setup-java@v1 + with: + java-version: 11 - - name: Check out base repository and branch - env: - PR_BASE_REF_NAME: ${{ matrix.prInfo.baseRefName }} - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ env.PR_BASE_REF_NAME }} - path: base + - name: Set up Bazel + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + uses: abhinavsingh/setup-bazel@v3 + with: + version: 6.5.0 - - name: Check out head repository and branch - env: - PR_HEAD_REF_NAME: ${{ matrix.prInfo.headRefName }} - uses: actions/checkout@v4 - with: - fetch-depth: 0 - repository: ${{ env.PR_HEAD }} - ref: ${{ env.PR_HEAD_REF_NAME }} - path: head + # For reference on this & the later cache actions, see: + # https://github.com/actions/cache/issues/239#issuecomment-606950711 & + # https://github.com/actions/cache/issues/109#issuecomment-558771281. Note that these work + # with Bazel since Bazel can share the most recent cache from an unrelated build and still + # benefit from incremental build performance (assuming that actions/cache aggressively removes + # older caches due to the 5GB cache limit size & Bazel's large cache size). + - uses: actions/cache@v2 + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + id: cache + with: + path: ${{ env.CACHE_DIRECTORY }} + key: ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary- + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel- - # Note that Bazel is shutdown between builds since multiple Bazel servers will otherwise end - # up being active (due to multiple repositories being used) and this can quickly overwhelm CI - # worker resources. - - name: Build Oppia dev, alpha, beta, and GA (feature branch) - run: | - cd head - git log -n 1 - bazel build -- //:oppia_dev //:oppia_alpha //:oppia_beta //:oppia_ga - cp bazel-bin/oppia_dev.aab ../develop/oppia_dev_with_changes.aab - cp bazel-bin/oppia_alpha.aab ../develop/oppia_alpha_with_changes.aab - cp bazel-bin/oppia_beta.aab ../develop/oppia_beta_with_changes.aab - cp bazel-bin/oppia_ga.aab ../develop/oppia_ga_with_changes.aab - bazel shutdown + # This check is needed to ensure that Bazel's unbounded cache growth doesn't result in a + # situation where the cache never updates (e.g. due to exceeding GitHub's cache size limit) + # thereby only ever using the last successful cache version. This solution will result in a + # few slower CI actions around the time cache is detected to be too large, but it should + # incrementally improve thereafter. + - name: Ensure cache size + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + # See https://stackoverflow.com/a/27485157 for reference. + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + CACHE_SIZE_MB=$(du -smc $EXPANDED_BAZEL_CACHE_PATH | grep total | cut -f1) + echo "Total size of Bazel cache (rounded up to MBs): $CACHE_SIZE_MB" + # Use a 4.5GB threshold since actions/cache compresses the results, and Bazel caches seem + # to only increase by a few hundred megabytes across changes for unrelated branches. This + # is also a reasonable upper-bound (local tests as of 2021-03-31 suggest that a full build + # of the codebase (e.g. //...) from scratch only requires a ~2.1GB uncompressed/~900MB + # compressed cache). + if [[ "$CACHE_SIZE_MB" -gt 4500 ]]; then + echo "Cache exceeds cut-off; resetting it (will result in a slow build)" + rm -rf $EXPANDED_BAZEL_CACHE_PATH + fi - - name: Build Oppia dev, alpha, beta, and GA (base branch) - run: | - cd base - git log -n 1 - bazel build -- //:oppia_dev //:oppia_alpha //:oppia_beta //:oppia_ga - cp bazel-bin/oppia_dev.aab ../develop/oppia_dev_without_changes.aab - cp bazel-bin/oppia_alpha.aab ../develop/oppia_alpha_without_changes.aab - cp bazel-bin/oppia_beta.aab ../develop/oppia_beta_without_changes.aab - cp bazel-bin/oppia_ga.aab ../develop/oppia_ga_without_changes.aab - bazel shutdown + - name: Configure Bazel to use a local cache + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + echo "Using $EXPANDED_BAZEL_CACHE_PATH as Bazel's cache path" + echo "build --disk_cache=$EXPANDED_BAZEL_CACHE_PATH" >> $HOME/.bazelrc + shell: bash - - name: Run stats analysis tool (develop branch) - run: | - cd develop - git log -n 1 - bazel run //scripts:compute_aab_differences -- \ - $(pwd)/brief_build_summary.log $(pwd)/full_build_summary.log \ - dev $(pwd)/oppia_dev_without_changes.aab $(pwd)/oppia_dev_with_changes.aab \ - alpha $(pwd)/oppia_alpha_without_changes.aab $(pwd)/oppia_alpha_with_changes.aab \ - beta $(pwd)/oppia_beta_without_changes.aab $(pwd)/oppia_beta_with_changes.aab \ - ga $(pwd)/oppia_ga_without_changes.aab $(pwd)/oppia_ga_with_changes.aab + # This checks out the actual true develop branch separately to ensure that the stats check is + # run from the latest develop rather than the base branch (which might be different for + # chained PRs). + - name: Check out develop repository + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + uses: actions/checkout@v4 + with: + path: develop - - name: Find CI workflow run for PR - id: find-workflow-run - uses: actions/github-script@v7 - continue-on-error: true - with: - script: | - const { owner, repo } = context.repo; - const runsResponse = await github.rest.actions.listWorkflowRuns({ - owner, - repo, - workflow_id: 'stats.yml', - }); + - name: Set up build environment + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + uses: ./develop/.github/actions/set-up-android-bazel-build-environment - const runs = runsResponse.data.workflow_runs; - runs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + - name: Check Bazel environment + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + run: | + cd develop + bazel info - const run = runs[1]; - if(!run) { - core.setFailed('Could not find a succesful workflow run'); - return; - } - console.log(run.id); + - name: Check out base repository and branch + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + PR_BASE_REF_NAME: ${{ matrix.prInfo.baseRefName }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.PR_BASE_REF_NAME }} + path: base - core.setOutput('run-id', run.id); + - name: Check out head repository and branch + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + PR_HEAD_REF_NAME: ${{ matrix.prInfo.headRefName }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: ${{ env.PR_HEAD }} + ref: ${{ env.PR_HEAD_REF_NAME }} + path: head - - name: Download previous build summary - uses: actions/download-artifact@v4 - with: - name: brief_build_summary_${{ matrix.prInfo.number }} - path: ./previous_build_logs - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ steps.find-workflow-run.outputs.run-id }} - continue-on-error: true # Ignore errors if the file doesn't exist (first run) + # Note that Bazel is shutdown between builds since multiple Bazel servers will otherwise end + # up being active (due to multiple repositories being used) and this can quickly overwhelm CI + # worker resources. + - name: Build Oppia dev, alpha, beta, and GA (feature branch) + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + run: | + cd head + git log -n 1 + bazel build -- //:oppia_dev //:oppia_alpha //:oppia_beta //:oppia_ga + cp bazel-bin/oppia_dev.aab ../develop/oppia_dev_with_changes.aab + cp bazel-bin/oppia_alpha.aab ../develop/oppia_alpha_with_changes.aab + cp bazel-bin/oppia_beta.aab ../develop/oppia_beta_with_changes.aab + cp bazel-bin/oppia_ga.aab ../develop/oppia_ga_with_changes.aab + bazel shutdown - - name: Compare current build summary with the previous one - id: build-comparison - run: | - if [ -f ./develop/brief_build_summary.log ]; then - echo "Comparing current and previous build summaries..." - if diff ./develop/brief_build_summary.log ./previous_build_logs/brief_build_summary.log > /dev/null; then - echo "No changes detected; skipping comment." - echo "skip_comment=true" >> $GITHUB_ENV - else - echo "Changes detected; proceeding with the comment." - echo "skip_comment=false" >> $GITHUB_ENV - fi - else - echo "No previous summary found; proceeding with the comment." - echo "skip_comment=false" >> $GITHUB_ENV - fi + - name: Build Oppia dev, alpha, beta, and GA (base branch) + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + run: | + cd base + git log -n 1 + bazel build -- //:oppia_dev //:oppia_alpha //:oppia_beta //:oppia_ga + cp bazel-bin/oppia_dev.aab ../develop/oppia_dev_without_changes.aab + cp bazel-bin/oppia_alpha.aab ../develop/oppia_alpha_without_changes.aab + cp bazel-bin/oppia_beta.aab ../develop/oppia_beta_without_changes.aab + cp bazel-bin/oppia_ga.aab ../develop/oppia_ga_without_changes.aab + bazel shutdown - - name: Upload current build summary for future comparison - uses: actions/upload-artifact@v4 - with: - name: brief_build_summary_${{ matrix.prInfo.number }} - path: ./develop/brief_build_summary.log + - name: Run stats analysis tool (develop branch) + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + run: | + cd develop + git log -n 1 + bazel run //scripts:compute_aab_differences -- \ + $(pwd)/brief_build_summary.log $(pwd)/full_build_summary.log \ + dev $(pwd)/oppia_dev_without_changes.aab $(pwd)/oppia_dev_with_changes.aab \ + alpha $(pwd)/oppia_alpha_without_changes.aab $(pwd)/oppia_alpha_with_changes.aab \ + beta $(pwd)/oppia_beta_without_changes.aab $(pwd)/oppia_beta_with_changes.aab \ + ga $(pwd)/oppia_ga_without_changes.aab $(pwd)/oppia_ga_with_changes.aab - # Reference: https://github.com/peter-evans/create-or-update-comment#setting-the-comment-body-from-a-file. - # Also, for multi-line env values, see: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings. - - name: Extract reports for uploading & commenting - env: - PR_NUMBER: ${{ matrix.prInfo.number }} - id: compute-comment-body - run: | - { - echo 'comment_body<> "$GITHUB_OUTPUT" - FULL_BUILD_SUMMARY_FILE_NAME="full_build_summary_pr_$PR_NUMBER.log" - FULL_BUILD_SUMMARY_FILE_PATH="$GITHUB_WORKSPACE/develop/$FULL_BUILD_SUMMARY_FILE_NAME" - echo "FULL_BUILD_SUMMARY_FILE_NAME=$FULL_BUILD_SUMMARY_FILE_NAME" >> "$GITHUB_ENV" - echo "FULL_BUILD_SUMMARY_FILE_PATH=$FULL_BUILD_SUMMARY_FILE_PATH" >> "$GITHUB_ENV" - cp "$GITHUB_WORKSPACE/develop/full_build_summary.log" "$FULL_BUILD_SUMMARY_FILE_PATH" + # Reference: https://github.com/peter-evans/create-or-update-comment#setting-the-comment-body-from-a-file. + # Also, for multi-line env values, see: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings. + - name: Extract reports for uploading & commenting + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + PR_NUMBER: ${{ matrix.prInfo.number }} + id: compute-comment-body + run: | + { + echo 'comment_body<> "$GITHUB_OUTPUT" + FULL_BUILD_SUMMARY_FILE_NAME="full_build_summary_pr_$PR_NUMBER.log" + FULL_BUILD_SUMMARY_FILE_PATH="$GITHUB_WORKSPACE/develop/$FULL_BUILD_SUMMARY_FILE_NAME" + echo "FULL_BUILD_SUMMARY_FILE_NAME=$FULL_BUILD_SUMMARY_FILE_NAME" >> "$GITHUB_ENV" + echo "FULL_BUILD_SUMMARY_FILE_PATH=$FULL_BUILD_SUMMARY_FILE_PATH" >> "$GITHUB_ENV" + cp "$GITHUB_WORKSPACE/develop/full_build_summary.log" "$FULL_BUILD_SUMMARY_FILE_PATH" - - name: Add build stats summary comment - if: ${{ env.skip_comment == 'false' }} - env: - PR_NUMBER: ${{ matrix.prInfo.number }} - uses: peter-evans/create-or-update-comment@v1 - with: - issue-number: ${{ env.PR_NUMBER }} - body: ${{ steps.compute-comment-body.outputs.comment_body }} + - name: Add build stats summary comment + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + env: + PR_NUMBER: ${{ matrix.prInfo.number }} + uses: peter-evans/create-or-update-comment@v1 + with: + issue-number: ${{ env.PR_NUMBER }} + body: ${{ steps.compute-comment-body.outputs.comment_body }} - - uses: actions/upload-artifact@v4 - with: - name: ${{ env.FULL_BUILD_SUMMARY_FILE_NAME }} - path: ${{ env.FULL_BUILD_SUMMARY_FILE_PATH }} + - uses: actions/upload-artifact@v4 + if: ${{ steps.track_commits.outputs.new_commits == 'true' }} + with: + name: ${{ env.FULL_BUILD_SUMMARY_FILE_NAME }} + path: ${{ env.FULL_BUILD_SUMMARY_FILE_PATH }}