mirror of
https://github.com/python/cpython.git
synced 2025-07-23 11:15:24 +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
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_source:
|
build-context:
|
||||||
name: Change detection
|
name: Change detection
|
||||||
# To use boolean outputs from this job, parse them as JSON.
|
# To use boolean outputs from this job, parse them as JSON.
|
||||||
# Here's some examples:
|
# 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'
|
# && 'truthy-branch'
|
||||||
# || 'falsy-branch'
|
# || 'falsy-branch'
|
||||||
# }}
|
# }}
|
||||||
#
|
#
|
||||||
uses: ./.github/workflows/reusable-change-detection.yml
|
uses: ./.github/workflows/reusable-context.yml
|
||||||
|
|
||||||
check-docs:
|
check-docs:
|
||||||
name: Docs
|
name: Docs
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: fromJSON(needs.check_source.outputs.run-docs)
|
if: fromJSON(needs.build-context.outputs.run-docs)
|
||||||
uses: ./.github/workflows/reusable-docs.yml
|
uses: ./.github/workflows/reusable-docs.yml
|
||||||
|
|
||||||
check_abi:
|
check_abi:
|
||||||
name: 'Check if the ABI has changed'
|
name: 'Check if the ABI has changed'
|
||||||
runs-on: ubuntu-22.04 # 24.04 causes spurious errors
|
runs-on: ubuntu-22.04 # 24.04 causes spurious errors
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
@ -96,8 +96,8 @@ jobs:
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/python/autoconf:2024.10.16.11360930377
|
image: ghcr.io/python/autoconf:2024.10.16.11360930377
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Install Git
|
- name: Install Git
|
||||||
run: |
|
run: |
|
||||||
|
@ -137,8 +137,8 @@ jobs:
|
||||||
# reproducible: to get the same tools versions (autoconf, aclocal, ...)
|
# reproducible: to get the same tools versions (autoconf, aclocal, ...)
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
@ -153,7 +153,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
path: config.cache
|
path: config.cache
|
||||||
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python
|
# 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
|
- name: Install dependencies
|
||||||
run: sudo ./.github/workflows/posix-deps-apt.sh
|
run: sudo ./.github/workflows/posix-deps-apt.sh
|
||||||
- name: Add ccache to PATH
|
- name: Add ccache to PATH
|
||||||
|
@ -196,8 +196,8 @@ jobs:
|
||||||
name: >-
|
name: >-
|
||||||
Windows
|
Windows
|
||||||
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: fromJSON(needs.check_source.outputs.run_tests)
|
if: fromJSON(needs.build-context.outputs.run-tests)
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -227,8 +227,8 @@ jobs:
|
||||||
build_windows_msi:
|
build_windows_msi:
|
||||||
name: >- # ${{ '' } is a hack to nest jobs under the same sidebar category
|
name: >- # ${{ '' } is a hack to nest jobs under the same sidebar category
|
||||||
Windows MSI${{ '' }}
|
Windows MSI${{ '' }}
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: fromJSON(needs.check_source.outputs.run-win-msi)
|
if: fromJSON(needs.build-context.outputs.run-windows-msi)
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch:
|
arch:
|
||||||
|
@ -243,8 +243,8 @@ jobs:
|
||||||
name: >-
|
name: >-
|
||||||
macOS
|
macOS
|
||||||
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -269,7 +269,7 @@ jobs:
|
||||||
free-threading: true
|
free-threading: true
|
||||||
uses: ./.github/workflows/reusable-macos.yml
|
uses: ./.github/workflows/reusable-macos.yml
|
||||||
with:
|
with:
|
||||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
config_hash: ${{ needs.build-context.outputs.config-hash }}
|
||||||
free-threading: ${{ matrix.free-threading }}
|
free-threading: ${{ matrix.free-threading }}
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
|
|
||||||
|
@ -277,8 +277,8 @@ jobs:
|
||||||
name: >-
|
name: >-
|
||||||
Ubuntu
|
Ubuntu
|
||||||
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
free-threading:
|
free-threading:
|
||||||
|
@ -286,15 +286,15 @@ jobs:
|
||||||
- true
|
- true
|
||||||
uses: ./.github/workflows/reusable-ubuntu.yml
|
uses: ./.github/workflows/reusable-ubuntu.yml
|
||||||
with:
|
with:
|
||||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
config_hash: ${{ needs.build-context.outputs.config-hash }}
|
||||||
free-threading: ${{ matrix.free-threading }}
|
free-threading: ${{ matrix.free-threading }}
|
||||||
|
|
||||||
build_ubuntu_ssltests:
|
build_ubuntu_ssltests:
|
||||||
name: 'Ubuntu SSL tests with OpenSSL'
|
name: 'Ubuntu SSL tests with OpenSSL'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -315,7 +315,7 @@ jobs:
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: config.cache
|
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
|
- name: Register gcc problem matcher
|
||||||
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
@ -352,18 +352,18 @@ jobs:
|
||||||
|
|
||||||
build_wasi:
|
build_wasi:
|
||||||
name: 'WASI'
|
name: 'WASI'
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
uses: ./.github/workflows/reusable-wasi.yml
|
uses: ./.github/workflows/reusable-wasi.yml
|
||||||
with:
|
with:
|
||||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
config_hash: ${{ needs.build-context.outputs.config-hash }}
|
||||||
|
|
||||||
test_hypothesis:
|
test_hypothesis:
|
||||||
name: "Hypothesis tests on Ubuntu"
|
name: "Hypothesis tests on Ubuntu"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
env:
|
env:
|
||||||
OPENSSL_VER: 3.0.15
|
OPENSSL_VER: 3.0.15
|
||||||
PYTHONSTRICTEXTENSIONBUILD: 1
|
PYTHONSTRICTEXTENSIONBUILD: 1
|
||||||
|
@ -410,7 +410,7 @@ jobs:
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CPYTHON_BUILDDIR }}/config.cache
|
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
|
- name: Configure CPython out-of-tree
|
||||||
working-directory: ${{ env.CPYTHON_BUILDDIR }}
|
working-directory: ${{ env.CPYTHON_BUILDDIR }}
|
||||||
run: |
|
run: |
|
||||||
|
@ -477,8 +477,8 @@ jobs:
|
||||||
name: 'Address sanitizer'
|
name: 'Address sanitizer'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-24.04]
|
os: [ubuntu-24.04]
|
||||||
|
@ -496,7 +496,7 @@ jobs:
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: config.cache
|
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
|
- name: Register gcc problem matcher
|
||||||
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
@ -540,8 +540,8 @@ jobs:
|
||||||
name: >-
|
name: >-
|
||||||
Thread sanitizer
|
Thread sanitizer
|
||||||
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_tests == 'true'
|
if: needs.build-context.outputs.run-tests == 'true'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
free-threading:
|
free-threading:
|
||||||
|
@ -549,7 +549,7 @@ jobs:
|
||||||
- true
|
- true
|
||||||
uses: ./.github/workflows/reusable-tsan.yml
|
uses: ./.github/workflows/reusable-tsan.yml
|
||||||
with:
|
with:
|
||||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
config_hash: ${{ needs.build-context.outputs.config-hash }}
|
||||||
free-threading: ${{ matrix.free-threading }}
|
free-threading: ${{ matrix.free-threading }}
|
||||||
|
|
||||||
# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
|
# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
|
||||||
|
@ -557,8 +557,8 @@ jobs:
|
||||||
name: CIFuzz
|
name: CIFuzz
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
needs: check_source
|
needs: build-context
|
||||||
if: needs.check_source.outputs.run_cifuzz == 'true'
|
if: needs.build-context.outputs.run-ci-fuzz == 'true'
|
||||||
permissions:
|
permissions:
|
||||||
security-events: write
|
security-events: write
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -597,7 +597,7 @@ jobs:
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
needs:
|
needs:
|
||||||
- check_source # Transitive dependency, needed to access `run_tests` value
|
- build-context # Transitive dependency, needed to access `run-tests` value
|
||||||
- check-docs
|
- check-docs
|
||||||
- check_autoconf_regen
|
- check_autoconf_regen
|
||||||
- check_generated_files
|
- check_generated_files
|
||||||
|
@ -625,14 +625,14 @@ jobs:
|
||||||
test_hypothesis,
|
test_hypothesis,
|
||||||
allowed-skips: >-
|
allowed-skips: >-
|
||||||
${{
|
${{
|
||||||
!fromJSON(needs.check_source.outputs.run-docs)
|
!fromJSON(needs.build-context.outputs.run-docs)
|
||||||
&& '
|
&& '
|
||||||
check-docs,
|
check-docs,
|
||||||
'
|
'
|
||||||
|| ''
|
|| ''
|
||||||
}}
|
}}
|
||||||
${{
|
${{
|
||||||
needs.check_source.outputs.run_tests != 'true'
|
needs.build-context.outputs.run-tests != 'true'
|
||||||
&& '
|
&& '
|
||||||
check_autoconf_regen,
|
check_autoconf_regen,
|
||||||
check_generated_files,
|
check_generated_files,
|
||||||
|
@ -643,21 +643,15 @@ jobs:
|
||||||
build_windows,
|
build_windows,
|
||||||
build_asan,
|
build_asan,
|
||||||
build_tsan,
|
build_tsan,
|
||||||
|
test_hypothesis,
|
||||||
'
|
'
|
||||||
|| ''
|
|| ''
|
||||||
}}
|
}}
|
||||||
${{
|
${{
|
||||||
!fromJSON(needs.check_source.outputs.run_cifuzz)
|
!fromJSON(needs.build-context.outputs.run-ci-fuzz)
|
||||||
&& '
|
&& '
|
||||||
cifuzz,
|
cifuzz,
|
||||||
'
|
'
|
||||||
|| ''
|
|| ''
|
||||||
}}
|
}}
|
||||||
${{
|
|
||||||
!fromJSON(needs.check_source.outputs.run_hypothesis)
|
|
||||||
&& '
|
|
||||||
test_hypothesis,
|
|
||||||
'
|
|
||||||
|| ''
|
|
||||||
}}
|
|
||||||
jobs: ${{ toJSON(needs) }}
|
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