- Add zero-config presets for customtkinter, playwright, moviepy, etc. - Add fault-tolerant fallback (OOM single-thread retry & icon lock resilience) - Add dual-mode live log viewer & pure vector Material SVG UI - Overhaul README & README_EN with badges, screenshots and platform matrix
263 lines
9.2 KiB
YAML
263 lines
9.2 KiB
YAML
name: Build and Publish
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
env:
|
|
PROJECT_NAME: qpypack
|
|
MAIN_SCRIPT: qpypack/main.py
|
|
ICON_PATH: qpypack/icon.ico
|
|
EXE_NAME_PREFIX: QPyPack
|
|
BUILD_PIP_PACKAGE: true
|
|
PUBLISH_TO_PYPI: true
|
|
COMPANY_NAME: QwejayHuang
|
|
PRODUCT_NAME: QPyPack
|
|
LEGAL_COPYRIGHT: Copyright (C) QwejayHuang
|
|
FILE_DESCRIPTION: Automation Python Packaging and Freezing Suite
|
|
|
|
jobs:
|
|
publish-pypi:
|
|
name: Publish to PyPI and Create Release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
steps:
|
|
- name: Checkout source code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: main
|
|
fetch-depth: 0
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Sync version from Tag to files
|
|
shell: python
|
|
run: |
|
|
import re
|
|
import os
|
|
|
|
tag = "${{ github.ref_name }}"
|
|
version = tag[1:] if tag.lower().startswith('v') else tag
|
|
print(f"Target version to sync: {version}")
|
|
|
|
toml_path = 'pyproject.toml'
|
|
if not os.path.exists(toml_path):
|
|
raise FileNotFoundError(f"Missing critical configuration: {toml_path}")
|
|
|
|
with open(toml_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
new_content = re.sub(r'(version\s*=\s*["\'])[^"\']+(["\'])', f'\\g<1>{version}\\g<2>', content, count=1)
|
|
with open(toml_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
print("Successfully synchronized pyproject.toml.")
|
|
|
|
main_path = os.environ['MAIN_SCRIPT']
|
|
if not os.path.exists(main_path):
|
|
raise FileNotFoundError(f"Missing critical entrypoint: {main_path}")
|
|
|
|
with open(main_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
new_content = re.sub(r'(__version__\s*=\s*["\'])[^"\']+(["\'])', f'\\g<1>{version}\\g<2>', content, count=1)
|
|
with open(main_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
print(f"Successfully synchronized {main_path}.")
|
|
|
|
- name: Commit and push version updates back to repository
|
|
run: |
|
|
git config --global user.name "QwejayHuang"
|
|
git config --global user.email "QwejayHaung@****.com"
|
|
git add pyproject.toml "${{ env.MAIN_SCRIPT }}"
|
|
git commit -m "chore: bump version to ${{ github.ref_name }} [skip ci]" || echo "No version changes to commit"
|
|
git push origin HEAD:main
|
|
|
|
- name: Install build dependencies
|
|
if: env.BUILD_PIP_PACKAGE == 'true'
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
python -m pip install build
|
|
|
|
- name: Build sdist and wheel
|
|
if: env.BUILD_PIP_PACKAGE == 'true'
|
|
run: python -m build
|
|
|
|
- name: Create GitHub Release
|
|
if: env.BUILD_PIP_PACKAGE == 'true'
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: |
|
|
dist/*.whl
|
|
dist/*.tar.gz
|
|
generate_release_notes: true
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Publish to PyPI
|
|
if: env.BUILD_PIP_PACKAGE == 'true' && env.PUBLISH_TO_PYPI == 'true'
|
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
with:
|
|
skip-existing: true
|
|
|
|
build-binaries:
|
|
name: Build Standalone for ${{ matrix.os }} (${{ matrix.arch }})
|
|
needs: publish-pypi
|
|
runs-on: ${{ matrix.os }}
|
|
permissions:
|
|
contents: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: windows-latest
|
|
arch: x64
|
|
artifact_name: QPyPack.exe
|
|
package_extension: zip
|
|
- os: macos-14
|
|
arch: arm64
|
|
artifact_name: QPyPack.app
|
|
package_extension: zip
|
|
- os: ubuntu-latest
|
|
arch: x64
|
|
artifact_name: QPyPack
|
|
package_extension: tar.gz
|
|
|
|
steps:
|
|
- name: Checkout source code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: main
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.10'
|
|
cache: 'pip'
|
|
|
|
- name: Extract version
|
|
shell: bash
|
|
run: |
|
|
TAG_NAME=${{ github.ref_name }}
|
|
VERSION=${TAG_NAME#v}
|
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
|
|
- name: Generate Windows Version Info File
|
|
if: runner.os == 'Windows'
|
|
shell: python
|
|
run: |
|
|
import os
|
|
import re
|
|
|
|
version = os.environ['VERSION']
|
|
parts = re.findall(r'\d+', version)
|
|
v_num = (parts + ['0', '0', '0', '0'])[:4]
|
|
v_tuple = ",".join(v_num)
|
|
print(f"Windows FileVersion tuple parsed: ({v_tuple})")
|
|
|
|
info_content = f"""
|
|
VSVersionInfo(
|
|
ffi=FixedFileInfo(
|
|
filevers=({v_tuple}),
|
|
prodvers=({v_tuple}),
|
|
mask=0x3f,
|
|
flags=0x0,
|
|
OS=0x40004,
|
|
fileType=0x1,
|
|
subtype=0x0,
|
|
date=(0,0)
|
|
),
|
|
kids=[
|
|
StringFileInfo(
|
|
[
|
|
StringTable(
|
|
'040904B0',
|
|
[
|
|
StringStruct('CompanyName', '${{ env.COMPANY_NAME }}'),
|
|
StringStruct('FileDescription', '${{ env.FILE_DESCRIPTION }}'),
|
|
StringStruct('FileVersion', '{version}'),
|
|
StringStruct('InternalName', '${{ env.EXE_NAME_PREFIX }}'),
|
|
StringStruct('LegalCopyright', '${{ env.LEGAL_COPYRIGHT }}'),
|
|
StringStruct('OriginalFilename', '${{ env.EXE_NAME_PREFIX }}.exe'),
|
|
StringStruct('ProductName', '${{ env.PRODUCT_NAME }}'),
|
|
StringStruct('ProductVersion', '{version}')
|
|
]
|
|
)
|
|
]
|
|
),
|
|
VarFileInfo([VarStruct('Translation', [1033, 1200])])
|
|
]
|
|
)
|
|
"""
|
|
with open('file_version_info.txt', 'w', encoding='utf-8') as f:
|
|
f.write(info_content.strip())
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
python -m pip install PySide6 Pillow pyinstaller
|
|
|
|
- name: Build with PyInstaller (Windows with Version Info)
|
|
if: runner.os == 'Windows'
|
|
run: |
|
|
pyinstaller --clean --onefile --noconsole --version-file=file_version_info.txt --icon=${{ env.ICON_PATH }} --add-data "${{ env.ICON_PATH }};." --add-data "qpypack/locales;locales" --name="${{ env.EXE_NAME_PREFIX }}_Single" ${{ env.MAIN_SCRIPT }}
|
|
|
|
pyinstaller --clean --onedir --noconsole --version-file=file_version_info.txt --icon=${{ env.ICON_PATH }} --add-data "${{ env.ICON_PATH }};." --add-data "qpypack/locales;locales" --name="${{ env.EXE_NAME_PREFIX }}_Portable" ${{ env.MAIN_SCRIPT }}
|
|
|
|
- name: Build with PyInstaller (macOS / Linux)
|
|
if: runner.os != 'Windows'
|
|
shell: bash
|
|
run: |
|
|
if [ "${{ runner.os }}" = "macOS" ]; then
|
|
pyinstaller --onefile --noconsole --name="${{ env.EXE_NAME_PREFIX }}_Single" --icon=${{ env.ICON_PATH }} --add-data "qpypack/locales:locales" ${{ env.MAIN_SCRIPT }}
|
|
pyinstaller --onedir --noconsole --name="${{ env.EXE_NAME_PREFIX }}_Portable" --icon=${{ env.ICON_PATH }} --add-data "qpypack/locales:locales" ${{ env.MAIN_SCRIPT }}
|
|
else
|
|
pyinstaller --onefile --noconsole --name="${{ env.EXE_NAME_PREFIX }}_Single" --add-data "qpypack/locales:locales" ${{ env.MAIN_SCRIPT }}
|
|
pyinstaller --onedir --noconsole --name="${{ env.EXE_NAME_PREFIX }}_Portable" --add-data "qpypack/locales:locales" ${{ env.MAIN_SCRIPT }}
|
|
fi
|
|
|
|
- name: Package Artifact (Windows)
|
|
if: runner.os == 'Windows'
|
|
shell: pwsh
|
|
run: |
|
|
$BASE_NAME = "${{ env.EXE_NAME_PREFIX }}-${{ env.VERSION }}-windows-${{ matrix.arch }}"
|
|
|
|
Compress-Archive -Path "dist/${{ env.EXE_NAME_PREFIX }}_Single.exe" -DestinationPath "$BASE_NAME-SingleFile.zip" -Force
|
|
|
|
Compress-Archive -Path "dist/${{ env.EXE_NAME_PREFIX }}_Portable" -DestinationPath "$BASE_NAME-Portable.zip" -Force
|
|
|
|
- name: Package Artifact (macOS / Linux)
|
|
if: runner.os != 'Windows'
|
|
shell: bash
|
|
run: |
|
|
VERSION=${{ env.VERSION }}
|
|
OS_NAME=$(echo "${{ runner.os }}" | tr '[:upper:]' '[:lower:]')
|
|
BASE_NAME="${{ env.EXE_NAME_PREFIX }}-${VERSION}-${OS_NAME}-${{ matrix.arch }}"
|
|
cd dist
|
|
|
|
if [ "${{ runner.os }}" = "macOS" ]; then
|
|
TARGET_SINGLE="${{ env.EXE_NAME_PREFIX }}_Single.app"
|
|
TARGET_PORTABLE="${{ env.EXE_NAME_PREFIX }}_Portable.app"
|
|
else
|
|
TARGET_SINGLE="${{ env.EXE_NAME_PREFIX }}_Single"
|
|
TARGET_PORTABLE="${{ env.EXE_NAME_PREFIX }}_Portable"
|
|
fi
|
|
|
|
if [ "${{ matrix.package_extension }}" = "zip" ]; then
|
|
zip -r "../${BASE_NAME}-SingleFile.zip" "$TARGET_SINGLE"
|
|
zip -r "../${BASE_NAME}-Portable.zip" "$TARGET_PORTABLE"
|
|
else
|
|
tar -czf "../${BASE_NAME}-SingleFile.tar.gz" "$TARGET_SINGLE"
|
|
tar -czf "../${BASE_NAME}-Portable.tar.gz" "$TARGET_PORTABLE"
|
|
fi
|
|
|
|
- name: Append Standalone Binary to GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: ${{ env.EXE_NAME_PREFIX }}-*.*
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|