La causa root del "Failed to resolve Python.Runtime.Loader.Initialize" era il Mark of the Web (Zone.Identifier ADS) che Windows aggiunge ai file estratti da uno zip scaricato dal browser. .NET Framework rifiuta SILENZIOSAMENTE di risolvere i symbol export degli assembly managed con MOTW. Fix multilivello: - main.py: al primo avvio cancella :Zone.Identifier da ogni .dll/.exe/.pyd nel bundle (via ctypes kernel32.DeleteFileW) - CI: Unblock-File su dist/ pre-zip - requirements: pythonnet==3.0.5 + clr-loader==0.2.7.post0 pinnati Vedi: https://github.com/r0x0r/pywebview/issues/1215 https://github.com/pyinstaller/pyinstaller/issues/7412
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""MusicTools — Entry point (pywebview + WebKit/EdgeChromium)."""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Windows: forza il caricamento del runtime pythonnet PRIMA di importare
|
|
# webview. Con PyInstaller il lazy-loader fallisce con
|
|
# "Failed to resolve Python.Runtime.Loader.Initialize" perche'
|
|
# Python.Runtime.dll viene bundlata ma il loader non sa risolverla
|
|
# senza l'inizializzazione esplicita. Fix conferma:
|
|
# https://github.com/pythonnet/pythonnet/issues/2178
|
|
if sys.platform == "win32":
|
|
# ============================================================
|
|
# Fix Windows: rimuovi Mark of the Web (MOTW) dalle DLL bundled.
|
|
# Quando l'utente scarica il .zip dal browser, Windows aggiunge
|
|
# uno stream NTFS Zone.Identifier a TUTTI i file estratti.
|
|
# .NET Framework 4.x rifiuta silenziosamente di risolvere i
|
|
# symbol export degli assembly managed con MOTW -> il loader
|
|
# netfx di pythonnet fallisce con "Failed to resolve
|
|
# Python.Runtime.Loader.Initialize".
|
|
# Soluzione: cancelliamo lo stream :Zone.Identifier da ogni
|
|
# .dll/.exe del bundle al primo avvio (operazione idempotente
|
|
# e sicura: l'utente ha gia eseguito l'exe esplicitamente).
|
|
# Vedi: https://github.com/r0x0r/pywebview/issues/1215
|
|
# ============================================================
|
|
if getattr(sys, "frozen", False):
|
|
try:
|
|
import ctypes
|
|
_kernel32 = ctypes.windll.kernel32
|
|
_bundle = Path(sys._MEIPASS) # type: ignore[attr-defined]
|
|
for _f in _bundle.rglob("*"):
|
|
if _f.suffix.lower() in (".dll", ".exe", ".pyd"):
|
|
_kernel32.DeleteFileW(f"{_f}:Zone.Identifier")
|
|
except Exception as _e:
|
|
print(f"[bootstrap] MOTW cleanup failed: {_e}")
|
|
|
|
try:
|
|
if getattr(sys, "frozen", False):
|
|
os.environ["PYTHONNET_RUNTIME"] = "netfx"
|
|
from pythonnet import load as _pythonnet_load
|
|
_pythonnet_load("netfx")
|
|
import clr # noqa: F401 - inizializza il loader
|
|
except Exception as _e:
|
|
print(f"[bootstrap] preload pythonnet failed: {_e}")
|
|
|
|
import webview
|
|
|
|
from api.bridge import Api
|
|
|
|
|
|
def _resource_path(*parts) -> str:
|
|
"""Trova il path di una risorsa sia in dev che bundled (PyInstaller)."""
|
|
if getattr(sys, "frozen", False):
|
|
base = Path(sys._MEIPASS) # type: ignore[attr-defined]
|
|
else:
|
|
base = Path(__file__).resolve().parent
|
|
return str(base.joinpath(*parts))
|
|
|
|
|
|
def main():
|
|
api = Api()
|
|
index_path = _resource_path("webui", "index.html")
|
|
|
|
window = webview.create_window(
|
|
title="MusicTools",
|
|
url=index_path,
|
|
js_api=api,
|
|
width=1080,
|
|
height=720,
|
|
min_size=(900, 600),
|
|
background_color="#0A0A0A",
|
|
resizable=True,
|
|
)
|
|
api.window = window
|
|
|
|
# Disable native menu / set dark on macOS
|
|
webview.start(debug=False)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|