diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 704634d..9938775 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,6 +5,29 @@ on: tags: - 'v*' +# ================================================================= +# ⚙️ 项目参数配置区 +# ================================================================= +env: + PROJECT_NAME: "qpypack" # 项目包名 + MAIN_SCRIPT: "qpypack/main.py" # Python入口文件 + ICON_PATH: "qpypack/icon.ico" # 应用程序图标 + EXE_NAME_PREFIX: "QPyPack" # 用程序前缀 + + # ------------------------------------------------------------- + # 🚀 功能开关 + # ------------------------------------------------------------- + BUILD_PIP_PACKAGE: "true" # 是否构建 pip 安装包 + PUBLISH_TO_PYPI: "true" # 是否发布到 PyPI 官方源 + # ------------------------------------------------------------- + + # 软件元数据 + COMPANY_NAME: "QwejayHuang" + PRODUCT_NAME: "QPyPack" + LEGAL_COPYRIGHT: "Copyright (C) QwejayHuang" + FILE_DESCRIPTION: "" +# ================================================================= + jobs: publish-pypi: name: Publish to PyPI and Create Release @@ -16,7 +39,7 @@ jobs: - name: Checkout source code uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -34,41 +57,45 @@ jobs: 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.") + 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 = 'src/qpypack/main.py' - 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("Successfully synchronized main.py.") + 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" - git add pyproject.toml src/qpypack/main.py + + 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 @@ -77,14 +104,17 @@ jobs: 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-windows-exe: name: Build Windows EXE @@ -115,18 +145,16 @@ jobs: import os version = os.environ['VERSION'] + main_path = os.environ['MAIN_SCRIPT'] print(f"Target version to sync on Windows: {version}") - main_path = 'src/qpypack/main.py' - 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("Windows workspace code version synchronized.") + 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"Windows workspace {main_path} synchronized.") - name: Install dependencies run: | @@ -143,6 +171,13 @@ jobs: 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""" @@ -163,13 +198,13 @@ jobs: StringTable( '040904B0', [ - StringStruct('CompanyName', 'QwejayHuang'), - StringStruct('FileDescription', 'Automated Python Packaging Tool'), + StringStruct('CompanyName', '{company}'), + StringStruct('FileDescription', '{description}'), StringStruct('FileVersion', '{version}'), - StringStruct('InternalName', 'QPyPack'), - StringStruct('LegalCopyright', 'Copyright (C) QwejayHuang'), - StringStruct('OriginalFilename', 'QPyPack.exe'), - StringStruct('ProductName', 'QPyPack'), + StringStruct('InternalName', '{exe_name}'), + StringStruct('LegalCopyright', '{copyright_str}'), + StringStruct('OriginalFilename', '{exe_name}.exe'), + StringStruct('ProductName', '{product}'), StringStruct('ProductVersion', '{version}') ] ) @@ -183,12 +218,28 @@ jobs: f.write(info_content.strip()) - name: Build EXE with PyInstaller + shell: bash run: | - pyinstaller --clean --onefile --noconsole --version-file=file_version_info.txt --icon=src/qpypack/icon.ico --add-data "src/qpypack/icon.ico;." --name="QPyPack_${{ env.VERSION }}" src/qpypack/main.py + PYINSTALLER_CMD="pyinstaller --clean --onefile --noconsole" + + if [ -f "file_version_info.txt" ]; then + PYINSTALLER_CMD="$PYINSTALLER_CMD --version-file=file_version_info.txt" + fi + + if [ -f "${{ env.ICON_PATH }}" ]; then + PYINSTALLER_CMD="$PYINSTALLER_CMD --icon=\"${{ env.ICON_PATH }}\" --add-data=\"${{ env.ICON_PATH }};.\"" + else + echo "Warning: Icon path '${{ env.ICON_PATH }}' not found. Packaging without a custom icon." + fi + + PYINSTALLER_CMD="$PYINSTALLER_CMD --name=\"${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}\" ${{ env.MAIN_SCRIPT }}" + + echo "Executing: $PYINSTALLER_CMD" + eval $PYINSTALLER_CMD - name: Upload EXE to GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/QPyPack_${{ env.VERSION }}.exe + files: dist/${{ env.EXE_NAME_PREFIX }}_${{ env.VERSION }}.exe env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index eaec6ac..8645a74 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # QPyPack -QPyPack 是一个为 Python 开发者设计的图形化打包配置工具。它将复杂的命令行打包流程转化为直观、简易的 GUI 界面操作,无需记忆繁琐的终端命令,即可快速生成可执行文件。 +QPyPack 是一个为 Python 开发者设计的图形化打包配置工具。它将命令行打包流程转换为直观、简易的 GUI 界面操作,无需记忆繁琐的终端命令,即可辅助生成可执行文件。

