v2.4.1: Main script updates and docs
This commit is contained in:
1 parent
fbce54e9d5
commit
b2eef5dec3
3 files changed
+413
-106
No files matched your search
@@ -31,7 +31,13 @@ QPyPack 是一个为 Python 开发者设计的图形化打包配置工具。它
|
||||
|
||||
---
|
||||
### 更新日志 (Changelog)
|
||||
|
||||
**2.4.1**
|
||||
* **重构设置TAB**:全面重构设置面板,将参数分类为“应用元数据”、“编译控制”、“资源与沙盒”、“软件设置”四大模块。
|
||||
* **保存路径配置**:支持将 EXE 输出至全局指定的自定输出目录,并支持对缺失的多级父目录进行全自动递归创建。
|
||||
* **并发编译限制**:支持手动限制打包引擎并发编译时所能占用的最大 CPU 核心数,避免由于硬件满载引发设备死机。
|
||||
* **编译完成声音反馈**:引入非外部重度依赖的系统级音频通知,在打包成功或异常中断时给予不同的声音提示。
|
||||
* **自动持久化打包日志**:任务结束后支持在目标输出路径自动保存运行日志(.log 格式),极大提升异常分析排查的便利性。
|
||||
|
||||
**2.4.0**
|
||||
* **控制台防闪退机制**:在非 GUI 模式打包时,工具会自动在临时脚本尾部注入防闪退控制代码(针对 Windows 平台下双击运行的非交互式程序),提升用户体验。
|
||||
* **提升文件删除的鲁棒性**:重构了临时目录清理机制,引入带重试延时与只读属性解除的 `robust_rmtree` 回收机制,减少因防病毒软件或进程占用导致清理失败的情况。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "qpypack"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "A modern graphical packaging config tool for PyInstaller, Nuitka."
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
||||
+405
-104
@@ -25,13 +25,13 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayo
|
||||
QComboBox, QFrame, QStackedLayout, QFormLayout, QTextEdit,
|
||||
QGraphicsOpacityEffect, QGroupBox, QGridLayout, QTabWidget,
|
||||
QMessageBox, QInputDialog, QFileIconProvider, QSizePolicy, QScrollArea,
|
||||
QGraphicsDropShadowEffect)
|
||||
QGraphicsDropShadowEffect, QSpinBox)
|
||||
from PySide6.QtCore import Qt, QThread, Signal, QPropertyAnimation, QEasingCurve, QParallelAnimationGroup, QFileInfo, QVariantAnimation, QTimer, QPointF, QRectF, QRect
|
||||
from PySide6.QtGui import QFont, QDragEnterEvent, QDropEvent, QTextCursor, QIcon, QPixmap, QPainter, QColor, QPen
|
||||
from PySide6.QtSvg import QSvgRenderer
|
||||
|
||||
__app_name__ = "QPyPack"
|
||||
__version__ = "2.4.0"
|
||||
__version__ = "2.4.1"
|
||||
__author__ = "QwejayHuang"
|
||||
__company__ = "QwejayHuang"
|
||||
__description__ = "自动化 Python 脚本打包构建工具"
|
||||
@@ -48,7 +48,7 @@ MATERIAL_ICONS = {
|
||||
'package': 'M20,2H4C3,2,2,2.9,2,4v3.01C2,7.73,2.43,8.35,3,8.7V20c0,1.1,1.1,2,2,2h14c0.9,0,2-0.9,2-2V8.7c0.57-0.35,1-0.97,1-1.69V4 C22,2.9,21,2,20,2z M19,20H5V9h14V20z M20,7H4V4h16V7z M9,12h6v2H9V12z',
|
||||
'back': 'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z',
|
||||
'info': 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z',
|
||||
'python': 'M12.06,1.48c-3.14,0-3.52,0.67-3.52,0.67l-0.01,2.44h3.63v0.52H7.43C5.12,5.11,4.5,6.58,4.5,8.81c0,2.34,0.38,3.48,2.3,3.48 h1.14v-1.62c0-1.48,1.23-2.65,2.7-2.65h3.69c1.47,0,2.66-1.19,2.66-2.65V3.88C16.99,1.83,14.67,1.48,12.06,1.48z M10.22,2.83 c0.41,0,0.73,0.33,0.73,0.74c0,0.41-0.33,0.74-0.73,0.74c-0.4,0-0.73-0.33-0.73-0.74C9.49,3.16,9.82,2.83,10.22,2.83z M16.71,9.89 v1.62c0,1.48-1.23,2.65-2.7,2.65H10.3c-1.47,0-2.66,1.19-2.66,2.65v1.49c0,2.05,2.32,2.41,4.92,2.41c3.14,0,3.52-0.67,3.52-0.67 l0.01-2.44h-3.63v-0.52h4.73c2.31,0,2.93-1.47,2.93-3.7c0-2.34-0.38-3.48-2.3-3.48H16.71z M13.88,18.96c0.41,0,0.73,0.33,0.73,0.74 c0,0.41-0.33,0.74-0.73,0.74c-0.4,0-0.73-0.33-0.73-0.74C13.15,19.29,13.48,18.96,13.88,18.96z',
|
||||
'python': 'M12.06,1.48c-3.14,0-3.52,0.67-3.52,0.67l-0.01,2.44h3.63v0.52H7.43C5.12,5.11,4.5,6.58,4.5,8.81c0,2.34,0.38,3.48,2.3,3.48 h1.14v-1.62c0-1.48,1.23-2.65,2.7-2.65h3.69c1.47,0,2.66-1.19,2.66-2.65V3.88C16.99,1.83,14.67,1.48,12.06,1.48z M10.22,2.83 c0.41,0,0.73,0.33,0.73,0.74c0,0.41-0.33,0.74-0.73,0.74c-0.4,0-0.73-0.33-0.73-0.74C9.49,3.16,9.82,2.83,10.22,2.83z M16.71,9.89 v1.62c0,1.48-1.23,2.65-2.7,2.65H10.3c-1.47,0-2.66,1.19-2.66,2.65v1.49c0,2.05,2.32,2.41,4.92,2.41c3.14,0,3.52-0.67,3.52-0.67 l0.01-2.44h-3.63v-0.52h4.73c2.31,0,2.93-1.47,2.93-3.7c0-2.34-0.38-3.48-2.3-3.48H16.71z M13.88,18.96c0.41,0,0.73,0.33,0.73,0.74c0,0.41-0.33,0.74-0.73,0.74c-0.4,0-0.73-0.33-0.73-0.74C13.15,19.29,13.48,18.96,13.88,18.96z',
|
||||
'close': 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'
|
||||
}
|
||||
|
||||
@@ -71,7 +71,10 @@ def load_config():
|
||||
'engine': 'PyInstaller', 'pip_index': 'https://pypi.tuna.tsinghua.edu.cn/simple',
|
||||
'onefile': 'True', 'noconsole': 'True', 'clean_all': 'True',
|
||||
'auto_icon': 'True', 'use_venv': 'True', 'use_reqs': 'True',
|
||||
'use_pipreqs': 'True', 'upx': 'False'
|
||||
'use_pipreqs': 'True', 'upx': 'False', 'concise_log': 'True',
|
||||
'cpu_cores': str(os.cpu_count() or 2), 'upx_path': '',
|
||||
'exclude_modules': '', 'out_mode': '0', 'custom_out_dir': '',
|
||||
'sound_notify': 'True', 'auto_save_log': 'False'
|
||||
}
|
||||
try:
|
||||
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
|
||||
@@ -81,6 +84,24 @@ def load_config():
|
||||
config.read(CONFIG_FILE, encoding='utf-8')
|
||||
if 'Mappings' not in config: config['Mappings'] = DEFAULT_MAPPINGS
|
||||
if 'Settings' not in config: config['Settings'] = {}
|
||||
default_updates = {
|
||||
'concise_log': 'True',
|
||||
'cpu_cores': str(os.cpu_count() or 2),
|
||||
'upx_path': '',
|
||||
'exclude_modules': '',
|
||||
'out_mode': '0',
|
||||
'custom_out_dir': '',
|
||||
'sound_notify': 'True',
|
||||
'auto_save_log': 'False'
|
||||
}
|
||||
updated = False
|
||||
for k, v in default_updates.items():
|
||||
if k not in config['Settings']:
|
||||
config['Settings'][k] = v
|
||||
updated = True
|
||||
if updated:
|
||||
try: save_config(config)
|
||||
except: pass
|
||||
return config
|
||||
|
||||
def save_config(config):
|
||||
@@ -309,6 +330,17 @@ def parse_add_data(add_data_str):
|
||||
datas.append((src_path, dst_path))
|
||||
return datas
|
||||
|
||||
def play_alert(success=True):
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
import winsound
|
||||
winsound.MessageBeep(winsound.MB_OK if success else winsound.MB_ICONHAND)
|
||||
else:
|
||||
sys.stdout.write('\a')
|
||||
sys.stdout.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class AnimatedButton(QPushButton):
|
||||
def __init__(self, text, parent=None):
|
||||
@@ -687,12 +719,19 @@ class SettingsPanel(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent_win = parent
|
||||
|
||||
self.upx_check = None
|
||||
self.upx_path_container = None
|
||||
self.app_scroll_area = None
|
||||
self.form_dest = None
|
||||
self.form_perf = None
|
||||
|
||||
self.setStyleSheet("""
|
||||
SettingsPanel { background-color: white; }
|
||||
QLabel { color: #3c4043; font-size: 13px; font-weight: bold; background: transparent; }
|
||||
QComboBox, QLineEdit { color: #3c4043; font-size: 13px; padding: 6px 10px; border: 1px solid #dadce0; border-radius: 6px; background: #fff; min-height: 24px; }
|
||||
QComboBox:hover, QLineEdit:hover { border-color: #bdc1c6; }
|
||||
QComboBox:focus, QLineEdit:focus { border-color: #1A73E8; }
|
||||
QComboBox, QLineEdit, QSpinBox { color: #3c4043; font-size: 13px; padding: 6px 10px; border: 1px solid #dadce0; border-radius: 6px; background: #fff; min-height: 24px; }
|
||||
QComboBox:hover, QLineEdit:hover, QSpinBox:hover { border-color: #bdc1c6; }
|
||||
QComboBox:focus, QLineEdit:focus, QSpinBox:focus { border-color: #1A73E8; }
|
||||
QGroupBox { border: 1px solid #e8eaed; border-radius: 8px; margin-top: 15px; padding-top: 15px; background-color: #f8f9fa; }
|
||||
QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left: 15px; top: 0px; color: #1A73E8; font-weight: bold; font-size: 13px; padding: 0 5px; background: transparent; }
|
||||
QCheckBox { font-size: 13px; color: #3c4043; spacing: 8px; background: transparent; }
|
||||
@@ -716,18 +755,18 @@ class SettingsPanel(QWidget):
|
||||
self.tabs = QTabWidget()
|
||||
self.tab_basic = QWidget()
|
||||
self.tab_env = QWidget()
|
||||
self.tab_adv = QWidget()
|
||||
self.tab_meta = QWidget()
|
||||
self.tab_app = QWidget()
|
||||
|
||||
self.tabs.addTab(self.tab_basic, get_svg_icon('package', "#1A73E8"), " 基础打包")
|
||||
self.tabs.addTab(self.tab_env, get_svg_icon('python', "#1A73E8"), " 环境与依赖")
|
||||
self.tabs.addTab(self.tab_adv, get_svg_icon('settings', "#1A73E8"), " 高级选项")
|
||||
self.tabs.addTab(self.tab_meta, get_svg_icon('info', "#1A73E8"), " 版本信息")
|
||||
self.tabs.addTab(self.tab_basic, get_svg_icon('package', "#1A73E8"), " 产物元数据")
|
||||
self.tabs.addTab(self.tab_env, get_svg_icon('python', "#1A73E8"), " 编译控制")
|
||||
self.tabs.addTab(self.tab_meta, get_svg_icon('info', "#1A73E8"), " 资源与沙盒")
|
||||
self.tabs.addTab(self.tab_app, get_svg_icon('settings', "#1A73E8"), " 软件设置")
|
||||
|
||||
self.build_basic_tab()
|
||||
self.build_env_tab()
|
||||
self.build_adv_tab()
|
||||
self.build_meta_tab()
|
||||
self.build_app_tab()
|
||||
layout.addWidget(self.tabs)
|
||||
|
||||
btn_lay = QHBoxLayout()
|
||||
@@ -773,7 +812,20 @@ class SettingsPanel(QWidget):
|
||||
self.venv_check.setChecked(s.getboolean('use_venv', True))
|
||||
self.reqs_check.setChecked(s.getboolean('use_reqs', True))
|
||||
self.pipreqs_check.setChecked(s.getboolean('use_pipreqs', True))
|
||||
self.upx_check.setChecked(s.getboolean('upx', False))
|
||||
|
||||
if self.upx_check:
|
||||
self.upx_check.setChecked(s.getboolean('upx', False))
|
||||
self.on_upx_toggled(self.upx_check.isChecked())
|
||||
self.upx_path_edit.setText(s.get('upx_path', ''))
|
||||
self.cores_spin.setValue(s.getint('cpu_cores', os.cpu_count() or 2))
|
||||
self.exclude_edit.setText(s.get('exclude_modules', ''))
|
||||
self.out_mode_combo.setCurrentIndex(int(s.get('out_mode', '0')))
|
||||
self.out_dir_edit.setText(s.get('custom_out_dir', ''))
|
||||
self.on_out_mode_changed(self.out_mode_combo.currentIndex())
|
||||
|
||||
self.concise_log_check.setChecked(s.getboolean('concise_log', True))
|
||||
self.sound_notify_check.setChecked(s.getboolean('sound_notify', True))
|
||||
self.auto_save_log_check.setChecked(s.getboolean('auto_save_log', False))
|
||||
|
||||
def save_to_config(self):
|
||||
config = load_config()
|
||||
@@ -788,7 +840,16 @@ class SettingsPanel(QWidget):
|
||||
s['use_venv'] = str(self.venv_check.isChecked())
|
||||
s['use_reqs'] = str(self.reqs_check.isChecked())
|
||||
s['use_pipreqs'] = str(self.pipreqs_check.isChecked())
|
||||
s['upx'] = str(self.upx_check.isChecked())
|
||||
if self.upx_check:
|
||||
s['upx'] = str(self.upx_check.isChecked())
|
||||
s['upx_path'] = self.upx_path_edit.text().strip()
|
||||
s['cpu_cores'] = str(self.cores_spin.value())
|
||||
s['exclude_modules'] = self.exclude_edit.text().strip()
|
||||
s['out_mode'] = str(self.out_mode_combo.currentIndex())
|
||||
s['custom_out_dir'] = self.out_dir_edit.text().strip()
|
||||
s['concise_log'] = str(self.concise_log_check.isChecked())
|
||||
s['sound_notify'] = str(self.sound_notify_check.isChecked())
|
||||
s['auto_save_log'] = str(self.auto_save_log_check.isChecked())
|
||||
save_config(config)
|
||||
|
||||
def build_basic_tab(self):
|
||||
@@ -808,11 +869,6 @@ class SettingsPanel(QWidget):
|
||||
form_core.setContentsMargins(15, 20, 15, 15)
|
||||
form_core.setVerticalSpacing(12)
|
||||
|
||||
self.engine_combo = QComboBox()
|
||||
self.engine_combo.addItems(["PyInstaller", "Nuitka"])
|
||||
self.engine_combo.currentIndexChanged.connect(self.on_engine_changed)
|
||||
form_core.addRow("构建引擎:", self.engine_combo)
|
||||
|
||||
self.name_edit = QLineEdit()
|
||||
self.name_edit.setPlaceholderText("留空则与入口脚本同名")
|
||||
form_core.addRow("输出名称:", self.name_edit)
|
||||
@@ -832,22 +888,32 @@ class SettingsPanel(QWidget):
|
||||
form_core.addRow("应用图标:", h_icon)
|
||||
lay.addWidget(grp_core)
|
||||
|
||||
grp_mode = QGroupBox("输出模式与行为")
|
||||
grp_mode = QGroupBox("打包模式")
|
||||
grid_mode = QGridLayout(grp_mode)
|
||||
grid_mode.setContentsMargins(15, 20, 15, 15)
|
||||
grid_mode.setVerticalSpacing(15)
|
||||
|
||||
self.onefile_check = QCheckBox("单文件模式 (OneFile)")
|
||||
self.noconsole_check = QCheckBox("隐藏控制台 (GUI模式)")
|
||||
self.clean_all_check = QCheckBox("编译后自动清理缓存")
|
||||
self.auto_icon_check = QCheckBox("自动匹配同目录图标")
|
||||
|
||||
grid_mode.addWidget(self.onefile_check, 0, 0)
|
||||
grid_mode.addWidget(self.noconsole_check, 0, 1)
|
||||
grid_mode.addWidget(self.clean_all_check, 1, 0)
|
||||
grid_mode.addWidget(self.auto_icon_check, 1, 1)
|
||||
lay.addWidget(grp_mode)
|
||||
|
||||
self.grp_meta = QGroupBox("版本元数据 (Metadata)")
|
||||
meta_form = QFormLayout(self.grp_meta)
|
||||
meta_form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
meta_form.setContentsMargins(15, 20, 15, 15)
|
||||
meta_form.setVerticalSpacing(12)
|
||||
|
||||
self.ver_ver = QLineEdit("1.0.0")
|
||||
self.ver_comp = QLineEdit("My Studio")
|
||||
self.ver_desc = QLineEdit("Python Executable")
|
||||
meta_form.addRow("版本序列:", self.ver_ver)
|
||||
meta_form.addRow("发行公司:", self.ver_comp)
|
||||
meta_form.addRow("文件描述:", self.ver_desc)
|
||||
lay.addWidget(self.grp_meta)
|
||||
|
||||
lay.addStretch()
|
||||
scroll.setWidget(content)
|
||||
main_lay = QVBoxLayout(self.tab_basic)
|
||||
@@ -865,18 +931,26 @@ class SettingsPanel(QWidget):
|
||||
lay = QVBoxLayout(content)
|
||||
lay.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
grp_env = QGroupBox("隔离与网络")
|
||||
form_env = QFormLayout(grp_env)
|
||||
form_env.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
form_env.setContentsMargins(15, 20, 15, 15)
|
||||
form_env.setVerticalSpacing(12)
|
||||
grp_engine = QGroupBox("编译器引擎")
|
||||
form_engine = QFormLayout(grp_engine)
|
||||
form_engine.setContentsMargins(15, 20, 15, 15)
|
||||
form_engine.setVerticalSpacing(12)
|
||||
|
||||
self.pip_source_edit = QLineEdit()
|
||||
form_env.addRow("PIP 镜像:", self.pip_source_edit)
|
||||
self.engine_combo = QComboBox()
|
||||
self.engine_combo.addItems(["PyInstaller", "Nuitka"])
|
||||
self.engine_combo.currentIndexChanged.connect(self.on_engine_changed)
|
||||
form_engine.addRow("构建引擎选择:", self.engine_combo)
|
||||
lay.addWidget(grp_engine)
|
||||
|
||||
grp_modules = QGroupBox("精细化模块过滤")
|
||||
form_mod = QFormLayout(grp_modules)
|
||||
form_mod.setContentsMargins(15, 20, 15, 15)
|
||||
form_mod.setVerticalSpacing(12)
|
||||
|
||||
self.venv_check = QCheckBox("启用独立打包环境")
|
||||
form_env.addRow("", self.venv_check)
|
||||
lay.addWidget(grp_env)
|
||||
self.exclude_edit = QLineEdit()
|
||||
self.exclude_edit.setPlaceholderText("例如: tkinter, matplotlib (排除打包,减少体积)")
|
||||
form_mod.addRow("屏蔽指定模块:", self.exclude_edit)
|
||||
lay.addWidget(grp_modules)
|
||||
|
||||
grp_dep = QGroupBox("包与依赖分析机制")
|
||||
dep_lay = QVBoxLayout(grp_dep)
|
||||
@@ -886,6 +960,7 @@ class SettingsPanel(QWidget):
|
||||
grid_dep = QGridLayout()
|
||||
self.reqs_check = QCheckBox("优先读取 requirements.txt")
|
||||
self.pipreqs_check = QCheckBox("启用 pipreqs 深度代码分析")
|
||||
|
||||
grid_dep.addWidget(self.reqs_check, 0, 0)
|
||||
grid_dep.addWidget(self.pipreqs_check, 0, 1)
|
||||
dep_lay.addLayout(grid_dep)
|
||||
@@ -902,8 +977,8 @@ class SettingsPanel(QWidget):
|
||||
h_hid.addWidget(btn_scan)
|
||||
form_dep.addRow("隐式依赖:", h_hid)
|
||||
dep_lay.addLayout(form_dep)
|
||||
|
||||
lay.addWidget(grp_dep)
|
||||
|
||||
lay.addStretch()
|
||||
|
||||
scroll.setWidget(content)
|
||||
@@ -911,43 +986,6 @@ class SettingsPanel(QWidget):
|
||||
main_lay.setContentsMargins(0,0,0,0)
|
||||
main_lay.addWidget(scroll)
|
||||
|
||||
def build_adv_tab(self):
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
||||
scroll.setStyleSheet("background: transparent;")
|
||||
|
||||
content = QWidget()
|
||||
content.setStyleSheet("background: white;")
|
||||
lay = QVBoxLayout(content)
|
||||
lay.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
grp_res = QGroupBox("附加资源与优化")
|
||||
form_res = QFormLayout(grp_res)
|
||||
form_res.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
form_res.setContentsMargins(15, 20, 15, 15)
|
||||
form_res.setVerticalSpacing(12)
|
||||
|
||||
self.add_data_edit = QLineEdit()
|
||||
self.add_data_edit.setPlaceholderText("例如: data/model.bin:data")
|
||||
btn_add = QPushButton("添加...")
|
||||
btn_add.setProperty("class", "ToolBtn")
|
||||
btn_add.clicked.connect(self.add_resource)
|
||||
h_res = QHBoxLayout()
|
||||
h_res.addWidget(self.add_data_edit)
|
||||
h_res.addWidget(btn_add)
|
||||
form_res.addRow("附加资源:", h_res)
|
||||
|
||||
self.upx_check = QCheckBox("启用 PyInstaller UPX")
|
||||
form_res.addRow("", self.upx_check)
|
||||
lay.addWidget(grp_res)
|
||||
lay.addStretch()
|
||||
|
||||
scroll.setWidget(content)
|
||||
main_lay = QVBoxLayout(self.tab_adv)
|
||||
main_lay.setContentsMargins(0,0,0,0)
|
||||
main_lay.addWidget(scroll)
|
||||
|
||||
def build_meta_tab(self):
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
@@ -959,23 +997,36 @@ class SettingsPanel(QWidget):
|
||||
lay = QVBoxLayout(content)
|
||||
lay.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
self.grp_meta = QGroupBox("版本元数据 (Metadata)")
|
||||
meta_form = QFormLayout(self.grp_meta)
|
||||
meta_form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
meta_form.setContentsMargins(15, 20, 15, 15)
|
||||
meta_form.setVerticalSpacing(12)
|
||||
grp_res = QGroupBox("附加资源归档")
|
||||
form_res = QFormLayout(grp_res)
|
||||
form_res.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
form_res.setContentsMargins(15, 20, 15, 15)
|
||||
form_res.setVerticalSpacing(12)
|
||||
|
||||
self.ver_ver = QLineEdit("1.0.0")
|
||||
self.ver_comp = QLineEdit("My Studio")
|
||||
self.ver_desc = QLineEdit("Python Executable")
|
||||
meta_form.addRow("版本序列:", self.ver_ver)
|
||||
meta_form.addRow("发行公司:", self.ver_comp)
|
||||
meta_form.addRow("文件描述:", self.ver_desc)
|
||||
lay.addWidget(self.grp_meta)
|
||||
self.add_data_edit = QLineEdit()
|
||||
self.add_data_edit.setPlaceholderText("例如: data/model.bin:data")
|
||||
btn_add = QPushButton("添加...")
|
||||
btn_add.setProperty("class", "ToolBtn")
|
||||
btn_add.clicked.connect(self.add_resource)
|
||||
h_res = QHBoxLayout()
|
||||
h_res.addWidget(self.add_data_edit)
|
||||
h_res.addWidget(btn_add)
|
||||
form_res.addRow("附加资源:", h_res)
|
||||
lay.addWidget(grp_res)
|
||||
|
||||
grp_env = QGroupBox("执行环境隔离")
|
||||
form_env = QFormLayout(grp_env)
|
||||
form_env.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
form_env.setContentsMargins(15, 20, 15, 15)
|
||||
form_env.setVerticalSpacing(12)
|
||||
|
||||
self.pip_source_edit = QLineEdit()
|
||||
form_env.addRow("PIP 镜像:", self.pip_source_edit)
|
||||
|
||||
self.venv_check = QCheckBox("启用独立沙盒打包环境 (Venv)")
|
||||
form_env.addRow("", self.venv_check)
|
||||
lay.addWidget(grp_env)
|
||||
|
||||
tip = QLabel("注:写入 EXE 版本信息可提升系统识别度。")
|
||||
tip.setStyleSheet("color: #9AA0A6; font-size: 12px; font-weight: normal; background: transparent;")
|
||||
lay.addWidget(tip)
|
||||
lay.addStretch()
|
||||
|
||||
scroll.setWidget(content)
|
||||
@@ -983,9 +1034,137 @@ class SettingsPanel(QWidget):
|
||||
main_lay.setContentsMargins(0,0,0,0)
|
||||
main_lay.addWidget(scroll)
|
||||
|
||||
def build_app_tab(self):
|
||||
self.app_scroll_area = QScrollArea()
|
||||
self.app_scroll_area.setWidgetResizable(True)
|
||||
self.app_scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.app_scroll_area.setStyleSheet("background: transparent;")
|
||||
|
||||
content = QWidget()
|
||||
content.setStyleSheet("background: white;")
|
||||
lay = QVBoxLayout(content)
|
||||
lay.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
# 编译资源限制组
|
||||
self.form_perf = QFormLayout()
|
||||
grp_perf = QGroupBox("编译资源限制与压缩优化")
|
||||
grp_perf.setLayout(self.form_perf)
|
||||
self.form_perf.setContentsMargins(15, 20, 15, 15)
|
||||
self.form_perf.setVerticalSpacing(12)
|
||||
|
||||
self.cores_spin = QSpinBox()
|
||||
self.cores_spin.setRange(1, os.cpu_count() or 4)
|
||||
self.cores_spin.setValue(os.cpu_count() or 2)
|
||||
self.form_perf.addRow("并发编译线程数:", self.cores_spin)
|
||||
|
||||
self.upx_check = QCheckBox("启用 PyInstaller UPX 压缩优化")
|
||||
self.upx_check.toggled.connect(self.on_upx_toggled)
|
||||
self.form_perf.addRow("", self.upx_check)
|
||||
|
||||
self.upx_path_edit = QLineEdit()
|
||||
self.upx_path_edit.setPlaceholderText("留空则在全局环境或当前目录下自动定位 UPX...")
|
||||
self.btn_upx_path = QPushButton("选择...")
|
||||
self.btn_upx_path.setProperty("class", "ToolBtn")
|
||||
self.btn_upx_path.clicked.connect(self.select_upx_path)
|
||||
|
||||
self.upx_path_container = QWidget()
|
||||
self.upx_path_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.upx_path_container.setMinimumHeight(32)
|
||||
h_upx = QHBoxLayout(self.upx_path_container)
|
||||
h_upx.setContentsMargins(0, 0, 0, 0)
|
||||
h_upx.setSpacing(6)
|
||||
h_upx.addWidget(self.upx_path_edit)
|
||||
h_upx.addWidget(self.btn_upx_path)
|
||||
self.form_perf.addRow("自定义 UPX 目录:", self.upx_path_container)
|
||||
lay.addWidget(grp_perf)
|
||||
|
||||
# 产物保存路径策略组
|
||||
self.form_dest = QFormLayout()
|
||||
grp_dest = QGroupBox("构建产物保存路径")
|
||||
grp_dest.setLayout(self.form_dest)
|
||||
self.form_dest.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
self.form_dest.setContentsMargins(15, 20, 15, 15)
|
||||
self.form_dest.setVerticalSpacing(12)
|
||||
|
||||
self.out_mode_combo = QComboBox()
|
||||
self.out_mode_combo.addItems(["默认路径 (保存在源脚本所在同目录)", "保存到自定义输出目录"])
|
||||
self.out_mode_combo.currentIndexChanged.connect(self.on_out_mode_changed)
|
||||
self.form_dest.addRow("保存位置模式:", self.out_mode_combo)
|
||||
|
||||
self.out_dir_edit = QLineEdit()
|
||||
self.out_dir_edit.setPlaceholderText("选择自定义输出保存路径...")
|
||||
self.out_dir_edit.setMinimumWidth(150)
|
||||
self.btn_out_dir = QPushButton("浏览...")
|
||||
self.btn_out_dir.setFixedWidth(75)
|
||||
self.btn_out_dir.setProperty("class", "ToolBtn")
|
||||
self.btn_out_dir.clicked.connect(self.select_out_dir)
|
||||
|
||||
self.out_dir_container = QWidget()
|
||||
self.out_dir_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.out_dir_container.setMinimumHeight(32)
|
||||
h_out_dir = QHBoxLayout(self.out_dir_container)
|
||||
h_out_dir.setContentsMargins(0, 0, 0, 0)
|
||||
h_out_dir.setSpacing(6)
|
||||
h_out_dir.addWidget(self.out_dir_edit)
|
||||
h_out_dir.addWidget(self.btn_out_dir)
|
||||
self.form_dest.addRow("自定义目录路径:", self.out_dir_container)
|
||||
lay.addWidget(grp_dest)
|
||||
|
||||
# 偏好控制与反馈组
|
||||
grp_app = QGroupBox("偏好习惯与通知反馈")
|
||||
grid_app = QGridLayout(grp_app)
|
||||
grid_app.setContentsMargins(15, 20, 15, 15)
|
||||
grid_app.setVerticalSpacing(15)
|
||||
|
||||
self.concise_log_check = QCheckBox("启用简洁日志模式 (自动屏蔽警告与垃圾调试信息)")
|
||||
self.clean_all_check = QCheckBox("编译结束后自动清理临时缓存文件目录")
|
||||
self.auto_icon_check = QCheckBox("自动匹配入口脚本同目录下的同名ico图标")
|
||||
self.sound_notify_check = QCheckBox("编译构建任务结束时发出声音反馈提示")
|
||||
self.auto_save_log_check = QCheckBox("打包结束后自动在目标输出目录导出运行日志 (.log)")
|
||||
|
||||
grid_app.addWidget(self.concise_log_check, 0, 0)
|
||||
grid_app.addWidget(self.clean_all_check, 1, 0)
|
||||
grid_app.addWidget(self.auto_icon_check, 2, 0)
|
||||
grid_app.addWidget(self.sound_notify_check, 3, 0)
|
||||
grid_app.addWidget(self.auto_save_log_check, 4, 0)
|
||||
lay.addWidget(grp_app)
|
||||
|
||||
lay.addStretch()
|
||||
|
||||
self.app_scroll_area.setWidget(content)
|
||||
main_lay = QVBoxLayout(self.tab_app)
|
||||
main_lay.setContentsMargins(0,0,0,0)
|
||||
main_lay.addWidget(self.app_scroll_area)
|
||||
|
||||
def on_engine_changed(self):
|
||||
engine = self.engine_combo.currentText()
|
||||
self.upx_check.setVisible(engine == "PyInstaller")
|
||||
if getattr(self, 'upx_check', None) is not None and getattr(self, 'form_perf', None) is not None:
|
||||
engine = self.engine_combo.currentText()
|
||||
is_pyi = (engine == "PyInstaller")
|
||||
self.upx_check.setVisible(is_pyi)
|
||||
self.form_perf.setRowVisible(self.upx_path_container, is_pyi and self.upx_check.isChecked())
|
||||
|
||||
def on_upx_toggled(self, checked):
|
||||
if getattr(self, 'form_perf', None) is not None:
|
||||
self.form_perf.setRowVisible(self.upx_path_container, checked)
|
||||
if checked and getattr(self, 'app_scroll_area', None) is not None:
|
||||
QTimer.singleShot(50, lambda: self.app_scroll_area.ensureWidgetVisible(self.upx_path_container))
|
||||
|
||||
def on_out_mode_changed(self, index):
|
||||
show_custom = (index == 1)
|
||||
if getattr(self, 'form_dest', None) is not None:
|
||||
self.form_dest.setRowVisible(self.out_dir_container, show_custom)
|
||||
if show_custom and getattr(self, 'app_scroll_area', None) is not None:
|
||||
QTimer.singleShot(50, lambda: self.app_scroll_area.ensureWidgetVisible(self.out_dir_container))
|
||||
|
||||
def select_out_dir(self):
|
||||
d = QFileDialog.getExistingDirectory(self, "选择输出保存目录")
|
||||
if d:
|
||||
self.out_dir_edit.setText(Path(d).resolve().as_posix())
|
||||
|
||||
def select_upx_path(self):
|
||||
d = QFileDialog.getExistingDirectory(self, "选择 UPX 工具根目录")
|
||||
if d:
|
||||
self.upx_path_edit.setText(Path(d).resolve().as_posix())
|
||||
|
||||
def update_icon_preview(self, path):
|
||||
if path and Path(path).exists():
|
||||
@@ -1039,7 +1218,7 @@ class SettingsPanel(QWidget):
|
||||
|
||||
|
||||
class ScriptAnalysisThread(QThread):
|
||||
analysis_done = Signal(str, str, str, str, set) # app_name, version, author, desc, imports
|
||||
analysis_done = Signal(str, str, str, str, set)
|
||||
|
||||
def __init__(self, path):
|
||||
super().__init__()
|
||||
@@ -1099,6 +1278,7 @@ class PackingThread(QThread):
|
||||
self.venv_dir = None
|
||||
self.temp_workpath = None
|
||||
self.temp_out_dir = None
|
||||
self.all_raw_logs = []
|
||||
|
||||
def cancel(self):
|
||||
self._is_cancelled = True
|
||||
@@ -1113,6 +1293,7 @@ class PackingThread(QThread):
|
||||
|
||||
timer = None
|
||||
is_timeout = [False]
|
||||
cmd_raw_lines = []
|
||||
|
||||
clean_env = os.environ.copy()
|
||||
clean_env.pop("PYTHONHOME", None)
|
||||
@@ -1141,11 +1322,26 @@ class PackingThread(QThread):
|
||||
|
||||
buffer = []
|
||||
last_emit = time.time()
|
||||
|
||||
def is_noisy_line(l):
|
||||
l_lower = l.lower()
|
||||
if "error:" in l_lower:
|
||||
return False
|
||||
return any(kw in l_lower for kw in ["warning:", "info:", "deprecation:", "userwarning:", "futurewarning:"])
|
||||
|
||||
for line in self.process.stdout:
|
||||
if self._is_cancelled:
|
||||
self.process.terminate()
|
||||
return False
|
||||
buffer.append(line.strip())
|
||||
|
||||
stripped = line.rstrip('\r\n')
|
||||
cmd_raw_lines.append(stripped)
|
||||
self.all_raw_logs.append(stripped)
|
||||
|
||||
if self.params.get('concise_log', True) and is_noisy_line(stripped):
|
||||
continue
|
||||
|
||||
buffer.append(stripped)
|
||||
if len(buffer) >= 15 or (time.time() - last_emit) > 0.1:
|
||||
self.progress.emit('\n'.join(buffer))
|
||||
buffer.clear()
|
||||
@@ -1160,7 +1356,14 @@ class PackingThread(QThread):
|
||||
self.progress.emit(f"[System] 子进程执行超时 (>{timeout}s),已自动中断并继续执行后续流水线...")
|
||||
return False
|
||||
|
||||
return self.process.returncode == 0
|
||||
success = self.process.returncode == 0
|
||||
|
||||
if not success and self.params.get('concise_log', True) and cmd_raw_lines:
|
||||
self.progress.emit("\n" + "!"*10 + " [诊断回溯: 以下是由于该环节执行异常产生的完整原始日志] " + "!"*10)
|
||||
self.progress.emit('\n'.join(cmd_raw_lines))
|
||||
self.progress.emit("!"*60 + "\n")
|
||||
|
||||
return success
|
||||
except FileNotFoundError as e:
|
||||
cmd_name = cmd[0] if isinstance(cmd, list) and cmd else str(cmd)
|
||||
if "python" in str(cmd_name).lower():
|
||||
@@ -1208,6 +1411,43 @@ class PackingThread(QThread):
|
||||
|
||||
return orig_path, False, ""
|
||||
|
||||
def detect_python_syntax_errors(self):
|
||||
import re
|
||||
script_path = self.params['script_path']
|
||||
script_name = Path(script_path).name
|
||||
log_text = "\n".join(self.all_raw_logs)
|
||||
|
||||
file_line_pat = re.compile(r'File "([^"]+)", line (\d+)', re.I)
|
||||
err_type_pat = re.compile(r'^(IndentationError|SyntaxError|TabError):\s*(.*)', re.M)
|
||||
|
||||
err_matches = list(err_type_pat.finditer(log_text))
|
||||
if err_matches:
|
||||
last_err = err_matches[-1]
|
||||
err_type = last_err.group(1)
|
||||
err_desc = last_err.group(2)
|
||||
|
||||
err_pos = last_err.start()
|
||||
line_no = "未知"
|
||||
file_name = script_name
|
||||
|
||||
file_line_matches = list(file_line_pat.finditer(log_text))
|
||||
for m in reversed(file_line_matches):
|
||||
if m.end() < err_pos:
|
||||
matched_filepath = m.group(1)
|
||||
if matched_filepath.endswith(('.py', '.pyw')):
|
||||
line_no = m.group(2)
|
||||
file_name = Path(matched_filepath).name
|
||||
break
|
||||
|
||||
return {
|
||||
"is_code_error": True,
|
||||
"type": err_type,
|
||||
"desc": err_desc,
|
||||
"line": line_no,
|
||||
"file": file_name
|
||||
}
|
||||
return {"is_code_error": False}
|
||||
|
||||
def run(self):
|
||||
os.environ["NUITKA_ACCEPT_DOWNLOADS"] = "yes"
|
||||
engine = self.params['engine']
|
||||
@@ -1341,21 +1581,29 @@ class PackingThread(QThread):
|
||||
|
||||
if self.params.get('version_file') and os.name == "nt": cmd.extend(["--version-file", self.params['version_file']])
|
||||
if self.params.get('upx'):
|
||||
upx_dir = (Path.cwd() / "upx").resolve()
|
||||
if upx_dir.exists(): cmd.append(f"--upx-dir={upx_dir.as_posix()}")
|
||||
upx_dir_custom = self.params.get('upx_path', '').strip()
|
||||
if upx_dir_custom and Path(upx_dir_custom).exists():
|
||||
cmd.append(f"--upx-dir={upx_dir_custom}")
|
||||
else:
|
||||
upx_dir_default = (Path.cwd() / "upx").resolve()
|
||||
if upx_dir_default.exists(): cmd.append(f"--upx-dir={upx_dir_default.as_posix()}")
|
||||
|
||||
for imp in self.params.get('hidden_imports', '').split(','):
|
||||
if imp.strip(): cmd.extend(["--hidden-import", imp.strip()])
|
||||
|
||||
for src, dst in parse_add_data(self.params.get('add_data', '')):
|
||||
cmd.extend(["--add-data", f"{src}{os.pathsep}{dst}"])
|
||||
|
||||
for excl in self.params.get('exclude_modules', '').split(','):
|
||||
if excl.strip(): cmd.extend(["--exclude-module", excl.strip()])
|
||||
|
||||
elif engine == "Nuitka":
|
||||
self.temp_out_dir = Path(tempfile.mkdtemp(prefix="nuitka_out_")).resolve()
|
||||
cmd = [python_exe, "-m", "nuitka", "--remove-output", "--assume-yes-for-downloads",
|
||||
f"--output-dir={self.temp_out_dir.as_posix()}", f"--output-filename={app_name}{ext}"]
|
||||
|
||||
cmd.append(f"--jobs={os.cpu_count() or 4}")
|
||||
cores = self.params.get('cpu_cores', os.cpu_count() or 2)
|
||||
cmd.append(f"--jobs={cores}")
|
||||
|
||||
if self.params['onefile']: cmd.append("--onefile")
|
||||
else: cmd.append("--standalone")
|
||||
@@ -1395,6 +1643,9 @@ class PackingThread(QThread):
|
||||
else:
|
||||
cmd.append(f"--include-data-files={src_path.resolve().as_posix()}={dst}")
|
||||
|
||||
for excl in self.params.get('exclude_modules', '').split(','):
|
||||
if excl.strip(): cmd.append(f"--exclude-module={excl.strip()}")
|
||||
|
||||
cmd.append(script_posix)
|
||||
|
||||
success = self.run_cmd(cmd, cwd=Path.cwd().resolve().as_posix())
|
||||
@@ -1405,7 +1656,18 @@ class PackingThread(QThread):
|
||||
if engine == "PyInstaller": src_out = cwd / "dist" / (f"{app_name}{ext}" if self.params['onefile'] else app_name)
|
||||
elif engine == "Nuitka": src_out = self.temp_out_dir / (f"{app_name}{ext}" if self.params['onefile'] else f"{app_name}.dist")
|
||||
|
||||
final_out = script_dir / src_out.name
|
||||
out_mode = int(self.params.get('out_mode', 0))
|
||||
custom_out = self.params.get('custom_out_dir', '').strip()
|
||||
if out_mode == 1 and custom_out:
|
||||
try:
|
||||
final_out_dir = Path(custom_out)
|
||||
final_out_dir.mkdir(parents=True, exist_ok=True)
|
||||
except:
|
||||
final_out_dir = script_dir
|
||||
else:
|
||||
final_out_dir = script_dir
|
||||
|
||||
final_out = final_out_dir / src_out.name
|
||||
if success and src_out.exists():
|
||||
try:
|
||||
if final_out.exists():
|
||||
@@ -1417,10 +1679,35 @@ class PackingThread(QThread):
|
||||
|
||||
if success and final_out.exists():
|
||||
self.progress.emit("[Pack] 正在校验最终程序完整性...")
|
||||
if self.params.get('auto_save_log') and self.all_raw_logs:
|
||||
try:
|
||||
log_file = final_out_dir / f"qpypack_build_{app_name}.log"
|
||||
log_file.write_text('\n'.join(self.all_raw_logs), encoding='utf-8')
|
||||
self.progress.emit(f"[Log] 打包日志已被导出至: {log_file.as_posix()}")
|
||||
except: pass
|
||||
self.finished.emit(True, f"[Success] 构建结束!路径: {final_out.resolve().as_posix()}")
|
||||
else:
|
||||
self.finished.emit(False, "[Failed] 构建任务异常中断,请参阅上方运行日志以排查错误。")
|
||||
err_info = self.detect_python_syntax_errors()
|
||||
if err_info["is_code_error"]:
|
||||
msg = (
|
||||
f"[❌ 语法错误] 检测到您的 Python 源代码存在语法/缩进问题!\n"
|
||||
f" - 错误文件: {err_info['file']}\n"
|
||||
f" - 错误类型: {err_info['type']}\n"
|
||||
f" - 错误位置: 第 {err_info['line']} 行附近\n"
|
||||
f" - 错误描述: {err_info['desc']}\n"
|
||||
f"\n提示: 此问题为源码本身不合规导致。请先修复脚本错误并能正常执行,然后再尝试打包。"
|
||||
)
|
||||
else:
|
||||
if self.params.get('concise_log', True) and self.all_raw_logs:
|
||||
self.progress.emit("\n" + "!"*10 + " [诊断提示: 以下为全局完整运行日志汇总] " + "!"*10)
|
||||
self.progress.emit('\n'.join(self.all_raw_logs[-100:]))
|
||||
msg = "[Failed] 构建任务异常中断,请参阅上方运行日志以排查错误。"
|
||||
self.finished.emit(False, msg)
|
||||
|
||||
except Exception as e:
|
||||
if self.params.get('concise_log', True) and self.all_raw_logs:
|
||||
self.progress.emit("\n" + "!"*10 + " [系统诊断: 崩溃前全局原始日志] " + "!"*10)
|
||||
self.progress.emit('\n'.join(self.all_raw_logs[-100:]))
|
||||
self.finished.emit(False, f"[System Error] 发生未处理的严重错误: {str(e)}")
|
||||
finally:
|
||||
if is_temp and build_script_path and build_script_path.exists():
|
||||
@@ -1447,7 +1734,6 @@ class PackingThread(QThread):
|
||||
robust_rmtree(cwd / p)
|
||||
Path(cwd / f"{app_name}.spec").unlink(missing_ok=True)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -1461,8 +1747,8 @@ class MainWindow(QMainWindow):
|
||||
def init_style(self):
|
||||
self.setWindowTitle(f"{__app_name__} {__version__} - {__author__}")
|
||||
|
||||
self.setMinimumSize(580, 520)
|
||||
self.resize(600, 560)
|
||||
self.setMinimumSize(610, 560)
|
||||
self.resize(610, 560)
|
||||
|
||||
icon_path = get_resource_path("icon.ico")
|
||||
if os.path.exists(icon_path):
|
||||
@@ -1774,6 +2060,11 @@ class MainWindow(QMainWindow):
|
||||
'hidden_imports': sp.hidden_edit.text(),
|
||||
'add_data': sp.add_data_edit.text(),
|
||||
'upx': sp.upx_check.isChecked() if engine == "PyInstaller" else False,
|
||||
'upx_path': sp.upx_path_edit.text().strip(),
|
||||
'cpu_cores': sp.cores_spin.value(),
|
||||
'exclude_modules': sp.exclude_edit.text().strip(),
|
||||
'out_mode': sp.out_mode_combo.currentIndex(),
|
||||
'custom_out_dir': sp.out_dir_edit.text().strip(),
|
||||
'use_venv': sp.venv_check.isChecked(),
|
||||
'clean_all': sp.clean_all_check.isChecked(),
|
||||
'version_file': version_file.as_posix() if version_file else None,
|
||||
@@ -1781,7 +2072,9 @@ class MainWindow(QMainWindow):
|
||||
'ver_comp': sp.ver_comp.text(),
|
||||
'ver_desc': sp.ver_desc.text(),
|
||||
'ver_ver': sp.ver_ver.text(),
|
||||
'pip_index_url': sp.pip_source_edit.text().strip()
|
||||
'pip_index_url': sp.pip_source_edit.text().strip(),
|
||||
'concise_log': sp.concise_log_check.isChecked(),
|
||||
'auto_save_log': sp.auto_save_log_check.isChecked()
|
||||
}
|
||||
|
||||
self.log.clear()
|
||||
@@ -1799,6 +2092,10 @@ class MainWindow(QMainWindow):
|
||||
def on_pack_finished(self, success, msg):
|
||||
self.append_log("\n" + "━"*50 + "\n" + msg)
|
||||
self.drop_area.stop_build_anim()
|
||||
|
||||
if self.settings_panel.sound_notify_check.isChecked():
|
||||
play_alert(success)
|
||||
|
||||
if success:
|
||||
icon_path = self.settings_panel.icon_edit.text().strip()
|
||||
self.drop_area.show_success(icon_path)
|
||||
@@ -1810,9 +2107,13 @@ class MainWindow(QMainWindow):
|
||||
self.update_ui_state("failed")
|
||||
|
||||
def open_dist(self):
|
||||
if self.settings_panel.clean_all_check.isChecked() and self.script_path:
|
||||
if self.settings_panel.out_mode_combo.currentIndex() == 1:
|
||||
target = Path(self.settings_panel.out_dir_edit.text().strip())
|
||||
elif self.settings_panel.clean_all_check.isChecked() and self.script_path:
|
||||
target = Path(self.script_path).parent
|
||||
else: target = Path.cwd() / "dist"
|
||||
else:
|
||||
target = Path.cwd() / "dist"
|
||||
|
||||
if target.exists():
|
||||
try:
|
||||
if os.name == 'nt': os.startfile(target)
|
||||
|
||||
Reference in new issue
Block a user