mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-21 19:33:08 -05:00
release-posts.js - Reads whole changelog groups (bold heading + wrapped bullets), not just the first line of each bullet. - X: link counted as 23 chars, replies packed to fit 280, markdown stripped. - Discord: full bullets when they fit 2000 chars, short ones otherwise; Reddit: real markdown sections instead of a code block. - Says "pre-release" when GitHub does (gh), --prerelease/--stable to override; --only and --out to pick a platform and write files. - make release-post calls it directly; the .sh wrapper is gone. Removed (outdated) - ps5-install-trace.py, ps5-install-watch.py, fpkg-mount-test.py: drove /api/pkg/install/start and the dpi-* routes, which no longer exist. - gather-fakelibs.py: superseded by the app's Backport scan. - pull_ps5_debug.py: pulled a debug dir the helper no longer writes to. hw-test-loop.sh ran cargo from the repo root (no workspace there); it now points at engine/Cargo.toml. README: a Highlights section up top (unified install, Convert to FPKG, sleep and wake), Android/web in the platforms badge, tested firmware 5.10 & 9.60. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1185 lines
60 KiB
Makefile
1185 lines
60 KiB
Makefile
# PS5 Upload - Root Makefile
|
||
#
|
||
# Tree layout (current):
|
||
# payload/ — PS5 C payload (FreeBSD 11)
|
||
# engine/ — Rust workspace: ftx2-proto, ps5upload-core, -engine HTTP service,
|
||
# -lab CLI, -tests mock server, -bench, -pkg
|
||
# client/ — Tauri 2 desktop app, cross-platform (Linux/macOS/Windows, x64+arm64)
|
||
# tests/ — root integration smoke + tests/lab/ (real-hardware shell scripts)
|
||
# bench/ — golden workloads, baselines, perf-gate helpers
|
||
# scripts/ — install + dev helpers (one per OS, plus shared mjs utilities)
|
||
#
|
||
# Retired pre-2.1: app/ (browser server), shared/ (legacy JS modules),
|
||
# ui/ (empty scaffold), client/electron/ (Tauri replaces Electron),
|
||
# specs/ (consolidated into in-tree doc comments + CHANGELOG), tools/
|
||
# (folded into scripts/). The 1.x C payload was already gone pre-rename.
|
||
|
||
JOBS ?= $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || nproc 2>/dev/null || echo 4)
|
||
# `npm ci` — NOT `npm install`. The lockfile carries `libc` fields that select
|
||
# musl vs glibc native binaries for the Alpine-based web UI image
|
||
# (engine/Dockerfile.webui, node:26-alpine). npm 11 writes them; npm 10 does
|
||
# not know the field and DELETES it on every `npm install`. Since this target
|
||
# runs on every build, an older npm silently stripped that metadata and it had
|
||
# to be hand-reverted before each release. `npm ci` installs strictly FROM the
|
||
# lockfile and never rewrites it, which is also what CI does.
|
||
NPM_INSTALL ?= npm ci --no-audit --no-fund
|
||
CARGO ?= cargo
|
||
|
||
PS5_HOST ?= 192.168.137.2
|
||
PS5_LOADER_PORT ?= 9021
|
||
|
||
# Default PS5 payload SDK location — override on the command line if installed
|
||
# elsewhere. `setup-payload` validates this path exists before any C build.
|
||
PS5_PAYLOAD_SDK ?= /opt/ps5-payload-sdk
|
||
export PS5_PAYLOAD_SDK
|
||
PS5_SDK_TAG := $(shell sed -n 's/^PS5_SDK_TAG=//p' scripts/ps5-sdk.env 2>/dev/null)
|
||
|
||
# On macOS the payload SDK's `prospero-clang` wrapper resolves `ld.lld`
|
||
# and `clang` through `prospero-llvm-config`. SDK v0.42 supports LLVM 16–22.
|
||
# Homebrew exposes the current major as `llvm` (not `llvm@22`), so discover
|
||
# its real prefix and retain versioned fallbacks for older installations.
|
||
# Linux/WSL picks up `llvm-config-<N>` from apt naturally.
|
||
ifeq ($(shell uname -s),Darwin)
|
||
MACOS_LLVM_CONFIG := $(shell \
|
||
if command -v brew >/dev/null 2>&1; then \
|
||
for formula in llvm llvm@22 llvm@18; do \
|
||
prefix="$$(brew --prefix "$$formula" 2>/dev/null || true)"; \
|
||
if [ -n "$$prefix" ] && [ -x "$$prefix/bin/llvm-config" ]; then \
|
||
printf '%s\n' "$$prefix/bin/llvm-config"; \
|
||
break; \
|
||
fi; \
|
||
done; \
|
||
fi)
|
||
LLVM_CONFIG ?= $(MACOS_LLVM_CONFIG)
|
||
export LLVM_CONFIG
|
||
endif
|
||
|
||
PAYLOAD_DIR := payload
|
||
PAYLOAD_ELF := $(PAYLOAD_DIR)/ps5upload.elf
|
||
# The PS5Upload installer daemon (our own; :9115, FW 11+ pkg installs).
|
||
# Built from the same SDK; the engine auto-sends it to the loader port
|
||
# (:9021) as a companion image when :9115 isn't already listening.
|
||
INSTALLER_DIR := $(PAYLOAD_DIR)/installer
|
||
INSTALLER_ELF := $(INSTALLER_DIR)/ps5upload-installer.elf
|
||
ENGINE_DIR := engine
|
||
CLIENT_DIR := client
|
||
|
||
# Android (Tauri mobile). The engine is linked in-process (no sidecar);
|
||
# only the PS5 payload is bundled. Needs the Android SDK (ANDROID_HOME),
|
||
# a JDK 17, an NDK, and the aarch64-linux-android Rust target via rustup
|
||
# (Homebrew `rust` ships only the host std and can't cross-compile).
|
||
# JDK/NDK are auto-detected where possible — override any on the CLI.
|
||
ANDROID_TARGET ?= aarch64
|
||
ANDROID_RUST_TARGET ?= aarch64-linux-android
|
||
# macOS resolves a JDK 17 via java_home; elsewhere fall back to $JAVA_HOME.
|
||
ANDROID_JAVA_HOME ?= $(shell /usr/libexec/java_home -v 17 2>/dev/null || echo "$$JAVA_HOME")
|
||
# Newest NDK installed under the SDK.
|
||
ANDROID_NDK_HOME ?= $(shell ls -d "$(ANDROID_HOME)"/ndk/* 2>/dev/null | sort -V | tail -1)
|
||
ANDROID_GEN_DIR := $(CLIENT_DIR)/src-tauri/gen/android
|
||
ANDROID_APK := $(ANDROID_GEN_DIR)/app/build/outputs/apk/universal/debug/app-universal-debug.apk
|
||
# Package id (matches tauri.conf.json `identifier`). Used to recover from a
|
||
# signature-mismatched prior install during the convenience deploy step.
|
||
ANDROID_APP_ID := com.phantomptr.ps5upload
|
||
# App-icon source (relative to CLIENT_DIR). `tauri android init` seeds
|
||
# placeholder launcher icons; we re-apply ours so the Android icon matches
|
||
# the desktop app icon. gen/ is gitignored, so this runs at init time.
|
||
ANDROID_ICON_SRC := src-tauri/icons/sources/macos_squircle_1024.png
|
||
# rustup's *real* toolchain bin (not the ~/.cargo/bin shim). The Android std
|
||
# lives only here; Homebrew `rust` ships only the host std. We pin PATH to this
|
||
# dir for the android recipes so cross-compilation works even when Homebrew's
|
||
# cargo precedes rustup on PATH (otherwise `cargo --target aarch64-linux-android`
|
||
# picks Homebrew's cargo and fails with "can't find crate for `core`/`std`").
|
||
RUSTUP_CARGO_BIN := $(shell dirname "$$(rustup which cargo 2>/dev/null)" 2>/dev/null)
|
||
# Env preamble shared by the android recipes.
|
||
ANDROID_ENV = JAVA_HOME="$(ANDROID_JAVA_HOME)" ANDROID_HOME="$(ANDROID_HOME)" NDK_HOME="$(ANDROID_NDK_HOME)" $(if $(RUSTUP_CARGO_BIN),PATH="$(RUSTUP_CARGO_BIN):$$PATH")
|
||
# adb from the SDK (falls back to PATH if a device-only adb is installed).
|
||
ADB ?= $(ANDROID_HOME)/platform-tools/adb
|
||
|
||
.PHONY: all help
|
||
.PHONY: install install-ubuntu install-macos install-windows
|
||
.PHONY: setup setup-engine setup-payload setup-client
|
||
.PHONY: build payload installer engine client _engine-release _payload-if-ready _android-build-if-ready
|
||
.PHONY: test test-root test-engine test-engine-coverage test-desktop test-payload test-client test-client-coverage
|
||
.PHONY: lint lint-scripts lint-client audit-scripts coverage coverage-engine coverage-client
|
||
.PHONY: quality quality-full quality-hardware ci ci-full
|
||
.PHONY: clean clean-payload clean-engine clean-client
|
||
.PHONY: verify info install-hooks
|
||
.PHONY: run-engine run-client dev start _check-tauri-system-deps
|
||
.PHONY: install-engine uninstall-engine
|
||
.PHONY: dist dist-win dist-win-arm dist-mac dist-mac-x64 dist-linux dist-linux-arm
|
||
.PHONY: android-deps android-init android-build android-deploy _android-install-if-device run-android run-android-background
|
||
.PHONY: emu-create emu-start emu-stop emu-status emu-test
|
||
.PHONY: send-payload gen-fixtures sweep validate validate-xl
|
||
.PHONY: sync-version sync-version-check
|
||
.PHONY: docker-engine docker-engine-run
|
||
|
||
# Default target
|
||
all: build
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Help
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
help:
|
||
@echo "PS5 Upload - Build System"
|
||
@echo ""
|
||
@echo "First-time setup (fresh machine):"
|
||
@echo " make install - Auto-detect host OS and install all dev deps"
|
||
@echo " make install-ubuntu - Ubuntu/Debian/WSL2: apt + rustup + node + PS5 SDK $(PS5_SDK_TAG)"
|
||
@echo " make install-macos - macOS: brew + rustup + LLVM + PS5 SDK $(PS5_SDK_TAG)"
|
||
@echo " make install-windows - Windows 11: winget + rustup + VS Build Tools + PS5 SDK $(PS5_SDK_TAG)"
|
||
@echo ""
|
||
@echo "Quick start (after install):"
|
||
@echo " 1. make setup - Check toolchains, install client deps"
|
||
@echo " 2. make build - Build payload ELF + Rust engine + client UI"
|
||
@echo " 3. make send-payload - Upload payload ELF to PS5 (PS5_HOST=$(PS5_HOST))"
|
||
@echo " 4. make run-client - Start the Tauri desktop app (spawns engine automatically)"
|
||
@echo ""
|
||
@echo "Validation:"
|
||
@echo " make validate - Rebuild + send + smoke + full sweep + timestamped report"
|
||
@echo " make validate-xl - Same as validate plus the 200k-file stress profile"
|
||
@echo " make sweep - Run sweep against an already-loaded payload"
|
||
@echo ""
|
||
@echo "Build parts:"
|
||
@echo " make payload - Build the PS5 payload in $(PAYLOAD_DIR)/"
|
||
@echo " make engine - Build the Rust engine workspace"
|
||
@echo " make client - Build the Tauri/React UI"
|
||
@echo ""
|
||
@echo "Testing:"
|
||
@echo " make lint - script syntax + frontend ESLint"
|
||
@echo " make lint-scripts - Node/Bash/Python/PowerShell syntax checks"
|
||
@echo " make audit-scripts - Inventory script/test utilities"
|
||
@echo " make quality - Full non-hardware validation gate"
|
||
@echo " make quality-full - quality + payload validation"
|
||
@echo " make quality-hardware - quality + live PS5 validate"
|
||
@echo " make coverage - Rust + frontend coverage reports"
|
||
@echo " make coverage-engine - Rust coverage report only"
|
||
@echo " make coverage-client - Frontend coverage report only"
|
||
@echo " make test-engine - cargo test --workspace"
|
||
@echo " make test-desktop - Tauri Rust cargo check/clippy/test"
|
||
@echo " make test-payload - Validate $(PAYLOAD_ELF)"
|
||
@echo " make test-client - Type-check + lint + unit tests + build client UI"
|
||
@echo ""
|
||
@echo "Version:"
|
||
@echo " make sync-version - Sync downstream files from VERSION (canonical source)"
|
||
@echo " make sync-version-check - Fail if any downstream file drifts from VERSION"
|
||
@echo ""
|
||
@echo "Packaging:"
|
||
@echo " make dist - Tauri bundle for the current OS"
|
||
@echo " make dist-win|dist-win-arm"
|
||
@echo " make dist-mac|dist-mac-x64"
|
||
@echo " make dist-linux|dist-linux-arm"
|
||
@echo ""
|
||
@echo "Android (Tauri mobile — needs Android SDK + JDK 17 + NDK + rustup):"
|
||
@echo " make run-android - Build + run with live reload; boots the emulator WITH its window"
|
||
@echo " make run-android-background - Same, but the emulator boots headless (no window)"
|
||
@echo " make android-deploy - Build APK + install/update it on connected device(s)"
|
||
@echo " make android-build - Build a debug APK (no device needed)"
|
||
@echo " make android-init - One-time: scaffold src-tauri/gen/android"
|
||
@echo " make android-deps - Check the Android toolchain is ready"
|
||
@echo " make emu-test - Build + run the app on a headless emulator, with a screenshot"
|
||
@echo " make emu-start - Boot the test emulator (headless); emu-stop to shut it down"
|
||
@echo " make emu-status - Show AVDs, whether one is running, and where artifacts go"
|
||
@echo " make run-android - Boots the emulator (windowed) only if nothing is attached"
|
||
@echo " emu-test leaves the emulator up for fast re-runs; PS5UPLOAD_EMU_TEARDOWN=1 stops it"
|
||
@echo ""
|
||
@echo "Auto-launch (engine starts at OS login):"
|
||
@echo " make install-engine - Register systemd/launchd/Task Scheduler job"
|
||
@echo " make uninstall-engine - Remove the auto-launch registration"
|
||
@echo ""
|
||
@echo "Docker (self-hosted engine; CI publishes the same image to GHCR):"
|
||
@echo " make docker-engine - Build the engine image locally (scratch + static binary)"
|
||
@echo " make docker-engine-run - Run it (PS5_HOST=$(PS5_HOST)); exposes :19113"
|
||
@echo ""
|
||
@echo "Environment overrides (defaults shown):"
|
||
@echo " PS5_HOST=$(PS5_HOST) PS5 IP address"
|
||
@echo " PS5_LOADER_PORT=$(PS5_LOADER_PORT) PS5 payload loader port"
|
||
@echo " PS5_PAYLOAD_SDK=$(PS5_PAYLOAD_SDK) PS5 C payload SDK install"
|
||
ifeq ($(shell uname -s),Darwin)
|
||
@echo " LLVM_CONFIG=$(LLVM_CONFIG)"
|
||
@echo " Auto-set on macOS (needs Homebrew llvm)"
|
||
endif
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Install — fresh-machine bootstrap (apt/brew/winget + rustup + Node + PS5 SDK)
|
||
#
|
||
# `setup` only *checks* toolchains and runs `npm install`; `install` actually
|
||
# installs everything from scratch. The per-OS scripts live in `scripts/` and
|
||
# are idempotent — each step skips if already satisfied — so re-running them
|
||
# after a partial setup is safe.
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
install:
|
||
@os="$$(uname -s)"; \
|
||
case "$$os" in \
|
||
Linux*) $(MAKE) install-ubuntu ;; \
|
||
Darwin*) $(MAKE) install-macos ;; \
|
||
MINGW*|MSYS*|CYGWIN*) $(MAKE) install-windows ;; \
|
||
*) echo "Unsupported host OS: $$os"; \
|
||
echo "Run install-ubuntu / install-macos / install-windows directly."; \
|
||
exit 1 ;; \
|
||
esac
|
||
|
||
install-ubuntu:
|
||
@bash scripts/install-ubuntu.sh
|
||
|
||
install-macos:
|
||
@bash scripts/install-macos.sh
|
||
|
||
# On Windows the script is invoked via PowerShell. Tries pwsh (PowerShell 7+)
|
||
# first, then falls back to legacy Windows PowerShell. Run from any shell where
|
||
# `powershell.exe` or `pwsh` is on PATH (Git Bash, MSYS2, native PowerShell).
|
||
install-windows:
|
||
@if command -v pwsh >/dev/null 2>&1; then \
|
||
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/install-windows.ps1; \
|
||
elif command -v powershell.exe >/dev/null 2>&1; then \
|
||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-windows.ps1; \
|
||
else \
|
||
echo "ERROR: neither pwsh nor powershell.exe on PATH."; \
|
||
echo " Run scripts/install-windows.ps1 directly from a PowerShell prompt."; \
|
||
exit 1; \
|
||
fi
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Setup
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
setup: setup-payload setup-engine setup-client
|
||
@echo ""
|
||
@echo "✓ Setup complete"
|
||
@echo ""
|
||
@echo "Suggested next steps:"
|
||
@echo " 1. make build"
|
||
@echo " 2. make run-client"
|
||
@echo ""
|
||
|
||
setup-payload:
|
||
@echo "Checking PS5 Payload SDK..."
|
||
@if [ -z "$(PS5_PAYLOAD_SDK)" ]; then \
|
||
echo ""; \
|
||
echo "ERROR: PS5_PAYLOAD_SDK is not set."; \
|
||
echo "Install the PS5 Payload SDK and export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk"; \
|
||
echo ""; \
|
||
exit 1; \
|
||
fi
|
||
@if [ ! -f "$(PS5_PAYLOAD_SDK)/toolchain/prospero.mk" ]; then \
|
||
echo ""; \
|
||
echo "ERROR: SDK files not found at $(PS5_PAYLOAD_SDK)"; \
|
||
echo ""; \
|
||
exit 1; \
|
||
fi
|
||
@echo "✓ PS5 SDK found: $(PS5_PAYLOAD_SDK)"
|
||
ifeq ($(shell uname -s),Darwin)
|
||
@if [ -z "$(LLVM_CONFIG)" ] || [ ! -x "$(LLVM_CONFIG)" ]; then \
|
||
echo ""; \
|
||
echo "ERROR: Homebrew llvm-config was not found."; \
|
||
echo "Install it with: brew install llvm"; \
|
||
echo "Or export LLVM_CONFIG=/path/to/llvm-config"; \
|
||
echo ""; \
|
||
exit 1; \
|
||
fi
|
||
@echo "✓ LLVM found: $(LLVM_CONFIG) ($$($(LLVM_CONFIG) --version))"
|
||
endif
|
||
|
||
setup-engine:
|
||
@echo "Checking Rust toolchain..."
|
||
@command -v rustc >/dev/null 2>&1 || { \
|
||
echo "ERROR: rustc is not installed."; \
|
||
echo "Install Rust via https://rustup.rs"; \
|
||
exit 1; \
|
||
}
|
||
@command -v $(CARGO) >/dev/null 2>&1 || { \
|
||
echo "ERROR: cargo is not installed."; \
|
||
echo "Install Rust via https://rustup.rs"; \
|
||
exit 1; \
|
||
}
|
||
@echo "✓ Rust toolchain found: rustc $$(rustc --version), cargo $$($(CARGO) --version)"
|
||
|
||
setup-client:
|
||
@echo "Checking client toolchain..."
|
||
@command -v node >/dev/null 2>&1 || { echo "ERROR: Node.js is not installed."; exit 1; }
|
||
@command -v npm >/dev/null 2>&1 || { echo "ERROR: npm is not installed."; exit 1; }
|
||
@echo "✓ Node.js toolchain found: node $$(node --version), npm $$(npm --version)"
|
||
@echo "Installing client dependencies..."
|
||
@cd $(CLIENT_DIR) && $(NPM_INSTALL)
|
||
@echo "✓ Client dependencies installed"
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Build
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
# Build the Android APK only when the toolchain is configured; otherwise skip
|
||
# with a notice so `make build` still works on machines without the Android
|
||
# SDK/NDK/JDK 17 (desktop-only contributors, CI desktop jobs). Uses the same
|
||
# vars `android-deps` checks. `android-build` self-depends on `payload`, so the
|
||
# embedded ELF is always current.
|
||
_android-build-if-ready:
|
||
@if command -v rustup >/dev/null 2>&1 && [ -n "$(ANDROID_HOME)" ] && [ -n "$(ANDROID_NDK_HOME)" ] && [ -n "$(ANDROID_JAVA_HOME)" ]; then \
|
||
echo "Android toolchain detected — building/refreshing the APK..."; \
|
||
$(MAKE) android-build && $(MAKE) _android-install-if-device; \
|
||
else \
|
||
echo "⚠ Skipping Android APK build — toolchain not configured."; \
|
||
echo " Need rustup + ANDROID_HOME + an NDK + JDK 17. Run 'make android-deps' to see what's missing."; \
|
||
fi
|
||
|
||
# Install the freshly-built universal debug APK onto every connected device
|
||
# (`adb install -r` updates in place, keeping app data). This is the step that
|
||
# actually refreshes the app ON the phone after a build. No-op with a notice
|
||
# when no device is attached — so it's safe in CI and on dev machines with no
|
||
# phone plugged in.
|
||
_android-install-if-device:
|
||
@apk="$(ANDROID_APK)"; \
|
||
adb="$(ADB)"; command -v "$$adb" >/dev/null 2>&1 || adb="adb"; \
|
||
if [ ! -f "$$apk" ]; then echo "⚠ No APK at $$apk — run 'make android-build' first."; exit 0; fi; \
|
||
if ! command -v "$$adb" >/dev/null 2>&1; then echo "⚠ adb not found — APK is at $$apk."; exit 0; fi; \
|
||
devs=$$("$$adb" devices 2>/dev/null | awk 'NR>1 && $$2=="device"{print $$1}'); \
|
||
if [ -z "$$devs" ]; then \
|
||
echo "⚠ No Android device connected — APK is at $$apk."; \
|
||
echo " Plug in a device (USB debugging on), then: $$adb install -r \"$$apk\""; \
|
||
else \
|
||
for d in $$devs; do \
|
||
echo "Updating the app on device $$d (adb install -r)..."; \
|
||
out=$$("$$adb" -s "$$d" install -r "$$apk" 2>&1); \
|
||
if [ $$? -eq 0 ] && ! printf '%s' "$$out" | grep -qi 'Failure'; then \
|
||
echo "✓ App updated on $$d"; \
|
||
elif printf '%s' "$$out" | grep -qi 'UPDATE_INCOMPATIBLE\|signatures do not match'; then \
|
||
echo "⚠ Existing install on $$d was signed with a different key — reinstalling fresh (its app data is cleared)..."; \
|
||
"$$adb" -s "$$d" uninstall "$(ANDROID_APP_ID)" >/dev/null 2>&1 || true; \
|
||
if "$$adb" -s "$$d" install "$$apk" >/dev/null 2>&1; then \
|
||
echo "✓ App reinstalled on $$d"; \
|
||
else \
|
||
echo "⚠ Could not install on $$d — APK is at $$apk (install manually: $$adb -s $$d install -r \"$$apk\")."; \
|
||
fi; \
|
||
else \
|
||
echo "⚠ adb install failed on $$d (build is fine — APK is at $$apk):"; \
|
||
printf '%s\n' "$$out" | sed 's/^/ /'; \
|
||
fi; \
|
||
done; \
|
||
fi
|
||
|
||
# Build the Android APK and install/update it on the connected device(s) in
|
||
# one step — the simplest "push my latest changes to the phone".
|
||
android-deploy: android-build _android-install-if-device
|
||
@echo "✓ Android build deployed to connected device(s)."
|
||
|
||
# Rebuild the payload only when the PS5 SDK is configured; otherwise skip so a
|
||
# frontend-only `make run-client` keeps working without the SDK. The shell
|
||
# embeds whatever ps5upload.elf.gz is on disk (build.rs re-embeds on change).
|
||
_payload-if-ready:
|
||
@if [ -n "$(PS5_PAYLOAD_SDK)" ] && [ -f "$(PS5_PAYLOAD_SDK)/toolchain/prospero.mk" ]; then \
|
||
$(MAKE) payload; \
|
||
else \
|
||
echo "⚠ Skipping payload rebuild (PS5_PAYLOAD_SDK not set) — using existing $(PAYLOAD_ELF) if present."; \
|
||
fi
|
||
|
||
# Build only the release engine binary (the binary Tauri spawns). Skips the
|
||
# dev-profile workspace build that the standalone `engine` target does —
|
||
# `make build` only needs the release binary, not the dev workspace.
|
||
build: sync-version-check payload _engine-release client
|
||
@$(MAKE) _android-build-if-ready
|
||
@echo ""
|
||
@echo "✓ Build complete"
|
||
@echo ""
|
||
@echo "Outputs:"
|
||
@echo " - $(PAYLOAD_ELF)"
|
||
@echo " - $(ENGINE_DIR)/target/"
|
||
@echo " - $(CLIENT_DIR)/dist/"
|
||
@echo " - $(ANDROID_APK) (when the Android toolchain is present)"
|
||
@echo ""
|
||
|
||
# VERSION (repo root) is the canonical source of truth. `sync-version`
|
||
# rewrites downstream files (payload config.h macro, client package
|
||
# manifests, Tauri conf + Cargo.toml) from it; `sync-version-check`
|
||
# fails if any downstream file has drifted — wired into `build` so
|
||
# a desync gets caught before we ship an ELF + bundle with mismatched
|
||
# version strings.
|
||
sync-version:
|
||
@node scripts/update-version.js
|
||
|
||
sync-version-check:
|
||
@node scripts/update-version.js --check
|
||
|
||
payload: setup-payload installer
|
||
@echo "Building PS5 payload..."
|
||
@$(MAKE) -C $(PAYLOAD_DIR) -j$(JOBS)
|
||
@echo "✓ Built $(PAYLOAD_ELF)"
|
||
|
||
# PS5Upload installer daemon (our own; supersedes the DPI daemon).
|
||
installer: setup-payload
|
||
@echo "Building PS5Upload installer daemon..."
|
||
@$(MAKE) -C $(INSTALLER_DIR) -j$(JOBS)
|
||
@echo "✓ Built $(INSTALLER_ELF)"
|
||
|
||
send-payload: payload
|
||
@echo "Sending payload to $(PS5_HOST):$(PS5_LOADER_PORT) ..."
|
||
@if ! command -v python3 >/dev/null 2>&1; then \
|
||
echo "ERROR: python3 not found — required for bounded payload upload"; exit 1; \
|
||
fi
|
||
@python3 -c 'import pathlib, socket, sys; host, port, payload_path = sys.argv[1], int(sys.argv[2]), pathlib.Path(sys.argv[3]); payload = payload_path.read_bytes(); sock = socket.create_connection((host, port), timeout=5); sock.settimeout(5); sock.sendall(payload); sock.shutdown(socket.SHUT_WR); sock.close()' "$(PS5_HOST)" "$(PS5_LOADER_PORT)" "$(PAYLOAD_ELF)"
|
||
@echo "✓ Payload sent — wait for PS5 notification before testing"
|
||
|
||
engine: setup-engine
|
||
@echo "Building Rust engine workspace..."
|
||
@cd $(ENGINE_DIR) && $(CARGO) build --workspace
|
||
@echo "Building release ps5upload-engine (the binary Tauri spawns)..."
|
||
@cd $(ENGINE_DIR) && $(CARGO) build --release -p ps5upload-engine
|
||
@echo "✓ Engine workspace built"
|
||
|
||
# Internal helper: build the `ps5upload-engine` release binary, which the
|
||
# Tauri setup hook (client/src-tauri/src/engine.rs) spawns as a child of
|
||
# the window process. `cargo build --release` is itself incremental, so
|
||
# this is a cheap no-op when sources are unchanged — and a real rebuild
|
||
# the moment they aren't. (An earlier version guarded with
|
||
# `[ ! -x binary ]`, which stayed stale through code changes; removed.)
|
||
_engine-release: setup-engine
|
||
@if [ -n "$(ENGINE_TARGET)" ]; then \
|
||
cd $(ENGINE_DIR) && $(CARGO) build --release -p ps5upload-engine --target "$(ENGINE_TARGET)"; \
|
||
else \
|
||
cd $(ENGINE_DIR) && $(CARGO) build --release -p ps5upload-engine; \
|
||
fi
|
||
|
||
client: setup-client
|
||
@echo "Building client UI..."
|
||
@cd $(CLIENT_DIR) && npm run build:vite
|
||
@echo "✓ Client UI built"
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Benchmarking + validation
|
||
#
|
||
# `make validate` is the single-command flow for "we are still progressing":
|
||
# rebuild payload, send to PS5, wait for ready, run smoke + full sweep, write
|
||
# a timestamped report under bench/reports/ and print a summary table.
|
||
# `make validate-xl` adds the 200k-file stress profile.
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
gen-fixtures:
|
||
@echo "Generating benchmark fixtures under bench/fixtures/..."
|
||
@node scripts/gen-fixtures.mjs
|
||
|
||
sweep:
|
||
@echo "Running FTX2 sweep against live PS5 at $(PS5_HOST):9113 ..."
|
||
@node bench/run-ftx2-sweep.mjs --spawn-engine --gen-fixtures
|
||
|
||
# Wait for the payload's runtime port to accept connections after send.
|
||
# Retries 15×/2s = 30s ceiling; exits non-zero if the port never opens.
|
||
_wait-payload-ready:
|
||
@echo "Waiting for PS5 runtime port 9113 ..."
|
||
@i=0; while [ $$i -lt 15 ]; do \
|
||
if nc -z -w 1 "$(PS5_HOST)" 9113 >/dev/null 2>&1; then \
|
||
echo "✓ runtime port open"; exit 0; \
|
||
fi; \
|
||
i=$$((i+1)); sleep 2; \
|
||
done; \
|
||
echo "ERROR: PS5 runtime port 9113 did not open within 30s"; exit 1
|
||
|
||
validate: send-payload _wait-payload-ready
|
||
@echo ""
|
||
@echo "── Running smoke suite ────────────────────────────────"
|
||
@npm run --silent smoke:hardware
|
||
@echo ""
|
||
@echo "── Running sweep (default profiles) ────────────────────"
|
||
@node bench/run-ftx2-sweep.mjs --spawn-engine --gen-fixtures
|
||
@echo ""
|
||
@echo "✓ validate complete — see bench/reports/ for the full report"
|
||
|
||
validate-xl: send-payload _wait-payload-ready
|
||
@echo ""
|
||
@echo "── Running smoke suite ────────────────────────────────"
|
||
@npm run --silent smoke:hardware
|
||
@echo ""
|
||
@echo "── Running sweep (INCLUDING XL 200k-file stress) ───────"
|
||
@node bench/run-ftx2-sweep.mjs --spawn-engine --gen-fixtures --xl
|
||
@echo ""
|
||
@echo "✓ validate-xl complete — see bench/reports/ for the full report"
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Distribution — all drive `tauri build`, producing ~14 MB .app/.AppImage/.msi
|
||
# instead of Electron's ~300 MB bundles. The Rust engine binary is bundled as
|
||
# a Tauri resource and discovered at runtime by client/src-tauri/src/engine.rs.
|
||
# `payload` and `_engine-release` run first so the bundle includes a current
|
||
# ELF and a freshly-built engine.
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
dist: payload _engine-release setup-client
|
||
@echo "Building desktop distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist
|
||
@echo "✓ Distribution packages built: $(CLIENT_DIR)/src-tauri/target/release/bundle/"
|
||
|
||
dist-win: ENGINE_TARGET := x86_64-pc-windows-msvc
|
||
dist-win: payload setup-client
|
||
@$(MAKE) _engine-release ENGINE_TARGET=$(ENGINE_TARGET)
|
||
@echo "Building Windows distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist:win
|
||
@echo "✓ Windows packages built: $(CLIENT_DIR)/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/"
|
||
|
||
dist-win-arm: ENGINE_TARGET := aarch64-pc-windows-msvc
|
||
dist-win-arm: payload setup-client
|
||
@$(MAKE) _engine-release ENGINE_TARGET=$(ENGINE_TARGET)
|
||
@echo "Building Windows ARM64 distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist:win-arm
|
||
@echo "✓ Windows ARM64 packages built: $(CLIENT_DIR)/src-tauri/target/aarch64-pc-windows-msvc/release/bundle/"
|
||
|
||
dist-mac: ENGINE_TARGET := aarch64-apple-darwin
|
||
dist-mac: payload setup-client
|
||
@$(MAKE) _engine-release ENGINE_TARGET=$(ENGINE_TARGET)
|
||
@echo "Building macOS Apple Silicon distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist:mac
|
||
@echo "✓ macOS Apple Silicon packages built: $(CLIENT_DIR)/src-tauri/target/aarch64-apple-darwin/release/bundle/"
|
||
|
||
dist-mac-x64: ENGINE_TARGET := x86_64-apple-darwin
|
||
dist-mac-x64: payload setup-client
|
||
@$(MAKE) _engine-release ENGINE_TARGET=$(ENGINE_TARGET)
|
||
@echo "Building macOS Intel distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist:mac-x64
|
||
@echo "✓ macOS Intel packages built: $(CLIENT_DIR)/src-tauri/target/x86_64-apple-darwin/release/bundle/"
|
||
|
||
dist-linux: ENGINE_TARGET := x86_64-unknown-linux-gnu
|
||
dist-linux: payload setup-client
|
||
@$(MAKE) _engine-release ENGINE_TARGET=$(ENGINE_TARGET)
|
||
@echo "Building Linux x64 distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist:linux
|
||
@echo "✓ Linux x64 packages built: $(CLIENT_DIR)/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/"
|
||
|
||
dist-linux-arm: ENGINE_TARGET := aarch64-unknown-linux-gnu
|
||
dist-linux-arm: payload setup-client
|
||
@$(MAKE) _engine-release ENGINE_TARGET=$(ENGINE_TARGET)
|
||
@echo "Building Linux ARM64 distribution (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run dist:linux-arm
|
||
@echo "✓ Linux ARM64 packages built: $(CLIENT_DIR)/src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/"
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Android (Tauri mobile). Like the `dist` targets but produces an APK. The
|
||
# engine is linked in-process (engine_mobile.rs) rather than spawned, and only
|
||
# the PS5 payload is bundled — so `payload` runs first to embed a current ELF.
|
||
# gen/ is gitignored, so the recipes auto-run `android-init` if it's missing.
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
android-deps:
|
||
@command -v rustup >/dev/null 2>&1 || { echo "ERROR: rustup is required for Android cross-compilation (Homebrew 'rust' ships only the host std). Install from https://rustup.rs"; exit 1; }
|
||
@test -n "$(ANDROID_HOME)" || { echo "ERROR: ANDROID_HOME is not set — point it at your Android SDK (e.g. ~/Library/Android/sdk)"; exit 1; }
|
||
@test -n "$(ANDROID_NDK_HOME)" || { echo "ERROR: no NDK under $(ANDROID_HOME)/ndk — install via Android Studio or 'sdkmanager \"ndk;28.0.12916984\"'"; exit 1; }
|
||
@test -n "$(ANDROID_JAVA_HOME)" || { echo "ERROR: JDK 17 not found — set JAVA_HOME or install Temurin 17"; exit 1; }
|
||
@rustup target list --installed 2>/dev/null | grep -qx '$(ANDROID_RUST_TARGET)' || rustup target add $(ANDROID_RUST_TARGET)
|
||
@echo "Android toolchain OK — JDK=$(ANDROID_JAVA_HOME) NDK=$(ANDROID_NDK_HOME) target=$(ANDROID_RUST_TARGET)"
|
||
|
||
# ─── Headless emulator for testing without a physical device ─────────────
|
||
# Lifecycle lives in scripts/android-emu.sh. Artifacts (screenshots, logcat,
|
||
# install logs) are written OUTSIDE the repo, to ~/.ps5upload/android-test/ —
|
||
# this repo has already leaked debug screenshots into a public repo, and a
|
||
# file that is not in the working tree cannot be committed by accident.
|
||
emu-create:
|
||
@bash scripts/android-emu.sh create
|
||
|
||
emu-start:
|
||
@bash scripts/android-emu.sh start
|
||
|
||
emu-stop:
|
||
@bash scripts/android-emu.sh stop
|
||
|
||
emu-status:
|
||
@bash scripts/android-emu.sh status
|
||
|
||
# Build the APK, then install + launch it on the emulator and capture proof.
|
||
# Depends on android-build so `make emu-test` is the single command.
|
||
emu-test: android-build
|
||
@bash scripts/android-emu.sh test
|
||
|
||
android-init: android-deps setup-client
|
||
@echo "Scaffolding Android project (tauri android init)..."
|
||
@cd $(CLIENT_DIR) && $(ANDROID_ENV) npx tauri android init
|
||
@echo "Applying app icon from $(ANDROID_ICON_SRC) (matches the desktop icon)..."
|
||
@cd $(CLIENT_DIR) && $(ANDROID_ENV) npx tauri icon $(ANDROID_ICON_SRC) >/dev/null
|
||
@echo "Allowing loopback cleartext so the in-process engine is reachable..."
|
||
@bash scripts/release/android-postinit-patch.sh
|
||
|
||
android-build: android-deps payload setup-client
|
||
@test -d $(ANDROID_GEN_DIR) || $(MAKE) android-init
|
||
@# Re-apply the (idempotent) post-init patches on every build. They used to
|
||
@# run only at init, so an existing gen/ never picked up a new patch step and
|
||
@# a local APK silently differed from the CI one, which inits fresh.
|
||
@bash scripts/release/android-postinit-patch.sh
|
||
@echo "Building Android APK (debug, $(ANDROID_TARGET))..."
|
||
@cd $(CLIENT_DIR) && $(ANDROID_ENV) npx tauri android build --debug --apk --target $(ANDROID_TARGET)
|
||
@apk=$$(find "$(ANDROID_GEN_DIR)/app/build/outputs/apk" -name 'app-*-debug.apk' -print -quit 2>/dev/null); \
|
||
echo "✓ APK built: $${apk:-under $(ANDROID_GEN_DIR)/app/build/outputs/apk}"
|
||
|
||
# Boots the headless test emulator when nothing is attached, and shuts down
|
||
# ONLY an emulator this invocation booted -- a phone you plugged in, or an
|
||
# emulator you already had running, is left alone. The trap covers Ctrl-C,
|
||
# which is how `tauri android dev` normally ends.
|
||
# run-android: the emulator boots WITH its window (foreground) so the app can be
|
||
# seen and used. run-android-background: it boots headless, as emu-test does.
|
||
# Either way the dev server stays attached here for live reload.
|
||
run-android: EMU_WINDOW := 1
|
||
run-android-background: EMU_WINDOW := 0
|
||
run-android run-android-background: android-deps payload setup-client
|
||
@test -d $(ANDROID_GEN_DIR) || $(MAKE) android-init
|
||
@state=$$(PS5UPLOAD_EMU_WINDOW=$(EMU_WINDOW) bash $(CURDIR)/scripts/android-emu.sh ensure) || exit 1; \
|
||
if [ "$$state" = "booted" ]; then \
|
||
trap 'trap - EXIT INT TERM; echo ""; echo "Shutting down the emulator this run booted..."; bash $(CURDIR)/scripts/android-emu.sh stop' EXIT INT TERM; \
|
||
else \
|
||
echo " (leaving the existing device/emulator running)"; \
|
||
fi; \
|
||
echo "Launching on the attached Android device/emulator (tauri android dev)..."; \
|
||
cd $(CLIENT_DIR) && $(ANDROID_ENV) npx tauri android dev
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Docker — self-hosted engine image. Mirrors what .github/workflows/
|
||
# docker-engine.yml publishes to ghcr.io/<owner>/ps5upload-engine on each
|
||
# release tag (multi-arch there; single-arch host build here). `engine/` is the
|
||
# build context; the Dockerfile is a scratch image wrapping the static binary.
|
||
#
|
||
# SECURITY: the engine API is unauthenticated. Bind to a LAN interface and set
|
||
# PS5UPLOAD_ALLOW_IP only on a trusted network — never expose it to the
|
||
# internet. See the header of engine/Dockerfile.
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
DOCKER ?= docker
|
||
DOCKER_ENGINE_IMAGE ?= ps5upload-engine
|
||
|
||
docker-engine:
|
||
@command -v $(DOCKER) >/dev/null 2>&1 || { echo "ERROR: docker not found on PATH."; exit 1; }
|
||
@echo "Building $(DOCKER_ENGINE_IMAGE) image (context: $(ENGINE_DIR)/)..."
|
||
@$(DOCKER) build -t $(DOCKER_ENGINE_IMAGE) $(ENGINE_DIR)
|
||
@echo "✓ Built image $(DOCKER_ENGINE_IMAGE) — run with: make docker-engine-run"
|
||
|
||
# Run the locally-built engine image. Binds the published port and points it at
|
||
# the PS5's transfer port. Override PS5_HOST / the bind address as needed.
|
||
docker-engine-run: docker-engine
|
||
@echo "Running $(DOCKER_ENGINE_IMAGE) — engine on :19113, PS5 at $(PS5_HOST):9113 ..."
|
||
@$(DOCKER) run --rm -p 19113:19113 -e PS5_ADDR=$(PS5_HOST):9113 $(DOCKER_ENGINE_IMAGE)
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Testing
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
test: test-root test-engine test-desktop test-payload test-client
|
||
@echo ""
|
||
@echo "✓ All tests passed"
|
||
@echo ""
|
||
|
||
lint: lint-scripts lint-client
|
||
|
||
lint-scripts:
|
||
@npm run scripts:check
|
||
|
||
lint-client:
|
||
@cd $(CLIENT_DIR) && npm run lint
|
||
|
||
audit-scripts:
|
||
@npm run scripts:audit
|
||
|
||
coverage:
|
||
@npm run coverage
|
||
|
||
coverage-engine:
|
||
@npm run coverage -- --engine-only
|
||
|
||
coverage-client:
|
||
@npm run coverage -- --client-only
|
||
|
||
quality:
|
||
@npm run validate
|
||
|
||
quality-full:
|
||
@npm run validate:full
|
||
|
||
quality-hardware:
|
||
@npm run validate:hardware
|
||
|
||
ci: quality
|
||
|
||
ci-full: quality-full
|
||
|
||
# Root-level tests: syntax-check the node scripts that back the bench + smoke
|
||
# harnesses. Hardware smoke coverage lives in `make sweep` / `make validate`.
|
||
test-root:
|
||
@echo "Syntax-checking root node scripts..."
|
||
@node --check tests/smoke-hardware.mjs
|
||
@node --check bench/run-ftx2-sweep.mjs
|
||
@node --check bench/run-ftx2-upload.mjs
|
||
@node --check bench/check-ftx2-baseline.mjs
|
||
@node --check scripts/gen-fixtures.mjs
|
||
@node --check scripts/check-lockfile.mjs
|
||
@node scripts/check-lockfile.mjs --self-test
|
||
@echo "Checking the client lockfile kept its libc metadata..."
|
||
@node scripts/check-lockfile.mjs
|
||
@echo "✓ Root scripts valid"
|
||
|
||
test-engine: setup-engine
|
||
@echo "Running Rust engine tests..."
|
||
@cd $(ENGINE_DIR) && $(CARGO) test --workspace
|
||
@echo "✓ Engine tests passed"
|
||
@# Android cross-compile check. The engine cfg's some modules out on
|
||
@# Android (remote_pkg, which needs an HTTP client), so a call site added
|
||
@# without the matching cfg builds fine on every desktop target and fails
|
||
@# ONLY here. That is exactly how v5.31.0 shipped with no APK: every local
|
||
@# gate was green on macOS while the Android build was broken, and it was
|
||
@# not caught until the release workflow. `cargo check` is enough — this
|
||
@# needs to catch a compile error, not produce an artifact.
|
||
@if rustup target list --installed 2>/dev/null | grep -q aarch64-linux-android; then \
|
||
echo "Checking engine against the Android target..."; \
|
||
cd $(ENGINE_DIR) && $(CARGO) check -p ps5upload-engine --target aarch64-linux-android; \
|
||
echo "✓ Engine compiles for Android"; \
|
||
else \
|
||
echo "⚠ aarch64-linux-android target not installed — SKIPPING the Android check."; \
|
||
echo " CI still runs it, so a cfg mistake will surface there instead of here."; \
|
||
echo " Install with: rustup target add aarch64-linux-android"; \
|
||
fi
|
||
|
||
test-engine-coverage:
|
||
@$(MAKE) coverage-engine
|
||
|
||
test-desktop: setup-engine
|
||
@echo "Checking Tauri Rust shell..."
|
||
@cd $(CLIENT_DIR)/src-tauri && $(CARGO) check --all-targets
|
||
@cd $(CLIENT_DIR)/src-tauri && $(CARGO) clippy --all-targets -- -D warnings
|
||
@cd $(CLIENT_DIR)/src-tauri && $(CARGO) test
|
||
@echo "✓ Desktop Rust checks passed"
|
||
|
||
test-payload: payload
|
||
@echo "Validating payload binaries..."
|
||
@for elf in "$(PAYLOAD_ELF)" "$(INSTALLER_ELF)"; do \
|
||
if [ ! -s "$$elf" ]; then \
|
||
echo "ERROR: $$elf is missing or empty."; \
|
||
exit 1; \
|
||
fi; \
|
||
file "$$elf" | grep -q "ELF.*FreeBSD" || { \
|
||
echo "ERROR: $$elf is not a PS5/FreeBSD ELF file."; \
|
||
exit 1; \
|
||
}; \
|
||
if [ ! -s "$$elf.gz" ]; then \
|
||
echo "ERROR: $$elf.gz is missing or empty."; \
|
||
exit 1; \
|
||
fi; \
|
||
gzip -t "$$elf.gz" || { \
|
||
echo "ERROR: $$elf.gz is not a valid gzip resource."; \
|
||
exit 1; \
|
||
}; \
|
||
done
|
||
@echo "✓ Main payload and PS5Upload installer are PS5 ELFs with gzip resources"
|
||
@echo "Running play-time launch/resume self-test (host build)..."
|
||
@echo "Running accept-recovery self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-accept-recovery-selftest \
|
||
$(PAYLOAD_DIR)/tests/accept_recovery_selftest.c
|
||
@/tmp/ps5upload-accept-recovery-selftest
|
||
@echo "✓ accept() failures keep the helper serving until its network is gone"
|
||
@echo "Running direct-commit apply self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-commit-apply-selftest \
|
||
$(PAYLOAD_DIR)/tests/commit_apply_selftest.c
|
||
@/tmp/ps5upload-commit-apply-selftest
|
||
@echo "✓ a repeat COMMIT never unlinks a destination it cannot replace"
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-activity-selftest \
|
||
$(PAYLOAD_DIR)/tests/activity_launch_selftest.c
|
||
@/tmp/ps5upload-activity-selftest
|
||
@echo "Running app-info layout self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-app-info-selftest \
|
||
$(PAYLOAD_DIR)/tests/app_info_selftest.c
|
||
@/tmp/ps5upload-app-info-selftest
|
||
@echo "Running metadata title-id normalizer self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-tmdb-id-selftest \
|
||
$(PAYLOAD_DIR)/tests/tmdb_id_selftest.c
|
||
@/tmp/ps5upload-tmdb-id-selftest
|
||
@echo "Running cheat XML UTF-16 normalizer self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-xml-encoding-selftest \
|
||
$(PAYLOAD_DIR)/tests/xml_encoding_selftest.c
|
||
@/tmp/ps5upload-xml-encoding-selftest
|
||
@echo "✓ UTF-16 SHN/MC4 cheat files convert so their cheats appear"
|
||
@echo "Checking per-console isolation..."
|
||
@./scripts/check-per-console-isolation.sh
|
||
@echo "✓ one console's data cannot be shown under another's name"
|
||
@echo "Checking Sony-API lock coverage..."
|
||
@./scripts/check-sony-api-lock.sh $(PAYLOAD_DIR)/src
|
||
@echo "✓ every sceUserService/sceRegMgr caller is serialized"
|
||
@echo "Running hw-guard fault-recovery self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-hw-guard-selftest \
|
||
$(PAYLOAD_DIR)/tests/hw_guard_selftest.c
|
||
@/tmp/ps5upload-hw-guard-selftest
|
||
@echo "✓ hw-guard recovers Sony-getter faults without dropping the helper"
|
||
@echo "Running wake-watchdog safety self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-wake-watchdog-selftest \
|
||
$(PAYLOAD_DIR)/tests/wake_watchdog_selftest.c
|
||
@/tmp/ps5upload-wake-watchdog-selftest
|
||
@echo "✓ clock adjustments and unknown firmware state cannot trigger kernel writes"
|
||
@echo "Running process-identity self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-proc-identity-selftest \
|
||
$(PAYLOAD_DIR)/tests/proc_identity_selftest.c
|
||
@/tmp/ps5upload-proc-identity-selftest
|
||
@echo "✓ only our own threads are ever in SIGKILL range"
|
||
@echo "Running prior-instance verdict self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-instance-verdict-selftest \
|
||
$(PAYLOAD_DIR)/tests/instance_verdict_selftest.c
|
||
@/tmp/ps5upload-instance-verdict-selftest
|
||
@echo "✓ an externally-killed predecessor is reported, never guessed at"
|
||
@echo "Running ptrace timeout-recovery self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-ptrace-recovery-selftest \
|
||
$(PAYLOAD_DIR)/tests/ptrace_recovery_selftest.c
|
||
@/tmp/ps5upload-ptrace-recovery-selftest
|
||
@echo "✓ ptrace timeout recovery never resumes injected registers"
|
||
@echo "Running app.db raw-scan self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-appdb-scan-selftest \
|
||
$(PAYLOAD_DIR)/tests/appdb_scan_selftest.c
|
||
@/tmp/ps5upload-appdb-scan-selftest
|
||
@echo "✓ app.db scan recovers titles without sqlite"
|
||
@echo "Running content-database self-test (host build)..."
|
||
@mkdir -p /tmp/ps5upload-cdb
|
||
@cc -O1 -w -c -o /tmp/ps5upload-cdb/sqlite3.o \
|
||
-DSQLITE_OS_UNIX=1 -DSQLITE_THREADSAFE=1 -DSQLITE_TEMP_STORE=2 \
|
||
-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_OMIT_DEPRECATED=1 \
|
||
-DSQLITE_OMIT_JSON=1 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DQS=0 \
|
||
-DSQLITE_UNTESTABLE=1 \
|
||
$(PAYLOAD_DIR)/third_party/sqlite3/sqlite3.c
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/include \
|
||
-I$(PAYLOAD_DIR)/third_party/sqlite3 \
|
||
-DCONTENT_DB_APP='"/tmp/ps5upload-cdb/app.db"' \
|
||
-DCONTENT_DB_APPINFO='"/tmp/ps5upload-cdb/appinfo.db"' \
|
||
-o /tmp/ps5upload-content-db-selftest \
|
||
$(PAYLOAD_DIR)/src/content_db.c \
|
||
$(PAYLOAD_DIR)/tests/content_db_selftest.c \
|
||
/tmp/ps5upload-cdb/sqlite3.o
|
||
@/tmp/ps5upload-content-db-selftest
|
||
@echo "✓ content databases read via SQL, with guarded writes"
|
||
@echo "Running Game Activity JSON self-test (host build)..."
|
||
@mkdir -p /tmp/ps5upload-actjson
|
||
@cc -O2 -Wall -Wextra -Werror -pthread \
|
||
-I$(PAYLOAD_DIR)/include -I$(PAYLOAD_DIR)/tests/hostshim \
|
||
-I$(PAYLOAD_DIR)/third_party/sqlite3 \
|
||
-DKERN_PROC_PROC=8 \
|
||
-DCONTENT_DB_APP='"/tmp/ps5upload-actjson/app.db"' \
|
||
-DCONTENT_DB_APPINFO='"/tmp/ps5upload-actjson/appinfo.db"' \
|
||
-DACTIVITY_FILE='"/tmp/ps5upload-actjson/activity.json"' \
|
||
-DACTIVITY_DIR='"/tmp/ps5upload-actjson"' \
|
||
-DACTIVITY_SL2_DB='"/tmp/ps5upload-actjson/sl2_log.db"' \
|
||
-o /tmp/ps5upload-actjson/selftest \
|
||
$(PAYLOAD_DIR)/src/content_db.c \
|
||
$(PAYLOAD_DIR)/tests/activity_json_selftest.c \
|
||
/tmp/ps5upload-cdb/sqlite3.o
|
||
@/tmp/ps5upload-actjson/selftest
|
||
@echo "✓ every Game Activity tab names its titles, in the keys the engine reads"
|
||
@echo "Running FTP wire-format self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-ftp-format-selftest \
|
||
$(PAYLOAD_DIR)/tests/ftp_format_selftest.c
|
||
@/tmp/ps5upload-ftp-format-selftest
|
||
@echo "✓ FTP PASV/EPSV/LIST replies match the shapes clients parse"
|
||
@echo "Running FTP lifecycle self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-ftp-lifecycle-selftest \
|
||
$(PAYLOAD_DIR)/tests/ftp_lifecycle_selftest.c
|
||
@/tmp/ps5upload-ftp-lifecycle-selftest
|
||
@echo "✓ FTP stop/start drains sessions without stale listeners or fd reuse"
|
||
@echo "Running timed initializer serialization self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-timed-init-selftest \
|
||
$(PAYLOAD_DIR)/tests/timed_init_selftest.c
|
||
@/tmp/ps5upload-timed-init-selftest
|
||
@echo "✓ timed-out Sony initialization cannot overlap a retry"
|
||
@echo "Running installer daemon logic self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/installer \
|
||
-o /tmp/ps5upload-installer-selftest \
|
||
$(PAYLOAD_DIR)/tests/installer_selftest.c \
|
||
$(PAYLOAD_DIR)/installer/protocol.c \
|
||
$(PAYLOAD_DIR)/installer/pathsafe.c \
|
||
$(PAYLOAD_DIR)/installer/jobs.c
|
||
@/tmp/ps5upload-installer-selftest
|
||
@echo "✓ installer request parse, replies, path safety and admission are sound"
|
||
@echo "Running installer HTTP/coverage self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/installer \
|
||
-o /tmp/ps5upload-installer-http-selftest \
|
||
$(PAYLOAD_DIR)/tests/installer_http_selftest.c \
|
||
$(PAYLOAD_DIR)/installer/http_range.c \
|
||
$(PAYLOAD_DIR)/installer/jobs.c
|
||
@/tmp/ps5upload-installer-http-selftest
|
||
@echo "✓ installer range parse, headers, coverage and job ring are sound"
|
||
@echo "Running installer loopback server self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/installer \
|
||
-o /tmp/ps5upload-installer-loopback-selftest \
|
||
$(PAYLOAD_DIR)/tests/installer_loopback_selftest.c \
|
||
$(PAYLOAD_DIR)/installer/loopback.c \
|
||
$(PAYLOAD_DIR)/installer/http_range.c \
|
||
$(PAYLOAD_DIR)/installer/pathsafe.c \
|
||
$(PAYLOAD_DIR)/installer/jobs.c
|
||
@/tmp/ps5upload-installer-loopback-selftest
|
||
@echo "✓ loopback server ranges, 404s foreign paths and .crc, serves a real sidecar"
|
||
@echo "Running installer hint-table self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/installer \
|
||
-o /tmp/ps5upload-installer-hints-selftest \
|
||
$(PAYLOAD_DIR)/tests/installer_hints_selftest.c \
|
||
$(PAYLOAD_DIR)/installer/hints.c
|
||
@/tmp/ps5upload-installer-hints-selftest
|
||
@echo "✓ installer hint table maps known Sony codes without third-party names"
|
||
@echo "Running param.json SDK-rewrite self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-sdk-param-selftest \
|
||
$(PAYLOAD_DIR)/tests/sdk_param_selftest.c
|
||
@/tmp/ps5upload-sdk-param-selftest
|
||
@echo "✓ SDK version rewrite reports what it actually changed"
|
||
@echo "Running ELF param-segment self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-elf-param-selftest \
|
||
$(PAYLOAD_DIR)/tests/elf_param_selftest.c
|
||
@/tmp/ps5upload-elf-param-selftest
|
||
@echo "✓ SDK patcher targets param segments, not stray magic bytes"
|
||
@echo "Running sandbox library-overlay unmount self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-sandbox-unmount-selftest \
|
||
$(PAYLOAD_DIR)/tests/sandbox_unmount_selftest.c
|
||
@/tmp/ps5upload-sandbox-unmount-selftest
|
||
@echo "✓ only a game sandbox's unionfs lib overlay may be force-unmounted"
|
||
@echo "Running SDK version-pair self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-sdk-pairs-selftest \
|
||
$(PAYLOAD_DIR)/tests/sdk_pairs_selftest.c
|
||
@/tmp/ps5upload-sdk-pairs-selftest
|
||
@echo "✓ an SDK downgrade writes both halves of the version pair"
|
||
@echo "Running libc.prx backport-patch self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-libc-backport-selftest \
|
||
$(PAYLOAD_DIR)/tests/libc_backport_selftest.c
|
||
@/tmp/ps5upload-libc-backport-selftest
|
||
@echo "✓ the libc.prx symbol swap is same-length and idempotent"
|
||
@echo "Running fakelib overlay path/refusal self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-fakelib-overlay-selftest \
|
||
$(PAYLOAD_DIR)/tests/fakelib_overlay_selftest.c
|
||
@/tmp/ps5upload-fakelib-overlay-selftest
|
||
@echo "✓ fakelib overlay accepts only game sandbox library targets"
|
||
@echo "Running sys_time settimeofday-fallback self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-sys-time-fallback-selftest \
|
||
$(PAYLOAD_DIR)/src/sys_time.c \
|
||
$(PAYLOAD_DIR)/tests/sys_time_fallback_selftest.c
|
||
@/tmp/ps5upload-sys-time-fallback-selftest
|
||
@echo "✓ clock set falls back to settimeofday only when the SCE path did not take"
|
||
@echo "Running cross-device rename-guard self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-cross-device-selftest \
|
||
$(PAYLOAD_DIR)/tests/cross_device_selftest.c
|
||
@/tmp/ps5upload-cross-device-selftest
|
||
@echo "✓ cross-mount renames are refused before they can panic the kernel"
|
||
@echo "Running Remote Play registry-key self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -o /tmp/ps5upload-rp-keys-selftest \
|
||
$(PAYLOAD_DIR)/tests/rp_keys_selftest.c
|
||
@/tmp/ps5upload-rp-keys-selftest
|
||
@echo "✓ Remote Play keys match Sony's entry-number formula"
|
||
@echo "Running SDK Changer filesystem-safety self-test (host build)..."
|
||
@cc -O2 -Wall -Wextra -Werror -I$(PAYLOAD_DIR)/include \
|
||
-o /tmp/ps5upload-sdk-changer-file-selftest \
|
||
$(PAYLOAD_DIR)/tests/sdk_changer_file_selftest.c
|
||
@/tmp/ps5upload-sdk-changer-file-selftest
|
||
@echo "✓ SDK Changer patches only tracked sources and restores durable backups"
|
||
|
||
test-client: setup-client
|
||
@echo "Testing client build..."
|
||
@cd $(CLIENT_DIR) && npm run typecheck
|
||
@cd $(CLIENT_DIR) && npm run lint
|
||
@cd $(CLIENT_DIR) && npm test
|
||
@cd $(CLIENT_DIR) && npm run build:vite
|
||
@if [ ! -d "$(CLIENT_DIR)/dist" ]; then \
|
||
echo "ERROR: client build failed - dist directory not found."; \
|
||
exit 1; \
|
||
fi
|
||
@echo "✓ Client checks passed"
|
||
|
||
test-client-coverage:
|
||
@$(MAKE) coverage-client
|
||
|
||
verify: test
|
||
@echo "Running client packaging validation (Tauri)..."
|
||
@cd $(CLIENT_DIR) && npm run build
|
||
@echo "✓ Client packaging validation passed"
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Auto-launch (engine starts on OS login)
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
install-engine: setup-engine
|
||
@echo "Installing engine auto-launch for this platform..."
|
||
@case "$$(uname -s)" in \
|
||
Linux) bash scripts/autolaunch/install-linux.sh ;; \
|
||
Darwin) bash scripts/autolaunch/install-macos.sh ;; \
|
||
*) echo "Use scripts/autolaunch/install-windows.ps1 on Windows" ;; \
|
||
esac
|
||
|
||
uninstall-engine:
|
||
@case "$$(uname -s)" in \
|
||
Linux) bash scripts/autolaunch/uninstall-linux.sh ;; \
|
||
Darwin) bash scripts/autolaunch/uninstall-macos.sh ;; \
|
||
*) echo "Use scripts/autolaunch/uninstall-windows.ps1 on Windows" ;; \
|
||
esac
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Run
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
run-engine: setup-engine
|
||
@echo "Starting ps5upload-engine (standalone; kept for diagnostics —"
|
||
@echo "in normal use the Tauri client spawns the engine automatically)..."
|
||
@cd $(ENGINE_DIR) && $(CARGO) run --release -p ps5upload-engine
|
||
|
||
# `run-client` is the one-command dev flow. `npm run dev` in client/ is wired
|
||
# to `tauri dev`, which starts Vite + the Rust main process. The Rust setup
|
||
# hook in client/src-tauri/src/engine.rs spawns ps5upload-engine as a child
|
||
# of the window process; killing the window tears the engine down cleanly.
|
||
run-client: setup-client _payload-if-ready _check-tauri-system-deps _engine-release _kill-stale-client
|
||
@echo "Starting PS5 Upload client (Tauri + Rust + Vite)..."
|
||
@if [ -z "$$DISPLAY" ] && [ -z "$$WAYLAND_DISPLAY" ] && [ "$$(uname -s)" = "Linux" ]; then \
|
||
if command -v xvfb-run >/dev/null 2>&1; then \
|
||
echo "No display detected, using xvfb-run..."; \
|
||
cd $(CLIENT_DIR) && xvfb-run --auto-servernum npm run dev; \
|
||
else \
|
||
echo "ERROR: No display server found ($$DISPLAY / $$WAYLAND_DISPLAY not set)."; \
|
||
echo " Install xvfb to run headless: sudo apt-get install -y xvfb"; \
|
||
exit 1; \
|
||
fi; \
|
||
else \
|
||
cd $(CLIENT_DIR) && npm run dev; \
|
||
fi
|
||
|
||
# Pre-flight system-library check for `tauri dev`. Tauri's Rust crates
|
||
# (gdk-sys, gio-sys, javascriptcore-rs-sys, soup3-sys, ...) link against
|
||
# native GTK/WebKit on Linux, system WebKit on macOS, and MSVC + WebView2
|
||
# on Windows. Without them, cargo gets ~30s into compilation before
|
||
# emitting a wall of pkg-config errors. This target fails fast with a
|
||
# copy-pasteable install command for the host OS.
|
||
_check-tauri-system-deps:
|
||
@os="$$(uname -s)"; \
|
||
case "$$os" in \
|
||
Linux) \
|
||
missing=""; \
|
||
command -v pkg-config >/dev/null 2>&1 || missing="$$missing pkg-config"; \
|
||
if command -v pkg-config >/dev/null 2>&1; then \
|
||
pkg-config --exists webkit2gtk-4.1 2>/dev/null || missing="$$missing libwebkit2gtk-4.1-dev"; \
|
||
pkg-config --exists gtk+-3.0 2>/dev/null || missing="$$missing libgtk-3-dev"; \
|
||
pkg-config --exists librsvg-2.0 2>/dev/null || missing="$$missing librsvg2-dev"; \
|
||
pkg-config --exists ayatana-appindicator3-0.1 2>/dev/null || missing="$$missing libayatana-appindicator3-dev"; \
|
||
fi; \
|
||
if [ -n "$$missing" ]; then \
|
||
echo "ERROR: Tauri requires Linux system libraries that are not installed."; \
|
||
echo " Missing:$$missing"; \
|
||
echo ""; \
|
||
if command -v apt-get >/dev/null 2>&1; then \
|
||
echo " Install (Debian / Ubuntu / WSL Ubuntu):"; \
|
||
echo " sudo apt update && sudo apt install -y \\"; \
|
||
echo " libwebkit2gtk-4.1-dev build-essential curl wget file \\"; \
|
||
echo " libxdo-dev libssl-dev libayatana-appindicator3-dev \\"; \
|
||
echo " librsvg2-dev pkg-config"; \
|
||
elif command -v dnf >/dev/null 2>&1; then \
|
||
echo " Install (Fedora / RHEL):"; \
|
||
echo " sudo dnf install -y webkit2gtk4.1-devel openssl-devel \\"; \
|
||
echo " curl wget file libappindicator-gtk3-devel librsvg2-devel \\"; \
|
||
echo " pkgconf-pkg-config @development-tools"; \
|
||
elif command -v pacman >/dev/null 2>&1; then \
|
||
echo " Install (Arch / Manjaro):"; \
|
||
echo " sudo pacman -S --needed webkit2gtk-4.1 base-devel curl wget \\"; \
|
||
echo " file openssl libappindicator-gtk3 librsvg pkgconf"; \
|
||
else \
|
||
echo " See https://tauri.app/start/prerequisites/ for your distro."; \
|
||
fi; \
|
||
exit 1; \
|
||
fi; \
|
||
;; \
|
||
Darwin) \
|
||
if ! xcode-select -p >/dev/null 2>&1; then \
|
||
echo "ERROR: Xcode Command Line Tools are not installed (Tauri needs the system WebKit framework)."; \
|
||
echo " Install with: xcode-select --install"; \
|
||
exit 1; \
|
||
fi; \
|
||
;; \
|
||
MINGW*|MSYS*|CYGWIN*) \
|
||
if ! command -v cl >/dev/null 2>&1 && ! command -v link >/dev/null 2>&1; then \
|
||
echo "ERROR: MSVC build tools (cl.exe / link.exe) not on PATH."; \
|
||
echo " Install 'Visual Studio Build Tools 2022' with the C++ workload:"; \
|
||
echo " https://visualstudio.microsoft.com/visual-cpp-build-tools/"; \
|
||
echo " Then run this from a 'Developer Command Prompt' or 'x64 Native Tools' shell."; \
|
||
echo " WebView2 is pre-installed on Windows 11; on Windows 10 install via:"; \
|
||
echo " https://developer.microsoft.com/microsoft-edge/webview2/"; \
|
||
exit 1; \
|
||
fi; \
|
||
;; \
|
||
esac
|
||
|
||
# Kill any stale ps5upload processes + Vite on :1420 that belongs to us.
|
||
# Narrow enough not to touch unrelated node servers. Invoked as a dep of
|
||
# run-client so repeated launches after a mis-terminated session don't
|
||
# fail with "Port 1420 already in use".
|
||
#
|
||
# Implementation lives in scripts/kill-stale-client.mjs because the native
|
||
# tooling differs across the three supported host OSes (pkill/lsof/ps on
|
||
# Linux+macOS vs. taskkill/netstat/PowerShell on Windows), and a pure-shell
|
||
# recipe doing both branches becomes unreadable. Node is already a hard
|
||
# dependency for run-client (via setup-client), so this adds no new tools.
|
||
.PHONY: _kill-stale-client
|
||
_kill-stale-client:
|
||
@node scripts/kill-stale-client.mjs
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Clean
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
clean: clean-payload clean-engine clean-client
|
||
@echo "✓ All clean"
|
||
|
||
clean-payload:
|
||
@echo "Cleaning payload artifacts..."
|
||
@if [ -d "$(PAYLOAD_DIR)" ]; then $(MAKE) -C $(PAYLOAD_DIR) clean; fi
|
||
@echo "✓ Payload cleaned"
|
||
|
||
clean-engine:
|
||
@echo "Cleaning Rust engine artifacts..."
|
||
@if [ -d "$(ENGINE_DIR)" ]; then cd $(ENGINE_DIR) && $(CARGO) clean; fi
|
||
@echo "✓ Engine cleaned"
|
||
|
||
clean-client:
|
||
@echo "Cleaning client artifacts..."
|
||
@rm -rf $(CLIENT_DIR)/dist
|
||
@rm -rf $(CLIENT_DIR)/release
|
||
@rm -rf $(CLIENT_DIR)/node_modules/.vite
|
||
@rm -rf $(CLIENT_DIR)/src-tauri/target
|
||
@echo "✓ Client cleaned"
|
||
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
# Development / Utility
|
||
#──────────────────────────────────────────────────────────────────────────────
|
||
|
||
info:
|
||
@echo "PS5 Upload - Build Information"
|
||
@echo ""
|
||
@echo "Environment:"
|
||
@echo " Pinned PS5 SDK: $(PS5_SDK_TAG)"
|
||
@echo " PS5_PAYLOAD_SDK: $(PS5_PAYLOAD_SDK)"
|
||
ifeq ($(shell uname -s),Darwin)
|
||
@echo " LLVM_CONFIG: $(LLVM_CONFIG)"
|
||
endif
|
||
@echo " Rust: $$(rustc --version 2>/dev/null || echo 'Not found')"
|
||
@echo " Cargo: $$($(CARGO) --version 2>/dev/null || echo 'Not found')"
|
||
@echo " Node: $$(node --version 2>/dev/null || echo 'Not found')"
|
||
@echo " npm: $$(npm --version 2>/dev/null || echo 'Not found')"
|
||
@echo " Make: $$(make --version | head -1)"
|
||
@echo ""
|
||
@echo "Artifacts:"
|
||
@echo " Payload:"
|
||
@[ -f "$(PAYLOAD_ELF)" ] && echo " ✓ $(PAYLOAD_ELF)" || echo " ✗ missing (run: make payload)"
|
||
@echo " Engine target dir:"
|
||
@[ -d "$(ENGINE_DIR)/target" ] && echo " ✓ $(ENGINE_DIR)/target" || echo " ✗ missing (run: make engine)"
|
||
@echo " Client build:"
|
||
@[ -d "$(CLIENT_DIR)/dist" ] && echo " ✓ $(CLIENT_DIR)/dist" || echo " ✗ missing (run: make client)"
|
||
@echo " Tauri bundles:"
|
||
@[ -d "$(CLIENT_DIR)/src-tauri/target/release/bundle" ] && echo " ✓ $(CLIENT_DIR)/src-tauri/target/release/bundle" || echo " ✗ missing (run: make dist)"
|
||
@echo ""
|
||
|
||
install-hooks:
|
||
@echo "Installing git hooks..."
|
||
@mkdir -p .git/hooks
|
||
@echo '#!/bin/bash' > .git/hooks/pre-commit
|
||
@echo 'echo "Checking documentation..."' >> .git/hooks/pre-commit
|
||
@echo 'git diff --cached --name-only | grep -E "\.(c|rs|py|h|md)$$" > /dev/null' >> .git/hooks/pre-commit
|
||
@echo 'if [ $$? -eq 0 ]; then' >> .git/hooks/pre-commit
|
||
@echo ' echo "Code or docs changed. Update README.md / FAQ.md / CHANGELOG.md if user-visible."' >> .git/hooks/pre-commit
|
||
@echo 'fi' >> .git/hooks/pre-commit
|
||
@chmod +x .git/hooks/pre-commit
|
||
@echo "✓ Git hooks installed"
|
||
|
||
# Convenience aliases
|
||
dev: run-client
|
||
start: run-client
|
||
|
||
release-post:
|
||
@node scripts/release-posts.js $(ARGS)
|