diff --git a/.gitignore b/.gitignore index fafcdb4..9683bee 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,10 @@ ui_*.h qrc_*.cpp *.moc +# ---- Windows packaging ---- +dist/ +*-Setup.exe + # ---- Go server ---- server/umt-tmdb-proxy server/*.exe diff --git a/build-linux.sh b/build-linux.sh new file mode 100755 index 0000000..cd86f1e --- /dev/null +++ b/build-linux.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# Build Ultimate Media Tracker on Linux (Qt6 + CMake). +# +# Usage: +# ./build-linux.sh configure + build (Release) +# ./build-linux.sh clean wipe the build dir first +# BUILD_TYPE=Debug ./build-linux.sh +# +set -euo pipefail +cd "$(dirname "$0")" + +BUILD_DIR="build" +BUILD_TYPE="${BUILD_TYPE:-Release}" + +if [[ "${1:-}" == "clean" ]]; then + echo "==> Removing $BUILD_DIR" + rm -rf "$BUILD_DIR" +fi + +# Prefer Ninja for a fresh configure; reuse the existing generator otherwise +# (passing -G on an already-configured dir errors out). +GEN=() +if [[ ! -f "$BUILD_DIR/CMakeCache.txt" ]] && command -v ninja >/dev/null 2>&1; then + GEN=(-G Ninja) +fi + +echo "==> Configuring ($BUILD_TYPE)" +cmake -S . -B "$BUILD_DIR" "${GEN[@]}" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" + +echo "==> Building" +cmake --build "$BUILD_DIR" -j + +echo +echo "==> Done. Binary: $BUILD_DIR/umt" diff --git a/build-windows.sh b/build-windows.sh new file mode 100755 index 0000000..790c461 --- /dev/null +++ b/build-windows.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# +# Build + package Ultimate Media Tracker for Windows. +# Run from the project root inside the MSYS2 *MINGW64* shell. +# +# Usage: +# ./build-windows.sh configure + build + bundle (+ installer) +# ./build-windows.sh clean wipe build/dist first +# +# Produces: +# dist/ portable build (zip & ship this folder) +# UltimateMediaTracker-Setup.exe NSIS installer (if makensis is installed) +# +set -euo pipefail +cd "$(dirname "$0")" + +BUILD_DIR="build-win" +DIST_DIR="dist" +EXE="umt.exe" +BUILD_TYPE="${BUILD_TYPE:-Release}" + +# Must run in the MINGW64 environment so CMake finds Qt under /mingw64. +if [[ "${MSYSTEM:-}" != "MINGW64" ]]; then + echo "ERROR: run this from the MSYS2 MINGW64 shell (MSYSTEM=MINGW64)." >&2 + exit 1 +fi + +WINDEPLOY="$(command -v windeployqt6 || command -v windeployqt || true)" +if [[ -z "$WINDEPLOY" ]]; then + echo "ERROR: windeployqt not found. Install with:" >&2 + echo " pacman -S mingw-w64-x86_64-qt6-tools" >&2 + exit 1 +fi + +if [[ "${1:-}" == "clean" ]]; then + echo "==> Removing $BUILD_DIR and $DIST_DIR" + rm -rf "$BUILD_DIR" "$DIST_DIR" +fi + +echo "==> Configuring ($BUILD_TYPE)" +# Set the generator only on a fresh dir; reusing one with -G errors out. +GEN=() +if [[ ! -f "$BUILD_DIR/CMakeCache.txt" ]]; then + GEN=(-G Ninja) +fi +cmake -S . -B "$BUILD_DIR" "${GEN[@]}" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" + +echo "==> Building" +cmake --build "$BUILD_DIR" -j + +echo "==> Assembling $DIST_DIR" +rm -rf "$DIST_DIR" +mkdir -p "$DIST_DIR" +cp "$BUILD_DIR/$EXE" "$DIST_DIR/" + +echo "==> Running windeployqt" +"$WINDEPLOY" --release --no-translations "$DIST_DIR/$EXE" + +echo "==> Bundling MinGW runtime + transitive dependencies" +# windeployqt does not copy third-party MinGW DLLs (harfbuzz, freetype, png, +# pcre2, zlib, glib, libgcc/libstdc++/winpthread ...). Resolve them via ldd and +# repeat a few passes so transitive deps (incl. those of the deployed plugins) +# are all picked up. +( cd "$DIST_DIR" + for _ in $(seq 1 6); do + find . \( -name '*.exe' -o -name '*.dll' \) -print0 \ + | xargs -0 -I{} ldd "{}" 2>/dev/null \ + | grep -io '/mingw64/bin/[^ ]*\.dll' \ + | sort -u \ + | while read -r f; do + [ -e "./$(basename "$f")" ] || cp "$f" . + done + done +) + +echo "==> Verifying dependencies" +if command -v ntldd >/dev/null 2>&1; then + missing="$(cd "$DIST_DIR" && ntldd -R "$EXE" 2>/dev/null | grep -i 'not found' || true)" + if [[ -n "$missing" ]]; then + echo "WARNING: unresolved dependencies remain:" >&2 + echo "$missing" >&2 + else + echo " all dependencies resolved." + fi +else + echo " (install mingw-w64-x86_64-ntldd to auto-verify)" +fi + +if command -v makensis >/dev/null 2>&1; then + echo "==> Building NSIS installer" + makensis installer.nsi + echo "==> Installer: UltimateMediaTracker-Setup.exe" +else + echo "NOTE: makensis not found; skipping installer." + echo " Install it with: pacman -S mingw-w64-x86_64-nsis" +fi + +echo +echo "==> Done. Portable build in: $DIST_DIR/" diff --git a/installer.nsi b/installer.nsi new file mode 100644 index 0000000..e175743 --- /dev/null +++ b/installer.nsi @@ -0,0 +1,78 @@ +; NSIS installer for Ultimate Media Tracker +; +; Build with: makensis installer.nsi +; Expects the packaged app in the dist\ folder (produced by build-windows.sh). + +Unicode true + +!define APPNAME "Ultimate Media Tracker" +!define COMPANY "UMT" +!define VERSION "1.0.0" +!define EXE "umt.exe" +!define SOURCEDIR "dist" +!define REGUNINST "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" + +!include "MUI2.nsh" + +Name "${APPNAME}" +OutFile "UltimateMediaTracker-Setup.exe" +InstallDir "$PROGRAMFILES64\${APPNAME}" +InstallDirRegKey HKLM "Software\${APPNAME}" "InstallDir" +RequestExecutionLevel admin + +VIProductVersion "${VERSION}.0" +VIAddVersionKey "ProductName" "${APPNAME}" +VIAddVersionKey "CompanyName" "${COMPANY}" +VIAddVersionKey "FileVersion" "${VERSION}" +VIAddVersionKey "FileDescription" "${APPNAME} Installer" +VIAddVersionKey "LegalCopyright" "${COMPANY}" + +!define MUI_ICON "resources\icons\appicon.ico" +!define MUI_UNICON "resources\icons\appicon.ico" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!define MUI_FINISHPAGE_RUN "$INSTDIR\${EXE}" +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +; First language listed is the default. +!insertmacro MUI_LANGUAGE "German" +!insertmacro MUI_LANGUAGE "English" + +Section "Install" + SetOutPath "$INSTDIR" + File /r "${SOURCEDIR}\*.*" + + ; Shortcuts + CreateDirectory "$SMPROGRAMS\${APPNAME}" + CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\${EXE}" + CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\${EXE}" + + ; Uninstaller + Add/Remove Programs entry + WriteUninstaller "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "Software\${APPNAME}" "InstallDir" "$INSTDIR" + WriteRegStr HKLM "${REGUNINST}" "DisplayName" "${APPNAME}" + WriteRegStr HKLM "${REGUNINST}" "DisplayVersion" "${VERSION}" + WriteRegStr HKLM "${REGUNINST}" "DisplayIcon" "$INSTDIR\${EXE}" + WriteRegStr HKLM "${REGUNINST}" "Publisher" "${COMPANY}" + WriteRegStr HKLM "${REGUNINST}" "UninstallString" "$INSTDIR\Uninstall.exe" + WriteRegDWORD HKLM "${REGUNINST}" "NoModify" 1 + WriteRegDWORD HKLM "${REGUNINST}" "NoRepair" 1 +SectionEnd + +Section "Uninstall" + Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" + RMDir "$SMPROGRAMS\${APPNAME}" + Delete "$DESKTOP\${APPNAME}.lnk" + + ; Removes the program files only. User library/settings live in + ; %APPDATA%\UMT and are intentionally left untouched. + RMDir /r "$INSTDIR" + + DeleteRegKey HKLM "${REGUNINST}" + DeleteRegKey HKLM "Software\${APPNAME}" +SectionEnd