311 lines
11 KiB
YAML
311 lines
11 KiB
YAML
name: Build and Publish
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
# =================================================================
|
|
# ⚙️ 项目参数配置区
|
|
# =================================================================
|
|
env:
|
|
PROJECT_NAME: "qpypack" # 项目包名
|
|
MAIN_SCRIPT: "qpypack/main.py" # Python 入口文件
|
|
ICON_PATH: "qpypack/icon.ico" # 默认应用程序图标 (Windows ICO)
|
|
EXE_NAME_PREFIX: "QPyPack" # 应用程序名前缀
|
|
|
|
# -------------------------------------------------------------
|
|
# 🚀 功能开关
|
|
# -------------------------------------------------------------
|
|
BUILD_PIP_PACKAGE: "true" # 是否构建 pip 安装包
|
|
PUBLISH_TO_PYPI: "true" # 是否发布到 PyPI 官方源
|
|
# -------------------------------------------------------------
|
|
|
|
# 软件元数据 (Windows 平台适用)
|
|
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:
|
|
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 os.path.exists(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 os.path.exists(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 "github-actions[bot]"
|
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
|
|
if [ -f "pyproject.toml" ]; then
|
|
git add pyproject.toml "${{ env.MAIN_SCRIPT }}"
|
|
else
|
|
git add "${{ env.MAIN_SCRIPT }}"
|
|
fi
|
|
|
|
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
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: |
|
|
dist/*.whl
|
|
dist/*.tar.gz
|
|
fail_on_unmatched_files: false
|
|
generate_release_notes: true
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Publish to PyPI
|
|
if: env.PUBLISH_TO_PYPI == 'true' && env.BUILD_PIP_PACKAGE == 'true'
|
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
with:
|
|
skip-existing: true
|
|
continue-on-error: true
|
|
|
|
build-executables:
|
|
name: Build Native Executables
|
|
runs-on: ${{ matrix.os }}
|
|
permissions:
|
|
contents: write
|
|
needs: publish-pypi
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: windows-latest
|
|
platform: windows
|
|
archive_ext: .zip
|
|
- os: macos-latest
|
|
platform: macos
|
|
archive_ext: .zip
|
|
- os: ubuntu-latest
|
|
platform: linux
|
|
archive_ext: .tar.gz
|
|
steps:
|
|
- name: Checkout source code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Extract version
|
|
shell: bash
|
|
run: |
|
|
TAG_NAME=${{ github.ref_name }}
|
|
VERSION=${TAG_NAME#v}
|
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
|
|
- name: Sync version in Workspace
|
|
shell: python
|
|
run: |
|
|
import re
|
|
import os
|
|
|
|
version = os.environ['VERSION']
|
|
main_path = os.environ['MAIN_SCRIPT']
|
|
print(f"Target version to sync on {os.name}: {version}")
|
|
|
|
if os.path.exists(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"Workspace {main_path} synchronized.")
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
python -m pip install PySide6 pyinstaller Pillow
|
|
|
|
- name: Generate Windows Version Info File
|
|
if: matrix.platform == '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)
|
|
|
|
company = os.environ.get('COMPANY_NAME', 'My Company')
|
|
product = os.environ.get('PRODUCT_NAME', 'My Product')
|
|
copyright_str = os.environ.get('LEGAL_COPYRIGHT', 'Copyright')
|
|
description = os.environ.get('FILE_DESCRIPTION', 'Python Executable')
|
|
exe_name = os.environ.get('EXE_NAME_PREFIX', 'App')
|
|
|
|
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', '{company}'),
|
|
StringStruct('FileDescription', '{description}'),
|
|
StringStruct('FileVersion', '{version}'),
|
|
StringStruct('InternalName', '{exe_name}'),
|
|
StringStruct('LegalCopyright', '{copyright_str}'),
|
|
StringStruct('OriginalFilename', '{exe_name}.exe'),
|
|
StringStruct('ProductName', '{product}'),
|
|
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: Configure Icon Options
|
|
shell: bash
|
|
run: |
|
|
ICON_OPTION=""
|
|
if [ "${{ matrix.platform }}" = "windows" ]; then
|
|
if [ -f "${{ env.ICON_PATH }}" ]; then
|
|
ICON_OPTION="--icon=\"${{ env.ICON_PATH }}\" --add-data=\"${{ env.ICON_PATH }};.\""
|
|
fi
|
|
elif [ "${{ matrix.platform }}" = "macos" ]; then
|
|
# macOS 优先检查工程是否存在标准的 .icns/icon.png,否则采用默认图标降级匹配
|
|
if [ -f "qpypack/icon.icns" ]; then
|
|
ICON_OPTION="--icon=\"qpypack/icon.icns\" --add-data=\"qpypack/icon.icns:.\""
|
|
elif [ -f "qpypack/icon.png" ]; then
|
|
ICON_OPTION="--icon=\"qpypack/icon.png\" --add-data=\"qpypack/icon.png:.\""
|
|
elif [ -f "${{ env.ICON_PATH }}" ]; then
|
|
ICON_OPTION="--icon=\"${{ env.ICON_PATH }}\" --add-data=\"${{ env.ICON_PATH }}:.\""
|
|
fi
|
|
elif [ "${{ matrix.platform }}" = "linux" ]; then
|
|
if [ -f "qpypack/icon.png" ]; then
|
|
ICON_OPTION="--add-data=\"qpypack/icon.png:.\""
|
|
elif [ -f "${{ env.ICON_PATH }}" ]; then
|
|
ICON_OPTION="--add-data=\"${{ env.ICON_PATH }}:.\""
|
|
fi
|
|
fi
|
|
echo "ICON_OPTION=$ICON_OPTION" >> $GITHUB_ENV
|
|
|
|
- name: Build EXE / APP / Binary with PyInstaller
|
|
shell: bash
|
|
run: |
|
|
PYINSTALLER_CMD="pyinstaller --clean --onefile --noconsole"
|
|
|
|
# Windows 元数据配置
|
|
if [ "${{ matrix.platform }}" = "windows" ] && [ -f "file_version_info.txt" ]; then
|
|
PYINSTALLER_CMD="$PYINSTALLER_CMD --version-file=file_version_info.txt"
|
|
fi
|
|
|
|
# macOS Bundle 属性标识符配置
|
|
if [ "${{ matrix.platform }}" = "macos" ]; then
|
|
PYINSTALLER_CMD="$PYINSTALLER_CMD --osx-bundle-identifier=\"com.qwejayhuang.qpypack\""
|
|
fi
|
|
|
|
# 挂载图标参数
|
|
if [ -n "$ICON_OPTION" ]; then
|
|
PYINSTALLER_CMD="$PYINSTALLER_CMD $ICON_OPTION"
|
|
fi
|
|
|
|
TARGET_NAME="${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}"
|
|
PYINSTALLER_CMD="$PYINSTALLER_CMD --name=\"$TARGET_NAME\" ${{ env.MAIN_SCRIPT }}"
|
|
|
|
echo "Executing build: $PYINSTALLER_CMD"
|
|
eval $PYINSTALLER_CMD
|
|
|
|
- name: Compress Build Artifact (Windows)
|
|
if: matrix.platform == 'windows'
|
|
shell: pwsh
|
|
run: |
|
|
Compress-Archive -Path "dist/${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}.exe" -DestinationPath "dist/${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}.zip"
|
|
|
|
- name: Compress Build Artifact (macOS)
|
|
if: matrix.platform == 'macos'
|
|
shell: bash
|
|
run: |
|
|
cd dist
|
|
zip -r "${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}.zip" "${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}.app"
|
|
|
|
- name: Compress Build Artifact (Linux)
|
|
if: matrix.platform == 'linux'
|
|
shell: bash
|
|
run: |
|
|
cd dist
|
|
tar -czvf "${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}.tar.gz" "${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}"
|
|
|
|
- name: Upload Artifacts to GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: dist/${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}${{ matrix.archive_ext }}
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |