Repository navigation
v2.7.3 - add Python env manager, folder mode & native AST scanner #35
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 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}.") | |
| # 已删除原有 git commit & git push 步骤(避免产生多余 Commit) | |
| - 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 | |
| - 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 | |
| # 确保编译二进制文件前,main.py 中的 __version__ 也被同步为 Tag 版本 | |
| - name: Sync version to main.py | |
| shell: python | |
| run: | | |
| import re | |
| import os | |
| version = os.environ['VERSION'] | |
| main_path = os.environ['MAIN_SCRIPT'] | |
| 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) | |
| - 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 }}_Standalone" ${{ 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 }}_Standalone" --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 }}_Standalone" --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 }}_Standalone.exe" -DestinationPath "$BASE_NAME-Standalone.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_STANDALONE="${{ env.EXE_NAME_PREFIX }}_Standalone.app" | |
| TARGET_PORTABLE="${{ env.EXE_NAME_PREFIX }}_Portable.app" | |
| else | |
| TARGET_STANDALONE="${{ env.EXE_NAME_PREFIX }}_Standalone" | |
| TARGET_PORTABLE="${{ env.EXE_NAME_PREFIX }}_Portable" | |
| fi | |
| if [ "${{ matrix.package_extension }}" = "zip" ]; then | |
| zip -r "../${BASE_NAME}-Standalone.zip" "$TARGET_STANDALONE" | |
| zip -r "../${BASE_NAME}-Portable.zip" "$TARGET_PORTABLE" | |
| else | |
| tar -czf "../${BASE_NAME}-Standalone.tar.gz" "$TARGET_STANDALONE" | |
| 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 }} |