Skip to content

Commit 555b277

Browse files
committed
[build] Finalize PyPI publish job and release checklist
1 parent ee167a3 commit 555b277

4 files changed

Lines changed: 74 additions & 82 deletions

File tree

.github/workflows/publish-pypi.yml

Lines changed: 69 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -16,103 +16,94 @@ on:
1616
default: 'testpypi'
1717

1818
jobs:
19+
# Production publishes require all three release workflows to be green for the tag.
20+
# TestPyPI publishes are exploratory and skip this job; the Resolve step in `publish`
21+
# still gates on a successful Python Distribution run so artifacts are guaranteed to exist.
1922
verify:
2023
name: Verify Build
24+
if: github.event.inputs.environment == 'pypi'
2125
runs-on: ubuntu-latest
26+
permissions:
27+
actions: read
2228
steps:
23-
- name: Check workflows
24-
uses: actions/github-script@v8
25-
with:
26-
script: |
27-
const { owner, repo } = context.repo;
28-
const tag = "${{ github.event.inputs.tag }}";
29-
const requiredWorkflows = ['Windows Distribution', 'Python Distribution'];
30-
let workflowConclusions = {};
31-
32-
console.log(`Checking for successful workflow runs for tag: ${tag}`);
33-
34-
const { data: response } = await github.rest.actions.listWorkflowRunsForRepo({
35-
owner,
36-
repo,
37-
event: 'push',
38-
});
39-
40-
const runsForTag = response.workflow_runs.filter(run => run.head_branch === tag);
41-
42-
for (const run of runsForTag) {
43-
if (requiredWorkflows.includes(run.name)) {
44-
if (!workflowConclusions[run.name] || new Date(run.created_at) > new Date(workflowConclusions[run.name].created_at)) {
45-
workflowConclusions[run.name] = {
46-
conclusion: run.conclusion,
47-
created_at: run.created_at,
48-
html_url: run.html_url,
49-
};
50-
}
51-
}
52-
}
53-
54-
let allSuccess = true;
55-
for (const workflowName of requiredWorkflows) {
56-
if (!workflowConclusions[workflowName]) {
57-
core.setFailed(`Workflow "${workflowName}" was not found for tag ${tag}.`);
58-
allSuccess = false;
59-
} else if (workflowConclusions[workflowName].conclusion !== 'success') {
60-
core.setFailed(`Workflow "${workflowName}" did not succeed for tag ${tag}. Conclusion was "${workflowConclusions[workflowName].conclusion}". See: ${workflowConclusions[workflowName].html_url}`);
61-
allSuccess = false;
62-
} else {
63-
console.log(`[OK] Workflow "${workflowName}" succeeded for tag ${tag}.`);
64-
}
65-
}
66-
67-
if (!allSuccess) {
68-
throw new Error("One or more required build workflows did not succeed.");
69-
}
29+
- name: Check required workflows succeeded for ${{ github.event.inputs.tag }}
30+
env:
31+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32+
GH_REPO: ${{ github.repository }}
33+
TAG: ${{ github.event.inputs.tag }}
34+
run: |
35+
set -euo pipefail
36+
required=("Python Distribution" "Windows Distribution" "Release Test Suite")
37+
failed=0
38+
for workflow in "${required[@]}"; do
39+
conclusion=$(gh run list \
40+
--workflow "$workflow" \
41+
--branch "$TAG" \
42+
--event push \
43+
--limit 1 \
44+
--json conclusion \
45+
-q '.[0].conclusion // ""')
46+
if [[ "$conclusion" != "success" ]]; then
47+
echo "::error::Workflow '$workflow' did not succeed for tag $TAG (got: '${conclusion:-no run found}')"
48+
failed=1
49+
else
50+
echo "[OK] $workflow"
51+
fi
52+
done
53+
[[ "$failed" -eq 0 ]]
7054
7155
publish:
72-
name: Building and Publishing to ${{ github.event.inputs.environment }} PyPI
56+
name: Publish ${{ github.event.inputs.tag }} to ${{ github.event.inputs.environment }}
7357
runs-on: ubuntu-latest
7458
needs: verify
59+
# Run when verify succeeded (pypi) or was skipped (testpypi).
60+
if: |
61+
always() &&
62+
(needs.verify.result == 'success' || needs.verify.result == 'skipped')
7563
7664
environment:
7765
name: ${{ github.event.inputs.environment }}
7866
url: ${{ github.event.inputs.environment == 'testpypi' && 'https://test.pypi.org/p/scenedetect' || 'https://pypi.org/p/scenedetect' }}
7967

