]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142278: Add granular change detection for platforms in CI (#142350)
authorStan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Mon, 8 Dec 2025 18:04:17 +0000 (18:04 +0000)
committerGitHub <noreply@github.com>
Mon, 8 Dec 2025 18:04:17 +0000 (18:04 +0000)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
.github/workflows/build.yml
.github/workflows/reusable-context.yml
Tools/build/compute-changes.py

index c43ec75fba3619e5bbdf016428f2829a76b47088..c7e51eea1028b808ac09e5b433c03f0bb1d6b525 100644 (file)
@@ -191,7 +191,7 @@ jobs:
       macOS
       ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-macos == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -217,7 +217,7 @@ jobs:
       ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
       ${{ fromJSON(matrix.bolt) && '(bolt)' || '' }}
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -248,7 +248,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     timeout-minutes: 60
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -304,7 +304,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     timeout-minutes: 60
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -368,7 +368,7 @@ jobs:
   build-android:
     name: Android (${{ matrix.arch }})
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-android == 'true'
     timeout-minutes: 60
     strategy:
       fail-fast: false
@@ -390,7 +390,7 @@ jobs:
   build-ios:
     name: iOS
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ios == 'true'
     timeout-minutes: 60
     runs-on: macos-15
     steps:
@@ -413,7 +413,7 @@ jobs:
   build-wasi:
     name: 'WASI'
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-wasi == 'true'
     uses: ./.github/workflows/reusable-wasi.yml
 
   test-hypothesis:
@@ -421,7 +421,7 @@ jobs:
     runs-on: ubuntu-24.04
     timeout-minutes: 60
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     env:
       OPENSSL_VER: 3.0.18
       PYTHONSTRICTEXTENSIONBUILD: 1
@@ -528,7 +528,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     timeout-minutes: 60
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -581,7 +581,7 @@ jobs:
     # ${{ '' } is a hack to nest jobs under the same sidebar category.
     name: Sanitizers${{ '' }}  # zizmor: ignore[obfuscation]
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -606,7 +606,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 60
     needs: build-context
-    if: needs.build-context.outputs.run-tests == 'true'
+    if: needs.build-context.outputs.run-ubuntu == 'true'
     steps:
       - uses: actions/checkout@v4
         with:
@@ -713,25 +713,24 @@ jobs:
           test-hypothesis,
           cifuzz,
         allowed-skips: >-
+          ${{ !fromJSON(needs.build-context.outputs.run-docs) && 'check-docs,' || '' }}
           ${{
-            !fromJSON(needs.build-context.outputs.run-docs)
+            needs.build-context.outputs.run-tests != 'true'
             && '
-            check-docs,
+            check-autoconf-regen,
+            check-generated-files,
             '
             || ''
           }}
+          ${{ !fromJSON(needs.build-context.outputs.run-windows-tests) && 'build-windows,' || '' }}
+          ${{ !fromJSON(needs.build-context.outputs.run-ci-fuzz) && 'cifuzz,' || '' }}
+          ${{ !fromJSON(needs.build-context.outputs.run-macos) && 'build-macos,' || '' }}
           ${{
-            needs.build-context.outputs.run-tests != 'true'
+            !fromJSON(needs.build-context.outputs.run-ubuntu)
             && '
-            check-autoconf-regen,
-            check-generated-files,
-            build-macos,
             build-ubuntu,
             build-ubuntu-ssltests-awslc,
             build-ubuntu-ssltests-openssl,
-            build-android,
-            build-ios,
-            build-wasi,
             test-hypothesis,
             build-asan,
             build-san,
@@ -739,18 +738,7 @@ jobs:
             '
             || ''
           }}
-          ${{
-            !fromJSON(needs.build-context.outputs.run-windows-tests)
-            && '
-            build-windows,
-            '
-            || ''
-          }}
-          ${{
-            !fromJSON(needs.build-context.outputs.run-ci-fuzz)
-            && '
-            cifuzz,
-            '
-            || ''
-          }}
+          ${{ !fromJSON(needs.build-context.outputs.run-android) && 'build-android,' || '' }}
+          ${{ !fromJSON(needs.build-context.outputs.run-ios) && 'build-ios,' || '' }}
+          ${{ !fromJSON(needs.build-context.outputs.run-wasi) && 'build-wasi,' || '' }}
         jobs: ${{ toJSON(needs) }}
index 66c7cc47de03fb1df73d8ac8b74f15c8f633f7da..ce5562f2d51fbbcf03040e30f17a381b238c0e57 100644 (file)
@@ -17,21 +17,36 @@ on:  # yamllint disable-line rule:truthy
       #        || 'falsy-branch'
       #   }}
       #
+      run-android:
+        description: Whether to run the Android tests
+        value: ${{ jobs.compute-changes.outputs.run-android }}  # bool
+      run-ci-fuzz:
+        description: Whether to run the CIFuzz job
+        value: ${{ jobs.compute-changes.outputs.run-ci-fuzz }}  # bool
       run-docs:
         description: Whether to build the docs
         value: ${{ jobs.compute-changes.outputs.run-docs }}  # bool
+      run-ios:
+        description: Whether to run the iOS tests
+        value: ${{ jobs.compute-changes.outputs.run-ios }}  # bool
+      run-macos:
+        description: Whether to run the macOS tests
+        value: ${{ jobs.compute-changes.outputs.run-macos }}  # bool
       run-tests:
         description: Whether to run the regular tests
         value: ${{ jobs.compute-changes.outputs.run-tests  }}  # bool
-      run-windows-tests:
-        description: Whether to run the Windows tests
-        value: ${{ jobs.compute-changes.outputs.run-windows-tests }}  # bool
+      run-ubuntu:
+        description: Whether to run the Ubuntu tests
+        value: ${{ jobs.compute-changes.outputs.run-ubuntu }}  # bool
+      run-wasi:
+        description: Whether to run the WASI tests
+        value: ${{ jobs.compute-changes.outputs.run-wasi }}  # bool
       run-windows-msi:
         description: Whether to run the MSI installer smoke tests
         value: ${{ jobs.compute-changes.outputs.run-windows-msi }}  # bool
-      run-ci-fuzz:
-        description: Whether to run the CIFuzz job
-        value: ${{ jobs.compute-changes.outputs.run-ci-fuzz }}  # bool
+      run-windows-tests:
+        description: Whether to run the Windows tests
+        value: ${{ jobs.compute-changes.outputs.run-windows-tests }}  # bool
 
 jobs:
   compute-changes:
@@ -39,9 +54,14 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 10
     outputs:
+      run-android: ${{ steps.changes.outputs.run-android }}
       run-ci-fuzz: ${{ steps.changes.outputs.run-ci-fuzz }}
       run-docs: ${{ steps.changes.outputs.run-docs }}
+      run-ios: ${{ steps.changes.outputs.run-ios }}
+      run-macos: ${{ steps.changes.outputs.run-macos }}
       run-tests: ${{ steps.changes.outputs.run-tests }}
+      run-ubuntu: ${{ steps.changes.outputs.run-ubuntu }}
+      run-wasi: ${{ steps.changes.outputs.run-wasi }}
       run-windows-msi: ${{ steps.changes.outputs.run-windows-msi }}
       run-windows-tests: ${{ steps.changes.outputs.run-windows-tests }}
     steps:
index b5993d29b92972fa8c59043db434d9a4a32ca19c..524d3066fbffa7c45ad9cbca542fc0ba2848e3b4 100644 (file)
@@ -45,12 +45,22 @@ UNIX_BUILD_SYSTEM_FILE_NAMES = frozenset({
 SUFFIXES_C_OR_CPP = frozenset({".c", ".h", ".cpp"})
 SUFFIXES_DOCUMENTATION = frozenset({".rst", ".md"})
 
+ANDROID_DIRS = frozenset({"Android"})
+IOS_DIRS = frozenset({"Apple", "iOS"})
+MACOS_DIRS = frozenset({"Mac"})
+WASI_DIRS = frozenset({Path("Tools", "wasm")})
+
 
 @dataclass(kw_only=True, slots=True)
 class Outputs:
+    run_android: bool = False
     run_ci_fuzz: bool = False
     run_docs: bool = False
+    run_ios: bool = False
+    run_macos: bool = False
     run_tests: bool = False
+    run_ubuntu: bool = False
+    run_wasi: bool = False
     run_windows_msi: bool = False
     run_windows_tests: bool = False
 
@@ -63,7 +73,15 @@ def compute_changes() -> None:
         outputs = process_changed_files(files)
     else:
         # Otherwise, just run the tests
-        outputs = Outputs(run_tests=True, run_windows_tests=True)
+        outputs = Outputs(
+            run_android=True,
+            run_ios=True,
+            run_macos=True,
+            run_tests=True,
+            run_ubuntu=True,
+            run_wasi=True,
+            run_windows_tests=True,
+        )
     outputs = process_target_branch(outputs, target_branch)
 
     if outputs.run_tests:
@@ -111,6 +129,21 @@ def get_changed_files(
     return frozenset(map(Path, filter(None, map(str.strip, changed_files))))
 
 
+def get_file_platform(file: Path) -> str | None:
+    if not file.parts:
+        return None
+    first_part = file.parts[0]
+    if first_part in MACOS_DIRS:
+        return "macos"
+    if first_part in IOS_DIRS:
+        return "ios"
+    if first_part in ANDROID_DIRS:
+        return "android"
+    if len(file.parts) >= 2 and Path(*file.parts[:2]) in WASI_DIRS: # Tools/wasm/
+        return "wasi"
+    return None
+
+
 def process_changed_files(changed_files: Set[Path]) -> Outputs:
     run_tests = False
     run_ci_fuzz = False
@@ -118,6 +151,9 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
     run_windows_tests = False
     run_windows_msi = False
 
+    platforms_changed = set()
+    has_platform_specific_change = True
+
     for file in changed_files:
         # Documentation files
         doc_or_misc = file.parts[0] in {"Doc", "Misc"}
@@ -126,10 +162,15 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
         if file.parent == GITHUB_WORKFLOWS_PATH:
             if file.name == "build.yml":
                 run_tests = run_ci_fuzz = True
+                has_platform_specific_change = False
             if file.name == "reusable-docs.yml":
                 run_docs = True
             if file.name == "reusable-windows-msi.yml":
                 run_windows_msi = True
+            if file.name == "reusable-macos.yml":
+                platforms_changed.add("macos")
+            if file.name == "reusable-wasi.yml":
+                platforms_changed.add("wasi")
 
         if not (
             doc_file
@@ -138,8 +179,13 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
         ):
             run_tests = True
 
-            if file not in UNIX_BUILD_SYSTEM_FILE_NAMES:
-                run_windows_tests = True
+            platform = get_file_platform(file)
+            if platform is not None:
+                platforms_changed.add(platform)
+            else:
+                has_platform_specific_change = False
+                if file not in UNIX_BUILD_SYSTEM_FILE_NAMES:
+                    run_windows_tests = True
 
         # The fuzz tests are pretty slow so they are executed only for PRs
         # changing relevant files.
@@ -159,12 +205,38 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
         if file.parts[:2] == ("Tools", "msi"):
             run_windows_msi = True
 
+    # Check which platform specific tests to run
+    if run_tests:
+        if not has_platform_specific_change or not platforms_changed:
+            run_android = True
+            run_ios = True
+            run_macos = True
+            run_ubuntu = True
+            run_wasi = True
+        else:
+            run_android = "android" in platforms_changed
+            run_ios = "ios" in platforms_changed
+            run_macos = "macos" in platforms_changed
+            run_ubuntu = False
+            run_wasi = "wasi" in platforms_changed
+    else:
+        run_android = False
+        run_ios = False
+        run_macos = False
+        run_ubuntu = False
+        run_wasi = False
+
     return Outputs(
+        run_android=run_android,
         run_ci_fuzz=run_ci_fuzz,
         run_docs=run_docs,
+        run_ios=run_ios,
+        run_macos=run_macos,
         run_tests=run_tests,
-        run_windows_tests=run_windows_tests,
+        run_ubuntu=run_ubuntu,
+        run_wasi=run_wasi,
         run_windows_msi=run_windows_msi,
+        run_windows_tests=run_windows_tests,
     )
 
 
@@ -191,11 +263,16 @@ def write_github_output(outputs: Outputs) -> None:
         return
 
     with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f:
+        f.write(f"run-android={bool_lower(outputs.run_android)}\n")
         f.write(f"run-ci-fuzz={bool_lower(outputs.run_ci_fuzz)}\n")
         f.write(f"run-docs={bool_lower(outputs.run_docs)}\n")
+        f.write(f"run-ios={bool_lower(outputs.run_ios)}\n")
+        f.write(f"run-macos={bool_lower(outputs.run_macos)}\n")
         f.write(f"run-tests={bool_lower(outputs.run_tests)}\n")
-        f.write(f"run-windows-tests={bool_lower(outputs.run_windows_tests)}\n")
+        f.write(f"run-ubuntu={bool_lower(outputs.run_ubuntu)}\n")
+        f.write(f"run-wasi={bool_lower(outputs.run_wasi)}\n")
         f.write(f"run-windows-msi={bool_lower(outputs.run_windows_msi)}\n")
+        f.write(f"run-windows-tests={bool_lower(outputs.run_windows_tests)}\n")
 
 
 def bool_lower(value: bool, /) -> str: