diff --git a/.gitignore b/.gitignore index 0d77332..e3771a9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ deps/* dev/windows/deps/ dev/macos/deps/ .idea +.cache/ +.qtcreator/ +CMakePresets.json *.pyc .DS_Store .*libreelec* diff --git a/CMakeLists.txt b/CMakeLists.txt index ce258c2..645724b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ option(CHECK_FOR_UPDATES "Enable checking for new versions of Jellyfin" OFF) option(OPENELEC "Make an OpenELEC build" OFF) option(LINUX_X11POWER "Enable non D-Bus screensaver management" OFF) option(USE_STATIC_MPVQT "Build MpvQt from bundled submodule instead of using system library" OFF) +option(MACOS_LAUNCH_WRAPPER "Wrap macOS binary in a shell script to block DYLD_FRAMEWORK_PATH injection" OFF) if (NOT CHECK_FOR_UPDATES) add_definitions(-DDISABLE_UPDATE_CHECK) diff --git a/CMakeModules/MacOSLaunchWrapper.cmake b/CMakeModules/MacOSLaunchWrapper.cmake new file mode 100644 index 0000000..6cbeba9 --- /dev/null +++ b/CMakeModules/MacOSLaunchWrapper.cmake @@ -0,0 +1,28 @@ +# Post-build script for Qt Creator on MacOS +# +# Qt Creator overwrites the DYLD_FRAMEWORK_PATH when launching a Mac binary. This +# may cause the wrong version of Qt to load when more than one version of Qt is present, +# for example the Homebrew version. +# +# This "one weird trick" fixes the issue by tricking Qt Creator into thinking we're +# loading a shell script instead of a Mac binary to prevent the path madness. +# +# Input variables: BINARY (path to the Mac binary just produced by the linker) + +if(NOT EXISTS "${BINARY}") + message(FATAL_ERROR "Binary not found: ${BINARY}") +endif() + +get_filename_component(BIN_NAME "${BINARY}" NAME) +set(REAL_BINARY "${BINARY}.bin") + +file(RENAME "${BINARY}" "${REAL_BINARY}") + +# Create a thin wrapper that execs the real binary. +file(WRITE "${BINARY}" +"#!/bin/bash\nexec \"$(dirname \"$0\")/${BIN_NAME}.bin\" \"$@\"\n") + +file(CHMOD "${BINARY}" + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) diff --git a/CMakeModules/QtConfiguration.cmake b/CMakeModules/QtConfiguration.cmake index 6a8dd16..39d2e48 100644 --- a/CMakeModules/QtConfiguration.cmake +++ b/CMakeModules/QtConfiguration.cmake @@ -5,6 +5,18 @@ if(WIN32) set(WINARCHSTR ARCHSTR windows-x86_64) endif(WIN32) +# Derive QTROOT from CMAKE_PREFIX_PATH if not explicitly set (e.g. when Qt Creator +# configures the project via its Kit rather than a CMake preset). +if("${QTROOT}" STREQUAL "") + foreach(_prefix ${CMAKE_PREFIX_PATH}) + if(EXISTS "${_prefix}/lib/cmake/Qt6/Qt6Config.cmake") + set(QTROOT "${_prefix}") + message(STATUS "QTROOT not set, derived from CMAKE_PREFIX_PATH: ${QTROOT}") + break() + endif() + endforeach() +endif() + if((NOT IS_DIRECTORY ${QTROOT}) AND (NOT "${QTROOT}" STREQUAL "")) # Write qt.conf in the Qt depends directory so that the Qt tools can find QML files set(QTCONFCONTENT "[Paths] diff --git a/dev/CMakePresets.json.in b/dev/CMakePresets.json.in new file mode 100644 index 0000000..f0a335e --- /dev/null +++ b/dev/CMakePresets.json.in @@ -0,0 +1,59 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "macos-dev", + "displayName": "macOS Development", + "description": "Debug build using deps from dev/macos/setup.sh", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_PREFIX_PATH": "@BREW_PREFIX@", + "QTROOT": "${sourceDir}/dev/macos/deps/qt/@QT_VERSION@/macos", + "USE_STATIC_MPVQT": "ON", + "MACOS_LAUNCH_WRAPPER": "ON" + } + }, + { + "name": "windows-dev", + "displayName": "Windows Development", + "description": "Debug build using deps from dev/windows/setup.bat", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "QTROOT": "${sourceDir}/dev/windows/deps/qt/@QT_VERSION@/msvc2022_64", + "MPV_INCLUDE_DIR": "${sourceDir}/dev/windows/deps/mpv/include", + "MPV_LIBRARY": "${sourceDir}/dev/windows/deps/mpv/libmpv-2.dll.lib", + "USE_STATIC_MPVQT": "ON" + } + }, + { + "name": "linux-dev", + "displayName": "Linux Development", + "description": "Debug build using system Qt and libraries", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "USE_STATIC_MPVQT": "ON" + } + } + ] +} diff --git a/dev/README.md b/dev/README.md index b6a0bae..d5079ab 100644 --- a/dev/README.md +++ b/dev/README.md @@ -1,5 +1,10 @@ # Development +After cloning this repo, you will need to initialize and update its submodules: +```bash +git submodule update --init --recursive +``` + Platform-specific build instructions: - [macOS](macos/README.md) - [Windows](windows/README.md) diff --git a/dev/macos/setup.sh b/dev/macos/setup.sh index 66a18a3..55de818 100755 --- a/dev/macos/setup.sh +++ b/dev/macos/setup.sh @@ -6,7 +6,7 @@ set -eu SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)" . "${SCRIPT_DIR}/common.sh" -echo "[1/4] Checking Xcode Command Line Tools..." +echo "[1/5] Checking Xcode Command Line Tools..." if ! xcode-select -p > /dev/null 2>&1; then echo "Installing Xcode Command Line Tools..." xcode-select --install @@ -14,16 +14,16 @@ if ! xcode-select -p > /dev/null 2>&1; then exit 0 fi -echo "[2/4] Checking Homebrew..." +echo "[2/5] Checking Homebrew..." if ! command -v brew > /dev/null; then echo "error: Homebrew not found. Install from https://brew.sh" >&2 exit 1 fi -echo "[3/4] Installing build tools..." +echo "[3/5] Installing build tools..." brew install aqtinstall mpv ninja cmake create-dmg -echo "[4/4] Installing Qt ${QT_VERSION}..." +echo "[4/5] Installing Qt ${QT_VERSION}..." if [ ! -d "${DEPS_DIR}/qt/${QT_VERSION}/macos" ]; then mkdir -p "${DEPS_DIR}/qt" (cd "${DEPS_DIR}" && aqt install-qt mac desktop "${QT_VERSION}" -m qtwebengine qtwebchannel qtpositioning -O "qt") @@ -31,5 +31,11 @@ else echo "Qt already installed, skipping" fi +echo "[5/5] Generating CMakePresets.json..." +BREW_PREFIX="$(brew --prefix)" +sed -e "s|@QT_VERSION@|${QT_VERSION}|g" \ + -e "s|@BREW_PREFIX@|${BREW_PREFIX}|g" \ + "${SCRIPT_DIR}/../CMakePresets.json.in" > "${PROJECT_ROOT}/CMakePresets.json" + echo "" echo "Setup complete. Run build.sh to build." diff --git a/dev/windows/setup.bat b/dev/windows/setup.bat index 0c64753..c6f938b 100644 --- a/dev/windows/setup.bat +++ b/dev/windows/setup.bat @@ -16,29 +16,29 @@ if not exist "%7ZIP%" ( if not exist "%DEPS_DIR%" mkdir "%DEPS_DIR%" -echo [1/13] Installing CMake... +echo [1/14] Installing CMake... winget install --id Kitware.CMake --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 echo Warning: CMake may already be installed -echo [2/13] Installing Ninja... +echo [2/14] Installing Ninja... winget install --id Ninja-build.Ninja --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 echo Warning: Ninja may already be installed -echo [3/13] Installing 7-Zip... +echo [3/14] Installing 7-Zip... winget install --id 7zip.7zip --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 echo Warning: 7-Zip may already be installed -echo [4/13] Installing aqtinstall... +echo [4/14] Installing aqtinstall... winget install --id miurahr.aqtinstall --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 echo Warning: aqtinstall may already be installed -echo [5/13] Installing Visual Studio 2022 Build Tools... +echo [5/14] Installing Visual Studio 2022 Build Tools... winget install --id Microsoft.VisualStudio.2022.BuildTools ^ --override "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" ^ --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 echo Warning: VS Build Tools may already be installed -echo [6/13] Installing MinGW (for gendef)... +echo [6/14] Installing MinGW (for gendef)... winget install --id mingw.mingw-w64-ucrt --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 ( echo Warning: mingw.mingw-w64-ucrt not available, trying msys2.msys2... @@ -46,11 +46,11 @@ if errorlevel 1 ( if errorlevel 1 echo Warning: MinGW/MSYS2 install skipped. gendef fallback may run during build. ) -echo [7/13] Installing Inno Setup... +echo [7/14] Installing Inno Setup... winget install --id JRSoftware.InnoSetup --accept-package-agreements --accept-source-agreements --silent if errorlevel 1 echo Warning: Inno Setup may already be installed -echo [8/13] Installing Qt %QT_VERSION%... +echo [8/14] Installing Qt %QT_VERSION%... if not exist "%DEPS_DIR%\qt\%QT_VERSION%\msvc2022_64" ( pushd "%DEPS_DIR%" aqt install-qt windows desktop %QT_VERSION% win64_msvc2022_64 -m qtwebengine qtwebchannel qtpositioning -O "qt" @@ -59,7 +59,7 @@ if not exist "%DEPS_DIR%\qt\%QT_VERSION%\msvc2022_64" ( echo Qt already installed, skipping ) -echo [9/13] Downloading libmpv AVX2... +echo [9/14] Downloading libmpv AVX2... if not exist "%DEPS_DIR%\mpv\libmpv-2.dll" ( echo Downloading libmpv... if exist "%DEPS_DIR%\mpv" rmdir /s /q "%DEPS_DIR%\mpv" @@ -100,7 +100,7 @@ if not exist "%DEPS_DIR%\mpv\libmpv-2.dll" ( echo libmpv already installed, skipping ) -echo [10/13] Downloading libmpv fallback (non-AVX2)... +echo [10/14] Downloading libmpv fallback (non-AVX2)... if not exist "%DEPS_DIR%\mpv-fallback\libmpv-2.dll" ( if exist "%DEPS_DIR%\mpv-fallback-tmp" rmdir /s /q "%DEPS_DIR%\mpv-fallback-tmp" curl -L "https://github.com/shinchiro/mpv-winbuild-cmake/releases/download/%MPV_RELEASE%/mpv-dev-x86_64-%MPV_VERSION%.7z" -o "%DEPS_DIR%\mpv-fallback.7z" @@ -137,7 +137,7 @@ if not exist "%DEPS_DIR%\mpv-fallback\libmpv-2.dll" ( echo libmpv fallback already installed, skipping ) -echo [11/13] Downloading VCRedist and WiX tools... +echo [11/14] Downloading VCRedist and WiX tools... if not exist "%DEPS_DIR%\vc_redist.x64.exe" ( echo Downloading vc_redist.x64.exe... curl -L -o "%DEPS_DIR%\vc_redist.x64.exe" https://aka.ms/vs/17/release/vc_redist.x64.exe @@ -150,7 +150,7 @@ if not exist "%DEPS_DIR%\wix" ( del "%DEPS_DIR%\wix.zip" ) -echo [12/13] Extracting VC runtime DLLs... +echo [12/14] Extracting VC runtime DLLs... if not exist "%DEPS_DIR%\vcruntime" ( echo Extracting VCRedist with dark.exe... mkdir "%DEPS_DIR%\vcruntime" @@ -167,7 +167,7 @@ if not exist "%DEPS_DIR%\vcruntime" ( echo VC runtime DLLs extracted to %DEPS_DIR%\vcruntime ) -echo [13/13] Generating mpv import library... +echo [13/14] Generating mpv import library... if exist "%DEPS_DIR%\mpv\libmpv-2.dll" ( if not exist "%DEPS_DIR%\mpv\libmpv-2.dll.lib" ( REM Find Visual Studio @@ -199,6 +199,9 @@ if exist "%DEPS_DIR%\mpv\libmpv-2.dll" ( ) ) +echo [14/14] Generating CMakePresets.json... +powershell -Command "(Get-Content '%SCRIPT_DIR%..\CMakePresets.json.in' -Raw) -replace '@QT_VERSION@','%QT_VERSION%' -replace '@BREW_PREFIX@','' | Set-Content '%PROJECT_ROOT%\CMakePresets.json' -NoNewline" + echo. echo Setup complete. Restart terminal to refresh PATH, then run build.bat endlocal diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf0afe6..c09dbd7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,12 +91,6 @@ if(APPLE) PROPERTIES COMPILE_FLAGS -fno-objc-arc ) - # copy the qt.conf so that when running PMP from a debugger it finds the plugins and QML files - if(EXISTS ${QTROOT}/bin/qt.conf) - add_resources(TARGET ${MAIN_TARGET} SOURCES ${QTROOT}/bin/qt.conf DEST ${INSTALL_RESOURCE_DIR}) - endif() - - set(ICON_FILE ${CMAKE_SOURCE_DIR}/bundle/osx/${MACOSX_BUNDLE_ICON_FILE}) list(APPEND RESOURCE_FILES ${ICON_FILE}) set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") @@ -106,6 +100,13 @@ if(WIN32) list(APPEND RESOURCE_FILES ${CMAKE_SOURCE_DIR}/bundle/win/iconres.rc) endif() +# Generate qt.conf so that dev builds find Qt plugins and QML files +# without needing DYLD_FRAMEWORK_PATH / QT_PLUGIN_PATH / PATH env vars. +if(NOT "${QTROOT}" STREQUAL "") + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/qt.conf "[Paths]\nPrefix=${QTROOT}\n") + add_resources(TARGET ${MAIN_TARGET} SOURCES ${CMAKE_CURRENT_BINARY_DIR}/qt.conf DEST ${INSTALL_RESOURCE_DIR}) +endif() + set(RESOURCE_ROOT .) if(NOT APPLE) set(rsrc_locations @@ -128,7 +129,7 @@ add_executable(${MAIN_TARGET} WIN32 MACOSX_BUNDLE ${MAIN_SRCS} ${BUNDLED_FILES} add_dependencies(${MAIN_TARGET} generate_resources) std_target_properties(${MAIN_TARGET}) if(APPLE) - set(_install_rpath "@executable_path/../Frameworks") + set(_install_rpath "@executable_path/../Frameworks;${QTROOT}/lib") else() set(_install_rpath "${QTROOT}/lib") endif() @@ -140,6 +141,17 @@ set_target_properties(${MAIN_TARGET} PROPERTIES copy_resources(${MAIN_TARGET}) clang_tidy(${MAIN_TARGET}) +# Workaround for Qt Creator locating incorrect Qt version via DYLD_FRAMEWORK_PATH. +# This forces the build to load qt.conf's paths instead, which should be correct. +if(APPLE AND MACOS_LAUNCH_WRAPPER) + add_custom_command(TARGET ${MAIN_TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} + -DBINARY=$ + -P ${CMAKE_SOURCE_DIR}/CMakeModules/MacOSLaunchWrapper.cmake + COMMENT "Creating macOS launch wrapper" + ) +endif() + # QML module for AOT compilation (eliminates runtime qmlcache) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ui/webview.qml PROPERTIES QT_RESOURCE_ALIAS webview.qml