updated
This commit is contained in:
155
build_exe.py
155
build_exe.py
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
PyInstaller build script for Label Printer GUI
|
||||
Run this to create a standalone Windows executable
|
||||
Run this to create a standalone Windows executable.
|
||||
|
||||
IMPORTANT: This script MUST be run on Windows to generate a Windows .exe file.
|
||||
If run on Linux/macOS, it will create a Linux/macOS binary that won't work on Windows.
|
||||
@@ -10,73 +10,133 @@ To build for Windows:
|
||||
2. Install dependencies: pip install -r requirements_windows.txt
|
||||
3. Run this script: python build_exe.py
|
||||
4. The Windows .exe will be created in the dist/ folder
|
||||
|
||||
GhostScript bundling
|
||||
--------------------
|
||||
If GhostScript is installed on this build machine the script will
|
||||
automatically copy the required files into conf\\ghostscript\\ before
|
||||
calling PyInstaller so they are embedded in LabelPrinter.exe.
|
||||
The target machine then needs NO separate GhostScript install.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import shutil
|
||||
|
||||
# Get the current directory
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# PyInstaller arguments
|
||||
args = [
|
||||
'label_printer_gui.py',
|
||||
'--onefile', # Create a single executable
|
||||
'--windowed', # Don't show console window
|
||||
'--name=LabelPrinter', # Executable name
|
||||
'--distpath=./dist', # Output directory
|
||||
'--workpath=./build', # Work directory (was --buildpath)
|
||||
'--hidden-import=kivy',
|
||||
'--hidden-import=kivy.core.window',
|
||||
'--hidden-import=kivy.core.text',
|
||||
'--hidden-import=kivy.core.image',
|
||||
'--hidden-import=kivy.uix.boxlayout',
|
||||
'--hidden-import=kivy.uix.gridlayout',
|
||||
'--hidden-import=kivy.uix.label',
|
||||
'--hidden-import=kivy.uix.textinput',
|
||||
'--hidden-import=kivy.uix.button',
|
||||
'--hidden-import=kivy.uix.spinner',
|
||||
'--hidden-import=kivy.uix.scrollview',
|
||||
'--hidden-import=kivy.uix.popup',
|
||||
'--hidden-import=kivy.clock',
|
||||
'--hidden-import=kivy.graphics',
|
||||
'--hidden-import=PIL',
|
||||
'--hidden-import=barcode',
|
||||
'--hidden-import=reportlab',
|
||||
'--hidden-import=print_label',
|
||||
'--hidden-import=print_label_pdf',
|
||||
'--hidden-import=svglib',
|
||||
'--hidden-import=cairosvg',
|
||||
'--hidden-import=watchdog',
|
||||
'--hidden-import=watchdog.observers',
|
||||
'--hidden-import=watchdog.events',
|
||||
'--hidden-import=pystray',
|
||||
'--hidden-import=win32timezone',
|
||||
]
|
||||
|
||||
def prepare_ghostscript():
|
||||
"""
|
||||
Find the system GhostScript installation and copy the minimum files
|
||||
needed for bundling into conf\\ghostscript\\.
|
||||
|
||||
Files copied:
|
||||
conf\\ghostscript\\bin\\gswin64c.exe (or 32-bit variant)
|
||||
conf\\ghostscript\\bin\\gsdll64.dll
|
||||
conf\\ghostscript\\lib\\*.ps (PostScript init files)
|
||||
|
||||
Returns True if GhostScript was found and prepared, False otherwise.
|
||||
"""
|
||||
import glob
|
||||
|
||||
gs_exe = None
|
||||
gs_dll = None
|
||||
gs_lib = None
|
||||
|
||||
for pf in [r"C:\Program Files", r"C:\Program Files (x86)"]:
|
||||
gs_base = os.path.join(pf, "gs")
|
||||
if not os.path.isdir(gs_base):
|
||||
continue
|
||||
# Iterate versions newest-first
|
||||
for ver_dir in sorted(os.listdir(gs_base), reverse=True):
|
||||
bin_dir = os.path.join(gs_base, ver_dir, "bin")
|
||||
lib_dir = os.path.join(gs_base, ver_dir, "lib")
|
||||
if os.path.exists(os.path.join(bin_dir, "gswin64c.exe")):
|
||||
gs_exe = os.path.join(bin_dir, "gswin64c.exe")
|
||||
gs_dll = os.path.join(bin_dir, "gsdll64.dll")
|
||||
gs_lib = lib_dir
|
||||
break
|
||||
if os.path.exists(os.path.join(bin_dir, "gswin32c.exe")):
|
||||
gs_exe = os.path.join(bin_dir, "gswin32c.exe")
|
||||
gs_dll = os.path.join(bin_dir, "gsdll32.dll")
|
||||
gs_lib = lib_dir
|
||||
break
|
||||
if gs_exe:
|
||||
break
|
||||
|
||||
if not gs_exe:
|
||||
print(" WARNING: GhostScript not found on this machine.")
|
||||
print(" The exe will still build but GhostScript will NOT be bundled.")
|
||||
print(" Install GhostScript from https://ghostscript.com/releases/gsdnld.html")
|
||||
print(" then rebuild for sharp vector-quality printing.")
|
||||
return False
|
||||
|
||||
dest_bin = os.path.join("conf", "ghostscript", "bin")
|
||||
dest_lib = os.path.join("conf", "ghostscript", "lib")
|
||||
os.makedirs(dest_bin, exist_ok=True)
|
||||
os.makedirs(dest_lib, exist_ok=True)
|
||||
|
||||
print(f" GhostScript found: {os.path.dirname(gs_exe)}")
|
||||
|
||||
shutil.copy2(gs_exe, dest_bin)
|
||||
print(f" Copied: {os.path.basename(gs_exe)}")
|
||||
|
||||
if os.path.exists(gs_dll):
|
||||
shutil.copy2(gs_dll, dest_bin)
|
||||
print(f" Copied: {os.path.basename(gs_dll)}")
|
||||
|
||||
count = 0
|
||||
for ps_file in glob.glob(os.path.join(gs_lib, "*.ps")):
|
||||
shutil.copy2(ps_file, dest_lib)
|
||||
count += 1
|
||||
print(f" Copied {count} .ps init files from lib/")
|
||||
|
||||
print(f" GhostScript prepared in conf\\ghostscript\\")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("=" * 60)
|
||||
print("Label Printer GUI - PyInstaller Build")
|
||||
print("=" * 60)
|
||||
print("\nBuilding standalone executable...")
|
||||
print("This may take a few minutes...\n")
|
||||
|
||||
# Change to script directory
|
||||
|
||||
# Change to script directory so relative paths work
|
||||
os.chdir(script_dir)
|
||||
|
||||
# Run PyInstaller directly with subprocess for better error reporting
|
||||
|
||||
# Step 1: Prepare GhostScript for bundling (Windows only)
|
||||
if sys.platform == "win32":
|
||||
print("\n[1/2] Preparing GhostScript for bundling...")
|
||||
prepare_ghostscript()
|
||||
else:
|
||||
print("\n[1/2] Skipping GhostScript prep (non-Windows build machine)")
|
||||
|
||||
# Step 2: Build with PyInstaller using the handcrafted spec file.
|
||||
# The spec's Python code auto-detects conf\\ghostscript\\ and includes it.
|
||||
print("\n[2/2] Building standalone executable via LabelPrinter.spec...")
|
||||
print("This may take a few minutes...\n")
|
||||
|
||||
try:
|
||||
result = subprocess.run(['pyinstaller'] + args, check=True)
|
||||
|
||||
result = subprocess.run(
|
||||
['pyinstaller', 'LabelPrinter.spec',
|
||||
'--distpath=./dist', '--workpath=./build', '-y'],
|
||||
check=True,
|
||||
)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Build Complete!")
|
||||
print("=" * 60)
|
||||
print("\nExecutable location: ./dist/LabelPrinter.exe")
|
||||
print("\nBundled components:")
|
||||
print(" - GhostScript (vector-quality printing)")
|
||||
print(" - SumatraPDF (fallback printing)")
|
||||
print(" - SVG templates, conf files")
|
||||
print("\nYou can now:")
|
||||
print("1. Double-click LabelPrinter.exe to run")
|
||||
print("2. Share the exe with others")
|
||||
print("3. Create a shortcut on desktop")
|
||||
print(" 1. Double-click LabelPrinter.exe to run")
|
||||
print(" 2. Copy the dist\\ folder to target machines")
|
||||
print(" 3. No extra software installation required on target machines")
|
||||
print("\nNote: First run may take a moment as Kivy initializes")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("\n" + "=" * 60)
|
||||
@@ -88,3 +148,4 @@ if __name__ == '__main__':
|
||||
except Exception as e:
|
||||
print(f"\nFatal error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user