Some checks failed
Lint & Test / test (push) Failing after 30s
release.yml: add fallback for existing releases on tag re-push. installer.nsi: add .onInit file lock check, use LaunchApp function instead of RUN_PARAMETERS to fix NSIS quoting bug. build-dist.ps1: copy start-hidden.vbs to dist scripts/. start-hidden.vbs: embedded Python fallback for installed/dev envs. Update ci-cd.md with version detection, NSIS best practices, local build testing, Gitea vs GitHub differences, troubleshooting. Update frontend.md with full entity type checklist and common pitfalls.
477 lines
16 KiB
Bash
477 lines
16 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Cross-build a portable Windows distribution of LedGrab from Linux.
|
|
#
|
|
# Downloads Windows embedded Python and win_amd64 wheels — no Wine or
|
|
# Windows runner needed. Produces the same ZIP as build-dist.ps1.
|
|
#
|
|
# Usage:
|
|
# ./build-dist-windows.sh [VERSION]
|
|
# ./build-dist-windows.sh v0.1.0-alpha.1
|
|
#
|
|
# Requirements: python3, pip, curl, unzip, zip, node/npm
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
BUILD_DIR="$SCRIPT_DIR/build"
|
|
DIST_NAME="LedGrab"
|
|
DIST_DIR="$BUILD_DIR/$DIST_NAME"
|
|
SERVER_DIR="$SCRIPT_DIR/server"
|
|
PYTHON_DIR="$DIST_DIR/python"
|
|
APP_DIR="$DIST_DIR/app"
|
|
PYTHON_VERSION="${PYTHON_VERSION:-3.11.9}"
|
|
|
|
# ── Version detection ────────────────────────────────────────
|
|
|
|
VERSION="${1:-}"
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
VERSION=$(git describe --tags --exact-match 2>/dev/null || true)
|
|
fi
|
|
if [ -z "$VERSION" ]; then
|
|
VERSION="${GITEA_REF_NAME:-${GITHUB_REF_NAME:-}}"
|
|
fi
|
|
if [ -z "$VERSION" ]; then
|
|
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' "$SERVER_DIR/src/wled_controller/__init__.py" 2>/dev/null || echo "0.0.0")
|
|
fi
|
|
|
|
VERSION_CLEAN="${VERSION#v}"
|
|
ZIP_NAME="LedGrab-v${VERSION_CLEAN}-win-x64.zip"
|
|
|
|
echo "=== Cross-building LedGrab v${VERSION_CLEAN} (Windows from Linux) ==="
|
|
echo " Embedded Python: $PYTHON_VERSION"
|
|
echo " Output: build/$ZIP_NAME"
|
|
echo ""
|
|
|
|
# ── Clean ────────────────────────────────────────────────────
|
|
|
|
if [ -d "$DIST_DIR" ]; then
|
|
echo "[1/9] Cleaning previous build..."
|
|
rm -rf "$DIST_DIR"
|
|
fi
|
|
mkdir -p "$DIST_DIR"
|
|
|
|
# ── Download Windows embedded Python ─────────────────────────
|
|
|
|
PYTHON_ZIP_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-embed-amd64.zip"
|
|
PYTHON_ZIP_PATH="$BUILD_DIR/python-embed-win.zip"
|
|
|
|
echo "[2/9] Downloading Windows embedded Python ${PYTHON_VERSION}..."
|
|
if [ ! -f "$PYTHON_ZIP_PATH" ]; then
|
|
curl -sL "$PYTHON_ZIP_URL" -o "$PYTHON_ZIP_PATH"
|
|
fi
|
|
mkdir -p "$PYTHON_DIR"
|
|
unzip -qo "$PYTHON_ZIP_PATH" -d "$PYTHON_DIR"
|
|
|
|
# ── Patch ._pth to enable site-packages ──────────────────────
|
|
|
|
echo "[3/9] Patching Python path configuration..."
|
|
PTH_FILE=$(ls "$PYTHON_DIR"/python*._pth 2>/dev/null | head -1)
|
|
if [ -z "$PTH_FILE" ]; then
|
|
echo "ERROR: Could not find python*._pth in $PYTHON_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Uncomment 'import site', add Lib\site-packages and app source path
|
|
sed -i 's/^#\s*import site/import site/' "$PTH_FILE"
|
|
if ! grep -q 'Lib\\site-packages' "$PTH_FILE"; then
|
|
echo 'Lib\site-packages' >> "$PTH_FILE"
|
|
fi
|
|
# Embedded Python ._pth overrides PYTHONPATH, so we must add the app
|
|
# source directory here for wled_controller to be importable
|
|
if ! grep -q '\.\./app/src' "$PTH_FILE"; then
|
|
echo '../app/src' >> "$PTH_FILE"
|
|
fi
|
|
echo " Patched $(basename "$PTH_FILE")"
|
|
|
|
# ── Bundle tkinter into embedded Python ───────────────────────
|
|
# Embedded Python doesn't include tkinter. We download the individual
|
|
# MSI packages from python.org (tcltk.msi + lib.msi) and extract them
|
|
# using msiextract (from msitools).
|
|
|
|
echo "[4/9] Bundling tkinter for screen overlay support..."
|
|
|
|
TK_EXTRACT="$BUILD_DIR/tk-extract"
|
|
rm -rf "$TK_EXTRACT"
|
|
mkdir -p "$TK_EXTRACT"
|
|
|
|
MSI_BASE="https://www.python.org/ftp/python/${PYTHON_VERSION}/amd64"
|
|
|
|
# Download tcltk.msi (contains _tkinter.pyd, tcl/tk DLLs, tcl8.6/, tk8.6/)
|
|
TCLTK_MSI="$BUILD_DIR/tcltk.msi"
|
|
if [ ! -f "$TCLTK_MSI" ]; then
|
|
curl -sL "$MSI_BASE/tcltk.msi" -o "$TCLTK_MSI"
|
|
fi
|
|
|
|
# Download lib.msi (contains tkinter/ Python package in the stdlib)
|
|
LIB_MSI="$BUILD_DIR/lib.msi"
|
|
if [ ! -f "$LIB_MSI" ]; then
|
|
curl -sL "$MSI_BASE/lib.msi" -o "$LIB_MSI"
|
|
fi
|
|
|
|
if command -v msiextract &>/dev/null; then
|
|
# Extract both MSIs
|
|
(cd "$TK_EXTRACT" && msiextract "$TCLTK_MSI" 2>/dev/null)
|
|
(cd "$TK_EXTRACT" && msiextract "$LIB_MSI" 2>/dev/null)
|
|
|
|
# Copy _tkinter.pyd
|
|
TKINTER_PYD=$(find "$TK_EXTRACT" -name "_tkinter.pyd" 2>/dev/null | head -1)
|
|
if [ -n "$TKINTER_PYD" ]; then
|
|
cp "$TKINTER_PYD" "$PYTHON_DIR/"
|
|
echo " Copied _tkinter.pyd"
|
|
else
|
|
echo " WARNING: _tkinter.pyd not found in tcltk.msi"
|
|
fi
|
|
|
|
# Copy Tcl/Tk DLLs
|
|
for dll in tcl86t.dll tk86t.dll; do
|
|
DLL_PATH=$(find "$TK_EXTRACT" -name "$dll" 2>/dev/null | head -1)
|
|
if [ -n "$DLL_PATH" ]; then
|
|
cp "$DLL_PATH" "$PYTHON_DIR/"
|
|
echo " Copied $dll"
|
|
fi
|
|
done
|
|
|
|
# Copy tkinter Python package
|
|
TKINTER_PKG=$(find "$TK_EXTRACT" -type d -name "tkinter" 2>/dev/null | head -1)
|
|
if [ -n "$TKINTER_PKG" ]; then
|
|
mkdir -p "$PYTHON_DIR/Lib"
|
|
cp -r "$TKINTER_PKG" "$PYTHON_DIR/Lib/tkinter"
|
|
echo " Copied tkinter/ package"
|
|
fi
|
|
|
|
# Copy tcl/tk data directories
|
|
for tcldir in tcl8.6 tk8.6; do
|
|
TCL_PATH=$(find "$TK_EXTRACT" -type d -name "$tcldir" 2>/dev/null | head -1)
|
|
if [ -n "$TCL_PATH" ]; then
|
|
cp -r "$TCL_PATH" "$PYTHON_DIR/$tcldir"
|
|
echo " Copied $tcldir/"
|
|
fi
|
|
done
|
|
|
|
echo " tkinter bundled successfully"
|
|
else
|
|
echo " WARNING: msiextract not found — skipping tkinter (install msitools)"
|
|
fi
|
|
|
|
# Add Lib to ._pth so tkinter package is importable
|
|
if ! grep -q '^Lib$' "$PTH_FILE"; then
|
|
echo 'Lib' >> "$PTH_FILE"
|
|
fi
|
|
|
|
rm -rf "$TK_EXTRACT"
|
|
|
|
# ── Download pip and install into embedded Python ────────────
|
|
|
|
echo "[5/9] Installing pip into embedded Python..."
|
|
SITE_PACKAGES="$PYTHON_DIR/Lib/site-packages"
|
|
mkdir -p "$SITE_PACKAGES"
|
|
|
|
# Download pip + setuptools wheels for Windows
|
|
pip download --quiet --dest "$BUILD_DIR/pip-wheels" \
|
|
--platform win_amd64 --python-version "3.11" \
|
|
--implementation cp --only-binary :all: \
|
|
pip setuptools 2>/dev/null || \
|
|
pip download --quiet --dest "$BUILD_DIR/pip-wheels" \
|
|
pip setuptools
|
|
|
|
# Unzip pip into site-packages (we just need it to exist, not to run)
|
|
for whl in "$BUILD_DIR/pip-wheels"/pip-*.whl; do
|
|
unzip -qo "$whl" -d "$SITE_PACKAGES"
|
|
done
|
|
for whl in "$BUILD_DIR/pip-wheels"/setuptools-*.whl; do
|
|
unzip -qo "$whl" -d "$SITE_PACKAGES"
|
|
done
|
|
|
|
# ── Download Windows wheels for all dependencies ─────────────
|
|
|
|
echo "[6/9] Downloading Windows dependencies..."
|
|
WHEEL_DIR="$BUILD_DIR/win-wheels"
|
|
mkdir -p "$WHEEL_DIR"
|
|
|
|
# Core dependencies (cross-platform, should have win_amd64 wheels)
|
|
# We parse pyproject.toml deps and download win_amd64 wheels.
|
|
# For packages that are pure Python, --only-binary will fail,
|
|
# so we fall back to allowing source for those.
|
|
DEPS=(
|
|
"fastapi>=0.115.0"
|
|
"uvicorn[standard]>=0.32.0"
|
|
"httpx>=0.27.2"
|
|
"mss>=9.0.2"
|
|
"Pillow>=10.4.0"
|
|
"numpy>=2.1.3"
|
|
"pydantic>=2.9.2"
|
|
"pydantic-settings>=2.6.0"
|
|
"PyYAML>=6.0.2"
|
|
"structlog>=24.4.0"
|
|
"python-json-logger>=3.1.0"
|
|
"python-dateutil>=2.9.0"
|
|
"python-multipart>=0.0.12"
|
|
"jinja2>=3.1.0"
|
|
"zeroconf>=0.131.0"
|
|
"pyserial>=3.5"
|
|
"psutil>=5.9.0"
|
|
"nvidia-ml-py>=12.0.0"
|
|
"sounddevice>=0.5"
|
|
"aiomqtt>=2.0.0"
|
|
"openrgb-python>=0.2.15"
|
|
# camera extra
|
|
"opencv-python-headless>=4.8.0"
|
|
)
|
|
|
|
# Windows-only deps
|
|
WIN_DEPS=(
|
|
"PyAudioWPatch>=0.2.12"
|
|
"winrt-Windows.UI.Notifications>=3.0.0"
|
|
"winrt-Windows.UI.Notifications.Management>=3.0.0"
|
|
"winrt-Windows.Foundation>=3.0.0"
|
|
"winrt-Windows.Foundation.Collections>=3.0.0"
|
|
"winrt-Windows.ApplicationModel>=3.0.0"
|
|
# System tray
|
|
"pystray>=0.19.0"
|
|
)
|
|
|
|
# Download cross-platform deps (prefer binary, allow source for pure Python)
|
|
for dep in "${DEPS[@]}"; do
|
|
pip download --quiet --dest "$WHEEL_DIR" \
|
|
--platform win_amd64 --python-version "3.11" \
|
|
--implementation cp --only-binary :all: \
|
|
"$dep" 2>/dev/null || \
|
|
pip download --quiet --dest "$WHEEL_DIR" \
|
|
--platform win_amd64 --python-version "3.11" \
|
|
--implementation cp \
|
|
"$dep" 2>/dev/null || \
|
|
pip download --quiet --dest "$WHEEL_DIR" "$dep" 2>/dev/null || \
|
|
echo " WARNING: Could not download $dep (skipping)"
|
|
done
|
|
|
|
# Download Windows-only deps (best effort)
|
|
for dep in "${WIN_DEPS[@]}"; do
|
|
pip download --quiet --dest "$WHEEL_DIR" \
|
|
--platform win_amd64 --python-version "3.11" \
|
|
--implementation cp --only-binary :all: \
|
|
"$dep" 2>/dev/null || \
|
|
pip download --quiet --dest "$WHEEL_DIR" \
|
|
--platform win_amd64 --python-version "3.11" \
|
|
--implementation cp \
|
|
"$dep" 2>/dev/null || \
|
|
echo " WARNING: Could not download $dep (skipping, Windows-only)"
|
|
done
|
|
|
|
# Install all downloaded wheels into site-packages
|
|
echo " Installing $(ls "$WHEEL_DIR"/*.whl 2>/dev/null | wc -l) wheels into site-packages..."
|
|
for whl in "$WHEEL_DIR"/*.whl; do
|
|
[ -f "$whl" ] && unzip -qo "$whl" -d "$SITE_PACKAGES" 2>/dev/null || true
|
|
done
|
|
|
|
# Also extract any .tar.gz source packages (pure Python only)
|
|
for sdist in "$WHEEL_DIR"/*.tar.gz; do
|
|
[ -f "$sdist" ] || continue
|
|
TMPDIR=$(mktemp -d)
|
|
tar -xzf "$sdist" -C "$TMPDIR" 2>/dev/null || continue
|
|
# Find the package directory inside and copy it
|
|
PKG_DIR=$(find "$TMPDIR" -maxdepth 2 -name "*.py" -path "*/setup.py" -exec dirname {} \; | head -1)
|
|
if [ -n "$PKG_DIR" ] && [ -d "$PKG_DIR/src" ]; then
|
|
cp -r "$PKG_DIR/src/"* "$SITE_PACKAGES/" 2>/dev/null || true
|
|
elif [ -n "$PKG_DIR" ]; then
|
|
# Copy any Python package directories
|
|
find "$PKG_DIR" -maxdepth 1 -type d -name "[a-z]*" -exec cp -r {} "$SITE_PACKAGES/" \; 2>/dev/null || true
|
|
fi
|
|
rm -rf "$TMPDIR"
|
|
done
|
|
|
|
# ── Reduce package size ────────────────────────────────────────
|
|
echo " Cleaning up to reduce size..."
|
|
|
|
# Remove caches, tests, docs, type stubs
|
|
find "$SITE_PACKAGES" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
find "$SITE_PACKAGES" -type d -name tests -exec rm -rf {} + 2>/dev/null || true
|
|
find "$SITE_PACKAGES" -type d -name test -exec rm -rf {} + 2>/dev/null || true
|
|
find "$SITE_PACKAGES" -type d -name "*.dist-info" -exec rm -rf {} + 2>/dev/null || true
|
|
find "$SITE_PACKAGES" -name "*.pyi" -delete 2>/dev/null || true
|
|
|
|
# Remove pip and setuptools (not needed at runtime)
|
|
rm -rf "$SITE_PACKAGES"/pip "$SITE_PACKAGES"/pip-* 2>/dev/null || true
|
|
rm -rf "$SITE_PACKAGES"/setuptools "$SITE_PACKAGES"/setuptools-* "$SITE_PACKAGES"/pkg_resources 2>/dev/null || true
|
|
rm -rf "$SITE_PACKAGES"/_distutils_hack 2>/dev/null || true
|
|
|
|
# Remove pythonwin GUI IDE and help file (ships with pywin32 but not needed)
|
|
rm -rf "$SITE_PACKAGES"/pythonwin 2>/dev/null || true
|
|
rm -f "$SITE_PACKAGES"/PyWin32.chm 2>/dev/null || true
|
|
|
|
# OpenCV: remove ffmpeg DLL (28MB, only for video file I/O, not camera),
|
|
# Haar cascades (2.6MB), and misc dev files
|
|
CV2_DIR="$SITE_PACKAGES/cv2"
|
|
if [ -d "$CV2_DIR" ]; then
|
|
rm -f "$CV2_DIR"/opencv_videoio_ffmpeg*.dll 2>/dev/null || true
|
|
rm -rf "$CV2_DIR/data" "$CV2_DIR/gapi" "$CV2_DIR/misc" "$CV2_DIR/utils" 2>/dev/null || true
|
|
rm -rf "$CV2_DIR/typing_stubs" "$CV2_DIR/typing" 2>/dev/null || true
|
|
fi
|
|
|
|
# numpy: remove tests, f2py, typing stubs
|
|
rm -rf "$SITE_PACKAGES/numpy/tests" "$SITE_PACKAGES/numpy/*/tests" 2>/dev/null || true
|
|
rm -rf "$SITE_PACKAGES/numpy/f2py" 2>/dev/null || true
|
|
rm -rf "$SITE_PACKAGES/numpy/typing" 2>/dev/null || true
|
|
rm -rf "$SITE_PACKAGES/numpy/_pyinstaller" 2>/dev/null || true
|
|
|
|
# Pillow: remove unused image plugins' test data
|
|
rm -rf "$SITE_PACKAGES/PIL/tests" 2>/dev/null || true
|
|
|
|
# winrt: remove type stubs
|
|
find "$SITE_PACKAGES/winrt" -name "*.pyi" -delete 2>/dev/null || true
|
|
|
|
# Remove wled_controller if it got installed
|
|
rm -rf "$SITE_PACKAGES"/wled_controller* "$SITE_PACKAGES"/wled*.dist-info 2>/dev/null || true
|
|
|
|
CLEANED_SIZE=$(du -sh "$SITE_PACKAGES" | cut -f1)
|
|
echo " Site-packages after cleanup: $CLEANED_SIZE"
|
|
|
|
WHEEL_COUNT=$(ls "$WHEEL_DIR"/*.whl 2>/dev/null | wc -l)
|
|
echo " Installed $WHEEL_COUNT packages"
|
|
|
|
# ── Build frontend ───────────────────────────────────────────
|
|
|
|
echo "[7/9] Building frontend bundle..."
|
|
(cd "$SERVER_DIR" && npm ci --loglevel error && npm run build) 2>&1 | {
|
|
grep -v 'RemoteException' || true
|
|
}
|
|
|
|
# ── Copy application files ───────────────────────────────────
|
|
|
|
echo "[8/9] Copying application files..."
|
|
mkdir -p "$APP_DIR"
|
|
|
|
cp -r "$SERVER_DIR/src" "$APP_DIR/src"
|
|
cp -r "$SERVER_DIR/config" "$APP_DIR/config"
|
|
mkdir -p "$DIST_DIR/data" "$DIST_DIR/logs"
|
|
|
|
# Clean up source maps and __pycache__
|
|
find "$APP_DIR" -name "*.map" -delete 2>/dev/null || true
|
|
find "$APP_DIR" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
|
|
# Pre-compile Python bytecode for faster startup
|
|
echo " Pre-compiling Python bytecode..."
|
|
python -m compileall -b -q "$APP_DIR/src" 2>/dev/null || true
|
|
python -m compileall -b -q "$SITE_PACKAGES" 2>/dev/null || true
|
|
|
|
# ── Create launcher ──────────────────────────────────────────
|
|
|
|
echo "[8b/9] Creating launcher and packaging..."
|
|
|
|
cat > "$DIST_DIR/LedGrab.bat" << LAUNCHER
|
|
@echo off
|
|
cd /d "%~dp0"
|
|
|
|
:: Set paths
|
|
set PYTHONPATH=%~dp0app\src
|
|
set WLED_CONFIG_PATH=%~dp0app\config\default_config.yaml
|
|
|
|
:: Create data directory if missing
|
|
if not exist "%~dp0data" mkdir "%~dp0data"
|
|
if not exist "%~dp0logs" mkdir "%~dp0logs"
|
|
|
|
:: Start the server (tray icon handles UI and exit)
|
|
"%~dp0python\pythonw.exe" -m wled_controller
|
|
LAUNCHER
|
|
|
|
# Convert launcher to Windows line endings
|
|
sed -i 's/$/\r/' "$DIST_DIR/LedGrab.bat"
|
|
|
|
# Copy hidden launcher VBS
|
|
mkdir -p "$DIST_DIR/scripts"
|
|
cp server/scripts/start-hidden.vbs "$DIST_DIR/scripts/"
|
|
|
|
# ── Create autostart scripts ─────────────────────────────────
|
|
|
|
cat > "$DIST_DIR/install-autostart.bat" << 'AUTOSTART'
|
|
@echo off
|
|
:: Install LedGrab to start automatically on Windows login
|
|
:: Creates a shortcut in the Startup folder
|
|
|
|
set SHORTCUT_NAME=LedGrab
|
|
set STARTUP_DIR=%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
|
|
set TARGET=%~dp0LedGrab.bat
|
|
set SHORTCUT=%STARTUP_DIR%\%SHORTCUT_NAME%.lnk
|
|
|
|
echo Installing LedGrab autostart...
|
|
|
|
:: Use PowerShell to create a proper shortcut
|
|
powershell -NoProfile -Command ^
|
|
"$ws = New-Object -ComObject WScript.Shell; ^
|
|
$sc = $ws.CreateShortcut('%SHORTCUT%'); ^
|
|
$sc.TargetPath = '%TARGET%'; ^
|
|
$sc.WorkingDirectory = '%~dp0'; ^
|
|
$sc.WindowStyle = 7; ^
|
|
$sc.Description = 'LedGrab ambient lighting server'; ^
|
|
$sc.Save()"
|
|
|
|
if exist "%SHORTCUT%" (
|
|
echo.
|
|
echo [OK] LedGrab will start automatically on login.
|
|
echo Shortcut: %SHORTCUT%
|
|
echo.
|
|
echo To remove: run uninstall-autostart.bat
|
|
) else (
|
|
echo.
|
|
echo [ERROR] Failed to create shortcut.
|
|
)
|
|
|
|
pause
|
|
AUTOSTART
|
|
sed -i 's/$/\r/' "$DIST_DIR/install-autostart.bat"
|
|
|
|
cat > "$DIST_DIR/uninstall-autostart.bat" << 'UNAUTOSTART'
|
|
@echo off
|
|
:: Remove LedGrab from Windows startup
|
|
|
|
set SHORTCUT=%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\LedGrab.lnk
|
|
|
|
if exist "%SHORTCUT%" (
|
|
del "%SHORTCUT%"
|
|
echo.
|
|
echo [OK] LedGrab autostart removed.
|
|
) else (
|
|
echo.
|
|
echo LedGrab autostart was not installed.
|
|
)
|
|
|
|
pause
|
|
UNAUTOSTART
|
|
sed -i 's/$/\r/' "$DIST_DIR/uninstall-autostart.bat"
|
|
|
|
# ── Create ZIP ───────────────────────────────────────────────
|
|
|
|
ZIP_PATH="$BUILD_DIR/$ZIP_NAME"
|
|
rm -f "$ZIP_PATH"
|
|
|
|
(cd "$BUILD_DIR" && zip -rq "$ZIP_NAME" "$DIST_NAME")
|
|
|
|
ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1)
|
|
|
|
# ── Build NSIS installer (if makensis is available) ──────────
|
|
|
|
SETUP_NAME="LedGrab-v${VERSION_CLEAN}-win-x64-setup.exe"
|
|
SETUP_PATH="$BUILD_DIR/$SETUP_NAME"
|
|
|
|
if command -v makensis &>/dev/null; then
|
|
echo "[9/9] Building NSIS installer..."
|
|
makensis -DVERSION="${VERSION_CLEAN}" "$SCRIPT_DIR/installer.nsi"
|
|
if [ -f "$SETUP_PATH" ]; then
|
|
SETUP_SIZE=$(du -h "$SETUP_PATH" | cut -f1)
|
|
echo " Installer: $SETUP_PATH ($SETUP_SIZE)"
|
|
else
|
|
echo " WARNING: makensis ran but installer not found at $SETUP_PATH"
|
|
fi
|
|
else
|
|
echo "[9/9] Skipping installer (makensis not found — install nsis to enable)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Build complete ==="
|
|
echo " ZIP: $ZIP_PATH ($ZIP_SIZE)"
|
|
if [ -f "$SETUP_PATH" ]; then
|
|
echo " Installer: $SETUP_PATH ($SETUP_SIZE)"
|
|
fi
|
|
echo ""
|