ci(workflow): Linux 构建改用 manylinux 容器并本地缓存 pip #47
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |