From 5af68d5e2072e31cfe0ffcbb8746d2fc73e2ed9d Mon Sep 17 00:00:00 2001 From: Arseniy Obolenskiy Date: Fri, 29 May 2026 01:24:52 +0200 Subject: [PATCH] [CI] Scope PR builds to changed tasks only if applicable Run build for one student task only in 'pull_request' scope. All tests are being run on push event --- .github/workflows/mac.yml | 7 ++ .github/workflows/main.yml | 36 ++++++++++ .github/workflows/static-analysis-pr.yml | 24 +++++++ .github/workflows/ubuntu.yml | 9 +++ .github/workflows/windows.yml | 8 +++ scripts/detect_ci_task_scope.py | 86 ++++++++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 scripts/detect_ci_task_scope.py diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 617c7b32..5bb8dd89 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -2,6 +2,12 @@ name: macOS on: workflow_call: + inputs: + ppc_tasks: + description: 'Tasks to build' + required: false + type: string + default: all permissions: contents: read @@ -37,6 +43,7 @@ jobs: -DCMAKE_C_FLAGS="-I$(brew --prefix)/opt/libomp/include" -DCMAKE_CXX_FLAGS="-I$(brew --prefix)/opt/libomp/include" -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_INSTALL_PREFIX=install + -D PPC_TASKS="${{ inputs.ppc_tasks }}" - name: Build project run: | cmake --build build --parallel -- --quiet diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37dccfd6..7298b95e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,22 +16,58 @@ concurrency: !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }} jobs: + ci-scope: + runs-on: ubuntu-24.04 + outputs: + ppc_tasks: ${{ steps.detect.outputs.ppc_tasks }} + task_scoped: ${{ steps.detect.outputs.task_scoped }} + steps: + - uses: actions/checkout@v6 + if: ${{ github.event_name == 'pull_request' }} + with: + fetch-depth: 0 + - name: Detect CI task scope + id: detect + env: + EVENT_NAME: ${{ github.event_name }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + if [ "$EVENT_NAME" != "pull_request" ]; then + echo "ppc_tasks=all" >> "$GITHUB_OUTPUT" + echo "task_scoped=false" >> "$GITHUB_OUTPUT" + echo "PPC_TASKS=all" + exit 0 + fi + + python3 scripts/detect_ci_task_scope.py \ + --base-sha "$BASE_SHA" \ + --head-sha "$HEAD_SHA" \ + --github-output "$GITHUB_OUTPUT" pre-commit: uses: ./.github/workflows/pre-commit.yml ubuntu: needs: + - ci-scope - pre-commit uses: ./.github/workflows/ubuntu.yml with: is_nightly: ${{ github.event_name == 'schedule' }} + ppc_tasks: ${{ needs.ci-scope.outputs.ppc_tasks || 'all' }} mac: needs: + - ci-scope - pre-commit uses: ./.github/workflows/mac.yml + with: + ppc_tasks: ${{ needs.ci-scope.outputs.ppc_tasks || 'all' }} windows: needs: + - ci-scope - pre-commit uses: ./.github/workflows/windows.yml + with: + ppc_tasks: ${{ needs.ci-scope.outputs.ppc_tasks || 'all' }} perf: needs: - ubuntu diff --git a/.github/workflows/static-analysis-pr.yml b/.github/workflows/static-analysis-pr.yml index ce4083ee..ccfb7e09 100644 --- a/.github/workflows/static-analysis-pr.yml +++ b/.github/workflows/static-analysis-pr.yml @@ -24,7 +24,28 @@ permissions: packages: read jobs: + ci-scope: + runs-on: ubuntu-24.04 + outputs: + ppc_tasks: ${{ steps.detect.outputs.ppc_tasks }} + task_scoped: ${{ steps.detect.outputs.task_scoped }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Detect CI task scope + id: detect + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + python3 scripts/detect_ci_task_scope.py \ + --base-sha "$BASE_SHA" \ + --head-sha "$HEAD_SHA" \ + --github-output "$GITHUB_OUTPUT" clang-tidy: + needs: + - ci-scope runs-on: ubuntu-24.04 container: image: ghcr.io/learning-process/ppc-ubuntu:1.2 @@ -48,6 +69,7 @@ jobs: run: > cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=RELEASE -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_COMPILE_WARNING_AS_ERROR=ON + -D PPC_TASKS="${{ needs.ci-scope.outputs.ppc_tasks || 'all' }}" env: CC: clang-22 CXX: clang++-22 @@ -73,6 +95,7 @@ jobs: exit 1 clang-tidy-for-gcc-build: needs: + - ci-scope - clang-tidy runs-on: ubuntu-24.04 container: @@ -97,6 +120,7 @@ jobs: run: > cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=RELEASE -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_COMPILE_WARNING_AS_ERROR=ON + -D PPC_TASKS="${{ needs.ci-scope.outputs.ppc_tasks || 'all' }}" env: CC: gcc-14 CXX: g++-14 diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 8203fb53..e8ba1537 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -8,6 +8,11 @@ on: required: false type: boolean default: false + ppc_tasks: + description: 'Tasks to build' + required: false + type: string + default: all permissions: contents: read @@ -39,6 +44,7 @@ jobs: run: > cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_INSTALL_PREFIX=install + -D PPC_TASKS="${{ inputs.ppc_tasks }}" env: CC: gcc-14 CXX: g++-14 @@ -148,6 +154,7 @@ jobs: run: > cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=install + -D PPC_TASKS="${{ inputs.ppc_tasks }}" env: CC: clang-22 CXX: clang++-22 @@ -253,6 +260,7 @@ jobs: -D CMAKE_BUILD_TYPE=RelWithDebInfo -D ENABLE_ADDRESS_SANITIZER=ON -D ENABLE_UB_SANITIZER=ON -D ENABLE_LEAK_SANITIZER=ON -D CMAKE_INSTALL_PREFIX=install + -D PPC_TASKS="${{ inputs.ppc_tasks }}" env: CC: clang-22 CXX: clang++-22 @@ -368,6 +376,7 @@ jobs: -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_VERBOSE_MAKEFILE=ON -D USE_COVERAGE=ON -DCMAKE_INSTALL_PREFIX=install + -D PPC_TASKS="${{ inputs.ppc_tasks }}" - name: Build project run: | cmake --build build --parallel -- --quiet diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 85d7e841..5030054c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -2,6 +2,12 @@ name: Windows on: workflow_call: + inputs: + ppc_tasks: + description: 'Tasks to build' + required: false + type: string + default: all permissions: contents: read @@ -26,6 +32,7 @@ jobs: cmake -S . -B build -G Ninja -D CMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -D CMAKE_C_COMPILER_LAUNCHER=ccache -D CMAKE_CXX_COMPILER_LAUNCHER=ccache -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_INSTALL_PREFIX=install + -D PPC_TASKS="${{ inputs.ppc_tasks }}" - name: Build project shell: bash run: | @@ -108,6 +115,7 @@ jobs: -D CMAKE_C_COMPILER_LAUNCHER=ccache -D CMAKE_CXX_COMPILER_LAUNCHER=ccache -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=install -D CMAKE_PREFIX_PATH="C:/Program Files/LLVM" + -D PPC_TASKS="${{ inputs.ppc_tasks }}" env: CC: clang-cl CXX: clang-cl diff --git a/scripts/detect_ci_task_scope.py b/scripts/detect_ci_task_scope.py new file mode 100644 index 00000000..2439d511 --- /dev/null +++ b/scripts/detect_ci_task_scope.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +from pathlib import Path, PurePosixPath + +FULL_SUITE = "all" + + +def _changed_files(base_sha: str, head_sha: str) -> list[str]: + result = subprocess.run( + ["git", "diff", "--name-only", "--no-renames", f"{base_sha}...{head_sha}"], + check=True, + stdout=subprocess.PIPE, + text=True, + ) + return [line for line in result.stdout.splitlines() if line] + + +def _task_from_path(path: str, tasks_root: Path) -> str | None: + parts = PurePosixPath(path).parts + if len(parts) < 3 or parts[0] != "tasks" or parts[1] == "common": + return None + + task_id = parts[1] + if not (tasks_root / task_id).is_dir(): + return None + + return task_id + + +def detect_scope(changed_files: list[str], tasks_root: Path) -> tuple[str, bool]: + task_ids = set() + + for changed_file in changed_files: + task_id = _task_from_path(changed_file, tasks_root) + if task_id is None: + return FULL_SUITE, False + task_ids.add(task_id) + + if not task_ids: + return FULL_SUITE, False + + return ";".join(sorted(task_ids)), True + + +def _write_github_output(github_output: Path, scope: str, task_scoped: bool) -> None: + with github_output.open("a", encoding="utf-8") as output: + output.write(f"ppc_tasks={scope}\n") + output.write(f"task_scoped={str(task_scoped).lower()}\n") + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Detect the PPC_TASKS value for CI from changed PR paths.", + ) + parser.add_argument( + "--base-sha", required=True, help="Base commit for the PR diff." + ) + parser.add_argument( + "--head-sha", required=True, help="Head commit for the PR diff." + ) + parser.add_argument( + "--tasks-root", default="tasks", type=Path, help="Path to the tasks directory." + ) + parser.add_argument( + "--github-output", + type=Path, + help="Optional GITHUB_OUTPUT file to append workflow outputs.", + ) + return parser.parse_args() + + +def main() -> None: + args = _parse_args() + scope, task_scoped = detect_scope( + _changed_files(args.base_sha, args.head_sha), args.tasks_root + ) + + print(f"PPC_TASKS={scope}") + if args.github_output is not None: + _write_github_output(args.github_output, scope, task_scoped) + + +if __name__ == "__main__": + main()