Fix doubled platformdirs paths; pick newest dotnet runtime
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.
This commit is contained in:
+6
-4
@@ -22,13 +22,15 @@ class Config(msgspec.Struct, omit_defaults=True):
|
|||||||
|
|
||||||
|
|
||||||
def config_dir() -> Path:
|
def config_dir() -> Path:
|
||||||
# Local (non-roaming) on Windows: config is machine-specific state,
|
# appauthor=False: avoid the doubled %LOCALAPPDATA%\mediahive\mediahive.
|
||||||
# not something to sync across a domain profile.
|
# roaming=False: config is machine-specific state, not something to sync
|
||||||
return user_config_path("mediahive", roaming=False)
|
# across a domain profile.
|
||||||
|
return user_config_path("mediahive", appauthor=False, roaming=False)
|
||||||
|
|
||||||
|
|
||||||
def log_dir() -> Path:
|
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:
|
def config_path() -> Path:
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ _icon_mac = _pkg / "assets" / "mediahive.icns"
|
|||||||
# scripts/guibuild.py); fall back to the legacy build/ffmpeg location.
|
# scripts/guibuild.py); fall back to the legacy build/ffmpeg location.
|
||||||
from platformdirs import user_cache_path
|
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():
|
if not _tools_dir.exists():
|
||||||
_tools_dir = Path(SPECPATH).parent / "build" / "ffmpeg"
|
_tools_dir = Path(SPECPATH).parent / "build" / "ffmpeg"
|
||||||
_tool_names = ["ffmpeg.exe"] if sys.platform == "win32" else ["ffmpeg"]
|
_tool_names = ["ffmpeg.exe"] if sys.platform == "win32" else ["ffmpeg"]
|
||||||
|
|||||||
+31
-19
@@ -47,7 +47,7 @@ _ASSETS_DIR = _REPO_ROOT / "mediahive" / "assets"
|
|||||||
|
|
||||||
def _build_cache_dir() -> Path:
|
def _build_cache_dir() -> Path:
|
||||||
"""Return the persistent cross-build cache dir for downloaded tools (CI wipes build/)."""
|
"""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"
|
_FFMPEG_STAGING = _build_cache_dir() / "ffmpeg"
|
||||||
@@ -202,12 +202,16 @@ def ensure_macos_icon() -> Path:
|
|||||||
return icon_icns
|
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:
|
def fetch_vpk() -> Path:
|
||||||
"""Download the Velopack CLI package into the persistent build cache.
|
"""Download the Velopack CLI package into the persistent build cache.
|
||||||
|
|
||||||
Returns the path to vpk.dll, runnable with `dotnet vpk.dll ...`.
|
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():
|
if vpk_dll.exists():
|
||||||
print(f"vpk already staged at {_VPK_STAGING}, skipping download.")
|
print(f"vpk already staged at {_VPK_STAGING}, skipping download.")
|
||||||
return vpk_dll
|
return vpk_dll
|
||||||
@@ -226,35 +230,35 @@ def fetch_vpk() -> Path:
|
|||||||
return vpk_dll
|
return vpk_dll
|
||||||
|
|
||||||
|
|
||||||
def _dotnet_has_runtime(exe: Path) -> bool:
|
def _dotnet_runtime_major(exe: Path) -> int | None:
|
||||||
"""Check that `exe` runs and has Microsoft.NETCore.App >= 8."""
|
"""Return the highest installed Microsoft.NETCore.App major version, or None."""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[str(exe), "--list-runtimes"], capture_output=True, text=True, timeout=30
|
[str(exe), "--list-runtimes"], capture_output=True, text=True, timeout=30
|
||||||
)
|
)
|
||||||
except (OSError, subprocess.TimeoutExpired):
|
except (OSError, subprocess.TimeoutExpired):
|
||||||
return False
|
return None
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
return False
|
return None
|
||||||
|
majors = []
|
||||||
for line in result.stdout.splitlines():
|
for line in result.stdout.splitlines():
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
if len(parts) >= 2 and parts[0] == "Microsoft.NETCore.App":
|
if len(parts) >= 2 and parts[0] == "Microsoft.NETCore.App":
|
||||||
try:
|
try:
|
||||||
if int(parts[1].split(".")[0]) >= 8:
|
majors.append(int(parts[1].split(".")[0]))
|
||||||
return True
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
return False
|
return max(majors, default=None)
|
||||||
|
|
||||||
|
|
||||||
def fetch_dotnet() -> str:
|
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 —
|
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
|
downloading a runtime per build is slow and flaky. Several dotnet
|
||||||
resolve to a runtime-only installation, and long-running services (CI
|
installations may coexist (PATH may resolve to a runtime-only .NET 8
|
||||||
runners) may carry a stale environment without DOTNET_ROOT or scoop
|
while scoop holds the SDK 10), so probe known locations and pick the
|
||||||
paths — so probe known locations explicitly.
|
newest runtime rather than the first that runs.
|
||||||
"""
|
"""
|
||||||
exe_name = "dotnet.exe" if sys.platform == "win32" else "dotnet"
|
exe_name = "dotnet.exe" if sys.platform == "win32" else "dotnet"
|
||||||
candidates: list[Path] = []
|
candidates: list[Path] = []
|
||||||
@@ -281,14 +285,22 @@ def fetch_dotnet() -> str:
|
|||||||
Path.home() / ".dotnet" / exe_name,
|
Path.home() / ".dotnet" / exe_name,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
best: tuple[int, Path] | None = None
|
||||||
for exe in candidates:
|
for exe in candidates:
|
||||||
if exe.exists() and _dotnet_has_runtime(exe):
|
if not exe.exists():
|
||||||
print(f"Using dotnet at {exe}")
|
continue
|
||||||
return str(exe)
|
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(
|
raise RuntimeError(
|
||||||
"No dotnet with Microsoft.NETCore.App >= 8 found. "
|
f"vpk requires Microsoft.NETCore.App >= {_VPK_REQUIRED_DOTNET_MAJOR} ({found}). "
|
||||||
"Install the .NET SDK on this build machine "
|
"Install the current .NET SDK on this build machine "
|
||||||
"(Windows: `scoop install dotnet-sdk`; macOS: `brew install dotnet-sdk`; "
|
"(Windows: `scoop install dotnet-sdk`; macOS: `brew install dotnet-sdk`; "
|
||||||
"Linux: distro `dotnet-sdk` package or the dotnet-install script)."
|
"Linux: distro `dotnet-sdk` package or the dotnet-install script)."
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user