From b40e8026274af0660d25b997d226b013b52618d0 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 24 Nov 2025 13:43:15 +0000 Subject: [PATCH] enhance build process to handle locked artifacts and ensure directory creation --- build.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/build.py b/build.py index 4f88215..41bc0d0 100644 --- a/build.py +++ b/build.py @@ -4,6 +4,8 @@ from pathlib import Path import PyInstaller.__main__ import typer from rich import print +import time +from datetime import datetime app = typer.Typer() @@ -70,17 +72,62 @@ def build(build: bool = True, copy: bool = True, test: bool = False, dest: str | except Exception as e: print(f"Warning: couldn't locate pythonnet runtime: {e}") + # Ensure dist/work/spec directories exist + dist_dir = project_root / "dist" / "test" + work_dir = project_root / "build" / "test" + spec_dir = project_root / "build" / "specs" + dist_dir.mkdir(parents=True, exist_ok=True) + work_dir.mkdir(parents=True, exist_ok=True) + spec_dir.mkdir(parents=True, exist_ok=True) + + # Decide build name and try to remove any locked previous artifact. + base_name = "Uploader_test" + output_exe = dist_dir / f"{base_name}.exe" + + def try_remove_with_retries(path: Path, retries: int = 5, delay: float = 0.5) -> bool: + """Try to remove a file with retries and backoff. Returns True if removed or not present.""" + if not path.exists(): + return True + for attempt in range(retries): + try: + path.unlink() + print(f"Removed existing file: {path}") + return True + except PermissionError: + # Try to move/rename it out of the way + try: + backup = path.with_suffix(f".old.{int(time.time())}") + path.rename(backup) + print(f"Renamed locked file to: {backup}") + return True + except Exception: + pass + print(f"File locked, retrying in {delay} seconds ({attempt+1}/{retries})") + time.sleep(delay) + delay *= 1.5 + except Exception as e: + print(f"Failed to remove existing file {path}: {e}") + return False + return False + + name_used = base_name + if not try_remove_with_retries(output_exe): + # If the previous artifact is locked, fall back to a unique name to avoid build failure + ts = datetime.now().strftime("%Y%m%dT%H%M%S") + name_used = f"{base_name}_{ts}" + print(f"Previous artifact appears locked; building as {name_used} to avoid PermissionError") + PyInstaller.__main__.run([ str(project_root / "nice.py"), "--onefile", "--name", - "Uploader_test", + name_used, "--distpath", - str(project_root / "dist" / "test"), + str(dist_dir), "--workpath", - str(project_root / "build" / "test"), + str(work_dir), "--specpath", - str(project_root / "build" / "specs"), + str(spec_dir), ] + add_data_args + add_binary_args + [ # add some common hidden imports that PyInstaller sometimes misses "--hidden-import",