Installers for all platforms and related fixes #1
+6
-4
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
|
||||
+31
-19
@@ -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)."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user