Jellyfin Desktop macOS 构建指南:Qt 6 + mpv 的依赖安装、编译、测试与 DMG 打包全流程
Jellyfin Desktop macOS 构建指南:Qt 6 + mpv 的依赖安装、编译、测试与 DMG 打包全流程
Jellyfin Desktop 是 Jellyfin 媒体服务器的官方桌面客户端(仓库根目录见 README.md),本项目为其 C++/Qt 实现。本文以仓库内的 dev/macos/README.md 为骨架,结合 dev/macos/ 下的全套 shell 脚本、CMake 配置与源码,完整讲解在 macOS 上从零搭建 Qt 6 + mpv 开发环境、编译、运行、跑单测、制作 DMG 安装包的完整流程。读完本文,你将掌握这套脚本体系每一步背后的实际命令、环境变量与源码依据,能够独立完成一次 macOS 端到端构建与打包。
快速开始:四步跑通 macOS 开发流程
官方文档给出了极简的起步命令,整个过程分为「装依赖 → 编译 → 运行 → 测试」四步:
dev/macos/setup.sh # 首次使用:安装全部依赖(Xcode CLT、Homebrew 工具、Qt、mpv)
dev/macos/build.sh # 配置并编译
dev/macos/run.sh # 运行构建出的应用(参数会原样透传给应用)
dev/macos/test.sh # 运行单元测试(会自动把 Qt/mpv 加入 PATH)
其中 setup.sh 只需要在首次构建前运行一次;之后每次改代码只需执行 build.sh + run.sh 即可。注意:文档中只列出 setup.sh、build.sh、run.sh、test.sh 四个脚本,打包脚本 bundle.sh 用于生成分发的 DMG,详见下文「脚本体系详解」。
前置条件
在运行 setup.sh 之前,机器上需要先具备两样基础工具,其余全部由脚本自动安装:
- Xcode Command Line Tools:执行
xcode-select --install安装。setup.sh会先检查(调用xcode-select -p),若未安装则触发系统安装并提示「安装完成后请重新运行本脚本」后退出(见 setup.sh)。 - Homebrew:官方安装地址见 https://brew.sh。
setup.sh检查command -v brew,未找到直接报错退出(见 setup.sh)。
setup.sh 会安装其余全部依赖(见 setup.sh):
| 依赖 | 用途 |
|---|---|
| CMake | 项目构建系统(见根目录 CMakeLists.txt) |
| Ninja | CMake 指定的构建器(-G Ninja) |
| aqtinstall | 官方 Qt 安装工具,用于下载指定版本 Qt 及附加模块 |
| Qt 6.10.1 | 应用运行所需的 Qt 框架(含 WebEngine 模块) |
| create-dmg | 打包 DMG 安装镜像的工具 |
| mpv | 视频播放后端 libmpv(通过 mpvqt 集成) |
需要说明的是,brew install 一行安装的是 aqtinstall mpv ninja cmake create-dmg 五个包;Qt 本身不是通过 brew 安装,而是由 aqt install-qt 下载到项目本地目录(详见下文「目录结构」),这一点保证了 Qt 版本与项目的精确锁定。
目录结构:依赖、开发构建与发布构建的区分
文档明确了三个关键目录(均相对仓库根目录):
dev/macos/deps/—— 下载的依赖(主要是 Qt)。脚本中DEPS_DIR="${SCRIPT_DIR}/deps",即<仓库根>/dev/macos/deps(见 common.sh)。build/—— 构建输出目录,可以安全删除(干净构建即删它,见下文)。build/src/Jellyfin Desktop.app—— 开发构建产物(未打包 Qt 框架,供run.sh直接运行,运行依赖通过环境变量注入,见下文setup_runtime)。build/output/Jellyfin Desktop.app—— 发布构建产物(由bundle.sh经ninja install+macdeployqt打入 Qt 框架后的完整 .app bundle)。
Qt 的具体路径为 dev/macos/deps/qt/6.10.1/macos,这个路径同时被写入 CMake 配置(QTROOT)和运行环境(DYLD_FRAMEWORK_PATH),是整套脚本体系的核心坐标。
脚本体系详解:五个脚本 + 一个公共库
dev/macos/ 下共有 6 个文件,除 README 外,5 个脚本均以 common.sh 为公共依赖(每个脚本开头都 . "${SCRIPT_DIR}/common.sh")。
common.sh:统一变量与运行环境
common.sh 定义的关键变量:
| 变量 | 值 | 说明 |
|---|---|---|
QT_VERSION |
6.10.1 |
全项目锁定的 Qt 版本 |
DEPS_DIR |
dev/macos/deps |
依赖下载目录 |
BUILD_DIR |
<仓库根>/build |
构建输出目录 |
APP_NAME |
Jellyfin Desktop.app |
应用包名(含空格,引用时需带引号) |
PROJECT_ROOT |
仓库根目录 | 由脚本自身定位,不依赖调用者当前目录 |
它还提供了 setup_runtime() 函数,为未打包的开发构建注入 Qt 运行环境:
export DYLD_FRAMEWORK_PATH="${QTROOT}/lib"
export QT_PLUGIN_PATH="${QTROOT}/plugins"
export QML_IMPORT_PATH="${QTROOT}/qml"
即:框架搜索路径指向 aqt 下载的 Qt 6.10.1、插件路径指向 plugins、QML 模块路径指向 qml。run.sh 与 test.sh 都调用该函数,所以开发构建不需要把 Qt 复制进 .app 也能运行。setup_runtime() 还会检查 BUILD_DIR 是否存在,不存在则提示「先运行 build.sh」。
setup.sh:五步完成全部依赖安装
setup.sh 的执行流程对应文档的 Quick Start:
- [1/5] 检查 Xcode Command Line Tools:
xcode-select -p失败则调用xcode-select --install并提示重跑。 - [2/5] 检查 Homebrew:不存在则报错退出。
- [3/5] 安装构建工具:
brew install aqtinstall mpv ninja cmake create-dmg。 - [4/5] 安装 Qt 6.10.1:先检查
dev/macos/deps/qt/6.10.1/macos是否已存在,存在则跳过(幂等设计);否则执行aqt install-qt mac desktop 6.10.1 -m qtwebengine qtwebchannel qtpositioning -O qt。-m指定的三个附加模块是关键:qtwebengine(Jellyfin Desktop 的界面由 QtWebEngine 承载)、qtwebchannel(C++ 与网页端通信通道)、qtpositioning。 - 5/5] 生成 CMakePresets.json:用
sed将模板 [dev/CMakePresets.json.in 中的@QT_VERSION@、@BREW_PREFIX@占位符替换为实际值(brew --prefix),生成根目录下的CMakePresets.json。该 preset 名为macos-dev,预设了CMAKE_BUILD_TYPE=Debug、QTROOT、USE_STATIC_MPVQT=ON、MACOS_LAUNCH_WRAPPER=ON,并带condition仅当宿主系统为 Darwin(macOS)时生效。
build.sh:CMake 配置 + Ninja 编译
build.sh 在构建前做两道前置校验:QTROOT 目录存在(否则提示先跑 setup.sh)、mpv 命令可用(否则同样提示先跑 setup.sh)。随后在 build/ 下执行:
cmake -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=output \
-DQTROOT="${QTROOT}" \
-DCMAKE_PREFIX_PATH="${QTROOT}" \
-DUSE_STATIC_MPVQT=ON \
"${PROJECT_ROOT}"
ninja
参数说明:
-DCMAKE_BUILD_TYPE=Release:脚本默认走发布模式;CMake preset(macos-dev)则是 Debug,两种方式并存,可按需选用。-DQTROOT/-DCMAKE_PREFIX_PATH:都指向 aqt 安装的 Qt,让 CMake 找到Qt6的 config 文件。-DUSE_STATIC_MPVQT=ON:对应根目录 CMakeLists.txt 的option(USE_STATIC_MPVQT "Build MpvQt from bundled submodule instead of using system library" OFF),即使用仓库内置的 external/mpvqt 子模块源码编译 mpvqt,而不是用系统库。- 项目还支持
MACOS_LAUNCH_WRAPPER(见 CMakeLists.txt),开启后由 CMakeModules/MacOSLaunchWrapper.cmake 把链接产物改名为.bin,再生成一个 exec 真实二进制的 shell 包装脚本——这是为了阻止 Qt Creator 注入DYLD_FRAMEWORK_PATH导致加载到错误版本 Qt 的问题(例如与 Homebrew Qt 冲突)。 - macOS 平台配置由 CMakeModules/AppleConfiguration.cmake 注入:链接 Foundation/AppKit/IOKit/Cocoa/Security/Carbon/MediaPlayer 等系统框架,并加入 HIDRemote、letsmove 两个外部库;C 编译标志含
-mmacosx-version-min=10.15。
编译完成后,产物即 build/src/Jellyfin Desktop.app。
bundle.sh:打包发布版 .app 与 DMG
bundle.sh 面向发布场景,流程为:
- 校验
build/src/Jellyfin Desktop.app存在(必须先 build)。 ninja install:执行macdeployqt将 Qt 框架打入应用包,安装到build/output/(CMAKE_INSTALL_PREFIX=output)。codesign --force --deep -s - "build/output/Jellyfin Desktop.app":对应用包做临时签名(ad-hoc,-s -),满足 macOS 对签名应用的要求。create-dmg制作 DMG:版本号取自仓库根目录 VERSION(当前为2.0.0-dev),架构取uname -m(如 arm64 / x86_64),产物命名为JellyfinDesktop-<版本>-<架构>.dmg:
create-dmg \
--volname "Jellyfin Desktop v${VERSION}" \
--no-internet-enable \
--window-size 500 300 \
--icon-size 100 \
--icon "Jellyfin Desktop.app" 125 150 \
--app-drop-link 375 150 \
"${DMG_NAME}" "${APP_BUNDLE}"
参数含义:--volname 设置磁盘卷名,--window-size/--icon-size 控制 Finder 打开时的窗口与图标尺寸,--icon 指定应用图标位置,--app-drop-link 生成一个「拖拽安装」快捷链接。最终 DMG 位于 build/JellyfinDesktop-<版本>-<架构>.dmg。
run.sh:直接运行开发构建
run.sh 调用 setup_runtime() 注入 Qt 运行环境,校验应用包存在后执行:
exec "${APP_PATH}/Contents/MacOS/Jellyfin Desktop" ${1+"$@"}
exec 让脚本进程直接替换为应用进程;${1+"$@"} 把所有命令行参数原样透传给应用——这正是文档「run.sh 透传参数」一说的来源。因此在运行期可以直接追加应用支持的命令行选项。
test.sh:运行单元测试
test.sh 同样先 setup_runtime() 注入 Qt/mpv 环境,然后:
cd "${BUILD_DIR}"
ctest --output-on-failure "$@"
即调用 CTest(由根目录 CMakeLists.txt 的 enable_testing() 与 add_subdirectory(tests) 开启,测试源文件见 tests/ 下的 test_log、test_settings、test_displaymanager、test_systemcomponent)。--output-on-failure 保证测试失败时打印完整输出,尾部的 "$@" 同样允许透传 ctest 参数(如 -R 过滤、-j 并行)。
干净构建
开发中若遇到诡异问题,文档推荐直接清空构建目录重新来过:
rm -rf build
dev/macos/build.sh
因为 build/ 是可再生的输出目录,删除后重新执行 build.sh 会完整重跑 CMake 配置与编译,setup.sh 装好的依赖(dev/macos/deps/)不受影响,无需重装 Qt。
故障排查
黑屏 / GPU 问题
文档给出的第一手段是强制软件渲染:
dev/macos/run.sh --software-rendering
通过 run.sh 的参数透传机制,该选项会到达应用进程。从 src/main.cpp 的命令行解析看,应用还提供了同类治理选项:--disable-gpu(禁用 QtWebEngine 的 GPU 加速)、--scale-factor <scale>(控制界面 DPI 缩放,默认 auto)、--platform <platform>(等价于 QT_QPA_PLATFORM)。另外主程序在启动早期会调用 detectOpenGLEarly()/detectOpenGLLate() 并显式设置 QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL)(见 main.cpp),说明 OpenGL 检测是启动流程的固定环节,遇到黑屏优先尝试上述软件渲染开关是符合源码设计的排查路径。
日志文件位置
运行日志写入:
~/Library/Logs/Jellyfin Desktop/
对应代码中 Log::Init() 与 Log::RotateLog() 的日志体系(见 src/utils/Log.cpp)。需要排查崩溃或播放问题时,优先查看该目录下的日志文件。
平台要求与注意事项
文档 Notes 部分明确了三条硬性约束:
- Intel 构建要求 macOS 12+
- Apple Silicon 构建要求 macOS 14+
- Qt 6.10+ 为 WebEngine 支持所必需——本项目锁定的正是 Qt 6.10.1,且
setup.sh单独安装了qtwebengine模块,与应用界面依赖 QtWebEngine 的架构一致(src/ui/webview.qml 为主界面载体)。
此外还有两个 macOS 专属细节值得注意:其一,发布构建(NDEBUG)下,main.cpp 会调用 PFMoveToApplicationsFolderIfNecessary()(来自 external/letsmove),提示用户把应用移动到「应用程序」文件夹,这是 macOS 分发场景的常见惯例;其二,macOS 上不会显式设置窗口图标(见 main.cpp 的注释:在 OSX 上设置窗口图标会破坏用户更换图标的能力)。
小结
至此,一套完整的 macOS 构建链路已经清晰:setup.sh 一次性装好 Xcode CLT/Homebrew/CMake/Ninja/aqtinstall/create-dmg/mpv 与 Qt 6.10.1;build.sh 用 CMake+Ninja 产出开发版 .app;run.sh 借助 DYLD_FRAMEWORK_PATH 等环境变量免打包运行;test.sh 经 CTest 跑单元测试;bundle.sh 则负责 macdeployqt、临时签名与 create-dmg 产出可分发的 DMG。每一步都可以在上述脚本与 CMake 源码中找到一一对应的实现依据,照着这份流程即可在 macOS 上完成 Jellyfin Desktop 的开发、调试与发布。