8068
permissions:
81-
id-token: write # IMPORTANT: mandatory for trusted publishing
69+
id-token: write # mandatory for trusted publishing
70+
actions: read # for cross-workflow artifact download
8271

8372
steps:
84-
- name: Checkout ${{ github.event.inputs.tag }}
85-
uses: actions/checkout@v5
86-
with:
87-
ref: ${{ github.event.inputs.tag }}
88-
89-
- name: Set up Python
90-
uses: actions/setup-python@v6
91-
with:
92-
python-version: "3.x"
93-
cache: 'pip'
94-
95-
- name: Install Dependencies
73+
- name: Resolve Python Distribution run for ${{ github.event.inputs.tag }}
74+
id: resolve
75+
env:
76+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77+
GH_REPO: ${{ github.repository }}
78+
TAG: ${{ github.event.inputs.tag }}
9679
run: |
97-
python -m pip install --upgrade pip
98-
pip install build twine
99-
100-
- name: Build Package
101-
run: |
102-
python -m build
103-
python packaging/build_headless.py
104-
python -m build
105-
mkdir pkg
106-
mv dist/*.tar.gz pkg/
107-
mv dist/*.whl pkg/
108-
109-
- name: Upload Package
110-
uses: actions/upload-artifact@v6
80+
set -euo pipefail
81+
run_id=$(gh run list \
82+
--workflow "Python Distribution" \
83+
--branch "$TAG" \
84+
--event push \
85+
--status success \
86+
--limit 1 \
87+
--json databaseId \
88+
-q '.[0].databaseId // ""')
89+
if [[ -z "$run_id" ]]; then
90+
echo "::error::No successful 'Python Distribution' run found for tag $TAG. Push the tag and wait for build.yml to finish before publishing."
91+
exit 1
92+
fi
93+
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
94+
echo "Using Python Distribution run $run_id"
95+
96+
- name: Download distribution artifact
97+
uses: actions/download-artifact@v7
11198
with:
11299
name: scenedetect-dist
113-
path: |
114-
pkg/*.tar.gz
115-
pkg/*.whl
100+
path: pkg/
101+
github-token: ${{ secrets.GITHUB_TOKEN }}
102+
repository: ${{ github.repository }}
103+
run-id: ${{ steps.resolve.outputs.run-id }}
104+
105+
- name: List artifact contents
106+
run: ls -la pkg/
116107

117108
- name: Publish Package
118109
uses: pypa/gh-action-pypi-publish@release/v1

RELEASE-PLAN.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ Optional: version referenced below as `X.Y[.Z]` - replace with the real version
5252
## 6. Publish & Release Checks
5353

5454
- [ ] Publish Github release
55-
- [ ] Upload to PyPI: `publish-pypi.yml` should run on the tag and upload. Verify both projects: https://pypi.org/project/scenedetect/ and https://pypi.org/project/scenedetect-headless/.
55+
- [ ] Upload to PyPI: `publish-pypi.yml` must be manually triggered on a release tag. Specify `testpypi` first, and make sure everything goes okay on the test instance. When verified and smoke tested, specify `pypi` as the environment, and publish the production package.
56+
- [ ] Verify both projects: https://pypi.org/project/scenedetect/ and https://pypi.org/project/scenedetect-headless/.
5657
- [ ] Merge `releases/X.Y` back into `main`.
5758
- [ ] Deploy website: `generate-website.yml` picks up the changelog / download page updates.
5859
- [ ] Deploy docs: `generate-docs.yml` publishes the new version.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ website = ["mkdocs==1.5.2", "jinja2>=3.1.6"]
5858

5959
[project.urls]
6060
Homepage = "https://www.scenedetect.com"
61-
Repository = "https://github.com/Breakthrough/PySceneDetect/"
6261
Documentation = "https://www.scenedetect.com/docs/"
63-
"Bug Tracker" = "https://github.com/Breakthrough/PySceneDetect/issues/"
62+
Source = "https://github.com/Breakthrough/PySceneDetect"
63+
Issues = "https://github.com/Breakthrough/PySceneDetect/issues"
6464

6565
[project.scripts]
6666
scenedetect = "scenedetect.__main__:main"

scenedetect/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575

7676
# Used for module identification and when printing version & about info
7777
# (e.g. calling `scenedetect version` or `scenedetect about`).
78-
__version__ = "0.7-dev0"
78+
__version__ = "0.7-dev1"
7979

8080
init_logger()
8181
logger = getLogger("pyscenedetect")

0 commit comments

Comments
 (0)