@@ -81,6 +81,16 @@ def _find_inno_compiler() -> str | None:
8181 return None
8282
8383
84+ def _require_path (path : Path , * , directory : bool = False ) -> Path :
85+ """Return an absolute path and fail before invoking a native packager."""
86+ path = path .resolve ()
87+ valid = path .is_dir () if directory else path .is_file ()
88+ if not valid :
89+ kind = "directory" if directory else "file"
90+ raise RuntimeError (f"Required { kind } does not exist: { path } " )
91+ return path
92+
93+
8494def build (* , clean : bool , runtime_dir : Path | None = None , gpu : bool = False ) -> Path :
8595 target = _platform_name ()
8696 architecture = _architecture ()
@@ -122,19 +132,18 @@ def build(*, clean: bool, runtime_dir: Path | None = None, gpu: bool = False) ->
122132 ]
123133 subprocess .run (command , cwd = ROOT , check = True )
124134
125- bundle = dist_dir / APP_NAME
135+ bundle = _require_path ( dist_dir / APP_NAME , directory = True )
126136 if runtime_dir is None :
127137 runtime_dir = PACKAGING_ROOT / "build" / f"runtime-{ tag } "
128138 builder = PACKAGING_ROOT / "runtime_builder.py"
129139 command = [sys .executable , str (builder ), str (runtime_dir )]
130140 if gpu :
131141 command .append ("--gpu" )
132142 subprocess .run (command , cwd = ROOT , check = True )
133- if not runtime_dir .is_dir ():
134- raise RuntimeError (f"Runtime directory does not exist: { runtime_dir } " )
143+ runtime_dir = _require_path (runtime_dir , directory = True )
135144 shutil .copytree (runtime_dir , bundle / "runtime" , dirs_exist_ok = True )
136- shutil .copy2 (ROOT / "run_app.py" , bundle / "run_app.py" )
137- shutil .copy2 (PACKAGING_ROOT / "runtime_setup.py" , bundle / "runtime_setup.py" )
145+ shutil .copy2 (_require_path ( ROOT / "run_app.py" ) , bundle / "run_app.py" )
146+ shutil .copy2 (_require_path ( PACKAGING_ROOT / "runtime_setup.py" ) , bundle / "runtime_setup.py" )
138147 for package_name in ("engine" , "interface" ):
139148 shutil .copytree (
140149 ROOT / package_name ,
@@ -143,14 +152,22 @@ def build(*, clean: bool, runtime_dir: Path | None = None, gpu: bool = False) ->
143152 dirs_exist_ok = True ,
144153 )
145154 if target == "windows" :
146- installer = OUTPUT_ROOT / f"{ APP_NAME } -{ architecture } -Setup.exe"
155+ # Keep compiler output outside the PyInstaller source tree. This avoids
156+ # Inno treating a partially-created output as another wildcard source.
157+ installer_output_dir = OUTPUT_ROOT / "installers"
158+ installer_output_dir .mkdir (parents = True , exist_ok = True )
159+ installer = installer_output_dir / f"{ APP_NAME } -{ architecture } -Setup.exe"
147160 iscc = _find_inno_compiler ()
148161 if not iscc :
149162 raise RuntimeError (
150163 "Inno Setup (ISCC.exe) is required for Windows installers. "
151164 "Install Inno Setup and rerun the packager."
152165 )
153- script = work_dir / "installer.iss"
166+ script = _require_path (work_dir , directory = True ) / "installer.iss"
167+ bundle = _require_path (bundle , directory = True )
168+ if icon_path is not None :
169+ icon_path = _require_path (icon_path )
170+ _require_path (installer_output_dir , directory = True )
154171 script .write_text (
155172 "\n " .join (
156173 [
@@ -160,7 +177,7 @@ def build(*, clean: bool, runtime_dir: Path | None = None, gpu: bool = False) ->
160177 "AppPublisher=DrunkenBot" ,
161178 f'OutputBaseFilename={ APP_NAME } -{ architecture } -Setup' ,
162179 f'DefaultDirName={{autopf}}\\ { APP_NAME } ' ,
163- f'OutputDir={ OUTPUT_ROOT } ' ,
180+ f'OutputDir={ installer_output_dir } ' ,
164181 "Uninstallable=yes" ,
165182 "Compression=lzma2" ,
166183 "SolidCompression=yes" ,
0 commit comments