v2.7.7: Atomic config save, encoding enhancements, anti-AV retry, RamDisk fixes
- feat: add atomic save mechanism for config write protection - feat: improve source/deps encoding compatibility (GBK, UTF-8 with BOM) - feat: add smart retry mechanism to prevent AV lock (Permission Denied) - feat: add global exception logging and isolate engine build env vars - fix: resolve [WinError 1] crash during packaging on RamDisk - fix: ensure temp directory setting fully applies to venv and PyInstaller cache
This commit is contained in:
1 parent
2af4ceb6eb
commit
94da38d7da
20 files changed
+142
-50
No files matched your search
+16
-1
@@ -12,7 +12,22 @@ QPyPack 项目的所有重要变更都将记录在此文件中。
|
||||
# 更新日志 (Changelog)
|
||||
|
||||
---
|
||||
## [2.8.0] - 2026-08-08
|
||||
|
||||
## [2.7.7] - 2026-08-09
|
||||
|
||||
#### 新增
|
||||
- **配置写入保护**:引入原子化保存机制,彻底杜绝因断电、死机或磁盘满导致的配置丢失/损坏问题。。
|
||||
- **提高编码兼容**:升级源码与依赖的读取逻辑,智能兼容 GBK、带 BOM 的 UTF-8 等多种复杂编码,告别乱码闪退。
|
||||
- **抗杀软拦截**:产物输出阶段加入智能重试机制,有效解决生成瞬间被安全软件锁定而导致的“权限不足”报错。
|
||||
- **稳定性增强**:增加全局异常日志捕获,并隔离了引擎编译时的环境变量,避免干扰主程序运行。
|
||||
|
||||
#### 修复
|
||||
- 修复在部分 RamDisk(内存盘)环境下打包时,因路径解析引发 [WinError 1] 导致程序崩溃的问题。
|
||||
- 修复设置面板中的“临时目录”选项未对虚拟环境和 PyInstaller 缓存完全生效的漏洞。
|
||||
|
||||
---
|
||||
|
||||
## [2.7.6] - 2026-08-08
|
||||
|
||||
#### 新增
|
||||
- **项目沙箱智能感知**:基于脚本路径哈希与 Python 版本/架构生成唯一沙箱标识,加载项目脚本时自动感知识别已有环境并快速复用。
|
||||
|
||||
+15
-1
@@ -9,7 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [2.8.0] - 2026-08-08
|
||||
## [2.7.7] - 2026-08-09
|
||||
|
||||
#### Added
|
||||
- **Configuration Write Protection**: Introduced an atomic save mechanism to completely prevent configuration loss or corruption caused by power outages, system crashes, or full disks.
|
||||
- **Enhanced Encoding Compatibility**: Upgraded the reading logic for source code and dependencies to intelligently support various complex encodings (such as GBK and UTF-8 with BOM), eliminating crashes and garbled text.
|
||||
- **Antivirus Interception Resistance**: Added a smart retry mechanism to the build output stage, effectively resolving "Permission Denied" errors caused by security software temporarily locking files during generation.
|
||||
- **Stability Enhancements**: Added global exception logging and isolated environment variables during engine compilation to avoid interfering with main program operations.
|
||||
|
||||
#### Fixed
|
||||
- Fixed a crash caused by path resolution issues resulting in `[WinError 1]` when packaging under certain RamDisk environments.
|
||||
- Fixed a bug where the "Temporary Directory" option in the settings panel did not fully apply to virtual environments and the PyInstaller cache.
|
||||
|
||||
---
|
||||
|
||||
## [2.7.6] - 2026-08-08
|
||||
|
||||
#### Added
|
||||
- **Smart Project Sandbox Sensing**: Generates unique sandbox identifiers based on script path hashes and Python version/architecture. Automatically detects and reuses existing virtual environments upon loading project scripts.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "qpypack"
|
||||
version = "2.7.4"
|
||||
version = "2.7.7"
|
||||
description = "A cross-platform GUI packaging and compilation suite based on PyInstaller and Nuitka."
|
||||
readme = "README_EN.md"
|
||||
authors = [
|
||||
|
||||
@@ -340,5 +340,6 @@
|
||||
"[INFO] Creating virtual environment ({name})...": "[معلومة] إنشاء بيئة افتراضية ({name})...",
|
||||
"[INFO] Reusing existing virtual environment: {path}": "[معلومة] إعادة استخدام البيئة الافتراضية الموجودة: {path}",
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[تحذير] فشل في إزالة البيئة غير الصالحة، سيتم استخدام بيئة مؤقتة: {error}",
|
||||
"[WARN] Python environment info detection fallback: {error}": "[تحذير] فشل في اكتشاف معلومات بيئة بايثون، سيتم استخدام طريقة بديلة: {error}"
|
||||
"[WARN] Python environment info detection fallback: {error}": "[تحذير] فشل في اكتشاف معلومات بيئة بايثون، سيتم استخدام طريقة بديلة: {error}",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[معلومة] تم اكتشاف بيئة افتراضية موجودة تلقائيًا '{venv}'. تم تحديد خيار 'الاحتفاظ بالبيئة الافتراضية المحلية'."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[WARN] Target project is in a Cloud Sync directory (e.g. OneDrive/Dropbox). Cloud sync may temporarily lock build files.": "[WARN] Projekt befindet sich in einem Cloud-Ordner. Dies kann Dateien sperren.",
|
||||
"[WARN] Timestamp server connection timed out, performing local fast signing...": "[WARN] Zeitstempel-Server Zeitüberschreitung, führe lokale schnelle Signierung durch...",
|
||||
"[WARN] Usage of '__file__' detected. In PyInstaller One-File mode, use 'sys._MEIPASS' to reliably locate bundled resource files!": "[WARN] Nutzung von '__file__' erkannt. Nutzen Sie im Einzeldatei-Modus 'sys._MEIPASS', um Ressourcen zu lokalisieren!",
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\nAusführung beendet. Enter drücken..."
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\nAusführung beendet. Enter drücken...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Automatisch erkannte bestehende virtuelle Umgebung '{venv}'. 'Lokale Venv behalten' ist aktiviert."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[INFO] Creating virtual environment ({name})...": "[INFO] Creando entorno virtual ({name})...",
|
||||
"[INFO] Reusing existing virtual environment: {path}": "[INFO] Reutilizando entorno virtual existente: {path}",
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[WARN] Fallo al purgar entorno no válido, usando sandbox temporal: {error}",
|
||||
"[WARN] Python environment info detection fallback: {error}": "[WARN] Detección de información del entorno Python falló, usando respaldo: {error}"
|
||||
"[WARN] Python environment info detection fallback: {error}": "[WARN] Detección de información del entorno Python falló, usando respaldo: {error}",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Entorno virtual existente '{venv}' auto-detectado. 'Mantener Venv Local' está marcado."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[WARN] Target project is in a Cloud Sync directory (e.g. OneDrive/Dropbox). Cloud sync may temporarily lock build files.": "[WARN] Le projet est dans un dossier Cloud. Les fichiers peuvent être verrouillés.",
|
||||
"[WARN] Timestamp server connection timed out, performing local fast signing...": "[WARN] Délai d'attente du serveur d'horodatage dépassé, signature rapide locale en cours...",
|
||||
"[WARN] Usage of '__file__' detected. In PyInstaller One-File mode, use 'sys._MEIPASS' to reliably locate bundled resource files!": "[WARN] Utilisation de '__file__' détectée. Dans le mode fichier unique, utilisez 'sys._MEIPASS' !",
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\nExécution terminée. Appuyez sur Entrée pour quitter..."
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\nExécution terminée. Appuyez sur Entrée pour quitter...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Environnement virtuel existant '{venv}' auto-détecté. 'Conserver le venv local' est coché."
|
||||
}
|
||||
@@ -313,5 +313,6 @@
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[AVVISO] Impossibile eliminare l'ambiente non valido, ripiego su sandbox temporanea: {error}",
|
||||
"[WARN] Icon conversion to .icns failed, building without icon.": "[AVVISO] Conversione icona in .icns non riuscita, build senza icona.",
|
||||
"[WARN] Icon resource writing blocked (possibly locked by system/antivirus), triggering fallback protection...": "[AVVISO] Scrittura risorsa icona bloccata (possibilmente bloccata da sistema/antivirus), attivazione protezione di fallback...",
|
||||
"[WARN] Low disk space detected on build target drive (Available: {free:.1f} GB, >= 5.0 GB recommended). Build process may interrupt due to disk exhaustion.": "[AVVISO] Spazio su disco insufficiente rilevato sull'unità di destinazione della build (Disponibile: {free:.1f} GB, >= 5.0 GB consigliati). Il processo di build potrebbe interrompersi per esaurimento dello spazio."
|
||||
"[WARN] Low disk space detected on build target drive (Available: {free:.1f} GB, >= 5.0 GB recommended). Build process may interrupt due to disk exhaustion.": "[AVVISO] Spazio su disco insufficiente rilevato sull'unità di destinazione della build (Disponibile: {free:.1f} GB, >= 5.0 GB consigliati). Il processo di build potrebbe interrompersi per esaurimento dello spazio.",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Ambiente virtuale esistente '{venv}' rilevato automaticamente. 'Mantieni Venv Locale' è selezionato."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[WARN] Target project is in a Cloud Sync directory (e.g. OneDrive/Dropbox). Cloud sync may temporarily lock build files.": "[WARN] クラウド同期フォルダに存在します。ファイルがロックされる可能性があります。",
|
||||
"[WARN] Timestamp server connection timed out, performing local fast signing...": "[WARN] タイムスタンプサーバー接続タイムアウト。ローカル高速署名を実行します...",
|
||||
"[WARN] Usage of '__file__' detected. In PyInstaller One-File mode, use 'sys._MEIPASS' to reliably locate bundled resource files!": "[WARN] '__file__' の使用を検出。単一ファイルモードでは 'sys._MEIPASS' を使用してください。",
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\n処理が完了しました。Enter キーを押して終了します..."
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\n処理が完了しました。Enter キーを押して終了します...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] 既存の仮想環境 '{venv}' を自動検出しました。'ローカルVenvを保持' がチェックされています。"
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[WARN] Target project is in a Cloud Sync directory (e.g. OneDrive/Dropbox). Cloud sync may temporarily lock build files.": "[WARN] 프로젝트가 클라우드 동기화 폴더에 있습니다. 파일이 잠길 수 있습니다.",
|
||||
"[WARN] Timestamp server connection timed out, performing local fast signing...": "[WARN] 타임스탬프 서버 연결 시간 초과, 로컬 빠른 서명을 진행합니다...",
|
||||
"[WARN] Usage of '__file__' detected. In PyInstaller One-File mode, use 'sys._MEIPASS' to reliably locate bundled resource files!": "[WARN] '__file__' 사용이 감지되었습니다. 단일 파일 모드에서는 리소스 파일을 찾기 위해 'sys._MEIPASS'를 사용하세요!",
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\n실행 완료. 종료하려면 Enter를 누르세요..."
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\n실행 완료. 종료하려면 Enter를 누르세요...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[정보] 기존 가상 환경 '{venv}'을(를) 자동으로 감지했습니다. '로컬 Venv 유지'가 선택되어 있습니다."
|
||||
}
|
||||
@@ -313,5 +313,6 @@
|
||||
"[ERROR] Target file is running or occupied. Please close the existing application and try again.": "[FOUT] Doelbestand wordt uitgevoerd of is bezet. Sluit de bestaande toepassing en probeer opnieuw.",
|
||||
"[ERROR] The specified icon file does not exist: {path}": "[FOUT] Het opgegeven pictogrambestand bestaat niet: {path}",
|
||||
"[FAILED] Build interrupted exceptionally!\n\nCommon Troubleshooting:\n1. Environment Mismatch (Most Common): The selected Python version is incompatible with your source code. Please go to [Build Settings] to switch to the Python version you normally use for this code.\n2. Missing Dependencies: Click 'Detailed Mode' above to check for ModuleNotFoundError.\n3. Antivirus Block: Ensure your security software is not blocking the build process.\n\n(Note: This is usually caused by environment/code discrepancies rather than the packaging engine itself. Please check the detailed log for exact reasons.)": "[MISLUKT] Build onderbroken met uitzonderingen!\n\nVeelvoorkomende probleemoplossing:\n1. Omgevingsverschil (meest voorkomend): De geselecteerde Python-versie is incompatibel met uw broncode. Ga naar [Buildinstellingen] om over te schakelen naar de Python-versie die u normaal voor deze code gebruikt.\n2. Ontbrekende afhankelijkheden: Klik op 'Gedetailleerde modus' hierboven om te controleren op ModuleNotFoundError.\n3. Antivirusblokkering: Zorg ervoor dat uw beveiligingssoftware het buildproces niet blokkeert.\n\n(Let op: Dit wordt meestal veroorzaakt door omgevings-/codeverschillen en niet door de verpakkingsengine zelf. Raadpleeg het gedetailleerde logboek voor exacte redenen.)",
|
||||
"[FAILED] Compilation interrupted with exceptions, please click 'Detailed Mode' above the log window for troubleshooting.": "[MISLUKT] Compilatie onderbroken met uitzonderingen. Klik op 'Gedetailleerde modus' boven het logvenster voor probleemoplossing."
|
||||
"[FAILED] Compilation interrupted with exceptions, please click 'Detailed Mode' above the log window for troubleshooting.": "[MISLUKT] Compilatie onderbroken met uitzonderingen. Klik op 'Gedetailleerde modus' boven het logvenster voor probleemoplossing.",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Automatisch bestaande virtuele omgeving '{venv}' gedetecteerd. 'Lokale Venv Behouden' is aangevinkt."
|
||||
}
|
||||
@@ -313,5 +313,6 @@
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[OSTRZEŻENIE] Nie udało się wyczyścić nieprawidłowego środowiska, przełączanie na tymczasowy sandbox: {error}",
|
||||
"[WARN] Icon conversion to .icns failed, building without icon.": "[OSTRZEŻENIE] Konwersja ikony do .icns nie powiodła się, budowanie bez ikony.",
|
||||
"[WARN] Icon resource writing blocked (possibly locked by system/antivirus), triggering fallback protection...": "[OSTRZEŻENIE] Zablokowano zapis zasobu ikony (prawdopodobnie zablokowany przez system/antywirus), uruchamianie ochrony zastępczej...",
|
||||
"[WARN] Low disk space detected on build target drive (Available: {free:.1f} GB, >= 5.0 GB recommended). Build process may interrupt due to disk exhaustion.": "[OSTRZEŻENIE] Wykryto mało miejsca na dysku docelowym (Dostępne: {free:.1f} GB, zalecane >= 5.0 GB). Proces budowania może zostać przerwany z powodu wyczerpania miejsca na dysku."
|
||||
"[WARN] Low disk space detected on build target drive (Available: {free:.1f} GB, >= 5.0 GB recommended). Build process may interrupt due to disk exhaustion.": "[OSTRZEŻENIE] Wykryto mało miejsca na dysku docelowym (Dostępne: {free:.1f} GB, zalecane >= 5.0 GB). Proces budowania może zostać przerwany z powodu wyczerpania miejsca na dysku.",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Wykryto automatycznie istniejące środowisko wirtualne '{venv}'. Opcja 'Zachowaj lokalne środowisko' jest zaznaczona."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[INFO] Creating virtual environment ({name})...": "[INFO] Criando ambiente virtual ({name})...",
|
||||
"[INFO] Reusing existing virtual environment: {path}": "[INFO] Reutilizando ambiente virtual existente: {path}",
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[WARN] Falha ao purgar ambiente inválido, usando sandbox temporário: {error}",
|
||||
"[WARN] Python environment info detection fallback: {error}": "[WARN] Detecção de informações do ambiente Python com fallback: {error}"
|
||||
"[WARN] Python environment info detection fallback: {error}": "[WARN] Detecção de informações do ambiente Python com fallback: {error}",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Ambiente virtual existente '{venv}' detectado automaticamente. 'Manter Venv Local' está marcado."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[INFO] Creating virtual environment ({name})...": "[INFO] Создание виртуального окружения ({name})...",
|
||||
"[INFO] Reusing existing virtual environment: {path}": "[INFO] Повторное использование существующего виртуального окружения: {path}",
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[WARN] Не удалось очистить недействительное окружение, используется временная песочница: {error}",
|
||||
"[WARN] Python environment info detection fallback: {error}": "[WARN] Запасной вариант определения информации об окружении Python: {error}"
|
||||
"[WARN] Python environment info detection fallback: {error}": "[WARN] Запасной вариант определения информации об окружении Python: {error}",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[ИНФО] Автоматически обнаружено существующее виртуальное окружение '{venv}'. Опция 'Сохранить локальное виртуальное окружение' отмечена."
|
||||
}
|
||||
@@ -340,5 +340,6 @@
|
||||
"[WARN] Target project is in a Cloud Sync directory (e.g. OneDrive/Dropbox). Cloud sync may temporarily lock build files.": "[คำเตือน] โปรเจกต์เป้าหมายอยู่ในไดเรกทอรีซิงก์คลาวด์ (เช่น OneDrive/Dropbox) การซิงก์คลาวด์อาจล็อกไฟล์สร้างชั่วคราว",
|
||||
"[WARN] Timestamp server connection timed out, performing local fast signing...": "[คำเตือน] การเชื่อมต่อเซิร์ฟเวอร์ประทับเวลาหมดเวลา กำลังดำเนินการลงนามอย่างรวดเร็วในเครื่อง...",
|
||||
"[WARN] Usage of '__file__' detected. In PyInstaller One-File mode, use 'sys._MEIPASS' to reliably locate bundled resource files!": "[คำเตือน] ตรวจพบการใช้ '__file__' ในโหมดไฟล์เดียวของ PyInstaller ให้ใช้ 'sys._MEIPASS' เพื่อค้นหาไฟล์ทรัพยากรที่รวมไว้อย่างน่าเชื่อถือ!",
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\nการทำงานของโปรแกรมเสร็จสิ้น กด Enter เพื่อออก..."
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\nการทำงานของโปรแกรมเสร็จสิ้น กด Enter เพื่อออก...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] ตรวจพบสภาพแวดล้อมเสมือน '{venv}' ที่มีอยู่โดยอัตโนมัติ ตัวเลือก 'เก็บ Venv ในเครื่อง' ถูกเลือกไว้"
|
||||
}
|
||||
@@ -313,5 +313,6 @@
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[UYARI] Geçersiz ortam temizlenemedi, geçici kum havuzuna geri dönülüyor: {error}",
|
||||
"[WARN] Icon conversion to .icns failed, building without icon.": "[UYARI] Simgenin .icns'e dönüştürülmesi başarısız oldu, simge olmadan derleniyor.",
|
||||
"[WARN] Icon resource writing blocked (possibly locked by system/antivirus), triggering fallback protection...": "[UYARI] Simge kaynağı yazma engellendi (muhtemelen sistem/antivirüs tarafından kilitlendi), yedek koruma tetikleniyor...",
|
||||
"[WARN] Low disk space detected on build target drive (Available: {free:.1f} GB, >= 5.0 GB recommended). Build process may interrupt due to disk exhaustion.": "[UYARI] Derleme hedef sürücüsünde düşük disk alanı algılandı (Kullanılabilir: {free:.1f} GB, önerilen >= 5.0 GB). Derleme işlemi disk tükenmesi nedeniyle kesintiye uğrayabilir."
|
||||
"[WARN] Low disk space detected on build target drive (Available: {free:.1f} GB, >= 5.0 GB recommended). Build process may interrupt due to disk exhaustion.": "[UYARI] Derleme hedef sürücüsünde düşük disk alanı algılandı (Kullanılabilir: {free:.1f} GB, önerilen >= 5.0 GB). Derleme işlemi disk tükenmesi nedeniyle kesintiye uğrayabilir.",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[BİLGİ] Mevcut sanal ortam '{venv}' otomatik olarak algılandı. 'Yerel Venv'i Koru' işaretli."
|
||||
}
|
||||
@@ -313,5 +313,6 @@
|
||||
"[WARN] Detected 'requests' or 'httpx'. Auto-bundling 'certifi' certificates to prevent SSL errors.": "[CẢNH BÁO] Phát hiện 'requests' hoặc 'httpx'. Tự động đóng gói chứng chỉ 'certifi' để ngăn lỗi SSL.",
|
||||
"[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}": "[CẢNH BÁO] Không thể dọn dẹp môi trường không hợp lệ, chuyển sang sandbox tạm thời: {error}",
|
||||
"[WARN] Icon conversion to .icns failed, building without icon.": "[CẢNH BÁO] Chuyển đổi biểu tượng sang .icns thất bại, xây dựng mà không có biểu tượng.",
|
||||
"[WARN] Icon resource writing blocked (possibly locked by system/antivirus), triggering fallback protection...": "[CẢNH BÁO] Ghi tài nguyên biểu tượng bị chặn (có thể bị khóa bởi hệ thống/phần mềm diệt virus), kích hoạt bảo vệ dự phòng..."
|
||||
"[WARN] Icon resource writing blocked (possibly locked by system/antivirus), triggering fallback protection...": "[CẢNH BÁO] Ghi tài nguyên biểu tượng bị chặn (có thể bị khóa bởi hệ thống/phần mềm diệt virus), kích hoạt bảo vệ dự phòng...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[THÔNG TIN] Tự động phát hiện môi trường ảo hiện có '{venv}'. Tùy chọn 'Giữ Venv Cục Bộ' đã được chọn."
|
||||
}
|
||||
@@ -279,6 +279,7 @@
|
||||
"[INFO] Analyzing source code and project dependencies...": "[INFO] 分析源码与依赖...",
|
||||
"[INFO] Applied digital signature with timestamp ({tsa})...": "[INFO] 已通过时间戳服务器 ({tsa}) 完成数字签名与盖章...",
|
||||
"[INFO] Applying smart digital signature...": "[INFO] 正在为应用程序添加数字签名...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.",
|
||||
"[INFO] Build Cancelled.": "[INFO] 构建已取消。",
|
||||
"[INFO] Build log exported to: {path}": "[INFO] 日志已导出: {path}",
|
||||
"[INFO] Compilation completed, archiving built files...": "[INFO] 编译完成,归档构建产物...",
|
||||
|
||||
@@ -340,5 +340,6 @@
|
||||
"[WARN] Target project is in a Cloud Sync directory (e.g. OneDrive/Dropbox). Cloud sync may temporarily lock build files.": "[WARN] 專案位於雲端同步目錄,可能鎖定建置檔案。",
|
||||
"[WARN] Timestamp server connection timed out, performing local fast signing...": "[WARN] 時間戳記伺服器連線逾時,進行本地快速簽章...",
|
||||
"[WARN] Usage of '__file__' detected. In PyInstaller One-File mode, use 'sys._MEIPASS' to reliably locate bundled resource files!": "[WARN] 偵測到 '__file__' 的使用。在單一檔案模式下,請使用 'sys._MEIPASS' 定位資源路徑!",
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\n程式執行完畢,按下 Enter 鍵退出..."
|
||||
"\\nProgram execution completed, press Enter to exit...": "\\n程式執行完畢,按下 Enter 鍵退出...",
|
||||
"[INFO] Auto-detected existing virtual environment '{venv}'. 'Keep Local Venv' is checked.": "[資訊] 自動偵測到現有的虛擬環境 '{venv}'。已勾選「保留本機虛擬環境」。"
|
||||
}
|
||||
+79
-32
@@ -18,6 +18,14 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
_orig_resolve = Path.resolve
|
||||
def _safe_resolve(self, strict=False):
|
||||
try:
|
||||
return _orig_resolve(self, strict=strict)
|
||||
except Exception:
|
||||
return self.absolute()
|
||||
Path.resolve = _safe_resolve
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if os.name == 'nt':
|
||||
@@ -52,7 +60,7 @@ except ImportError:
|
||||
HAS_QT_AUDIO = False
|
||||
|
||||
__app_name__ = "QPyPack"
|
||||
__version__ = "2.7.4"
|
||||
__version__ = "2.7.7"
|
||||
__author__ = "QwejayHuang"
|
||||
__company__ = "QwejayHuang"
|
||||
__description__ = "Modern Cross-Platform Python Packaging GUI Powered by PyInstaller & Nuitka"
|
||||
@@ -751,9 +759,15 @@ def load_config(retry=True):
|
||||
|
||||
def save_config(config):
|
||||
try:
|
||||
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
|
||||
temp_file = CONFIG_FILE + ".tmp"
|
||||
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||
config.write(f)
|
||||
except: pass
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
os.replace(temp_file, CONFIG_FILE)
|
||||
else:
|
||||
os.rename(temp_file, CONFIG_FILE)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def create_pleasant_audio_files():
|
||||
success_wav = _CONFIG_DIR / "sound_success.wav"
|
||||
@@ -874,12 +888,7 @@ def extract_project_imports_via_ast(target_path, scan_dir: bool = False) -> set:
|
||||
|
||||
for file_p in files_to_scan:
|
||||
try:
|
||||
raw = file_p.read_bytes()
|
||||
try:
|
||||
code = raw.decode('utf-8-sig')
|
||||
except Exception:
|
||||
code = raw.decode(locale.getpreferredencoding(), errors='ignore')
|
||||
|
||||
code = safe_read_text(file_p)
|
||||
tree = ast.parse(code, filename=file_p.as_posix())
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
@@ -890,7 +899,6 @@ def extract_project_imports_via_ast(target_path, scan_dir: bool = False) -> set:
|
||||
pass
|
||||
return imports
|
||||
|
||||
|
||||
def query_target_env_packages(python_exe: str) -> dict:
|
||||
if not python_exe or not os.path.exists(python_exe):
|
||||
return {}
|
||||
@@ -1347,6 +1355,18 @@ def get_python_executable():
|
||||
|
||||
return find_system_python()
|
||||
|
||||
def safe_read_text(path: Path) -> str:
|
||||
try:
|
||||
raw = path.read_bytes()
|
||||
for enc in ('utf-8-sig', 'utf-8', 'gbk', 'gb18030', 'big5', 'latin-1'):
|
||||
try:
|
||||
return raw.decode(enc)
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
return raw.decode(locale.getpreferredencoding(), errors='ignore')
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def remove_readonly(func, path, exc_info=None):
|
||||
try:
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
@@ -3650,10 +3670,10 @@ class ScriptAnalysisThread(QThread):
|
||||
script_imports = set()
|
||||
|
||||
try:
|
||||
with open(self.path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read(10240)
|
||||
content = safe_read_text(Path(self.path))[:10240]
|
||||
|
||||
v_match = re.search(r'^(?:__version__|VERSION|version)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
|
||||
if v_match: version = v_match.group(1)
|
||||
|
||||
c_match = re.search(r'^(?:__company__|COMPANY)\s*=\s*[\'"]([^\'"]+)[\'"]', content, re.M | re.I)
|
||||
@@ -3714,6 +3734,9 @@ class PackingThread(QThread):
|
||||
|
||||
clean_env = os.environ.copy()
|
||||
clean_env.pop("PYTHONHOME", None)
|
||||
|
||||
for c_var in ("VIRTUAL_ENV", "CONDA_PREFIX", "CONDA_DEFAULT_ENV", "CONDA_PYTHON_EXE", "CONDA_PROMPT_MODIFIER"):
|
||||
clean_env.pop(c_var, None)
|
||||
|
||||
if not (extra_env and "PYTHONPATH" in extra_env):
|
||||
if not ("PYTHONPATH" in clean_env and "_qpypack_temp_" in clean_env["PYTHONPATH"]):
|
||||
@@ -3725,6 +3748,7 @@ class PackingThread(QThread):
|
||||
clean_env["LC_ALL"] = "en_US.UTF-8"
|
||||
clean_env["PYTHONUNBUFFERED"] = "1"
|
||||
clean_env["PLAYWRIGHT_BROWSERS_PATH"] = "0"
|
||||
clean_env["NUITKA_ACCEPT_DOWNLOADS"] = "yes"
|
||||
|
||||
if extra_env:
|
||||
clean_env.update(extra_env)
|
||||
@@ -3833,9 +3857,7 @@ class PackingThread(QThread):
|
||||
|
||||
if not self.params['noconsole']:
|
||||
try:
|
||||
raw = orig_path.read_bytes()
|
||||
try: code = raw.decode('utf-8-sig')
|
||||
except: code = raw.decode(locale.getpreferredencoding(), errors='ignore')
|
||||
code = safe_read_text(orig_path)
|
||||
|
||||
pause_prompt_str = _("\\nProgram execution completed, press Enter to exit...").replace('\\n', '\n')
|
||||
|
||||
@@ -4099,7 +4121,6 @@ class PackingThread(QThread):
|
||||
silent_error=True, timeout=15)
|
||||
|
||||
def run(self):
|
||||
os.environ["NUITKA_ACCEPT_DOWNLOADS"] = "yes"
|
||||
engine = self.params['engine']
|
||||
app_name = self.params.get('app_name', 'app').strip() or 'app'
|
||||
pip_idx = self.params.get('pip_index_url', '').strip()
|
||||
@@ -4111,6 +4132,15 @@ class PackingThread(QThread):
|
||||
|
||||
script_dir = Path(self.params['script_path']).parent if self.params.get('script_path') else Path.cwd()
|
||||
|
||||
sandbox_mode = int(self.params.get('temp_sandbox_mode', 0) or 0)
|
||||
if sandbox_mode == 0:
|
||||
custom_temp_base = (script_dir / ".qpypack_build").resolve()
|
||||
custom_temp_base.mkdir(parents=True, exist_ok=True)
|
||||
temp_dir_arg = custom_temp_base.as_posix()
|
||||
else:
|
||||
custom_temp_base = Path(tempfile.gettempdir()).resolve()
|
||||
temp_dir_arg = None
|
||||
|
||||
try:
|
||||
if self._is_cancelled:
|
||||
return self.build_finished.emit(False, _("[INFO] Build Cancelled."), [])
|
||||
@@ -4229,12 +4259,11 @@ class PackingThread(QThread):
|
||||
if req_file.exists():
|
||||
try:
|
||||
if is_cloud_locked(req_file): raise ValueError("Requirements file is locked")
|
||||
raw_req = req_file.read_bytes()
|
||||
try: req_content = raw_req.decode('utf-8-sig')
|
||||
except: req_content = raw_req.decode(locale.getpreferredencoding(), errors='ignore')
|
||||
req_content = safe_read_text(req_file)
|
||||
|
||||
for line in req_content.splitlines():
|
||||
canon_name, full_spec = parse_req_line(line)
|
||||
|
||||
if canon_name and canon_name.lower() not in target_std_libs:
|
||||
final_dependencies[canon_name] = full_spec
|
||||
reqs_declared_pkgs.add(canon_name)
|
||||
@@ -4290,7 +4319,7 @@ class PackingThread(QThread):
|
||||
if self.params.get('keep_venv'):
|
||||
self.venv_dir = (script_dir / safe_venv_name).resolve()
|
||||
else:
|
||||
self.venv_dir = Path(tempfile.mkdtemp(prefix=f"qpypack_env_{short_stem}_")).resolve()
|
||||
self.venv_dir = Path(tempfile.mkdtemp(prefix=f"qpypack_env_{short_stem}_", dir=temp_dir_arg)).resolve()
|
||||
|
||||
python_exe_in_venv = (self.venv_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")).as_posix()
|
||||
|
||||
@@ -4317,7 +4346,7 @@ class PackingThread(QThread):
|
||||
robust_rmtree(self.venv_dir)
|
||||
except Exception as e:
|
||||
self.progress.emit(_("[WARN] Failed to purge invalid environment, falling back to temp sandbox: {error}", error=str(e)))
|
||||
self.venv_dir = Path(tempfile.mkdtemp(prefix=f"qpypack_env_{short_stem}_")).resolve()
|
||||
self.venv_dir = Path(tempfile.mkdtemp(prefix=f"qpypack_env_{short_stem}_", dir=temp_dir_arg)).resolve()
|
||||
python_exe_in_venv = (self.venv_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")).as_posix()
|
||||
|
||||
self.progress.emit(_("[INFO] Creating virtual environment ({name})...", name=self.venv_dir.name))
|
||||
@@ -4406,9 +4435,10 @@ class PackingThread(QThread):
|
||||
|
||||
dedup_install_list = [pkg for pkg in list(final_install_dict.values()) if re.split(r'[=><!~]', pkg)[0].strip().lower() not in target_std_libs]
|
||||
|
||||
temp_unified_reqs = Path(tempfile.gettempdir()) / f"qpypack_atomic_reqs_{int(time.time())}.txt"
|
||||
temp_unified_reqs = custom_temp_base / f"qpypack_atomic_reqs_{int(time.time())}.txt"
|
||||
temp_unified_reqs.write_text('\n'.join(dedup_install_list), encoding='utf-8')
|
||||
|
||||
|
||||
total_pkgs = len(dedup_install_list)
|
||||
pkg_names_str = ', '.join(sorted(dedup_install_list))
|
||||
|
||||
@@ -4504,8 +4534,8 @@ 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="qpypack_build_")).resolve()
|
||||
self.temp_dist_dir = Path(tempfile.mkdtemp(prefix="qpypack_dist_")).resolve()
|
||||
self.temp_workpath = Path(tempfile.mkdtemp(prefix="qpypack_build_", dir=temp_dir_arg)).resolve()
|
||||
self.temp_dist_dir = Path(tempfile.mkdtemp(prefix="qpypack_dist_", dir=temp_dir_arg)).resolve()
|
||||
cmd = [
|
||||
python_exe, "-m", "PyInstaller", "--clean", "--noconfirm",
|
||||
f"--distpath={self.temp_dist_dir.as_posix()}",
|
||||
@@ -4595,13 +4625,7 @@ class PackingThread(QThread):
|
||||
cmd.extend(["--collect-all", web_fw])
|
||||
|
||||
elif engine == "Nuitka":
|
||||
sandbox_mode = int(self.params.get('temp_sandbox_mode', 0) or 0)
|
||||
if sandbox_mode == 1:
|
||||
self.temp_out_dir = Path(tempfile.mkdtemp(prefix="qpypack_nuitka_")).resolve()
|
||||
else:
|
||||
self.temp_out_dir = (script_dir / ".qpypack_build").resolve()
|
||||
self.temp_out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.temp_out_dir = Path(tempfile.mkdtemp(prefix="qpypack_nuitka_", dir=temp_dir_arg)).resolve()
|
||||
cmd = [
|
||||
python_exe, "-m", "nuitka", "--remove-output", "--assume-yes-for-downloads",
|
||||
f"--output-dir={self.temp_out_dir.as_posix()}",
|
||||
@@ -4880,8 +4904,17 @@ class PackingThread(QThread):
|
||||
except PermissionError:
|
||||
time.sleep(1)
|
||||
final_out.unlink(missing_ok=True)
|
||||
shutil.move(src_out.as_posix(), final_out.as_posix())
|
||||
|
||||
for _retry in range(15):
|
||||
try:
|
||||
shutil.move(src_out.as_posix(), final_out.as_posix())
|
||||
break
|
||||
except PermissionError:
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
raise PermissionError("File locked by Antivirus or other process.")
|
||||
except PermissionError:
|
||||
|
||||
success = False
|
||||
self.progress.emit(_("[ERROR] Target file is running or occupied. Please close the existing application and try again."))
|
||||
except Exception as e:
|
||||
@@ -4972,6 +5005,13 @@ class PackingThread(QThread):
|
||||
if spec_file.exists():
|
||||
try: spec_file.unlink()
|
||||
except: pass
|
||||
|
||||
if sandbox_mode == 0 and custom_temp_base and custom_temp_base.exists():
|
||||
try:
|
||||
if not any(custom_temp_base.iterdir()):
|
||||
custom_temp_base.rmdir()
|
||||
except Exception: pass
|
||||
|
||||
|
||||
class PythonInstallMonitorThread(QThread):
|
||||
finished_signal = Signal(bool)
|
||||
@@ -6260,7 +6300,14 @@ class MainWindow(QMainWindow):
|
||||
except Exception as e:
|
||||
self.show_error_log(_("[ERROR] Failed to export log file: {error}", error=str(e)))
|
||||
|
||||
def global_excepthook(exctype, value, tb):
|
||||
import traceback
|
||||
err_text = "".join(traceback.format_exception(exctype, value, tb))
|
||||
logger.critical(f"Unhandled exception: {err_text}")
|
||||
print(err_text, file=sys.stderr)
|
||||
|
||||
def main():
|
||||
sys.excepthook = global_excepthook
|
||||
try:
|
||||
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
||||
except Exception: pass
|
||||
|
||||
Reference in new issue
Block a user