FORCE_COLOR: 1
jobs:
- check_source:
+ build-context:
name: Change detection
# To use boolean outputs from this job, parse them as JSON.
# Here's some examples:
#
- # if: fromJSON(needs.check_source.outputs.run-docs)
+ # if: fromJSON(needs.build-context.outputs.run-docs)
#
# ${{
- # fromJSON(needs.check_source.outputs.run_tests)
+ # fromJSON(needs.build-context.outputs.run-tests)
# && 'truthy-branch'
# || 'falsy-branch'
# }}
#
- uses: ./.github/workflows/reusable-change-detection.yml
+ uses: ./.github/workflows/reusable-context.yml
check-docs:
name: Docs
- needs: check_source
- if: fromJSON(needs.check_source.outputs.run-docs)
+ needs: build-context
+ if: fromJSON(needs.build-context.outputs.run-docs)
uses: ./.github/workflows/reusable-docs.yml
check_abi:
name: 'Check if the ABI has changed'
runs-on: ubuntu-22.04 # 24.04 causes spurious errors
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
steps:
- uses: actions/checkout@v4
with:
container:
image: ghcr.io/python/autoconf:2024.10.16.11360930377
timeout-minutes: 60
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
steps:
- name: Install Git
run: |
# reproducible: to get the same tools versions (autoconf, aclocal, ...)
runs-on: ubuntu-24.04
timeout-minutes: 60
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
steps:
- uses: actions/checkout@v4
with:
with:
path: config.cache
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python
- key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.check_source.outputs.config_hash }}-${{ env.pythonLocation }}
+ key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}-${{ env.pythonLocation }}
- name: Install dependencies
run: sudo ./.github/workflows/posix-deps-apt.sh
- name: Add ccache to PATH
name: >-
Windows
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
- needs: check_source
- if: fromJSON(needs.check_source.outputs.run_tests)
+ needs: build-context
+ if: fromJSON(needs.build-context.outputs.run-tests)
strategy:
fail-fast: false
matrix:
build_windows_msi:
name: >- # ${{ '' } is a hack to nest jobs under the same sidebar category
Windows MSI${{ '' }}
- needs: check_source
- if: fromJSON(needs.check_source.outputs.run-win-msi)
+ needs: build-context
+ if: fromJSON(needs.build-context.outputs.run-windows-msi)
strategy:
matrix:
arch:
name: >-
macOS
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
strategy:
fail-fast: false
matrix:
free-threading: true
uses: ./.github/workflows/reusable-macos.yml
with:
- config_hash: ${{ needs.check_source.outputs.config_hash }}
+ config_hash: ${{ needs.build-context.outputs.config-hash }}
free-threading: ${{ matrix.free-threading }}
os: ${{ matrix.os }}
name: >-
Ubuntu
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
strategy:
matrix:
free-threading:
- true
uses: ./.github/workflows/reusable-ubuntu.yml
with:
- config_hash: ${{ needs.check_source.outputs.config_hash }}
+ config_hash: ${{ needs.build-context.outputs.config-hash }}
free-threading: ${{ matrix.free-threading }}
build_ubuntu_ssltests:
name: 'Ubuntu SSL tests with OpenSSL'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
strategy:
fail-fast: false
matrix:
uses: actions/cache@v4
with:
path: config.cache
- key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.check_source.outputs.config_hash }}
+ key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Install dependencies
build_wasi:
name: 'WASI'
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
uses: ./.github/workflows/reusable-wasi.yml
with:
- config_hash: ${{ needs.check_source.outputs.config_hash }}
+ config_hash: ${{ needs.build-context.outputs.config-hash }}
test_hypothesis:
name: "Hypothesis tests on Ubuntu"
runs-on: ubuntu-24.04
timeout-minutes: 60
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
env:
OPENSSL_VER: 3.0.15
PYTHONSTRICTEXTENSIONBUILD: 1
uses: actions/cache@v4
with:
path: ${{ env.CPYTHON_BUILDDIR }}/config.cache
- key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.check_source.outputs.config_hash }}
+ key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Configure CPython out-of-tree
working-directory: ${{ env.CPYTHON_BUILDDIR }}
run: |
name: 'Address sanitizer'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
strategy:
matrix:
os: [ubuntu-24.04]
uses: actions/cache@v4
with:
path: config.cache
- key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.check_source.outputs.config_hash }}
+ key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Install dependencies
name: >-
Thread sanitizer
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
- needs: check_source
- if: needs.check_source.outputs.run_tests == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
strategy:
matrix:
free-threading:
- true
uses: ./.github/workflows/reusable-tsan.yml
with:
- config_hash: ${{ needs.check_source.outputs.config_hash }}
+ config_hash: ${{ needs.build-context.outputs.config-hash }}
free-threading: ${{ matrix.free-threading }}
# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
name: CIFuzz
runs-on: ubuntu-latest
timeout-minutes: 60
- needs: check_source
- if: needs.check_source.outputs.run_cifuzz == 'true'
+ needs: build-context
+ if: needs.build-context.outputs.run-ci-fuzz == 'true'
permissions:
security-events: write
strategy:
if: always()
needs:
- - check_source # Transitive dependency, needed to access `run_tests` value
+ - build-context # Transitive dependency, needed to access `run-tests` value
- check-docs
- check_autoconf_regen
- check_generated_files
test_hypothesis,
allowed-skips: >-
${{
- !fromJSON(needs.check_source.outputs.run-docs)
+ !fromJSON(needs.build-context.outputs.run-docs)
&& '
check-docs,
'
|| ''
}}
${{
- needs.check_source.outputs.run_tests != 'true'
+ needs.build-context.outputs.run-tests != 'true'
&& '
check_autoconf_regen,
check_generated_files,
build_windows,
build_asan,
build_tsan,
+ test_hypothesis,
'
|| ''
}}
${{
- !fromJSON(needs.check_source.outputs.run_cifuzz)
+ !fromJSON(needs.build-context.outputs.run-ci-fuzz)
&& '
cifuzz,
'
|| ''
}}
- ${{
- !fromJSON(needs.check_source.outputs.run_hypothesis)
- && '
- test_hypothesis,
- '
- || ''
- }}
jobs: ${{ toJSON(needs) }}
+++ /dev/null
-name: Reusable change detection
-
-on: # yamllint disable-line rule:truthy
- workflow_call:
- outputs:
- # Some of the referenced steps set outputs conditionally and there may be
- # cases when referencing them evaluates to empty strings. It is nice to
- # work with proper booleans so they have to be evaluated through JSON
- # conversion in the expressions. However, empty strings used like that
- # may trigger all sorts of undefined and hard-to-debug behaviors in
- # GitHub Actions CI/CD. To help with this, all of the outputs set here
- # that are meant to be used as boolean flags (and not arbitrary strings),
- # MUST have fallbacks with default values set. A common pattern would be
- # to add ` || false` to all such expressions here, in the output
- # definitions. They can then later be safely used through the following
- # idiom in job conditionals and other expressions. Here's some examples:
- #
- # if: fromJSON(needs.change-detection.outputs.run-docs)
- #
- # ${{
- # fromJSON(needs.change-detection.outputs.run-tests)
- # && 'truthy-branch'
- # || 'falsy-branch'
- # }}
- #
- config_hash:
- description: Config hash value for use in cache keys
- value: ${{ jobs.compute-changes.outputs.config-hash }} # str
- run-docs:
- description: Whether to build the docs
- value: ${{ jobs.compute-changes.outputs.run-docs || false }} # bool
- run_tests:
- description: Whether to run the regular tests
- value: ${{ jobs.compute-changes.outputs.run-tests || false }} # bool
- run-win-msi:
- description: Whether to run the MSI installer smoke tests
- value: >- # bool
- ${{ jobs.compute-changes.outputs.run-win-msi || false }}
- run_hypothesis:
- description: Whether to run the Hypothesis tests
- value: >- # bool
- ${{ jobs.compute-changes.outputs.run-hypothesis || false }}
- run_cifuzz:
- description: Whether to run the CIFuzz job
- value: >- # bool
- ${{ jobs.compute-changes.outputs.run-cifuzz || false }}
-
-jobs:
- compute-changes:
- name: Compute changed files
- runs-on: ubuntu-latest
- timeout-minutes: 10
- outputs:
- config-hash: ${{ steps.config-hash.outputs.hash }}
- run-cifuzz: ${{ steps.check.outputs.run-cifuzz }}
- run-docs: ${{ steps.docs-changes.outputs.run-docs }}
- run-hypothesis: ${{ steps.check.outputs.run-hypothesis }}
- run-tests: ${{ steps.check.outputs.run-tests }}
- run-win-msi: ${{ steps.win-msi-changes.outputs.run-win-msi }}
- steps:
- - run: >-
- echo '${{ github.event_name }}'
- - uses: actions/checkout@v4
- with:
- persist-credentials: false
- - name: Check for source changes
- id: check
- run: |
- if [ -z "$GITHUB_BASE_REF" ]; then
- echo "run-tests=true" >> $GITHUB_OUTPUT
- else
- git fetch origin $GITHUB_BASE_REF --depth=1
- # git diff "origin/$GITHUB_BASE_REF..." (3 dots) may be more
- # reliable than git diff "origin/$GITHUB_BASE_REF.." (2 dots),
- # but it requires to download more commits (this job uses
- # "git fetch --depth=1").
- #
- # git diff "origin/$GITHUB_BASE_REF..." (3 dots) works with Git
- # 2.26, but Git 2.28 is stricter and fails with "no merge base".
- #
- # git diff "origin/$GITHUB_BASE_REF.." (2 dots) should be enough on
- # GitHub, since GitHub starts by merging origin/$GITHUB_BASE_REF
- # into the PR branch anyway.
- #
- # https://github.com/python/core-workflow/issues/373
- git diff --name-only origin/$GITHUB_BASE_REF.. | grep -qvE '(\.rst$|^Doc|^Misc|^\.pre-commit-config\.yaml$|\.ruff\.toml$|\.md$|mypy\.ini$)' && echo "run-tests=true" >> $GITHUB_OUTPUT || true
- fi
-
- # Check if we should run hypothesis tests
- GIT_BRANCH=${GITHUB_BASE_REF:-${GITHUB_REF#refs/heads/}}
- echo $GIT_BRANCH
- if $(echo "$GIT_BRANCH" | grep -q -w '3\.\(8\|9\|10\|11\)'); then
- echo "Branch too old for hypothesis tests"
- echo "run-hypothesis=false" >> $GITHUB_OUTPUT
- else
- echo "Run hypothesis tests"
- echo "run-hypothesis=true" >> $GITHUB_OUTPUT
- fi
-
- # oss-fuzz maintains a configuration for fuzzing the main branch of
- # CPython, so CIFuzz should be run only for code that is likely to be
- # merged into the main branch; compatibility with older branches may
- # be broken.
- FUZZ_RELEVANT_FILES='(\.c$|\.h$|\.cpp$|^configure$|^\.github/workflows/build\.yml$|^Modules/_xxtestfuzz)'
- if [ "$GITHUB_BASE_REF" = "main" ] && [ "$(git diff --name-only origin/$GITHUB_BASE_REF.. | grep -qE $FUZZ_RELEVANT_FILES; echo $?)" -eq 0 ]; then
- # The tests are pretty slow so they are executed only for PRs
- # changing relevant files.
- echo "Run CIFuzz tests"
- echo "run-cifuzz=true" >> $GITHUB_OUTPUT
- else
- echo "Branch too old for CIFuzz tests; or no C files were changed"
- echo "run-cifuzz=false" >> $GITHUB_OUTPUT
- fi
- - name: Compute hash for config cache key
- id: config-hash
- run: |
- echo "hash=${{ hashFiles('configure', 'configure.ac', '.github/workflows/build.yml') }}" >> $GITHUB_OUTPUT
- - name: Get a list of the changed documentation-related files
- if: github.event_name == 'pull_request'
- id: changed-docs-files
- uses: Ana06/get-changed-files@v2.3.0
- with:
- filter: |
- Doc/**
- Misc/**
- .github/workflows/reusable-docs.yml
- format: csv # works for paths with spaces
- - name: Check for docs changes
- # We only want to run this on PRs when related files are changed,
- # or when user triggers manual workflow run.
- if: >-
- (
- github.event_name == 'pull_request'
- && steps.changed-docs-files.outputs.added_modified_renamed != ''
- ) || github.event_name == 'workflow_dispatch'
- id: docs-changes
- run: |
- echo "run-docs=true" >> "${GITHUB_OUTPUT}"
- - name: Get a list of the MSI installer-related files
- if: github.event_name == 'pull_request'
- id: changed-win-msi-files
- uses: Ana06/get-changed-files@v2.3.0
- with:
- filter: |
- Tools/msi/**
- .github/workflows/reusable-windows-msi.yml
- format: csv # works for paths with spaces
- - name: Check for changes in MSI installer-related files
- # We only want to run this on PRs when related files are changed,
- # or when user triggers manual workflow run.
- if: >-
- (
- github.event_name == 'pull_request'
- && steps.changed-win-msi-files.outputs.added_modified_renamed != ''
- ) || github.event_name == 'workflow_dispatch'
- id: win-msi-changes
- run: |
- echo "run-win-msi=true" >> "${GITHUB_OUTPUT}"
--- /dev/null
+name: Reusable build context
+
+on: # yamllint disable-line rule:truthy
+ workflow_call:
+ outputs:
+ # Every referenced step MUST always set its output variable,
+ # either via ``Tools/build/compute-changes.py`` or in this workflow file.
+ # Boolean outputs (generally prefixed ``run-``) can then later be used
+ # safely through the following idiom in job conditionals and other
+ # expressions. Here's some examples:
+ #
+ # if: fromJSON(needs.build-context.outputs.run-tests)
+ #
+ # ${{
+ # fromJSON(needs.build-context.outputs.run-tests)
+ # && 'truthy-branch'
+ # || 'falsy-branch'
+ # }}
+ #
+ config-hash:
+ description: Config hash value for use in cache keys
+ value: ${{ jobs.compute-changes.outputs.config-hash }} # str
+ run-docs:
+ description: Whether to build the docs
+ value: ${{ jobs.compute-changes.outputs.run-docs }} # bool
+ run-tests:
+ description: Whether to run the regular tests
+ value: ${{ jobs.compute-changes.outputs.run-tests }} # 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
+
+jobs:
+ compute-changes:
+ name: Create context from changed files
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ outputs:
+ config-hash: ${{ steps.config-hash.outputs.hash }}
+ run-ci-fuzz: ${{ steps.changes.outputs.run-ci-fuzz }}
+ run-docs: ${{ steps.changes.outputs.run-docs }}
+ run-tests: ${{ steps.changes.outputs.run-tests }}
+ run-windows-msi: ${{ steps.changes.outputs.run-windows-msi }}
+ steps:
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3"
+
+ - run: >-
+ echo '${{ github.event_name }}'
+
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ ref: >-
+ ${{
+ github.event_name == 'pull_request'
+ && github.event.pull_request.head.sha
+ || ''
+ }}
+
+ # Adapted from https://github.com/actions/checkout/issues/520#issuecomment-1167205721
+ - name: Fetch commits to get branch diff
+ if: github.event_name == 'pull_request'
+ run: |
+ set -eux
+
+ # Fetch enough history to find a common ancestor commit (aka merge-base):
+ git fetch origin "${refspec_pr}" --depth=$(( commits + 1 )) \
+ --no-tags --prune --no-recurse-submodules
+
+ # This should get the oldest commit in the local fetched history (which may not be the commit the PR branched from):
+ COMMON_ANCESTOR=$( git rev-list --first-parent --max-parents=0 --max-count=1 "${branch_pr}" )
+ DATE=$( git log --date=iso8601 --format=%cd "${COMMON_ANCESTOR}" )
+
+ # Get all commits since that commit date from the base branch (eg: main):
+ git fetch origin "${refspec_base}" --shallow-since="${DATE}" \
+ --no-tags --prune --no-recurse-submodules
+ env:
+ branch_pr: 'origin/${{ github.event.pull_request.head.ref }}'
+ commits: ${{ github.event.pull_request.commits }}
+ refspec_base: '+${{ github.event.pull_request.base.sha }}:remotes/origin/${{ github.event.pull_request.base.ref }}'
+ refspec_pr: '+${{ github.event.pull_request.head.sha }}:remotes/origin/${{ github.event.pull_request.head.ref }}'
+
+ # We only want to run tests on PRs when related files are changed,
+ # or when someone triggers a manual workflow run.
+ - name: Compute changed files
+ id: changes
+ run: python Tools/build/compute-changes.py
+ env:
+ GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+
+ - name: Compute hash for config cache key
+ id: config-hash
+ run: |
+ echo "hash=${{ hashFiles('configure', 'configure.ac', '.github/workflows/build.yml') }}" >> "$GITHUB_OUTPUT"
--- /dev/null
+"""Determine which GitHub Actions workflows to run.
+
+Called by ``.github/workflows/reusable-context.yml``.
+We only want to run tests on PRs when related files are changed,
+or when someone triggers a manual workflow run.
+This improves developer experience by not doing (slow)
+unnecessary work in GHA, and saves CI resources.
+"""
+
+from __future__ import annotations
+
+import os
+import subprocess
+from dataclasses import dataclass
+from pathlib import Path
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Set
+
+GITHUB_DEFAULT_BRANCH = os.environ["GITHUB_DEFAULT_BRANCH"]
+GITHUB_CODEOWNERS_PATH = Path(".github/CODEOWNERS")
+GITHUB_WORKFLOWS_PATH = Path(".github/workflows")
+CONFIGURATION_FILE_NAMES = frozenset({
+ ".pre-commit-config.yaml",
+ ".ruff.toml",
+ "mypy.ini",
+})
+SUFFIXES_C_OR_CPP = frozenset({".c", ".h", ".cpp"})
+SUFFIXES_DOCUMENTATION = frozenset({".rst", ".md"})
+
+
+@dataclass(kw_only=True, slots=True)
+class Outputs:
+ run_ci_fuzz: bool = False
+ run_docs: bool = False
+ run_tests: bool = False
+ run_windows_msi: bool = False
+
+
+def compute_changes() -> None:
+ target_branch, head_branch = git_branches()
+ if target_branch and head_branch:
+ # Getting changed files only makes sense on a pull request
+ files = get_changed_files(
+ f"origin/{target_branch}", f"origin/{head_branch}"
+ )
+ outputs = process_changed_files(files)
+ else:
+ # Otherwise, just run the tests
+ outputs = Outputs(run_tests=True)
+ outputs = process_target_branch(outputs, target_branch)
+
+ if outputs.run_tests:
+ print("Run tests")
+
+ if outputs.run_ci_fuzz:
+ print("Run CIFuzz tests")
+ else:
+ print("Branch too old for CIFuzz tests; or no C files were changed")
+
+ if outputs.run_docs:
+ print("Build documentation")
+
+ if outputs.run_windows_msi:
+ print("Build Windows MSI")
+
+ print(outputs)
+
+ write_github_output(outputs)
+
+
+def git_branches() -> tuple[str, str]:
+ target_branch = os.environ.get("GITHUB_BASE_REF", "")
+ target_branch = target_branch.removeprefix("refs/heads/")
+ print(f"target branch: {target_branch!r}")
+
+ head_branch = os.environ.get("GITHUB_HEAD_REF", "")
+ head_branch = head_branch.removeprefix("refs/heads/")
+ print(f"head branch: {head_branch!r}")
+ return target_branch, head_branch
+
+
+def get_changed_files(
+ ref_a: str = GITHUB_DEFAULT_BRANCH, ref_b: str = "HEAD"
+) -> Set[Path]:
+ """List the files changed between two Git refs, filtered by change type."""
+ args = ("git", "diff", "--name-only", f"{ref_a}...{ref_b}", "--")
+ print(*args)
+ changed_files_result = subprocess.run(
+ args, stdout=subprocess.PIPE, check=True, encoding="utf-8"
+ )
+ changed_files = changed_files_result.stdout.strip().splitlines()
+ return frozenset(map(Path, filter(None, map(str.strip, changed_files))))
+
+
+def process_changed_files(changed_files: Set[Path]) -> Outputs:
+ run_tests = False
+ run_ci_fuzz = False
+ run_docs = False
+ run_windows_msi = False
+
+ for file in changed_files:
+ # Documentation files
+ doc_or_misc = file.parts[0] in {"Doc", "Misc"}
+ doc_file = file.suffix in SUFFIXES_DOCUMENTATION or doc_or_misc
+
+ if file.parent == GITHUB_WORKFLOWS_PATH:
+ if file.name == "build.yml":
+ run_tests = run_ci_fuzz = True
+ if file.name == "reusable-docs.yml":
+ run_docs = True
+ if file.name == "reusable-windows-msi.yml":
+ run_windows_msi = True
+
+ if not (
+ doc_file
+ or file == GITHUB_CODEOWNERS_PATH
+ or file.name in CONFIGURATION_FILE_NAMES
+ ):
+ run_tests = True
+
+ # The fuzz tests are pretty slow so they are executed only for PRs
+ # changing relevant files.
+ if file.suffix in SUFFIXES_C_OR_CPP:
+ run_ci_fuzz = True
+ if file.parts[:2] in {
+ ("configure",),
+ ("Modules", "_xxtestfuzz"),
+ }:
+ run_ci_fuzz = True
+
+ # Check for changed documentation-related files
+ if doc_file:
+ run_docs = True
+
+ # Check for changed MSI installer-related files
+ if file.parts[:2] == ("Tools", "msi"):
+ run_windows_msi = True
+
+ return Outputs(
+ run_ci_fuzz=run_ci_fuzz,
+ run_docs=run_docs,
+ run_tests=run_tests,
+ run_windows_msi=run_windows_msi,
+ )
+
+
+def process_target_branch(outputs: Outputs, git_branch: str) -> Outputs:
+ if not git_branch:
+ outputs.run_tests = True
+
+ # CIFuzz / OSS-Fuzz compatibility with older branches may be broken.
+ if git_branch != GITHUB_DEFAULT_BRANCH:
+ outputs.run_ci_fuzz = False
+
+ if os.environ.get("GITHUB_EVENT_NAME", "").lower() == "workflow_dispatch":
+ outputs.run_docs = True
+ outputs.run_windows_msi = True
+
+ return outputs
+
+
+def write_github_output(outputs: Outputs) -> None:
+ # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
+ # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-output-parameter
+ if "GITHUB_OUTPUT" not in os.environ:
+ print("GITHUB_OUTPUT not defined!")
+ return
+
+ with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f:
+ 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-tests={bool_lower(outputs.run_tests)}\n")
+ f.write(f"run-windows-msi={bool_lower(outputs.run_windows_msi)}\n")
+
+
+def bool_lower(value: bool, /) -> str:
+ return "true" if value else "false"
+
+
+if __name__ == "__main__":
+ compute_changes()