Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
338 changes: 253 additions & 85 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,85 +1,253 @@
name: Build Executables

on:
push:
tags:
- 'v*'
workflow_dispatch:

jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
name: Linux
build_name: 错题集生成器-linux
final_name: 错题集生成器-linux-x86_64.AppImage
- os: windows-latest
name: Windows
build_name: 错题集生成器-windows
final_name: 错题集生成器-windows-x86_64.exe

runs-on: ${{ matrix.os }}
name: Build for ${{ matrix.name }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller

- name: Build with PyInstaller (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
pyinstaller --onefile --windowed --name="${{ matrix.build_name }}" --add-data="ui/icons:ui/icons" main.py
mv "dist/${{ matrix.build_name }}" "dist/${{ matrix.final_name }}"

- name: Build with PyInstaller (Windows)
if: matrix.os == 'windows-latest'
run: |
pyinstaller --onefile --windowed --name="${{ matrix.build_name }}" --add-data="ui/icons;ui/icons" main.py
mv "dist/${{ matrix.build_name }}.exe" "dist/${{ matrix.final_name }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.final_name }}
path: dist/${{ matrix.final_name }}

release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')

steps:
- name: Download Linux artifact
uses: actions/download-artifact@v4
with:
name: 错题集生成器-linux-x86_64.AppImage
path: ./artifacts

- name: Download Windows artifact
uses: actions/download-artifact@v4
with:
name: 错题集生成器-windows-x86_64.exe
path: ./artifacts

- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: ./artifacts/*
draft: false
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Build and Release

on:
push:
tags:
- "v*"
workflow_dispatch:

concurrency:
group: build-${{ github.ref }}
cancel-in-progress: false

jobs:
test:
name: Test before packaging
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: |
pyproject.toml
requirements*.txt

- name: Install project and test dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

- name: Verify project and tag versions
run: |
python - <<'PY'
import os
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
from pathlib import Path
from version import APP_VERSION

with Path("pyproject.toml").open("rb") as file:
project_version = tomllib.load(file)["project"]["version"]

if project_version != APP_VERSION:
raise SystemExit(
f"Version mismatch: version.py={APP_VERSION}, "
f"pyproject.toml={project_version}"
)

if os.environ.get("GITHUB_REF_TYPE") == "tag":
tag_version = os.environ["GITHUB_REF_NAME"].removeprefix("v")
if tag_version != APP_VERSION:
raise SystemExit(
f"Tag mismatch: tag={tag_version}, project={APP_VERSION}"
)
print(f"Packaging version {APP_VERSION}")
PY

- name: Compile Python sources
run: python -m compileall -q core.py main.py version.py ui tests

- name: Run tests
run: python -m pytest -q

python-package:
name: Build Python package
needs: test
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install build frontend
run: |
python -m pip install --upgrade pip
python -m pip install build

- name: Build wheel and source distribution
run: python -m build

- name: Validate wheel contents
run: |
python - <<'PY'
import glob
import zipfile

wheels = glob.glob("dist/*.whl")
if len(wheels) != 1:
raise SystemExit(f"Expected one wheel, found: {wheels}")

required_suffixes = ("core.py", "main.py", "version.py", "ui/main_window.py")
with zipfile.ZipFile(wheels[0]) as archive:
names = archive.namelist()

missing = [
suffix
for suffix in required_suffixes
if not any(name.endswith(suffix) for name in names)
]
if missing:
raise SystemExit(f"Wheel is missing runtime files: {missing}")
print(f"Validated {wheels[0]}")
PY

- name: Upload Python package artifacts
uses: actions/upload-artifact@v4
with:
name: python-package
path: |
dist/*.whl
dist/*.tar.gz
if-no-files-found: error

executable:
name: Build ${{ matrix.display-name }} executable
needs: test
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
display-name: Windows
artifact-name: windows-x86_64
- os: ubuntu-latest
display-name: Linux
artifact-name: linux-x86_64

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: |
pyproject.toml
requirements*.txt

- name: Install project and PyInstaller
run: |
python -m pip install --upgrade pip
python -m pip install . pyinstaller

- name: Build Windows executable
if: runner.os == 'Windows'
shell: pwsh
run: |
pyinstaller `
--noconfirm `
--clean `
--onefile `
--windowed `
--name "错题集生成器-windows-x86_64" `
--add-data "ui/icons;ui/icons" `
main.py

$artifact = "dist/错题集生成器-windows-x86_64.exe"
if (-not (Test-Path $artifact)) {
throw "Windows executable was not created: $artifact"
}
if ((Get-Item $artifact).Length -lt 1000000) {
throw "Windows executable is unexpectedly small"
}
$hash = (Get-FileHash $artifact -Algorithm SHA256).Hash.ToLower()
"$hash 错题集生成器-windows-x86_64.exe" |
Set-Content -Encoding ascii "$artifact.sha256"

- name: Build Linux executable
if: runner.os == 'Linux'
shell: bash
run: |
pyinstaller \
--noconfirm \
--clean \
--onefile \
--windowed \
--name "错题集生成器-linux-x86_64" \
--add-data "ui/icons:ui/icons" \
main.py

test -s "dist/错题集生成器-linux-x86_64"
test "$(stat -c%s 'dist/错题集生成器-linux-x86_64')" -ge 1000000
sha256sum "dist/错题集生成器-linux-x86_64" > \
"dist/错题集生成器-linux-x86_64.sha256"
tar -C dist -czf "dist/错题集生成器-linux-x86_64.tar.gz" \
"错题集生成器-linux-x86_64" \
"错题集生成器-linux-x86_64.sha256"

- name: Upload Windows artifact
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: |
dist/错题集生成器-windows-x86_64.exe
dist/错题集生成器-windows-x86_64.exe.sha256
if-no-files-found: error

- name: Upload Linux artifact
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: dist/错题集生成器-linux-x86_64.tar.gz
if-no-files-found: error

release:
name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
needs: [python-package, executable]
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true

- name: List release assets
run: find release-assets -maxdepth 2 -type f -print

- name: Publish release
uses: softprops/action-gh-release@v2
with:
files: release-assets/*
generate_release_notes: true
draft: false
prerelease: false
fail_on_unmatched_files: true
Loading
Loading