image @@ -8,73 +8,80 @@ QPyPack 是一个为 Python 开发者设计的图形化打包配置工具。它

--- -## 核心设计:让打包变简单 +## 核心设计:简化打包流程 -为了解决传统命令行打包参数多、依赖易冲突、配置繁琐的问题,QPyPack 围绕“易用性”与“官方最佳实践”进行了深度设计: +为了降低传统命令行打包的配置成本,解决依赖易冲突等问题,QPyPack 整合了以下辅助功能: -### 1. 极简的交互流程 +### 1. 简易的交互流程 * **拖拽即装载**:无需手动输入路径,直接将您的 `.py` 或 `.pyw` 源代码文件拖入软件窗口,即可自动识别并准备构建。 -* **智能图标匹配**:脚本载入后,程序会自动检索同目录下的常用图标文件(如 `icon.ico` / `logo.ico` / `icon.svg` / `logo.svg`)并自动填充和预览。 -* **一键直达结果**:打包完成后,点击“打开输出目录”按钮即可自动弹出资源管理器并定位到生成的程序。 +* **智能图标匹配**:脚本载入后,程序会自动检索同目录下的常用图标文件(如 `icon.ico` / `logo.ico` / `icon.svg` / `logo.svg`)并进行自动填充和预览。 +* **一键直达结果**:打包完成后,点击“打开输出目录”按钮即可自动打开资源管理器并定位到生成的程序。 ### 2. 自动化的依赖与环境处理 -* **智能沙盒隔离 (Venv)**:支持一键开启虚拟环境。工具会在临时目录下自动创建干净的沙盒环境,仅安装打包所需的库,有效减小最终生成的可执行文件的体积。 +* **独立打包环境 (Venv)**:支持一键开启虚拟环境。工具会在临时目录下自动创建干净的沙盒环境,仅安装打包所需的库,有助于控制最终生成的可执行文件的体积。 * **多维度依赖扫描**: * 自动检测并优先读取项目目录下的 `requirements.txt`。 - * 提供辅助的 AST(静态语法树)扫描选项,自动分析代码中导入的第三方模块,帮助您查漏补缺隐式依赖。 -* **内置国内加速源**:预设清华大学等国内 PIP 镜像源,在沙盒中拉取打包工具或依赖库时自动加速。 + * 提供辅助的 AST(静态语法树)扫描选项,分析代码中导入的第三方模块,帮助查漏补缺隐式依赖。 +* **内置国内加速源**:预设清华大学等国内 PIP 镜像源,在沙盒中拉取打包工具或依赖库时提供加速通道。 -### 3. 可视化高级参数配置 -* **双引擎无缝切换**:在下拉菜单中即可在 PyInstaller 和 Nuitka 之间灵活切换,相关的参数输入框与高级选项会根据所选引擎特性自动启用或隐藏。 -* **可视化版本属性**:无需编写复杂的版本配置文件,直接在界面文本框中输入版本号、公司名和产品描述,工具会自动将其写入生成的可执行文件元数据中,提升系统识别度。 -* **直观的资源添加**:通过交互式对话框选择需要打包的图片、数据文件夹,引擎会自动进行跨平台兼容的路径映射与资源封装。 +### 3. 可视化参数配置 +* **双引擎切换**:在下拉菜单中即可在 PyInstaller 和 Nuitka 之间灵活切换,相关的参数输入框与高级选项会根据所选引擎特性自动启用或隐藏。 +* **可视化版本属性**:无需编写复杂的版本配置文件,直接在界面文本框中输入版本号、公司名和产品描述,工具会自动将其写入生成的可执行文件元数据中。 +* **直观的资源添加**:通过交互式对话框选择需要打包的图片、数据文件夹,引擎会自动进行路径映射与资源封装。 --- -### Changelog +### 更新日志 (Changelog) + +**2.4.0** +* **控制台防闪退机制**:在非 GUI 模式打包时,工具会自动在临时脚本尾部注入防闪退控制代码(针对 Windows 平台下双击运行的非交互式程序),提升用户体验。 +* **提升文件删除的鲁棒性**:重构了临时目录清理机制,引入带重试延时与只读属性解除的 `robust_rmtree` 回收机制,减少因防病毒软件或进程占用导致清理失败的情况。 +* **新增云同步锁检测**:对于处于 OneDrive 等云同步盘锁定状态的文件,提供前置 I/O 异常检测,避免打包过程意外被系统挂起。 +* **完善引擎参数映射**:优化 Nuitka 的插件自动加载逻辑(自适应 PyQt/PySide 家族、numpy, matplotlib, tkinter 等常见重型库),使其配置更加贴合实际运行环境。 +* **新增 CI/CD 工作流**:推送版本标签自动触发 PyPI 发布,并同步编译 Windows 运行包(`.exe`)上传至 GitHub Release 附件。 +* **版本自动同步**:构建时自动对齐 `pyproject.toml` 与入口脚本中的版本号,降低手动维护出错率。 + **2.3.6** -* **新增 SVG 格式图标支持**:现在能够自动加载 `icon.svg` / `logo.svg`为程序图标。 +* **新增 SVG 格式图标支持**:支持加载 `icon.svg` / `logo.svg` 作为程序图标。 * **增加 `.pyw` 格式脚本支持**:全面支持无控制台窗口的 Python 脚本格式,优化拖拽校验与文件选择过滤器。 -* **修复 Windows 绝对路径分割 BUG**:重构附加资源解析器,避免绝对路径中的盘符冒号(如 `C:`)被错误截断,确保跨引擎数据装载安全。 +* **修复 Windows 绝对路径分割问题**:重构附加资源解析器,避免绝对路径中的盘符冒号(如 `C:`)被错误截断,确保跨引擎数据装载安全。 * **优化 AST 本地依赖过滤**:安装前主动检索当前目录结构,自动过滤本地辅助模块或文件夹,防止向 PyPI 发起不必要的安装请求。 * **强化元数据清理机制**:将临时版本配置文件的生命周期管理提升至最高级别,确保即使在未启用“一键清理”时,构建结束也会无条件收回临时文件。 **2.3.5** -* **防漏包机制**:彻底废弃极易误判的短路判定,重构依赖拉取架构为“三重绝对安全网”(Requirements -> Pipreqs -> AST源码强扫)。即使前置分析工具漏报,最终的 AST 保底扫描也会强行抓取 `PySide6` 等核心隐式库并塞入沙盒,终结漏包闪退问题。 -* **工作区“零污染”改造**:贯彻绝对纯净的构建理念。将 `QPyPack.ini` 配置文件平滑迁移至全局系统路径(`~/.qpypack/config.ini`),并将所有 AST 缓存、依赖清单等中间产物全部转移至操作系统 `Temp` 目录,确保不留任何垃圾文件污染用户的工程代码区。 -* **构建日志工程化规范**:全面重构控制台与 UI 日志的输出文案,引入规范化的 CI/CD 生命周期状态标签(如 `[Init]`, `[Env]`, `[Deps]`, `[Build]`, `[Pack]`),大幅提升状态诊断的清晰度与专业性。 -* **虚拟环境沙盒智能自愈**:在分配独立隔离的 venv 打包沙盒后,首个初始化动作变更为静默自动升级 `pip`,清除因老旧模块引发的大段黄色警告乱码。 -* **核心 BUG 修复**:精准修复了因底层 `configparser` 映射字典降级读取失败,导致抛出 `'dict' object has no attribute 'lower'` 的系统崩溃问题。 -* **优化日志表述**:调整了日志输出文字的语言表述,使之更具工程专业度。 +* **重构依赖拉取架构**:采用三重安全检测(Requirements -> Pipreqs -> AST 源码扫描),即使前置分析工具漏报,最终的 AST 扫描也会尝试抓取核心隐式库。 +* **工作区优化**:将 `QPyPack.ini` 配置文件平滑迁移至全局系统路径(`~/.qpypack/config.ini`),并将所有 AST 缓存、依赖清单等中间产物全部转移至操作系统 `Temp` 目录,避免对用户的工程代码区造成额外文件占用。 +* **构建日志标准化**:重构控制台与 UI 日志的输出,引入规范化的生命周期状态标签(如 `[Init]`, `[Env]`, `[Deps]`, `[Build]`, `[Pack]`),提升状态诊断的清晰度。 +* **虚拟环境静默优化**:分配独立隔离的 venv 打包沙盒后,首个动作变更为自动升级 `pip`,减少因旧版本引起的警告提示。 +* **核心 BUG 修复**:修复了因底层 `configparser` 映射字典读取异常,导致抛出 `'dict' object has no attribute 'lower'` 的问题。 **2.3.0** -* **UI框架重构**:由 PyQt5 升级至 PySide6。优化了现代高分屏(High-DPI)下的文字与图标缩放表现。 -* **精简架构**:移除 `cx_Freeze` 打包引擎,专注打磨更现代、更高效的 PyInstaller 与 Nuitka 双核心引擎。 -* **Nuitka 官方最佳实践深度集成**: - * 单文件 (`--onefile`) 模式下,强制启用 `zstandard` 核心压缩环境,大幅优化产物体积与解压启动性能。 - * 全局引入 `anti-bloat` 防膨胀插件,智能拦截并避免 `pytest`、`IPython` 等无用巨大依赖被误打包。 - * 新增对 `tkinter` 官方探针 of 自动识别与插件激活。 - * 重构附加资源打包逻辑,引擎将自动探测目标类型,严格区分并适配单文件 (`--include-data-files`) 与文件夹 (`--include-data-dir`) 打包规范。 +* **UI 框架升级**:由 PyQt5 升级至 PySide6。优化了高分屏(High-DPI)下的文字与图标缩放表现。 +* **精简架构**:移除 `cx_Freeze` 打包引擎,专注打磨更实用的 PyInstaller 与 Nuitka 双核心引擎。 +* **Nuitka 最佳实践集成**: + * 单文件 (`--onefile`) 模式下,自适应启用 `zstandard` 核心压缩环境,优化产物体积。 + * 全局引入 `anti-bloat` 防膨胀插件,避免 `pytest`、`IPython` 等无用依赖被误打包。 + * 新增对 `tkinter` 官方探针的自动识别与插件激活。 + * 重构附加资源打包逻辑,自适应单文件 (`--include-data-files`) 与文件夹 (`--include-data-dir`) 打包规范。 * **PyInstaller 深度优化**: * 移除过时的强制 PyQt 隐藏导入逻辑,交由现代 Hook 自动处理。 - * 彻底修复附加资源在跨平台构建时的路径分隔符 (`os.pathsep`) 匹配与切割异常问题。 + * 修复附加资源在跨平台构建时的路径分隔符 (`os.pathsep`) 匹配问题。 **2.2.0** -* 更名为 QPyPack,规范目录结构 for PyPI,优化内部沙盒与临时文件命名规范。 -* 超时守护 (Watchdog):引入看门狗机制,为关键打包步骤设置超时限制,防止因网络问题导致构建无限期挂起。 -* 依赖推推导优化:pipreqs 原生支持国内镜像站重定向加速,并增加 15 秒超时自愈,失败时自动降级为本地纯静态 AST 解析。 -* 智能命名输出:自动解析脚本版本,预设输出文件名为 `{Name}_{Version}` 格式(例如:QPyPack_2.2.0.exe)。 -* 编译兼容扩展:Nuitka 编译模式新增对 PyQt6、PySide、NumPy、Matplotlib 等常用插件的自适应识别。 -* 细节打磨:改进日志高频刷新滚动体验,优化版本信息元数据抓取退级逻辑。 +* 规范目录结构,优化内部沙盒与临时文件命名规范。 +* **超时守护 (Watchdog)**:为关键打包步骤设置超时限制,防止打包过程因网络等外界干扰无限制挂起。 +* **依赖推导优化**:pipreqs 增加 15 秒超时保护,失败时自动降级为本地纯静态 AST 解析。 +* **智能命名输出**:自动解析脚本版本,预设输出文件名为 `{Name}_{Version}` 格式。 +* **编译兼容扩展**:Nuitka 编译模式新增对 PyQt6、PySide、NumPy、Matplotlib 等常用插件的自适应识别。 **2.1.0** -* Nuitka 单文件打包解压路径修改为 `%LOCALAPPDATA%`。 +* 将 Nuitka 单文件打包解压路径调整为 `%LOCALAPPDATA%`。 * 增加构建失败的 UI 动画反馈。 -* 尝试修复打包后容易被 Windows 安全中心误报拦截的问题。 +* 改善打包程序防杀毒软件误报拦截的兼容表现。 **2.0.1** -* 修正 Nuitka 参数传递方式:将 `cmd.extend(["--include-data-files", "src=dest"])` 修改为更符合 Nuitka 规范的单参数追加形式:`cmd.append(f"--include-data-files={src}={dest}")`。 -* 加入平台感知后缀:动态识别操作系统(Windows 使用 `.exe` 后缀,而 macOS/Linux 不使用),避免跨平台定位产物失败。 -* 安全过滤平台专有参数:对于 Windows 特有的元数据写入(如公司名称、描述、文件版本、ICO 图标),只有当 `os.name == "nt"` 时才追加对应指令。 +* 修正 Nuitka 参数传递方式:采用更符合 Nuitka 规范的单参数追加形式:`cmd.append(f"--include-data-files={src}={dest}")`。 +* 加入平台感知后缀:动态识别操作系统(Windows 使用 `.exe` 后缀,而 macOS/Linux 不使用后缀),避免跨平台定位产物失败。 +* 安全过滤平台专有参数:针对 Windows 特有的元数据写入,只有当 `os.name == "nt"` 时才追加对应指令。 --- @@ -83,12 +90,12 @@ QPyPack 是一个为 Python 开发者设计的图形化打包配置工具。它 只需三步,即可完成打包: 1. **拖入脚本**:将您的 Python 脚本(`.py` 或 `.pyw`)拖拽至主界面。 -2. **选择引擎**:点击右下角设置,在“基础打包”中选择您想使用的引擎(推荐默认的 PyInstaller 进行快速验证)。 +2. **选择引擎**:点击右下角设置,在“基础打包”中选择您想使用的引擎(默认推荐 PyInstaller 进行快速验证)。 3. **开始构建**:返回主界面点击“开始构建”,并在下方展开的控制台中实时查看打包进度。 --- ### 启动应用 -在 Python >=3.8 环境中执行以下命令启动程序: +在 Python >= 3.8 环境中执行以下命令安装并启动程序: ```bash pip install qpypack diff --git a/pyproject.toml b/pyproject.toml index 8a7c336..1a83fc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "qpypack" -version = "2.3.9" +version = "2.4.0" description = "A modern graphical packaging config tool for PyInstaller, Nuitka." readme = "README.md" authors = [ diff --git a/src/qpypack/icon.ico b/qpypack/icon.ico similarity index 100% rename from src/qpypack/icon.ico rename to qpypack/icon.ico diff --git a/src/qpypack/main.py b/qpypack/main.py similarity index 99% rename from src/qpypack/main.py rename to qpypack/main.py index 170e684..32e6ecd 100644 --- a/src/qpypack/main.py +++ b/qpypack/main.py @@ -31,7 +31,7 @@ from PySide6.QtGui import QFont, QDragEnterEvent, QDropEvent, QTextCursor, QIcon from PySide6.QtSvg import QSvgRenderer __app_name__ = "QPyPack" -__version__ = "2.3.9" +__version__ = "2.4.0" __author__ = "QwejayHuang" __company__ = "QwejayHuang" __description__ = "自动化 Python 脚本打包构建工具"