From 023db9d9ed28c0c6ad77fb2a50751f5a9d312d9a Mon Sep 17 00:00:00 2001 From: Yoshihiro Misawa Date: Mon, 21 Sep 2026 04:35:14 +0900 Subject: [PATCH 1/2] Avoid duplicate post-merge dev wheel builds --- .github/workflows/build-dev.yml | 8 ++-- docs/v3-ci-execution-policy.md | 50 +++++++++++++++++++++++++ tests/test_dev_workflow_policy.py | 61 +++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 docs/v3-ci-execution-policy.md create mode 100644 tests/test_dev_workflow_policy.py diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index fc042d4..9c423ab 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -1,11 +1,9 @@ name: Build wheels (dev) on: - push: - branches: [dev] - paths-ignore: - - '**.md' - - '**.rst' + # dev requires a PR; build the full wheel matrix before merging. + # Post-merge tests remain in test-dev.yml. For an explicit rebuild use + # the existing Build wheels workflow (build.yaml) with ref=dev. pull_request: branches: [dev] paths-ignore: diff --git a/docs/v3-ci-execution-policy.md b/docs/v3-ci-execution-policy.md new file mode 100644 index 0000000..663492b --- /dev/null +++ b/docs/v3-ci-execution-policy.md @@ -0,0 +1,50 @@ + +# devのwheel CI重複実行を避ける方針 + +## 背景と変更 + +PRとmerge後のdev pushで、同じ3 OSのwheel matrixを繰り返していた。 +たとえばPR #142のwheel runは35526469811、merge後は35527522837。 +Actionsの実行量を減らし、同じ変更を短い間隔で再buildしないようにする。 + +変更は`build-dev.yml`のpush triggerだけを削除する。 +PRでは従来どおりLinux/macOS/Windowsの全wheel matrixを実行する。 +Python/ABI/architecture、smoke、共有build actionを縮小しない。 +merge後も`test-dev.yml`のPython matrix、native sanitizer、distribution検証は残す。 + +| event | wheel | test | +| --- | --- | --- | +| dev向けcode PR | 全matrix | 従来どおり | +| devへのmerge/push | 自動実行しない | 従来どおり | +| dev向けdocs-only PR/push | 自動実行しない | 自動実行しない | +| master・release tag | 変更なし | 変更なし | + +devはPR必須・削除/force-push禁止のruleset +`Protect dev integration`(23727562)がactiveであることを確認した。 +必須status check自体はdocs-only省略のため設定せず、統合担当が該当PRのCI成功を確認する。 +rulesetやmasterの必須checkは変更しない。 + +PR対象headと実際のmerge結果が異なる変更を含む場合や、PRを経ない例外的なpushでは、 +必要に応じてmerge後の明示wheel buildを実行する。PR成功が任意の将来commitの +wheel互換性を保証するわけではない。 + +## 明示的なdev再build + +既存のdefault branchにもある`build.yaml`のworkflow_dispatchを使う。 +dev専用workflowをdefault branchへ追加する必要はない。 + +```sh +gh workflow run build.yaml --ref dev --repo PyYoshi/cChardet +``` + +これは利用者が必要と判断して実行するコマンド例であり、 +この変更のために追加の手動buildを起動したわけではない。 +明示dispatchは自動docs-only省略とは別である。 +tag refではなくdevを指定し、release処理は起動しない。 + +## 検証範囲 + +構造testで、dev wheelのPR event、docs除外、3 OS matrix、共有action、 +dev pushのtest維持、master/tag/manual eventの維持を確認する。 +workflow構造の検証とGitHub上での実イベントの検証は別であり、 +PR/merge後の実行状況も統合時に確認する。 diff --git a/tests/test_dev_workflow_policy.py b/tests/test_dev_workflow_policy.py new file mode 100644 index 0000000..81a63bd --- /dev/null +++ b/tests/test_dev_workflow_policy.py @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: MIT +"""Structural guards for dev-only wheel deduplication and docs-only skipping.""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def workflow(name): + return (ROOT / ".github" / "workflows" / name).read_text(encoding="utf-8") + + +def events(text): + block = text.split("\non:\n", 1)[1].split("\npermissions:", 1)[0] + return "\n".join(line for line in block.splitlines() if not line.lstrip().startswith("#")) + + +def test_dev_wheels_only_run_automatically_for_pull_requests(): + assert events(workflow("build-dev.yml")).strip() == """pull_request: + branches: [dev] + paths-ignore: + - '**.md' + - '**.rst'""" + + +def test_dev_retains_post_merge_tests_and_docs_only_skips(): + block = events(workflow("test-dev.yml")) + assert block.strip() == """push: + branches: [dev] + paths-ignore: + - '**.md' + - '**.rst' + pull_request: + branches: [dev] + paths-ignore: + - '**.md' + - '**.rst'""" + assert "uses: ./.github/workflows/test.yml" in workflow("test-dev.yml") + + +def test_full_wheel_matrix_and_read_only_permissions_remain(): + text = workflow("build-dev.yml") + assert "os: [ubuntu-latest, windows-latest, macos-latest]" in text + assert "uses: ./.github/actions/build-wheels" in text + assert "submodules: recursive" in text + assert "permissions:\n contents: read" in text + + +def test_master_release_and_explicit_rebuild_events_remain(): + assert events(workflow("build.yaml")).strip() == """push: + branches: [master] + tags: ["v*"] + pull_request: + branches-ignore: [dev] + workflow_dispatch:""" + assert events(workflow("test.yml")).strip() == """push: + branches: [master] + pull_request: + branches-ignore: [dev] + workflow_dispatch: + workflow_call:""" From 7b9ee5118ed77e65d82293aa97aa44d1bb5599c5 Mon Sep 17 00:00:00 2001 From: Yoshihiro Misawa Date: Mon, 21 Sep 2026 04:54:11 +0900 Subject: [PATCH 2/2] Allow explicit dev PR wheel opt-out with skip-build title marker --- .github/workflows/build-dev.yml | 4 +++- docs/v3-ci-execution-policy.md | 25 +++++++++++++++++++++++-- tests/test_dev_workflow_policy.py | 10 ++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 9c423ab..5aaf516 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -1,7 +1,7 @@ name: Build wheels (dev) on: - # dev requires a PR; build the full wheel matrix before merging. + # dev requires a PR; build wheels unless its title explicitly opts out. # Post-merge tests remain in test-dev.yml. For an explicit rebuild use # the existing Build wheels workflow (build.yaml) with ref=dev. pull_request: @@ -19,6 +19,8 @@ concurrency: jobs: wheels: + # Evaluate before expanding the matrix; never interpolate the title into shell. + if: ${{ !contains(github.event.pull_request.title, '[skip-build]') }} name: Wheels / ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: diff --git a/docs/v3-ci-execution-policy.md b/docs/v3-ci-execution-policy.md index 663492b..3f309d7 100644 --- a/docs/v3-ci-execution-policy.md +++ b/docs/v3-ci-execution-policy.md @@ -7,14 +7,15 @@ PRとmerge後のdev pushで、同じ3 OSのwheel matrixを繰り返していた たとえばPR #142のwheel runは35526469811、merge後は35527522837。 Actionsの実行量を減らし、同じ変更を短い間隔で再buildしないようにする。 -変更は`build-dev.yml`のpush triggerだけを削除する。 -PRでは従来どおりLinux/macOS/Windowsの全wheel matrixを実行する。 +`build-dev.yml`のpush triggerを削除する。 +PRでは原則としてLinux/macOS/Windowsの全wheel matrixを実行する。 Python/ABI/architecture、smoke、共有build actionを縮小しない。 merge後も`test-dev.yml`のPython matrix、native sanitizer、distribution検証は残す。 | event | wheel | test | | --- | --- | --- | | dev向けcode PR | 全matrix | 従来どおり | +| dev向け`[skip-build]`付きPR | wheel jobをskip | 従来どおり | | devへのmerge/push | 自動実行しない | 従来どおり | | dev向けdocs-only PR/push | 自動実行しない | 自動実行しない | | master・release tag | 変更なし | 変更なし | @@ -28,6 +29,26 @@ PR対象headと実際のmerge結果が異なる変更を含む場合や、PRを 必要に応じてmerge後の明示wheel buildを実行する。PR成功が任意の将来commitの wheel互換性を保証するわけではない。 +## PRタイトルでの明示的な省略 + +dev向けPRタイトルに`[skip-build]`を含めると、wheel jobをmatrix展開前に省略する。 +例: `[skip-build] ci: workflowの実行条件を整理`。 +GitHub Actionsの`contains`は大文字小文字を区別しない。角括弧を含むmarkerを使い、 +通常の文章にある`skip-build`だけでは省略しない。 + +Python test、native sanitizer、distribution検証は省略しない。 +master向けPR、release tag、手動buildにはこの条件を適用しない。 +workflow自体は起動し、wheel jobの結果は`skipped`となる。成功したwheel検証とは扱わない。 + +タイトルはPR作成前に設定するのが基本。判定はPRのopened/synchronize/reopened event時に行う。 +タイトルや本文の編集だけでは新しいCIを起動せず、実行中のbuildも停止しない。 +後からmarkerを変更した場合は次のpushまたはreopenで反映する。 +これにより本文の追記や通常のタイトル修正による重い再buildを避ける。 + +build設定、native/Cythonコード、依存関係、配布対象の変更では原則markerを使わない。 +CI運用や非配布tool等、wheel検証を省略する理由を説明できる場合に限定し、 +PR本文に省略理由を残す。markerはレビューやdev保護ruleを迂回するものではない。 + ## 明示的なdev再build 既存のdefault branchにもある`build.yaml`のworkflow_dispatchを使う。 diff --git a/tests/test_dev_workflow_policy.py b/tests/test_dev_workflow_policy.py index 81a63bd..1e3f375 100644 --- a/tests/test_dev_workflow_policy.py +++ b/tests/test_dev_workflow_policy.py @@ -46,6 +46,16 @@ def test_full_wheel_matrix_and_read_only_permissions_remain(): assert "permissions:\n contents: read" in text +def test_title_opt_out_is_limited_to_dev_wheels(): + text = workflow("build-dev.yml") + guard = " if: ${{ !contains(github.event.pull_request.title, '[skip-build]') }}" + assert guard in text.split(" wheels:\n", 1)[1].split(" steps:", 1)[0] + assert text.count("github.event.pull_request.title") == 1 + for name in ("build.yaml", "test.yml", "test-dev.yml"): + assert "skip-build" not in workflow(name) + assert "github.event.pull_request.title" not in workflow(name) + + def test_master_release_and_explicit_rebuild_events_remain(): assert events(workflow("build.yaml")).strip() == """push: branches: [master]