Building wolf
This commit is contained in:
parent
6fe5ab1963
commit
b407aef557
6 changed files with 486 additions and 23 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -29,6 +29,7 @@ __pycache__
|
|||
util/forge/.forge_version.json
|
||||
util/ace/*.exe
|
||||
util/ace/.tools_version.json
|
||||
util/wolfdawn/.wolf_version.json
|
||||
!util/ace/offline/*.exe
|
||||
|
||||
# WolfDawn: keep the committed prebuilt binaries (offline) under version control.
|
||||
|
|
|
|||
51
gui/main.py
51
gui/main.py
|
|
@ -63,8 +63,8 @@ def check_tool_update() -> str | None:
|
|||
|
||||
|
||||
def check_all_updates() -> dict:
|
||||
"""Check tool, Ace, and Forge for available updates."""
|
||||
results = {"tool": None, "ace": False, "forge": False}
|
||||
"""Check tool, Ace, Forge, and WolfDawn for available updates."""
|
||||
results = {"tool": None, "ace": False, "forge": False, "wolf": False}
|
||||
results["tool"] = check_tool_update()
|
||||
try:
|
||||
from util.ace.update_tools import check_ace_tools_update
|
||||
|
|
@ -76,15 +76,25 @@ def check_all_updates() -> dict:
|
|||
results["forge"] = check_forge_plugins_update()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from util.wolfdawn.update_tools import check_wolfdawn_update
|
||||
results["wolf"] = check_wolfdawn_update()
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
|
||||
|
||||
def any_updates_available(results: dict) -> bool:
|
||||
return bool(results.get("tool")) or results.get("ace") or results.get("forge")
|
||||
return (
|
||||
bool(results.get("tool"))
|
||||
or results.get("ace")
|
||||
or results.get("forge")
|
||||
or results.get("wolf")
|
||||
)
|
||||
|
||||
|
||||
class BackgroundUpdateCheckThread(QThread):
|
||||
"""Checks for tool, Ace, and Forge updates without blocking the UI."""
|
||||
"""Checks for tool, Ace, Forge, and WolfDawn updates without blocking the UI."""
|
||||
|
||||
finished = pyqtSignal(dict)
|
||||
|
||||
|
|
@ -93,15 +103,22 @@ class BackgroundUpdateCheckThread(QThread):
|
|||
|
||||
|
||||
class AuxToolsUpdateThread(QThread):
|
||||
"""Download Ace and/or Forge updates without updating the main tool."""
|
||||
"""Download Ace, Forge, and/or WolfDawn updates without updating the main tool."""
|
||||
|
||||
progress = pyqtSignal(str)
|
||||
finished = pyqtSignal(bool, str)
|
||||
|
||||
def __init__(self, update_ace: bool = False, update_forge: bool = False, parent=None):
|
||||
def __init__(
|
||||
self,
|
||||
update_ace: bool = False,
|
||||
update_forge: bool = False,
|
||||
update_wolf: bool = False,
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.update_ace = update_ace
|
||||
self.update_forge = update_forge
|
||||
self.update_wolf = update_wolf
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
|
|
@ -117,6 +134,12 @@ class AuxToolsUpdateThread(QThread):
|
|||
if not ensure_forge_plugins(force=True, log_fn=lambda m: self.progress.emit(m)):
|
||||
self.finished.emit(False, "Forge plugin update failed.")
|
||||
return
|
||||
if self.update_wolf:
|
||||
from util.wolfdawn.update_tools import ensure_wolfdawn_binary
|
||||
self.progress.emit("Updating WolfDawn…")
|
||||
if not ensure_wolfdawn_binary(force=True, log_fn=lambda m: self.progress.emit(m)):
|
||||
self.finished.emit(False, "WolfDawn update failed.")
|
||||
return
|
||||
self.finished.emit(True, "components_updated")
|
||||
except Exception as exc:
|
||||
self.finished.emit(False, str(exc))
|
||||
|
|
@ -235,6 +258,12 @@ class UpdateThread(QThread):
|
|||
ensure_forge_plugins(log_fn=lambda m: self.progress.emit(m))
|
||||
except Exception as exc:
|
||||
self.progress.emit(f"Forge update skipped: {exc}")
|
||||
try:
|
||||
from util.wolfdawn.update_tools import ensure_wolfdawn_binary
|
||||
self.progress.emit("Updating WolfDawn…")
|
||||
ensure_wolfdawn_binary(log_fn=lambda m: self.progress.emit(m))
|
||||
except Exception as exc:
|
||||
self.progress.emit(f"WolfDawn update skipped: {exc}")
|
||||
self.finished.emit(True, f"updated:{latest_sha[:8]}")
|
||||
|
||||
|
||||
|
|
@ -282,6 +311,8 @@ class UpdateDialog(QDialog):
|
|||
labels.append("RPG Maker Ace tools")
|
||||
if self._pending.get("forge"):
|
||||
labels.append("Forge plugins")
|
||||
if self._pending.get("wolf"):
|
||||
labels.append("WolfDawn")
|
||||
return labels
|
||||
|
||||
def _show_pending_updates(self):
|
||||
|
|
@ -324,7 +355,7 @@ class UpdateDialog(QDialog):
|
|||
if message == "already_up_to_date":
|
||||
self.status_label.setText("✅ Already up to date.")
|
||||
elif message == "components_updated":
|
||||
self._pending = {"tool": None, "ace": False, "forge": False}
|
||||
self._pending = {"tool": None, "ace": False, "forge": False, "wolf": False}
|
||||
self.status_label.setText("✅ Component updates installed.")
|
||||
if isinstance(self.parent(), DazedMTLGUI):
|
||||
self.parent().clear_update_indicator()
|
||||
|
|
@ -339,7 +370,7 @@ class UpdateDialog(QDialog):
|
|||
self.layout().insertWidget(self.layout().count() - 1, update_btn)
|
||||
elif message.startswith("updated:"):
|
||||
sha = message.split(":", 1)[1]
|
||||
self._pending = {"tool": None, "ace": False, "forge": False}
|
||||
self._pending = {"tool": None, "ace": False, "forge": False, "wolf": False}
|
||||
self.status_label.setText(
|
||||
f"✅ Updated to {sha}.\n\nPlease restart the tool for changes to take effect."
|
||||
)
|
||||
|
|
@ -371,11 +402,13 @@ class UpdateDialog(QDialog):
|
|||
|
||||
update_ace = bool(self._pending.get("ace"))
|
||||
update_forge = bool(self._pending.get("forge"))
|
||||
if update_ace or update_forge:
|
||||
update_wolf = bool(self._pending.get("wolf"))
|
||||
if update_ace or update_forge or update_wolf:
|
||||
self.status_label.setText("Downloading component updates…")
|
||||
self._thread = AuxToolsUpdateThread(
|
||||
update_ace=update_ace,
|
||||
update_forge=update_forge,
|
||||
update_wolf=update_wolf,
|
||||
parent=self,
|
||||
)
|
||||
self._thread.progress.connect(self.status_label.setText)
|
||||
|
|
|
|||
|
|
@ -124,6 +124,26 @@ def wolf_binary_path() -> Path:
|
|||
return bundled_binary_path()
|
||||
|
||||
|
||||
def _latest_release_asset(platform: str) -> Optional[tuple[str, str]]:
|
||||
"""Return ``(tag_name, download_url)`` for the newest release with a platform zip."""
|
||||
match = _RELEASE_ASSET_MATCH.get(platform)
|
||||
if not match:
|
||||
return None
|
||||
req = urllib.request.Request(_RELEASES_API, headers={"User-Agent": _DOWNLOAD_UA})
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
releases = json.loads(resp.read().decode("utf-8"))
|
||||
link_re = re.compile(r"\[([^\]]+)\]\((/uploads/[0-9a-f]+/[^)]+)\)")
|
||||
for release in releases:
|
||||
tag = release.get("tag_name") or ""
|
||||
description = release.get("description") or ""
|
||||
for filename, upload_path in link_re.findall(description):
|
||||
name = filename.lower()
|
||||
if match in name and name.endswith(".zip"):
|
||||
url = f"{_GITGUD_HOST}/-/project/{_WOLFDAWN_PROJECT_ID}{upload_path}"
|
||||
return tag, url
|
||||
return None
|
||||
|
||||
|
||||
def _find_release_upload_url(platform: str) -> Optional[str]:
|
||||
"""Look up the newest release and return the download URL of ``platform``'s zip.
|
||||
|
||||
|
|
@ -133,20 +153,8 @@ def _find_release_upload_url(platform: str) -> Optional[str]:
|
|||
numeric-project uploads URL (which bypasses the Cloudflare challenge) or
|
||||
``None`` when the platform isn't published.
|
||||
"""
|
||||
match = _RELEASE_ASSET_MATCH.get(platform)
|
||||
if not match:
|
||||
return None
|
||||
req = urllib.request.Request(_RELEASES_API, headers={"User-Agent": _DOWNLOAD_UA})
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
releases = json.loads(resp.read().decode("utf-8"))
|
||||
link_re = re.compile(r"\[([^\]]+)\]\((/uploads/[0-9a-f]+/[^)]+)\)")
|
||||
for release in releases:
|
||||
description = release.get("description") or ""
|
||||
for filename, upload_path in link_re.findall(description):
|
||||
name = filename.lower()
|
||||
if match in name and name.endswith(".zip"):
|
||||
return f"{_GITGUD_HOST}/-/project/{_WOLFDAWN_PROJECT_ID}{upload_path}"
|
||||
return None
|
||||
asset = _latest_release_asset(platform)
|
||||
return asset[1] if asset else None
|
||||
|
||||
|
||||
def _extract_wolf_from_zip(zip_bytes: bytes, dest: Path, log_fn=None) -> Path:
|
||||
|
|
|
|||
Binary file not shown.
215
util/wolfdawn/build_tools.py
Normal file
215
util/wolfdawn/build_tools.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
"""Build WolfDawn ``wolf`` binaries from upstream source (temporary shallow clone)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from util.wolfdawn import (
|
||||
WolfDawnError,
|
||||
_DOWNLOAD_UA,
|
||||
_GITGUD_HOST,
|
||||
bundled_binary_path,
|
||||
)
|
||||
|
||||
WOLFDAWN_PROJECT = "zero64801/wolfdawn"
|
||||
WOLFDAWN_BRANCH = "master"
|
||||
GITGUD_API = "https://gitgud.io/api/v4"
|
||||
CLONE_URL = f"{_GITGUD_HOST}/{WOLFDAWN_PROJECT}.git"
|
||||
|
||||
_BUILD_TARGETS: dict[str, dict[str, str | None]] = {
|
||||
"linux": {"triple": None, "exe": "wolf"},
|
||||
"windows": {"triple": "x86_64-pc-windows-gnu", "exe": "wolf.exe"},
|
||||
}
|
||||
|
||||
|
||||
def _platform_dir() -> str:
|
||||
import sys
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
return "windows"
|
||||
if sys.platform == "darwin":
|
||||
return "macos"
|
||||
return "linux"
|
||||
|
||||
|
||||
def _log(msg: str, log_fn) -> None:
|
||||
if log_fn:
|
||||
log_fn(msg)
|
||||
else:
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def _project_id() -> str:
|
||||
return urllib.parse.quote(WOLFDAWN_PROJECT, safe="")
|
||||
|
||||
|
||||
def upstream_commit() -> str:
|
||||
url = f"{GITGUD_API}/projects/{_project_id()}/repository/commits/{WOLFDAWN_BRANCH}"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": _DOWNLOAD_UA})
|
||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
commit = data.get("id") or data.get("sha") or ""
|
||||
if not commit:
|
||||
raise RuntimeError(f"Could not resolve upstream commit for {WOLFDAWN_PROJECT}")
|
||||
return commit
|
||||
|
||||
|
||||
def _cargo_available() -> bool:
|
||||
return shutil.which("cargo") is not None
|
||||
|
||||
|
||||
def _git_available() -> bool:
|
||||
return shutil.which("git") is not None
|
||||
|
||||
|
||||
def _mingw_linker_available() -> bool:
|
||||
return bool(shutil.which("x86_64-w64-mingw32-gcc"))
|
||||
|
||||
|
||||
def buildable_from_source(platform: str) -> bool:
|
||||
"""Return True when ``platform`` can plausibly be built on this machine."""
|
||||
if not _cargo_available():
|
||||
return False
|
||||
host = _platform_dir()
|
||||
if platform == host:
|
||||
return True
|
||||
if platform == "windows" and host == "linux":
|
||||
return _mingw_linker_available()
|
||||
return False
|
||||
|
||||
|
||||
def _run(cmd: list[str], *, cwd: Path | None = None, env: dict[str, str] | None = None, log_fn=None) -> bool:
|
||||
_log(f"$ {' '.join(cmd)}", log_fn)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
cwd=str(cwd) if cwd else None,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
except OSError as exc:
|
||||
_log(f"ERROR: {exc}", log_fn)
|
||||
return False
|
||||
if proc.stdout.strip():
|
||||
for line in proc.stdout.strip().splitlines()[-8:]:
|
||||
_log(line, log_fn)
|
||||
if proc.returncode != 0:
|
||||
err = (proc.stderr or proc.stdout or "").strip()
|
||||
if err:
|
||||
for line in err.splitlines()[-8:]:
|
||||
_log(line, log_fn)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _ensure_rust_target(triple: str | None, log_fn) -> bool:
|
||||
if not triple:
|
||||
return True
|
||||
return _run(["rustup", "target", "add", triple], log_fn=log_fn)
|
||||
|
||||
|
||||
def clone_source(dest: Path, log_fn=print) -> bool:
|
||||
"""Shallow-clone WolfDawn ``master`` into ``dest``."""
|
||||
if not _git_available():
|
||||
_log("ERROR: git not found; cannot clone WolfDawn source.", log_fn)
|
||||
return False
|
||||
if dest.exists():
|
||||
shutil.rmtree(dest)
|
||||
return _run(
|
||||
["git", "clone", "--depth", "1", "--branch", WOLFDAWN_BRANCH, CLONE_URL, str(dest)],
|
||||
log_fn=log_fn,
|
||||
)
|
||||
|
||||
|
||||
def _artifact_path(source_dir: Path, platform: str) -> Path:
|
||||
spec = _BUILD_TARGETS[platform]
|
||||
triple = spec["triple"]
|
||||
exe = spec["exe"]
|
||||
if triple:
|
||||
return source_dir / "target" / triple / "release" / exe
|
||||
return source_dir / "target" / "release" / exe
|
||||
|
||||
|
||||
def build_platform(source_dir: Path, platform: str, log_fn=print) -> Path | None:
|
||||
"""Compile ``wolf`` for ``platform`` inside an existing source checkout."""
|
||||
if platform not in _BUILD_TARGETS:
|
||||
_log(f"ERROR: unsupported WolfDawn build platform '{platform}'.", log_fn)
|
||||
return None
|
||||
if not buildable_from_source(platform):
|
||||
_log(f"Skipping source build for '{platform}' on this machine.", log_fn)
|
||||
return None
|
||||
|
||||
spec = _BUILD_TARGETS[platform]
|
||||
triple = spec["triple"]
|
||||
if not _ensure_rust_target(triple, log_fn):
|
||||
return None
|
||||
|
||||
cmd = ["cargo", "build", "--release", "-p", "wolf-cli"]
|
||||
if triple:
|
||||
cmd += ["--target", triple]
|
||||
target_dir = source_dir / "target"
|
||||
env = os.environ.copy()
|
||||
env["CARGO_TARGET_DIR"] = str(target_dir)
|
||||
if not _run(cmd, cwd=source_dir, env=env, log_fn=log_fn):
|
||||
return None
|
||||
|
||||
built = _artifact_path(source_dir, platform)
|
||||
if not built.is_file():
|
||||
_log(f"ERROR: expected build output missing: {built}", log_fn)
|
||||
return None
|
||||
return built
|
||||
|
||||
|
||||
def install_built_binary(artifact: Path, platform: str, log_fn=print) -> Path:
|
||||
"""Copy a built ``wolf`` executable into the bundled offline path."""
|
||||
dest = bundled_binary_path(platform)
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(artifact, dest)
|
||||
try:
|
||||
dest.chmod(0o755)
|
||||
except OSError:
|
||||
pass
|
||||
_log(f"Installed source-built WolfDawn binary at {dest}", log_fn)
|
||||
return dest
|
||||
|
||||
|
||||
def build_and_install_platform(source_dir: Path, platform: str, log_fn=print) -> bool:
|
||||
artifact = build_platform(source_dir, platform, log_fn=log_fn)
|
||||
if artifact is None:
|
||||
return False
|
||||
install_built_binary(artifact, platform, log_fn=log_fn)
|
||||
return True
|
||||
|
||||
|
||||
def build_and_install_platforms(
|
||||
platforms: tuple[str, ...] = ("linux", "windows"),
|
||||
log_fn=print,
|
||||
) -> dict[str, bool]:
|
||||
"""Clone upstream source to a temp dir, build each platform, install successes."""
|
||||
results = {platform: False for platform in platforms}
|
||||
buildable = [p for p in platforms if buildable_from_source(p)]
|
||||
if not buildable:
|
||||
_log("No WolfDawn platforms can be built from source on this machine.", log_fn)
|
||||
return results
|
||||
if not _cargo_available():
|
||||
_log("cargo not found; skipping WolfDawn source builds.", log_fn)
|
||||
return results
|
||||
|
||||
_log(f"Cloning WolfDawn ({WOLFDAWN_BRANCH}) for source build…", log_fn)
|
||||
with tempfile.TemporaryDirectory(prefix="wolfdawn-src-") as tmp:
|
||||
source_dir = Path(tmp) / "wolfdawn"
|
||||
if not clone_source(source_dir, log_fn=log_fn):
|
||||
return results
|
||||
for platform in buildable:
|
||||
results[platform] = build_and_install_platform(source_dir, platform, log_fn=log_fn)
|
||||
return results
|
||||
206
util/wolfdawn/update_tools.py
Normal file
206
util/wolfdawn/update_tools.py
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
"""Check for and apply WolfDawn ``wolf`` binary updates.
|
||||
|
||||
Resolution order for each platform:
|
||||
1. Temporary shallow clone + ``cargo`` source build (when possible on this machine)
|
||||
2. Prebuilt zip from the latest GitLab release
|
||||
3. Keep the existing offline bundled binary
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
from util.wolfdawn import (
|
||||
WolfDawnError,
|
||||
_latest_release_asset,
|
||||
_platform_dir,
|
||||
bundled_binary_path,
|
||||
download_wolf_binary,
|
||||
)
|
||||
from util.wolfdawn.build_tools import (
|
||||
build_and_install_platforms,
|
||||
buildable_from_source,
|
||||
upstream_commit,
|
||||
)
|
||||
|
||||
_PKG_ROOT = Path(__file__).resolve().parent
|
||||
VERSION_FILE = _PKG_ROOT / ".wolf_version.json"
|
||||
BUNDLED_PLATFORMS: tuple[str, ...] = ("linux", "windows")
|
||||
|
||||
|
||||
def _load_versions() -> dict:
|
||||
if not VERSION_FILE.is_file():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(VERSION_FILE.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _save_versions(data: dict) -> None:
|
||||
VERSION_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def _log(msg: str, log_fn) -> None:
|
||||
if log_fn:
|
||||
log_fn(msg)
|
||||
else:
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def _platform_versions() -> dict[str, str]:
|
||||
versions = _load_versions()
|
||||
platforms = versions.get("platforms")
|
||||
if isinstance(platforms, dict):
|
||||
return {str(k): str(v) for k, v in platforms.items() if v}
|
||||
tag = versions.get("tag", "")
|
||||
if tag:
|
||||
return {p: str(tag) for p in BUNDLED_PLATFORMS}
|
||||
commit = versions.get("commit", "")
|
||||
if commit:
|
||||
return {p: str(commit) for p in BUNDLED_PLATFORMS}
|
||||
return {}
|
||||
|
||||
|
||||
def _local_version(platform: str) -> str:
|
||||
return _platform_versions().get(platform, "")
|
||||
|
||||
|
||||
def _record_platform_version(platform: str, version: str) -> None:
|
||||
versions = _load_versions()
|
||||
platforms = versions.setdefault("platforms", {})
|
||||
if not isinstance(platforms, dict):
|
||||
platforms = {}
|
||||
versions["platforms"] = platforms
|
||||
platforms[platform] = version
|
||||
if version and not versions.get("commit"):
|
||||
versions["commit"] = version
|
||||
_save_versions(versions)
|
||||
|
||||
|
||||
def _desired_version(platform: str) -> str:
|
||||
"""Best upstream version we can install for ``platform`` on this machine."""
|
||||
if buildable_from_source(platform):
|
||||
return upstream_commit()
|
||||
asset = _latest_release_asset(platform)
|
||||
if asset:
|
||||
return asset[0]
|
||||
return upstream_commit()
|
||||
|
||||
|
||||
def check_wolfdawn_update(platform: str | None = None) -> bool:
|
||||
"""Return True when upstream WolfDawn differs from the bundled binary."""
|
||||
platform = platform or _platform_dir()
|
||||
if not bundled_binary_path(platform).is_file():
|
||||
return True
|
||||
try:
|
||||
return _local_version(platform) != _desired_version(platform)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _refresh_from_release(platform: str, log_fn=print) -> str | None:
|
||||
asset = _latest_release_asset(platform)
|
||||
if not asset:
|
||||
return None
|
||||
tag, _ = asset
|
||||
try:
|
||||
download_wolf_binary(platform=platform, log_fn=log_fn)
|
||||
except WolfDawnError as exc:
|
||||
_log(f"Warning: release download failed for {platform} ({exc}).", log_fn)
|
||||
return None
|
||||
return tag
|
||||
|
||||
|
||||
def refresh_wolfdawn_binary(
|
||||
platform: str | None = None,
|
||||
*,
|
||||
platforms: Sequence[str] | None = None,
|
||||
log_fn=print,
|
||||
) -> bool:
|
||||
"""Refresh WolfDawn binaries, trying source builds first."""
|
||||
targets = tuple(platforms or (platform or _platform_dir(),))
|
||||
try:
|
||||
upstream = upstream_commit()
|
||||
except Exception as exc:
|
||||
_log(f"ERROR: could not contact WolfDawn upstream ({exc})", log_fn)
|
||||
return False
|
||||
|
||||
_log(f"Upstream WolfDawn commit: {upstream[:12]}", log_fn)
|
||||
source_results = build_and_install_platforms(
|
||||
tuple(p for p in targets if p in BUNDLED_PLATFORMS),
|
||||
log_fn=log_fn,
|
||||
)
|
||||
|
||||
ok = True
|
||||
versions = _load_versions()
|
||||
versions["commit"] = upstream
|
||||
platform_versions = versions.setdefault("platforms", {})
|
||||
if not isinstance(platform_versions, dict):
|
||||
platform_versions = {}
|
||||
versions["platforms"] = platform_versions
|
||||
|
||||
for plat in targets:
|
||||
if source_results.get(plat):
|
||||
platform_versions[plat] = upstream
|
||||
_log(f"WolfDawn updated from source ({plat}, {upstream[:12]})", log_fn)
|
||||
continue
|
||||
|
||||
tag = _refresh_from_release(plat, log_fn=log_fn)
|
||||
if tag:
|
||||
platform_versions[plat] = tag
|
||||
_log(f"WolfDawn updated from release ({plat}, {tag})", log_fn)
|
||||
continue
|
||||
|
||||
if bundled_binary_path(plat).is_file():
|
||||
_log(f"Keeping existing offline WolfDawn binary ({plat}).", log_fn)
|
||||
if plat not in platform_versions:
|
||||
platform_versions[plat] = _local_version(plat) or "offline"
|
||||
continue
|
||||
|
||||
_log(f"ERROR: no WolfDawn binary available for '{plat}'.", log_fn)
|
||||
ok = False
|
||||
|
||||
_save_versions(versions)
|
||||
return ok
|
||||
|
||||
|
||||
def ensure_wolfdawn_binary(force: bool = False, log_fn=print) -> bool:
|
||||
"""Ensure the ``wolf`` binary is present; refresh when missing or stale."""
|
||||
platform = _platform_dir()
|
||||
bundled = bundled_binary_path(platform)
|
||||
missing = not bundled.is_file()
|
||||
|
||||
need_fetch = missing or force
|
||||
if not need_fetch:
|
||||
try:
|
||||
need_fetch = check_wolfdawn_update(platform)
|
||||
except Exception as exc:
|
||||
if missing:
|
||||
_log(f"ERROR: WolfDawn upstream unavailable ({exc})", log_fn)
|
||||
return False
|
||||
_log(f"Warning: could not check WolfDawn update ({exc}); using local copy.", log_fn)
|
||||
|
||||
if need_fetch:
|
||||
if refresh_wolfdawn_binary(platforms=BUNDLED_PLATFORMS, log_fn=log_fn):
|
||||
return bundled_binary_path(platform).is_file()
|
||||
if force or missing:
|
||||
_log("ERROR: WolfDawn update failed.", log_fn)
|
||||
return False
|
||||
_log("Warning: could not update WolfDawn; using local copy.", log_fn)
|
||||
|
||||
return bundled.is_file()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if "--refresh-all" in sys.argv:
|
||||
return 0 if refresh_wolfdawn_binary(platforms=BUNDLED_PLATFORMS) else 1
|
||||
force = "--force" in sys.argv or "-f" in sys.argv
|
||||
return 0 if ensure_wolfdawn_binary(force=force) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue