From 3142bc2cb278b31e9ce39b0b3b8a853a3d3db6ec Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 23 Sep 2026 00:49:38 +0000 Subject: [PATCH] Fix doubled platformdirs paths; pick newest dotnet runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit appauthor=False/opinion=False avoids mediahive\mediahive\Cache style paths on Windows. fetch_dotnet now probes all known dotnet locations and selects the highest Microsoft.NETCore.App major, requiring the version vpk's TFM targets (10) — PATH on the Windows CI runner resolves to a runtime-only .NET 8 while scoop holds the SDK 10. --- mediahive/config.py | 10 +++++---- scripts/MediaHive.spec | 4 +++- scripts/guibuild.py | 50 ++++++++++++++++++++++++++---------------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/mediahive/config.py b/mediahive/config.py index e8b4a24..32e27a0 100644 --- a/mediahive/config.py +++ b/mediahive/config.py @@ -22,13 +22,15 @@ class Config(msgspec.Struct, omit_defaults=True): def config_dir() -> Path: - # Local (non-roaming) on Windows: config is machine-specific state, - # not something to sync across a domain profile. - return user_config_path("mediahive", roaming=False) + # appauthor=False: avoid the doubled %LOCALAPPDATA%\mediahive\mediahive. + # roaming=False: config is machine-specific state, not something to sync + # across a domain profile. + return user_config_path("mediahive", appauthor=False, roaming=False) def log_dir() -> Path: - return user_log_path("mediahive", opinion=False) + # opinion=False: no extra Logs/ subdir; mediahive.log sits beside config. + return user_log_path("mediahive", appauthor=False, opinion=False) def config_path() -> Path: diff --git a/scripts/MediaHive.spec b/scripts/MediaHive.spec index 99f1f44..3088eeb 100644 --- a/scripts/MediaHive.spec +++ b/scripts/MediaHive.spec @@ -24,7 +24,9 @@ _icon_mac = _pkg / "assets" / "mediahive.icns" # scripts/guibuild.py); fall back to the legacy build/ffmpeg location. from platformdirs import user_cache_path -_tools_dir = user_cache_path("mediahive-build") / "ffmpeg" +_tools_dir = ( + user_cache_path("mediahive-build", appauthor=False, opinion=False) / "ffmpeg" +) if not _tools_dir.exists(): _tools_dir = Path(SPECPATH).parent / "build" / "ffmpeg" _tool_names = ["ffmpeg.exe"] if sys.platform == "win32" else ["ffmpeg"] diff --git a/scripts/guibuild.py b/scripts/guibuild.py index b1201ee..401344c 100755 --- a/scripts/guibuild.py +++ b/scripts/guibuild.py @@ -47,7 +47,7 @@ _ASSETS_DIR = _REPO_ROOT / "mediahive" / "assets" def _build_cache_dir() -> Path: """Return the persistent cross-build cache dir for downloaded tools (CI wipes build/).""" - return user_cache_path("mediahive-build") + return user_cache_path("mediahive-build", appauthor=False, opinion=False) _FFMPEG_STAGING = _build_cache_dir() / "ffmpeg" @@ -202,12 +202,16 @@ def ensure_macos_icon() -> Path: return icon_icns +_VPK_TFM = "net10.0" +_VPK_REQUIRED_DOTNET_MAJOR = int(re.fullmatch(r"net(\d+)\.0", _VPK_TFM).group(1)) + + def fetch_vpk() -> Path: """Download the Velopack CLI package into the persistent build cache. Returns the path to vpk.dll, runnable with `dotnet vpk.dll ...`. """ - vpk_dll = _VPK_STAGING / "tools" / "net10.0" / "any" / "vpk.dll" + vpk_dll = _VPK_STAGING / "tools" / _VPK_TFM / "any" / "vpk.dll" if vpk_dll.exists(): print(f"vpk already staged at {_VPK_STAGING}, skipping download.") return vpk_dll @@ -226,35 +230,35 @@ def fetch_vpk() -> Path: return vpk_dll -def _dotnet_has_runtime(exe: Path) -> bool: - """Check that `exe` runs and has Microsoft.NETCore.App >= 8.""" +def _dotnet_runtime_major(exe: Path) -> int | None: + """Return the highest installed Microsoft.NETCore.App major version, or None.""" try: result = subprocess.run( [str(exe), "--list-runtimes"], capture_output=True, text=True, timeout=30 ) except (OSError, subprocess.TimeoutExpired): - return False + return None if result.returncode != 0: - return False + return None + majors = [] for line in result.stdout.splitlines(): parts = line.split() if len(parts) >= 2 and parts[0] == "Microsoft.NETCore.App": try: - if int(parts[1].split(".")[0]) >= 8: - return True + majors.append(int(parts[1].split(".")[0])) except ValueError: continue - return False + return max(majors, default=None) def fetch_dotnet() -> str: - """Resolve a dotnet host with a modern (>= 8) runtime from the system. + """Resolve a system dotnet host able to run vpk (needs .NET >= 10). The .NET SDK is a build prerequisite installed on the build machine — - downloading a runtime per build is slow and flaky. A plain `dotnet` may - resolve to a runtime-only installation, and long-running services (CI - runners) may carry a stale environment without DOTNET_ROOT or scoop - paths — so probe known locations explicitly. + downloading a runtime per build is slow and flaky. Several dotnet + installations may coexist (PATH may resolve to a runtime-only .NET 8 + while scoop holds the SDK 10), so probe known locations and pick the + newest runtime rather than the first that runs. """ exe_name = "dotnet.exe" if sys.platform == "win32" else "dotnet" candidates: list[Path] = [] @@ -281,14 +285,22 @@ def fetch_dotnet() -> str: Path.home() / ".dotnet" / exe_name, ] + best: tuple[int, Path] | None = None for exe in candidates: - if exe.exists() and _dotnet_has_runtime(exe): - print(f"Using dotnet at {exe}") - return str(exe) + if not exe.exists(): + continue + major = _dotnet_runtime_major(exe) + if major is not None and (best is None or major > best[0]): + best = (major, exe) + if best is not None and best[0] >= _VPK_REQUIRED_DOTNET_MAJOR: + print(f"Using dotnet at {best[1]} (.NET {best[0]})") + return str(best[1]) + + found = f"newest found is .NET {best[0]} at {best[1]}" if best else "none found" raise RuntimeError( - "No dotnet with Microsoft.NETCore.App >= 8 found. " - "Install the .NET SDK on this build machine " + f"vpk requires Microsoft.NETCore.App >= {_VPK_REQUIRED_DOTNET_MAJOR} ({found}). " + "Install the current .NET SDK on this build machine " "(Windows: `scoop install dotnet-sdk`; macOS: `brew install dotnet-sdk`; " "Linux: distro `dotnet-sdk` package or the dotnet-install script)." )