mirror of
https://github.com/soxoj/maigret.git
synced 2026-08-17 19:25:41 +02:00
@@ -4,6 +4,24 @@ on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
|
||||
# Two builds racing on the same branch could leave the nightly tag pointing at
|
||||
# one commit while the .exe attached to the release was built from the other.
|
||||
concurrency:
|
||||
group: pyinstaller-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Needed by "Move the nightly tag to the built commit" below, which force-updates
|
||||
# a ref under refs/tags/.
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
# Deliberately prefixed. A tag named after the branch it was built from shadows
|
||||
# that branch, and a bare `main` then resolves to the tag in every clone:
|
||||
# `git rev-parse main` warns "refname 'main' is ambiguous" and answers with the
|
||||
# tag, not the branch head.
|
||||
NIGHTLY_TAG: nightly-${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -41,6 +59,20 @@ jobs:
|
||||
with:
|
||||
name: maigret_standalone_win32
|
||||
|
||||
# release-action creates a tag only when it does not exist yet, and GitHub
|
||||
# ignores target_commitish once the tag is there ("Unused if the Git tag
|
||||
# already exists"). So the tag has to be moved here, otherwise it stays
|
||||
# pinned to the first build forever while the attached .exe keeps being
|
||||
# replaced on every push.
|
||||
- name: Move the nightly tag to the built commit
|
||||
if: success()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git tag --force "$NIGHTLY_TAG" "$GITHUB_SHA"
|
||||
# Tag pushes do not match `on.push.branches`, and pushes authenticated
|
||||
# with GITHUB_TOKEN never start a workflow run, so this cannot recurse.
|
||||
git push --force origin "refs/tags/$NIGHTLY_TAG"
|
||||
|
||||
- name: Create New Release and Upload PyInstaller Binary to Release
|
||||
if: success()
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
@@ -48,18 +80,23 @@ jobs:
|
||||
with:
|
||||
allowUpdates: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
# A development build must never outrank a stable release. `prerelease`
|
||||
# keeps it out of /releases/latest, and `makeLatest: false` stops it
|
||||
# from stealing the marker back on the next push to main.
|
||||
prerelease: true
|
||||
artifactErrorsFailBuild: true
|
||||
makeLatest: true
|
||||
makeLatest: false
|
||||
replacesArtifacts: true
|
||||
artifacts: maigret_standalone.exe
|
||||
name: Development Windows Release [${{ github.ref_name }}]
|
||||
tag: ${{ github.ref_name }}
|
||||
tag: ${{ env.NIGHTLY_TAG }}
|
||||
commit: ${{ github.sha }}
|
||||
body: |
|
||||
This is a development release built from the **${{ github.ref_name }}** branch.
|
||||
This is a development release built from the **${{ github.ref_name }}** branch, at commit ${{ github.sha }}.
|
||||
The `${{ env.NIGHTLY_TAG }}` tag is moved to that commit on every build, so it always matches the binary attached below.
|
||||
|
||||
Take into account that `dev` releases may be unstable.
|
||||
Please, use [the development release](https://github.com/soxoj/maigret/releases/tag/main) build from the **main** branch.
|
||||
It is **not** a stable release — for that, see [the latest release](https://github.com/soxoj/maigret/releases/latest).
|
||||
${{ github.ref_name == 'dev' && 'The `dev` branch is more experimental than `main`. Unless you need a specific unreleased fix, prefer [the `main` development build](https://github.com/soxoj/maigret/releases/tag/nightly-main).' || '' }}
|
||||
|
||||
## How to run
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Guards for the release-publishing GitHub Actions workflow.
|
||||
|
||||
Regression tests for #2959. The PyInstaller development build used to publish
|
||||
itself with ``makeLatest: true`` under a tag named after the branch it was built
|
||||
from, which caused three separate problems:
|
||||
|
||||
1. the Windows dev build held the repository's "Latest release" marker ahead of
|
||||
every stable release, and stole it back on every push to ``main``;
|
||||
2. the tag never moved, so it kept naming the first build while the attached
|
||||
``.exe`` was replaced on every push;
|
||||
3. the tags ``main`` / ``dev`` shadowed the branches of the same name, making a
|
||||
bare ``main`` refname ambiguous in every clone.
|
||||
|
||||
These tests read the workflow as data, so they fail if any of the three
|
||||
conditions is reintroduced.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
yaml = pytest.importorskip("yaml")
|
||||
|
||||
WORKFLOW_PATH = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
||||
".github",
|
||||
"workflows",
|
||||
"pyinstaller.yml",
|
||||
)
|
||||
|
||||
RELEASE_ACTION = "ncipollo/release-action"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def workflow():
|
||||
with open(WORKFLOW_PATH, encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def release_step(workflow):
|
||||
steps = workflow["jobs"]["build"]["steps"]
|
||||
matching = [s for s in steps if RELEASE_ACTION in s.get("uses", "")]
|
||||
assert len(matching) == 1, f"expected exactly one {RELEASE_ACTION} step"
|
||||
return matching[0]
|
||||
|
||||
|
||||
def _push_branches(workflow):
|
||||
# PyAML resolves the bare `on` key to the boolean True (YAML 1.1 treats it as
|
||||
# a truthy literal), so accept either spelling.
|
||||
triggers = workflow.get("on", workflow.get(True))
|
||||
return triggers["push"]["branches"]
|
||||
|
||||
|
||||
def _resolve(expression, workflow, branch):
|
||||
"""Expand the workflow-level env and `github.ref_name` in an expression."""
|
||||
for name, value in (workflow.get("env") or {}).items():
|
||||
expression = expression.replace("${{ env.%s }}" % name, str(value))
|
||||
return expression.replace("${{ github.ref_name }}", branch)
|
||||
|
||||
|
||||
def test_dev_build_is_a_prerelease(release_step):
|
||||
# Keeps the build out of /releases/latest and out of the `release: released`
|
||||
# event that publishes to PyPI.
|
||||
assert str(release_step["with"]["prerelease"]).lower() == "true"
|
||||
|
||||
|
||||
def test_dev_build_never_claims_the_latest_marker(release_step):
|
||||
assert str(release_step["with"]["makeLatest"]).lower() == "false"
|
||||
|
||||
|
||||
def test_release_tag_does_not_shadow_a_branch(workflow, release_step):
|
||||
tag = release_step["with"]["tag"]
|
||||
for branch in _push_branches(workflow):
|
||||
resolved = _resolve(tag, workflow, branch)
|
||||
assert resolved != branch, (
|
||||
f"tag {resolved!r} shadows the {branch!r} branch: a bare "
|
||||
f"{branch!r} refname would resolve to the tag in every clone"
|
||||
)
|
||||
assert resolved, f"tag resolved to an empty string for branch {branch!r}"
|
||||
|
||||
|
||||
def test_release_tag_is_moved_to_the_built_commit(workflow, release_step):
|
||||
# release-action only creates a tag when it is missing, and GitHub ignores
|
||||
# target_commitish for an existing tag, so an explicit push is what keeps the
|
||||
# tag in step with the attached binary.
|
||||
tag = release_step["with"]["tag"]
|
||||
scripts = [s["run"] for s in workflow["jobs"]["build"]["steps"] if "run" in s]
|
||||
moves_tag = any(
|
||||
"refs/tags/" in script and "--force" in script for script in scripts
|
||||
)
|
||||
assert moves_tag, (
|
||||
f"no step force-pushes {tag!r}; without it the tag stays pinned to the "
|
||||
"first build while the release keeps getting new binaries"
|
||||
)
|
||||
assert workflow.get("permissions", {}).get("contents") == "write"
|
||||
Reference in New Issue
Block a user