Skip to content

ci(workflow): Linux 构建改用 manylinux 容器并本地缓存 pip #47

ci(workflow): Linux 构建改用 manylinux 容器并本地缓存 pip

ci(workflow): Linux 构建改用 manylinux 容器并本地缓存 pip #47

Workflow file for this run

name: Build and Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- name: windows-amd64
runner: windows-latest
setup_python: true
python_cmd: python
exe_name: integrated_script.exe
out_name: integrated_script-${{ github.ref_name }}-windows-amd64.exe
- name: linux-amd64
runner: ubuntu-latest
setup_python: false
use_container: true
container_image: quay.io/pypa/manylinux_2_28_x86_64
docker_cmd: docker
exe_name: integrated_script
out_name: integrated_script-${{ github.ref_name }}-linux-amd64
- name: linux-arm64
runner:
- self-hosted
- Linux
- ARM64
setup_python: false
use_container: true
container_image: quay.io/pypa/manylinux_2_28_aarch64
docker_cmd: sudo docker
exe_name: integrated_script
out_name: integrated_script-${{ github.ref_name }}-linux-arm64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
if: ${{ matrix.setup_python == true }}
with:
python-version: '3.11'
- name: Build (container)
if: ${{ matrix.use_container == true }}
shell: bash
run: |
mkdir -p .pip-cache
${{ matrix.docker_cmd }} run --rm \
-v "$PWD:/work" \
-v "$PWD/.pip-cache:/root/.cache/pip" \
-w /work \
${{ matrix.container_image }} \
bash -lc "/opt/python/cp311-cp311/bin/python -m pip install -U pip && \
/opt/python/cp311-cp311/bin/python -m pip install -r requirements.txt pyinstaller && \
/opt/python/cp311-cp311/bin/python build_exe.py"
- name: Build (hosted)
if: ${{ matrix.use_container != true }}
shell: bash
run: |
python -m pip install -U pip
python -m pip install -r requirements.txt pyinstaller
python build_exe.py
- name: Rename executable with release tag
env:
EXE_NAME: ${{ matrix.exe_name }}
OUT_NAME: ${{ matrix.out_name }}
shell: bash
run: |
python - <<'PY'
import os
from pathlib import Path
exe_name = os.environ.get("EXE_NAME")
out_name = os.environ.get("OUT_NAME")
exe = Path("dist") / exe_name
if exe.exists() and out_name:
exe.rename(exe.with_name(out_name))
PY
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: dist/${{ matrix.out_name }}
release:
name: Create Release
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: dist
- name: Generate release notes
env:
TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
shell: bash
run: |
python - <<'PY'
import os
import re
import subprocess
from datetime import datetime, timedelta
tag = os.environ.get("TAG", "").strip()
repo = os.environ.get("REPO", "").strip()
def run(*args: str) -> str:
return subprocess.check_output(
list(args),
text=True,
encoding="utf-8",
errors="replace",
).strip()
prev = None
if tag:
tags = run(
"git",
"for-each-ref",
"--sort=version:refname",
"--format=%(refname:short)",
"refs/tags/v[0-9]*.[0-9]*.[0-9]*",
).splitlines()
try:
idx = tags.index(tag)
except ValueError:
idx = -1
if idx > 0:
prev = tags[idx - 1]
rev_range = f"{prev}..{tag}" if prev else tag
raw = run(
"git",
"log",
"--no-merges",
"--pretty=format:%s|%h",
rev_range,
)
lines = [ln for ln in raw.splitlines() if ln.strip()]
categories = {
"新增": [],
"修复": [],
"文档": [],
"CI/构建": [],
"重构": [],
"测试": [],
"其他": [],
}
type_re = re.compile(r"^(?P<type>\\w+)(?:\\((?P<scope>[^)]+)\\))?:\\s*(?P<msg>.+)$")
for ln in lines:
subject, sha = ln.rsplit("|", 1)
subject = subject.strip()
sha = sha.strip()
lower = subject.lower()
m = type_re.match(subject)
if m:
msg = m.group("msg").strip()
scope = (m.group("scope") or "").strip()
display = f"{msg} ({scope})" if scope else msg
t = m.group("type").lower()
else:
display = subject
t = lower.split(":", 1)[0]
if t.startswith("feat"):
categories["新增"].append((display, sha))
elif t.startswith("fix"):
categories["修复"].append((display, sha))
elif t.startswith("docs"):
categories["文档"].append((display, sha))
elif t.startswith("ci"):
categories["CI/构建"].append((display, sha))
elif t.startswith("refactor"):
categories["重构"].append((display, sha))
elif t.startswith("test"):
categories["测试"].append((display, sha))
else:
categories["其他"].append((display, sha))
def format_section(title: str, items: list[tuple[str, str]]) -> str:
if not items:
return ""
lines = [f"## {title}", ""]
for subject, sha in items:
lines.append(f"- {subject} ({sha})")
lines.append("")
return "\n".join(lines)
body = []
body.append(f"# {tag}")
body.append("")
if prev:
body.append(f"对比范围:{prev} → {tag}")
if repo:
body.append(
f"完整对比:https://github.com/{repo}/compare/{prev}...{tag}"
)
else:
body.append("对比范围:初始发布")
body.append("")
for section in ("新增", "修复", "文档", "CI/构建", "重构", "测试", "其他"):
part = format_section(section, categories[section])
if part:
body.append(part)
if not any(categories.values()):
body.append("本次没有可展示的提交记录。")
body.append("")
utc8 = datetime.utcnow().replace(microsecond=0) + timedelta(hours=8)
body.append(f"生成时间:{utc8.strftime('%Y-%m-%d %H:%M')} 北京时间")
body.append("")
with open("release_body.md", "w", encoding="utf-8") as f:
f.write("\n".join(body))
PY
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
dist/windows-amd64/integrated_script-${{ github.ref_name }}-windows-amd64.exe
dist/linux-amd64/integrated_script-${{ github.ref_name }}-linux-amd64
dist/linux-arm64/integrated_script-${{ github.ref_name }}-linux-arm64
body_path: release_body.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}