mirror of
https://github.com/python/cpython.git
synced 2025-07-24 03:35:53 +00:00
[3.13] Convert change detection to a Python script (GH-129627) (#130367)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua>
This commit is contained in:
parent
8ef89474b9
commit
019918a626
4 changed files with 328 additions and 209 deletions
96
.github/workflows/build.yml
vendored
96
.github/workflows/build.yml
vendored
|
@ -22,32 +22,32 @@ env:
|
|||
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:
|
||||
|
@ -96,8 +96,8 @@ jobs:
|
|||
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: |
|
||||
|
@ -137,8 +137,8 @@ jobs:
|
|||
# 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:
|
||||
|
@ -153,7 +153,7 @@ jobs:
|
|||
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
|
||||
|
@ -196,8 +196,8 @@ jobs:
|
|||
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:
|
||||
|
@ -227,8 +227,8 @@ jobs:
|
|||
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:
|
||||
|
@ -243,8 +243,8 @@ jobs:
|
|||
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:
|
||||
|
@ -269,7 +269,7 @@ jobs:
|
|||
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 }}
|
||||
|
||||
|
@ -277,8 +277,8 @@ jobs:
|
|||
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:
|
||||
|
@ -286,15 +286,15 @@ jobs:
|
|||
- 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:
|
||||
|
@ -315,7 +315,7 @@ jobs:
|
|||
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
|
||||
|
@ -352,18 +352,18 @@ jobs:
|
|||
|
||||
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
|
||||
|
@ -410,7 +410,7 @@ jobs:
|
|||
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: |
|
||||
|
@ -477,8 +477,8 @@ jobs:
|
|||
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]
|
||||
|
@ -496,7 +496,7 @@ jobs:
|
|||
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
|
||||
|
@ -540,8 +540,8 @@ jobs:
|
|||
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:
|
||||
|
@ -549,7 +549,7 @@ jobs:
|
|||
- 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/
|
||||
|
@ -557,8 +557,8 @@ jobs:
|
|||
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:
|
||||
|
@ -597,7 +597,7 @@ jobs:
|
|||
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
|
||||
|
@ -625,14 +625,14 @@ jobs:
|
|||
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,
|
||||
|
@ -643,21 +643,15 @@ jobs:
|
|||
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) }}
|
||||
|
|
158
.github/workflows/reusable-change-detection.yml
vendored
158
.github/workflows/reusable-change-detection.yml
vendored
|
@ -1,158 +0,0 @@
|
|||
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}"
|
100
.github/workflows/reusable-context.yml
vendored
Normal file
100
.github/workflows/reusable-context.yml
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
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"
|
183
Tools/build/compute-changes.py
Normal file
183
Tools/build/compute-changes.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
"""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()
|
Loading…
Add table
Add a link
Reference in a new issue