v2.2.0: Rename repository to QPyPack and release updates
Update
This commit is contained in:
1 parent
93d1cbf3fb
commit
74d8f4405b
6 files changed
+206
-53
No files matched your search
@@ -1,16 +1,17 @@
|
||||
# PyPack
|
||||
# QPyPack
|
||||
|
||||
PyPack 是一个为 Python 开发者设计的图形化打包配置工具。它将复杂的命令行打包流程(支持 **PyInstaller**、**Nuitka** 和 **cx_Freeze**)转化为直观、简易的 GUI 界面操作,无需记忆繁琐的终端命令,即可快速生成可执行文件(.exe)。
|
||||
QPyPack 是一个为 Python 开发者设计的图形化打包配置工具。它将复杂的命令行打包流程(支持 **PyInstaller**、**Nuitka** 和 **cx_Freeze**)转化为直观、简易的 GUI 界面操作,无需记忆繁琐的终端命令,即可快速生成可执行文件(.exe)。
|
||||
|
||||
<p align="center">
|
||||
<img width="904" height="887" alt="image" src="https://github.com/user-attachments/assets/abe4c3ea-ccfe-41ab-9dd5-c99a882d69d4" />
|
||||
<img width="904" height="887" alt="image" src="https://github.com/user-attachments/assets/7a9ceed9-e070-4036-aabc-3b37bc13e036" />
|
||||
<img width="904" height="887" alt="image" src="https://github.com/user-attachments/assets/0f7831a2-68fc-460c-a5a2-75b39e36a7af" />
|
||||
<img width="904" height="887" alt="image" src="https://github.com/user-attachments/assets/ce50d3a0-95df-4773-bd56-763701bfd16a" />
|
||||
|
||||
</p>
|
||||
|
||||
---
|
||||
## 核心设计:让打包变简单
|
||||
|
||||
为了解决传统命令行打包参数多、依赖易冲突、配置繁琐的问题,PyPack 2.0 围绕“易用性”进行了重新设计:
|
||||
为了解决传统命令行打包参数多、依赖易冲突、配置繁琐的问题,QPyPack 2.0 围绕“易用性”进行了重新设计:
|
||||
|
||||
### 1. 极简的交互流程
|
||||
* **拖拽即装载**:无需手动输入路径,直接将您的 `.py` 源代码文件拖入软件窗口,即可自动识别并准备构建。
|
||||
@@ -31,6 +32,14 @@ PyPack 是一个为 Python 开发者设计的图形化打包配置工具。它
|
||||
|
||||
---
|
||||
### Changelog
|
||||
2.2
|
||||
* 更名为 QPyPack,规范目录结构for PyPI,优化内部沙盒与临时文件命名规范。
|
||||
* 超时守护 (Watchdog):引入看门狗机制,为关键打包步骤设置超时限制,防止因网络问题导致构建无限期挂起。
|
||||
* 依赖推导优化:pipreqs 原生支持国内镜像站重定向加速,并增加 15 秒超时自愈,失败时自动降级为本地纯静态 AST 解析。
|
||||
* 智能命名输出:自动解析脚本版本,预设输出文件名为 {Name}_{Version} 格式(例如:QPyPack_2.2.0.exe)。
|
||||
* 编译兼容扩展:Nuitka 编译模式新增对 PyQt6、PySide、NumPy、Matplotlib 等常用插件的自适应识别。
|
||||
* 细节打磨:改进日志高频刷新滚动体验,优化版本信息元数据抓取退级逻辑。
|
||||
|
||||
2.1
|
||||
* Nuitka单文件打包解压路径修改为%LOCALAPPDATA%
|
||||
* 增加建构失败动画
|
||||
@@ -51,11 +60,13 @@ PyPack 是一个为 Python 开发者设计的图形化打包配置工具。它
|
||||
3. **开始构建**:返回主界面点击“开始构建”,并在下方展开的控制台中实时查看打包进度。
|
||||
|
||||
---
|
||||
|
||||
## 系统要求
|
||||
|
||||
* **支持系统**:Windows (推荐), macOS, Linux
|
||||
* **Python 环境**:Python 3.8+
|
||||
* **基础依赖**:只需在您的本地系统提前安装好 `PyQt5` 即可运行:
|
||||
```bash
|
||||
pip install PyQt5
|
||||
### 启动应用
|
||||
Python >=3.8 环境中执行以下命令启动程序。
|
||||
```bash
|
||||
pip install qpypack
|
||||
```
|
||||
```bash
|
||||
qpypack
|
||||
```
|
||||
### 或者使用打包好的二进制文件直接运行
|
||||
[GitHub Releases 下载链接](https://github.com/Qwejay/PyPack/releases)
|
||||
@@ -0,0 +1,32 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "qpypack"
|
||||
version = "2.2.0"
|
||||
description = "A modern graphical packaging config tool for PyInstaller, Nuitka, and cx_Freeze."
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "QwejayHuang" }
|
||||
]
|
||||
license = { text = "MIT" }
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"PyQt5>=5.15.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/qwejay/QPyPack"
|
||||
|
||||
[project.scripts]
|
||||
qpypack = "qpypack.main:main"
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
qpypack = ["icon.ico"]
|
||||
@@ -0,0 +1,2 @@
|
||||
PyQt5==5.15.11
|
||||
pyqt5_sip==12.18.0
|
||||
Whitespace-only changes.
File renamed without changes.
|
Before Width: | Height: | Size: 551 KiB After Width: | Height: | Size: 551 KiB |
+148
-40
@@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
PyPack 2.1 - 自动化构建工具
|
||||
支持引擎: PyInstaller / Nuitka / cx_Freeze
|
||||
"""
|
||||
|
||||
__app_name__ = "QPyPack"
|
||||
__version__ = "2.2.0"
|
||||
__author__ = "QwejayHuang"
|
||||
__company__ = "Qwesoft"
|
||||
__description__ = "自动化 Python 脚本打包构建工具"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -16,6 +18,7 @@ import stat
|
||||
import ast
|
||||
import locale
|
||||
import math
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["QT_LOGGING_RULES"] = "qt.text.font.db=false"
|
||||
@@ -32,7 +35,6 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal, QPropertyAnimation, QEasingCur
|
||||
from PyQt5.QtGui import QFont, QDragEnterEvent, QDropEvent, QTextCursor, QIcon, QPixmap, QPainter, QColor, QPen
|
||||
from PyQt5.QtSvg import QSvgRenderer
|
||||
|
||||
# ======================== Material Design 内置 SVG 引擎 ========================
|
||||
MATERIAL_ICONS = {
|
||||
'settings': 'M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.06-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.73,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.06,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.43-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.49-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z',
|
||||
'refresh': 'M17.65,6.35C16.2,4.9,14.21,4,12,4c-4.42,0-7.99,3.58-7.99,8s3.57,8,7.99,8c3.73,0,6.84-2.55,7.73-6h-2.08 c-0.82,2.33-3.04,4-5.65,4c-3.31,0-6-2.69-6-6s2.69-6,6-6c1.66,0,3.14,0.69,4.22,1.78L13,11h7V4L17.65,6.35z',
|
||||
@@ -65,7 +67,6 @@ def get_svg_pixmap(name, color="#5F6368", size=64):
|
||||
return get_svg_icon(name, color, size).pixmap(size, size)
|
||||
|
||||
|
||||
# ======================== 工具与配置 ========================
|
||||
def get_stdlib_names():
|
||||
libs = {'os', 'sys', 're', 'math', 'time', 'datetime', 'json', 'urllib', 'sqlite3', 'csv',
|
||||
'subprocess', 'shutil', 'threading', 'multprocessing', 'queue', 'socket',
|
||||
@@ -106,7 +107,6 @@ def robust_rmtree(path: Path, retries=15, delay=0.8):
|
||||
return False
|
||||
|
||||
|
||||
# ======================== 界面动效组件 ========================
|
||||
class AnimatedButton(QPushButton):
|
||||
def __init__(self, text, parent=None):
|
||||
super().__init__(text, parent)
|
||||
@@ -148,13 +148,12 @@ class AnimatedButton(QPushButton):
|
||||
super().leaveEvent(event)
|
||||
|
||||
class TargetIconWidget(QWidget):
|
||||
"""自定义图标渲染组件,接管无损抗锯齿与复合重绘效果"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFixedSize(200, 200)
|
||||
self.pixmap = None
|
||||
self.base_pixmap = None
|
||||
self.file_pixmap = None # 用于专门记住当前加载文件的专属图标
|
||||
self.file_pixmap = None
|
||||
self.current_size = 88
|
||||
|
||||
self.is_building = False
|
||||
@@ -180,7 +179,6 @@ class TargetIconWidget(QWidget):
|
||||
self.burst_anim.setEasingCurve(QEasingCurve.OutQuad)
|
||||
self.burst_anim.valueChanged.connect(self._animate_burst)
|
||||
|
||||
# 抖动动画配置(用于构建失败)
|
||||
self.shake_offset = 0
|
||||
self.shake_anim = QVariantAnimation(self)
|
||||
self.shake_anim.setDuration(500)
|
||||
@@ -213,7 +211,6 @@ class TargetIconWidget(QWidget):
|
||||
self.update()
|
||||
|
||||
def start_building(self):
|
||||
# 恢复之前保存的文件专属图标,覆盖掉可能残留的“成功”或“失败”图标
|
||||
if getattr(self, 'file_pixmap', None) and not self.file_pixmap.isNull():
|
||||
self.pixmap = self.file_pixmap
|
||||
self.current_size = 88
|
||||
@@ -242,7 +239,7 @@ class TargetIconWidget(QWidget):
|
||||
def start_failure(self):
|
||||
self.stop_building()
|
||||
self.success_effect.setBlurRadius(40)
|
||||
self.success_effect.setColor(QColor(217, 48, 37, 180)) # 红色警告发光
|
||||
self.success_effect.setColor(QColor(217, 48, 37, 180))
|
||||
self.shake_anim.start()
|
||||
|
||||
def reset(self):
|
||||
@@ -252,7 +249,7 @@ class TargetIconWidget(QWidget):
|
||||
self.burst_value = 0.0
|
||||
self.shake_offset = 0
|
||||
self.success_effect.setColor(QColor(0, 0, 0, 0))
|
||||
self.file_pixmap = None # 清空文件专属图标
|
||||
self.file_pixmap = None
|
||||
self.pixmap = self.base_pixmap
|
||||
self.current_size = 88
|
||||
self.update()
|
||||
@@ -416,7 +413,6 @@ class DropArea(QFrame):
|
||||
if not pixmap:
|
||||
pixmap = get_svg_pixmap('package', color="#1A73E8", size=88)
|
||||
|
||||
# 使用 set_file_pixmap,避免覆盖 base_pixmap 导致复位失败
|
||||
self.icon_widget.set_file_pixmap(pixmap, 88)
|
||||
|
||||
self.label.setText(f"已加载:{filename}")
|
||||
@@ -766,7 +762,7 @@ class SettingsPanel(QWidget):
|
||||
if not script_path: return QMessageBox.warning(self, "提示", "请先在主界面加载脚本!")
|
||||
try:
|
||||
raw = Path(script_path).read_bytes()
|
||||
if b"__CLOUDSYNC_ENC__" in raw[:1024]: return QMessageBox.critical(self, "错误", "脚本被云盘加密,请解密!")
|
||||
if b"__CLOUDSYNC_ENC__" in raw[:1024]: return QMessageBox.warning(self, "错误", "脚本被云盘加密,请解密!")
|
||||
try: code = raw.decode('utf-8-sig')
|
||||
except: code = raw.decode('gbk', errors='ignore')
|
||||
|
||||
@@ -784,7 +780,7 @@ class SettingsPanel(QWidget):
|
||||
hidden = [m for m in hidden if m not in STD_LIBS]
|
||||
self.hidden_edit.setText(','.join(hidden))
|
||||
QMessageBox.information(self, "扫描完成", f"捕获到 {len(hidden)} 项依赖。")
|
||||
except Exception as e: QMessageBox.critical(self, "错误", f"扫描失败: {e}")
|
||||
except Exception as e: QMessageBox.warning(self, "错误", f"扫描失败: {e}")
|
||||
|
||||
def add_resource(self):
|
||||
choice = QMessageBox.question(self, "添加资源", "Yes=文件, No=文件夹", QMessageBox.Yes | QMessageBox.No)
|
||||
@@ -832,13 +828,32 @@ class PackingThread(QThread):
|
||||
else: self.process.kill()
|
||||
except: pass
|
||||
|
||||
def run_cmd(self, cmd, cwd=None):
|
||||
def run_cmd(self, cmd, cwd=None, timeout=None):
|
||||
if self._is_cancelled: return False
|
||||
|
||||
timer = None
|
||||
is_timeout = [False]
|
||||
|
||||
try:
|
||||
kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.STDOUT, "cwd": cwd, "text": True, "errors": "replace"}
|
||||
if os.name == 'nt': kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
||||
|
||||
self.process = subprocess.Popen(cmd, **kwargs)
|
||||
|
||||
if timeout:
|
||||
def kill_proc():
|
||||
is_timeout[0] = True
|
||||
try:
|
||||
if os.name == "nt":
|
||||
subprocess.run(["taskkill", "/F", "/T", "/PID", str(self.process.pid)],
|
||||
capture_output=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
else:
|
||||
self.process.kill()
|
||||
except:
|
||||
pass
|
||||
timer = threading.Timer(timeout, kill_proc)
|
||||
timer.start()
|
||||
|
||||
for line in self.process.stdout:
|
||||
if self._is_cancelled:
|
||||
self.process.terminate()
|
||||
@@ -846,10 +861,18 @@ class PackingThread(QThread):
|
||||
self.progress.emit(line.strip())
|
||||
|
||||
self.process.wait()
|
||||
|
||||
if is_timeout[0]:
|
||||
self.progress.emit(f"[提示] 该步骤执行超时 ({timeout}秒),已自动跳过并进入下一步...")
|
||||
return False
|
||||
|
||||
return self.process.returncode == 0
|
||||
except Exception as e:
|
||||
self.progress.emit(f"[系统异常] 进程执行失败: {e}")
|
||||
return False
|
||||
finally:
|
||||
if timer:
|
||||
timer.cancel()
|
||||
|
||||
def sanitize_script(self, orig_path: Path):
|
||||
try:
|
||||
@@ -858,8 +881,6 @@ class PackingThread(QThread):
|
||||
return None, False, "目标脚本被云盘锁定加密!"
|
||||
except Exception:
|
||||
pass
|
||||
# 移除了所有重命名及生成 pypack_temp_ 前缀文件的死代码
|
||||
# 直接透传原始文件可防止 Nuitka 生成带有临时前缀的 DLL 触发 Windows 智能应用控制的启发式拦截
|
||||
return orig_path, False, ""
|
||||
|
||||
def run(self):
|
||||
@@ -884,7 +905,7 @@ class PackingThread(QThread):
|
||||
|
||||
if self.params['use_venv']:
|
||||
self.progress.emit("[环境] 初始化虚拟隔离沙盒...")
|
||||
self.venv_dir = Path(tempfile.mkdtemp(prefix="pypack_env_")).resolve()
|
||||
self.venv_dir = Path(tempfile.mkdtemp(prefix="qpypack_env_")).resolve()
|
||||
if not self.run_cmd([get_python_executable(), "-m", "venv", self.venv_dir.as_posix()]):
|
||||
return self.finished.emit(False, "虚拟环境初始化失败。")
|
||||
python_exe = (self.venv_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")).as_posix()
|
||||
@@ -899,6 +920,8 @@ class PackingThread(QThread):
|
||||
self.run_cmd([python_exe, "-m", "pip", "install", "-q"] + core_pkgs + pip_args)
|
||||
|
||||
req_success = False
|
||||
|
||||
# 1. 优先尝试部署 requirements.txt
|
||||
if self.params.get('use_reqs'):
|
||||
req_file = script_dir / "requirements.txt"
|
||||
if req_file.exists():
|
||||
@@ -914,16 +937,43 @@ class PackingThread(QThread):
|
||||
req_success = True
|
||||
temp_req.unlink(missing_ok=True)
|
||||
except Exception as e: self.progress.emit(f"[警告] {e}")
|
||||
|
||||
# 2. requirements.txt 未能成功解决依赖,则启用 pipreqs(带镜像站原生参数及 15 秒超时自愈)
|
||||
if self.params.get('use_pipreqs') and not req_success:
|
||||
self.progress.emit("[依赖] 执行 pipreqs 深度推导...")
|
||||
self.run_cmd([python_exe, "-m", "pip", "install", "pipreqs", "-q"] + pip_args)
|
||||
temp_pipreqs = script_dir / "pypack_pipreqs_out.txt"
|
||||
self.run_cmd([python_exe, "-m", "pipreqs.pipreqs", script_dir.as_posix(), "--encoding", "utf-8", "--force", "--savepath", temp_pipreqs.as_posix()])
|
||||
if temp_pipreqs.exists():
|
||||
if self.run_cmd([python_exe, "-m", "pip", "install", "-q", "-r", temp_pipreqs.as_posix()] + pip_args): req_success = True
|
||||
temp_pipreqs.unlink(missing_ok=True)
|
||||
|
||||
pypi_server = None
|
||||
if pip_idx:
|
||||
pypi_server = re.sub(r'/simple/?$', '/pypi', pip_idx.strip(), flags=re.I).rstrip('/')
|
||||
|
||||
pipreqs_cmd = [
|
||||
python_exe, "-m", "pipreqs.pipreqs",
|
||||
script_dir.as_posix(),
|
||||
"--encoding", "utf-8",
|
||||
"--force",
|
||||
"--savepath", temp_pipreqs.as_posix()
|
||||
]
|
||||
|
||||
if pypi_server:
|
||||
pipreqs_cmd.extend(["--pypi-server", pypi_server])
|
||||
self.progress.emit(f"[依赖] pipreqs 已重定向至本地镜像接口: {pypi_server}")
|
||||
|
||||
if self.run_cmd(pipreqs_cmd, timeout=15):
|
||||
if temp_pipreqs.exists():
|
||||
if self.run_cmd([python_exe, "-m", "pip", "install", "-q", "-r", temp_pipreqs.as_posix()] + pip_args):
|
||||
req_success = True
|
||||
temp_pipreqs.unlink(missing_ok=True)
|
||||
else:
|
||||
self.progress.emit("[提示] pipreqs 深度解析超时或失败,转为本地 AST 解析...")
|
||||
if temp_pipreqs.exists():
|
||||
try: temp_pipreqs.unlink()
|
||||
except: pass
|
||||
|
||||
# 3. 如果以上方案均未成功解决依赖,则采用本地纯静态 AST 降级解析兜底
|
||||
if not req_success and (self.params.get('use_reqs') or self.params.get('use_pipreqs')):
|
||||
self.progress.emit("[依赖] 执行 AST 语法树降级解析...")
|
||||
self.progress.emit("[依赖] 正在启用 AST 本地语法树解析机制兜底...")
|
||||
try:
|
||||
code = build_script_path.read_text(encoding='utf-8', errors='ignore')
|
||||
third_party = set()
|
||||
@@ -949,18 +999,17 @@ class PackingThread(QThread):
|
||||
icon_path = Path(self.params['icon']).resolve().as_posix() if self.params.get('icon') else None
|
||||
|
||||
if engine == "PyInstaller":
|
||||
self.temp_workpath = Path(tempfile.mkdtemp(prefix="pypack_build_")).resolve()
|
||||
self.temp_workpath = Path(tempfile.mkdtemp(prefix="qpypack_build_")).resolve()
|
||||
cmd = [python_exe, "-m", "PyInstaller", "--clean", "-y", f"--workpath={self.temp_workpath.as_posix()}", f"--name={app_name}"]
|
||||
|
||||
if self.params.get('onefile'):
|
||||
cmd.append("--onefile")
|
||||
if os.name == "nt":
|
||||
cmd.append(f"--windows-onefile-tempdir-spec=%LOCALAPPDATA%\\{app_name}_Env")
|
||||
else:
|
||||
cmd.append("--standalone")
|
||||
cmd.append("--onedir")
|
||||
|
||||
if self.params.get('noconsole'): cmd.append("--windows-console-mode=disable")
|
||||
if self.params.get('noconsole'):
|
||||
cmd.append("--noconsole")
|
||||
|
||||
|
||||
if icon_path:
|
||||
cmd.extend(["--icon", icon_path])
|
||||
sep = ';' if os.name == 'nt' else ':'
|
||||
@@ -998,7 +1047,6 @@ class PackingThread(QThread):
|
||||
cmd.append(f"--windows-icon-from-ico={icon_path}")
|
||||
elif sys.platform == "darwin" and icon_path.endswith(".icns"):
|
||||
cmd.append(f"--macos-app-icon={icon_path}")
|
||||
# 使用精确的文件名作为目标部署位置,以避开 Nuitka '.' 校验致命错误
|
||||
cmd.append(f"--include-data-files={icon_path}={Path(icon_path).name}")
|
||||
|
||||
if os.name == "nt":
|
||||
@@ -1007,8 +1055,19 @@ class PackingThread(QThread):
|
||||
if self.params.get('ver_ver'): cmd.append(f"--file-version={self.params['ver_ver']}")
|
||||
|
||||
try:
|
||||
if 'PyQt5' in build_script_path.read_text(encoding='utf-8', errors='ignore'):
|
||||
script_text = build_script_path.read_text(encoding='utf-8', errors='ignore')
|
||||
if 'PyQt5' in script_text:
|
||||
cmd.append("--enable-plugin=pyqt5")
|
||||
elif 'PyQt6' in script_text:
|
||||
cmd.append("--enable-plugin=pyqt6")
|
||||
elif 'PySide2' in script_text:
|
||||
cmd.append("--enable-plugin=pyside2")
|
||||
elif 'PySide6' in script_text:
|
||||
cmd.append("--enable-plugin=pyside6")
|
||||
elif 'numpy' in script_text:
|
||||
cmd.append("--enable-plugin=numpy")
|
||||
elif 'matplotlib' in script_text:
|
||||
cmd.append("--enable-plugin=matplotlib")
|
||||
except: pass
|
||||
|
||||
for imp in self.params.get('hidden_imports', '').split(','):
|
||||
@@ -1077,7 +1136,7 @@ class PackingThread(QThread):
|
||||
self.progress.emit("[清理] 抹除环境与沙盒...")
|
||||
for p in [self.venv_dir, self.temp_workpath, self.temp_out_dir]:
|
||||
if p and p.exists(): robust_rmtree(p)
|
||||
|
||||
|
||||
cwd = Path.cwd().resolve()
|
||||
app_name = self.params.get('app_name', 'app')
|
||||
robust_rmtree(cwd / "dist")
|
||||
@@ -1098,7 +1157,8 @@ class MainWindow(QMainWindow):
|
||||
self.init_ui()
|
||||
|
||||
def init_style(self):
|
||||
self.setWindowTitle("PyPack 2.1")
|
||||
self.setWindowTitle(f"{__app_name__} {__version__} - {__author__}")
|
||||
|
||||
self.setMinimumSize(580, 520)
|
||||
self.resize(600, 560)
|
||||
|
||||
@@ -1280,8 +1340,49 @@ class MainWindow(QMainWindow):
|
||||
except: pass
|
||||
|
||||
self.script_path = path
|
||||
self.settings_panel.name_edit.setText(Path(path).stem)
|
||||
|
||||
# 智能元数据预设逻辑
|
||||
app_name = Path(path).stem
|
||||
version = ""
|
||||
author = "My Studio"
|
||||
desc = "Python Executable"
|
||||
|
||||
try:
|
||||
content = Path(path).read_text(encoding='utf-8', errors='ignore')
|
||||
|
||||
# 解析版本
|
||||
v_match = re.search(r'^(?:__version__|VERSION|version)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
if v_match:
|
||||
version = v_match.group(1)
|
||||
self.settings_panel.ver_ver.setText(version)
|
||||
|
||||
# 解析发行公司(无company则降级尝试绑定author)
|
||||
c_match = re.search(r'^(?:__company__|COMPANY)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
if c_match:
|
||||
author = c_match.group(1)
|
||||
else:
|
||||
a_match = re.search(r'^(?:__author__|AUTHOR)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
if a_match: author = a_match.group(1)
|
||||
self.settings_panel.ver_comp.setText(author)
|
||||
|
||||
# 解析名称
|
||||
n_match = re.search(r'^(?:__title__|__app_name__|APP_NAME)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
if n_match:
|
||||
app_name = n_match.group(1)
|
||||
|
||||
# 解析描述
|
||||
d_match = re.search(r'^(?:__description__|DESCRIPTION)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
if d_match:
|
||||
desc = d_match.group(1)
|
||||
self.settings_panel.ver_desc.setText(desc)
|
||||
|
||||
except: pass
|
||||
|
||||
# 拼装生成带版本后缀的默认文件名
|
||||
default_output_name = f"{app_name}_{version}" if version else app_name
|
||||
self.settings_panel.name_edit.setText(default_output_name)
|
||||
|
||||
# 图标自动配对
|
||||
script_dir = Path(path).parent
|
||||
auto_icon = None
|
||||
|
||||
@@ -1322,8 +1423,12 @@ class MainWindow(QMainWindow):
|
||||
version_file = None
|
||||
if engine == "PyInstaller" and sp.ver_ver.text().strip():
|
||||
try:
|
||||
content = f'''VSVersionInfo(ffi=FixedFileInfo(filevers=(1,0,0,0),prodvers=(1,0,0,0),mask=0x3f,flags=0x0,OS=0x40004,fileType=0x1,subtype=0x0,date=(0,0)),kids=[StringFileInfo([StringTable('040904B0',[StringStruct('CompanyName','{sp.ver_comp.text()}'),StringStruct('FileDescription','{sp.ver_desc.text()}'),StringStruct('FileVersion','{sp.ver_ver.text()}'),StringStruct('ProductVersion','{sp.ver_ver.text()}'),StringStruct('OriginalFilename','{app_name}.exe')])])])'''
|
||||
version_file = Path(tempfile.gettempdir()) / f"pypack_{app_name}_version.txt"
|
||||
v_str = sp.ver_ver.text().strip()
|
||||
v_nums = re.findall(r'\d+', v_str)
|
||||
v_tuple = ",".join((v_nums + ['0', '0', '0', '0'])[:4])
|
||||
|
||||
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','{sp.ver_comp.text()}'),StringStruct('FileDescription','{sp.ver_desc.text()}'),StringStruct('FileVersion','{v_str}'),StringStruct('ProductVersion','{v_str}'),StringStruct('OriginalFilename','{app_name}.exe')])])])'''
|
||||
version_file = Path(tempfile.gettempdir()) / f"qpypack_{app_name}_version.txt"
|
||||
version_file.write_text(content, encoding='utf-8')
|
||||
except: pass
|
||||
|
||||
@@ -1398,10 +1503,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def append_log(self, msg):
|
||||
self.log.append(msg)
|
||||
self.log.moveCursor(QTextCursor.End)
|
||||
self.log.ensureCursorVisible()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
if hasattr(Qt, 'HighDpiScaleFactorRoundingPolicy'):
|
||||
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
||||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
||||
@@ -1413,3 +1518,6 @@ if __name__ == "__main__":
|
||||
win = MainWindow()
|
||||
win.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user