diff --git a/exporter/CMakeLists.txt b/exporter/CMakeLists.txt index 8d44482e4d..00f7a04d94 100644 --- a/exporter/CMakeLists.txt +++ b/exporter/CMakeLists.txt @@ -255,6 +255,32 @@ if (APPLE) VERBATIM ) + # Find macdeployqt next to the Qt bin directory + find_program(DEPLOYQT_EXECUTABLE macdeployqt + PATHS ${CMAKE_PREFIX_PATH}/../../bin + REQUIRED) + + # Deploy Qt and ffmovie into the plugin bundle + set(PLUGIN_BUNDLE_DIR "$") + set(PLUGIN_FRAMEWORKS_DIR "${PLUGIN_BUNDLE_DIR}/Contents/Frameworks") + set(PLUGIN_FFMOVIE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../viewer/vendor/ffmovie/mac/${ARCH}/libffmovie.dylib") + + add_custom_command( + TARGET PAGExporter POST_BUILD + # Extract dSYM before macdeployqt (which may strip the binary) + COMMAND /bin/sh -c "dsymutil '${PLUGIN_BUNDLE_DIR}/Contents/MacOS/PAGExporter' -o '${CMAKE_BINARY_DIR}/PAGExporter.dSYM' 2>/dev/null || true" + # Copy libffmovie.dylib + COMMAND /bin/sh -c "mkdir -p '${PLUGIN_FRAMEWORKS_DIR}' && [ -f '${PLUGIN_FRAMEWORKS_DIR}/libffmovie.dylib' ] || cp '${PLUGIN_FFMOVIE_SRC}' '${PLUGIN_FRAMEWORKS_DIR}/'" + # Deploy Qt (skip if already deployed). macdeployqt only supports .app bundles + # (CFBundlePackageType=APPL), so it may exit non-zero for .plugin bundles. + # The non-zero exit is safe to ignore as Qt frameworks are still copied successfully. + COMMAND /bin/sh -c "[ -d '${PLUGIN_FRAMEWORKS_DIR}/QtCore.framework' ] || '${DEPLOYQT_EXECUTABLE}' -no-strip '${PLUGIN_BUNDLE_DIR}' -qmldir='${CMAKE_CURRENT_SOURCE_DIR}/assets/qml' || true" + # Set rpath + COMMAND /bin/sh -c "install_name_tool -add_rpath '@loader_path/../Frameworks' '${PLUGIN_BUNDLE_DIR}/Contents/MacOS/PAGExporter' 2>/dev/null || true" + COMMENT "Deploying runtime dependencies into PAGExporter.plugin" + VERBATIM + ) + if (CMAKE_BUILD_TYPE STREQUAL "Debug") add_custom_command( TARGET PAGExporter POST_BUILD diff --git a/exporter/scripts/mac/debugbuild.sh b/exporter/scripts/mac/debugbuild.sh index 860f794545..de18701974 100755 --- a/exporter/scripts/mac/debugbuild.sh +++ b/exporter/scripts/mac/debugbuild.sh @@ -5,8 +5,8 @@ plugiPath=$1 script_path=$(realpath "$0") script_dir=$(dirname "$script_path") rootPath=$(realpath "$script_dir/../..") -if ! [ -w '${AE_PLUGIN_PATH}/PAGExporter.plugin' ]; then +if ! [ -w "${AE_PLUGIN_PATH}/PAGExporter.plugin" ]; then ${rootPath}/scripts/mac/takeControl fi -rm -r -f "${AE_PLUGIN_PATH}/PAGExporter.plugin" -cp -r -f ${plugiPath} "${AE_PLUGIN_PATH}/" +rm -rf "${AE_PLUGIN_PATH}/PAGExporter.plugin" +cp -a "${plugiPath}" "${AE_PLUGIN_PATH}/" diff --git a/exporter/src/platform/mac/PlatformHelper.mm b/exporter/src/platform/mac/PlatformHelper.mm index 2c256fd007..7b44e2ddc4 100644 --- a/exporter/src/platform/mac/PlatformHelper.mm +++ b/exporter/src/platform/mac/PlatformHelper.mm @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "platform/PAGViewerCheck.h" #include "platform/PAGViewerInstaller.h" @@ -79,9 +80,13 @@ bool IsAEWindowActive() { } std::string GetQmlPath() { - // Use shared Qt resources directory to avoid modifying plugin bundle (which would break code - // signature) - return "/Library/Application Support/PAGExporter/Resources/qml"; + Dl_info info; + if (dladdr((void*)GetQmlPath, &info) && info.dli_fname) { + std::filesystem::path exePath(info.dli_fname); + auto pluginContents = exePath.parent_path().parent_path(); + return (pluginContents / "Resources" / "qml").string(); + } + return ""; } std::string GetDownloadsPath() { diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index a84de2cc96..8b64c73e88 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -224,3 +224,66 @@ endif () target_include_directories(PAGViewer PUBLIC ${PAG_VIEWER_INCLUDES}) target_include_directories(PAGViewer SYSTEM PRIVATE ${PAG_VIEWER_SYSTEM_INCLUDES}) target_link_libraries(PAGViewer ${PAG_VIEWER_PLATFORM_LIBS} ${VIEWER_VENDOR_LIBRARIES}) + +if (APPLE) + # Find macdeployqt next to the Qt bin directory + find_program(DEPLOYQT_EXECUTABLE macdeployqt + PATHS ${CMAKE_PREFIX_PATH}/../../bin + REQUIRED) + + set_target_properties(PAGViewer PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME "PAGViewer" + MACOSX_BUNDLE_GUI_IDENTIFIER "im.pag.viewer" + BUILD_RPATH "@executable_path/../Frameworks" + INSTALL_RPATH "@executable_path/../Frameworks" + ) + + set(VIEWER_BUNDLE_DIR "$") + set(VIEWER_FRAMEWORKS_DIR "${VIEWER_BUNDLE_DIR}/Contents/Frameworks") + set(VIEWER_RESOURCES_DIR "${VIEWER_BUNDLE_DIR}/Contents/Resources") + set(VIEWER_FFMOVIE_SRC "${CMAKE_SOURCE_DIR}/vendor/ffmovie/mac/${ARCH}/libffmovie.dylib") + set(VIEWER_SPARKLE_SRC "${CMAKE_SOURCE_DIR}/vendor/sparkle/mac/Sparkle.framework") + + add_custom_command(TARGET PAGViewer POST_BUILD + # Extract dSYM before macdeployqt (which may strip the binary) + COMMAND /bin/sh -c "dsymutil '${VIEWER_BUNDLE_DIR}/Contents/MacOS/PAGViewer' -o '${CMAKE_BINARY_DIR}/PAGViewer.dSYM' 2>/dev/null || true" + # Copy libffmovie.dylib + COMMAND /bin/sh -c "mkdir -p '${VIEWER_FRAMEWORKS_DIR}' && [ -f '${VIEWER_FRAMEWORKS_DIR}/libffmovie.dylib' ] || cp '${VIEWER_FFMOVIE_SRC}' '${VIEWER_FRAMEWORKS_DIR}/'" + # Copy Sparkle.framework + COMMAND /bin/sh -c "[ -d '${VIEWER_FRAMEWORKS_DIR}/Sparkle.framework' ] || cp -R '${VIEWER_SPARKLE_SRC}' '${VIEWER_FRAMEWORKS_DIR}/'" + # Copy resources + COMMAND /bin/sh -c "mkdir -p '${VIEWER_RESOURCES_DIR}' && cp -f '${CMAKE_SOURCE_DIR}/assets/images/appIcon.icns' '${VIEWER_RESOURCES_DIR}/' && cp -f '${CMAKE_SOURCE_DIR}/assets/images/pagIcon.icns' '${VIEWER_RESOURCES_DIR}/'" + # Copy Info.plist + COMMAND /bin/sh -c "cp -f '${CMAKE_SOURCE_DIR}/package/templates/Info.plist' '${VIEWER_BUNDLE_DIR}/Contents/'" + # Deploy Qt (skip if already deployed) + COMMAND /bin/sh -c "[ -d '${VIEWER_FRAMEWORKS_DIR}/QtCore.framework' ] || '${DEPLOYQT_EXECUTABLE}' '${VIEWER_BUNDLE_DIR}' -qmldir='${CMAKE_SOURCE_DIR}/assets/qml'" + COMMENT "Deploying runtime dependencies into PAGViewer.app" + VERBATIM + ) +elseif (WIN32) + get_target_property(qmake_executable Qt6::qmake IMPORTED_LOCATION) + if (NOT qmake_executable) + message(FATAL_ERROR "qmake executable not found") + endif () + get_filename_component(qt_bin_dir "${qmake_executable}" DIRECTORY) + find_program(DEPLOYQT_EXECUTABLE windeployqt HINTS "${qt_bin_dir}") + if (NOT DEPLOYQT_EXECUTABLE) + message(FATAL_ERROR "windeployqt.exe not found in ${qt_bin_dir}") + endif () + + set(VIEWER_OUTPUT_DIR "$") + set(VIEWER_WINSPARKLE_DLL "${CMAKE_SOURCE_DIR}/vendor/winsparkle/win/x64/${CMAKE_BUILD_TYPE}/WinSparkle.dll") + set(VIEWER_FFMOVIE_DLL "${CMAKE_SOURCE_DIR}/vendor/ffmovie/win/${ARCH}/ffmovie.dll") + + add_custom_command(TARGET PAGViewer POST_BUILD + # Copy WinSparkle.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VIEWER_WINSPARKLE_DLL}" "${VIEWER_OUTPUT_DIR}/" + # Copy ffmovie.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VIEWER_FFMOVIE_DLL}" "${VIEWER_OUTPUT_DIR}/" + # Deploy Qt (skip if already deployed) + COMMAND cmd /c "if not exist \"${VIEWER_OUTPUT_DIR}\\Qt6Core.dll\" \"${DEPLOYQT_EXECUTABLE}\" \"$\" --qmldir=\"${CMAKE_SOURCE_DIR}/assets/qml\"" + COMMENT "Deploying runtime dependencies for PAGViewer" + VERBATIM + ) +endif () diff --git a/viewer/assets/translation/Chinese.ts b/viewer/assets/translation/Chinese.ts index 6bd5ae19de..9f5df745f6 100644 --- a/viewer/assets/translation/Chinese.ts +++ b/viewer/assets/translation/Chinese.ts @@ -630,17 +630,17 @@ pag::PluginInstaller - + Install Adobe After Effects Plug-in 安装 Adobe After Effects 插件 - + Do you want to install the Adobe After Effects plugins? 是否要安装 Adobe After Effects 插件? - + Update Adobe After Effects Plug-in 更新 Adobe After Effects 插件 @@ -649,105 +649,105 @@ 有新版本的插件可用。是否要安装? - + Reinstall Adobe After Effects Plug-in 重新安装 Adobe After Effects 插件 - + The plugins are already installed with the latest version. Do you want to reinstall them? 插件已是最新版本。是否要重新安装? - - + + Please close Adobe After Effects 请关闭 Adobe After Effects - - + + Adobe After Effects is currently running. Please close it and click OK to continue, or Cancel to abort. Adobe After Effects 正在运行。请关闭它并点击确定继续,或点击取消中止。 - - + + Installation Failed 安装失败 - + Plugin source not found: %1 未找到插件源:%1 - + Installation Complete 安装完成 - + Adobe After Effects plugins have been successfully installed! Adobe After Effects 插件已成功安装! - + Adobe After Effects plug-in installed successfully. Adobe After Effects 插件安装成功。 - + Failed to install Adobe After Effects plugins. Please check permissions. 安装 Adobe After Effects 插件失败。请检查权限。 - + Failed to install Adobe After Effects plug-in due to permission issues. 由于权限问题,安装 Adobe After Effects 插件失败。 - + Uninstall Plugins 卸载插件 - + Are you sure you want to uninstall the selected plugins? 确定要卸载选中的插件吗? - + Uninstallation Complete 卸载完成 - + Plugins have been successfully removed! 插件已成功移除! - + Uninstalled AE Plug-in Successfully AE 插件卸载成功 - + Uninstallation Failed 卸载失败 - + Failed to uninstall plugins. Please check permissions. 卸载插件失败。请检查权限。 - + Failed to uninstall AE Plug-in due to permission issues. 由于权限问题,卸载 AE 插件失败。 - + A new version of the Adobe After Effects plugin is available. Would you like to update it now? Adobe After Effects 插件有新版本可用,是否立即更新? diff --git a/viewer/package/build_mac.sh b/viewer/package/build_mac.sh index 2f0235cec3..5903ed1016 100755 --- a/viewer/package/build_mac.sh +++ b/viewer/package/build_mac.sh @@ -81,16 +81,16 @@ function generateUniversalDsym() { local armDsym="${armDir}/${binaryName}.dSYM" local universalDsym="${outputDir}/${binaryName}.dSYM" - dsymutil "${x86Exe}" -o "${x86Dsym}" - if [ $? -ne 0 ]; - then - exitWithError "Generate ${binaryName} x86_64 dSYM failed" + # dSYM may already be extracted by CMake POST_BUILD (before macdeployqt runs). + # Skip extraction if it exists, otherwise extract from the original binary. + if [ ! -d "${x86Dsym}" ]; then + dsymutil "${x86Exe}" -o "${x86Dsym}" || \ + exitWithError "Generate ${binaryName} x86_64 dSYM failed" fi - dsymutil "${armExe}" -o "${armDsym}" - if [ $? -ne 0 ]; - then - exitWithError "Generate ${binaryName} arm64 dSYM failed" + if [ ! -d "${armDsym}" ]; then + dsymutil "${armExe}" -o "${armDsym}" || \ + exitWithError "Generate ${binaryName} arm64 dSYM failed" fi cp -R "${x86Dsym}" "${universalDsym}" @@ -111,12 +111,49 @@ function generateUniversalDsym() { function cleanAbsoluteRpaths() { local binary="$1" - otool -l "${binary}" | grep -A2 "LC_RPATH" | grep "path " | sed 's/.*path \(.*\) (offset.*/\1/' | while IFS= read -r rpath; do + local tmpfile + tmpfile=$(mktemp) + otool -l "${binary}" | grep -A2 "LC_RPATH" | grep "path " | sed 's/.*path \(.*\) (offset.*/\1/' > "${tmpfile}" + while IFS= read -r rpath; do case "${rpath}" in @*|/Library/*) ;; *) install_name_tool -delete_rpath "${rpath}" "${binary}" 2>/dev/null || true ;; esac - done + done < "${tmpfile}" + rm -f "${tmpfile}" +} + +function mergeUniversalBundle() { + local x86Dir="$1" + local armDir="$2" + local outDir="$3" + local label="$4" + + cp -a "${armDir}" "${outDir}" + + local mergedCount=0 + local skippedCount=0 + while IFS= read -r -d '' f; do + local relPath="${f#${outDir}/}" + local x86File="${x86Dir}/${relPath}" + if [ -f "${x86File}" ] && file "${f}" | grep -q "Mach-O"; then + local outArch + local x86Arch + outArch=$(lipo -info "${f}" 2>/dev/null | awk -F': ' '{print $NF}') + x86Arch=$(lipo -info "${x86File}" 2>/dev/null | awk -F': ' '{print $NF}') + # Skip if already fat, or both files are single-arch with the same architecture + if lipo -info "${f}" 2>/dev/null | grep -q "Architectures in the fat file" || [ "${outArch}" = "${x86Arch}" ]; then + skippedCount=$((skippedCount + 1)) + continue + fi + lipo -create "${x86File}" "${f}" -output "${f}.universal" \ + || exitWithError "Failed to lipo merge: ${relPath}" + mv "${f}.universal" "${f}" + mergedCount=$((mergedCount + 1)) + fi + done < <(find "${outDir}" -type f -print0) + + logSuccess "${label}: ${mergedCount} merged, ${skippedCount} skipped (already universal or same arch)" } function createDmg() @@ -246,66 +283,83 @@ then fi # 3 Organize resources -# 3.1 Merge PAGViewer printSection 3 "Organize resources" + +# Clean absolute build rpaths from single-arch binaries before merging +printStep "Clean absolute rpaths" + +cleanAbsoluteRpaths "${x86_64BuildDir}/PAGViewer.app/Contents/MacOS/PAGViewer" +cleanAbsoluteRpaths "${arm64BuildDir}/PAGViewer.app/Contents/MacOS/PAGViewer" +cleanAbsoluteRpaths "${x86_64BuildDirForPlugin}/PAGExporter.plugin/Contents/MacOS/PAGExporter" +cleanAbsoluteRpaths "${arm64BuildDirForPlugin}/PAGExporter.plugin/Contents/MacOS/PAGExporter" + +# 3.1 Merge PAGViewer universal app bundle printStep "Merge PAGViewer" AppDir="${BuildDir}/PAGViewer.app" -ExeDir="${AppDir}/Contents/MacOS" -ExePath="${ExeDir}/PAGViewer" -x86_64ExePath="${x86_64BuildDir}/PAGViewer" -arm64ExePath="${arm64BuildDir}/PAGViewer" +x86_64AppDir="${x86_64BuildDir}/PAGViewer.app" +arm64AppDir="${arm64BuildDir}/PAGViewer.app" -# Clean previous app bundle to avoid incremental build issues rm -rf "${AppDir}" +mergeUniversalBundle "${x86_64AppDir}" "${arm64AppDir}" "${AppDir}" "PAGViewer" -install_name_tool -delete_rpath "${SourceDir}/vendor/ffmovie/mac/x64" "${x86_64ExePath}" 2>/dev/null || true -install_name_tool -delete_rpath "${SourceDir}/vendor/ffmovie/mac/arm64" "${arm64ExePath}" 2>/dev/null || true - -mkdir -p "${ExeDir}" -lipo -create "${x86_64ExePath}" "${arm64ExePath}" -output "${ExePath}" -if [ $? -ne 0 ]; -then - exitWithError "Failed to merge PAGViewer universal binary at ${ExePath}" -fi +ExePath="${AppDir}/Contents/MacOS/PAGViewer" +FrameworkDir="${AppDir}/Contents/Frameworks" +ContentsDir="${AppDir}/Contents" +ResourcesDir="${AppDir}/Contents/Resources" # 3.1.1 Generate dSYM files for PAGViewer printStep "Generate dSYM for PAGViewer" +x86_64ExePath="${x86_64AppDir}/Contents/MacOS/PAGViewer" +arm64ExePath="${arm64AppDir}/Contents/MacOS/PAGViewer" generateUniversalDsym "PAGViewer" "${x86_64BuildDir}" "${arm64BuildDir}" "${BuildDir}" "${x86_64ExePath}" "${arm64ExePath}" -# 3.2 Obtain the dependencies of PAGViewer -printStep "Deploy Qt dependencies" -"${Deployqt}" "${AppDir}" -qmldir="${SourceDir}/assets/qml" -if [ $? -ne 0 ]; -then - exitWithError "Deploy Qt dependencies (viewer qml) failed" -fi -"${Deployqt}" "${AppDir}" -qmldir="${PluginSourceDir}/assets/qml" -if [ $? -ne 0 ]; +# 3.2 Merge PAGExporter universal plugin bundle +printStep "Merge PAGExporter" +x86_64PluginPath="${x86_64BuildDirForPlugin}/PAGExporter.plugin" +arm64PluginPath="${arm64BuildDirForPlugin}/PAGExporter.plugin" +PluginPath="${ResourcesDir}/PAGExporter.plugin" + +mergeUniversalBundle "${x86_64PluginPath}" "${arm64PluginPath}" "${PluginPath}" "PAGExporter" + +PluginExePath="${PluginPath}/Contents/MacOS/PAGExporter" + +# 3.2.1 Generate dSYM files for PAGExporter +printStep "Generate dSYM for PAGExporter" +x86_64PluginExePath="${x86_64PluginPath}/Contents/MacOS/PAGExporter" +arm64PluginExePath="${arm64PluginPath}/Contents/MacOS/PAGExporter" +generateUniversalDsym "PAGExporter" "${x86_64BuildDirForPlugin}" "${arm64BuildDirForPlugin}" "${BuildDir}" "${x86_64PluginExePath}" "${arm64PluginExePath}" + +# 3.3 Move Qt dependencies from Viewer into PAGExporter plugin +printStep "Consolidate Qt dependencies into PAGExporter" +PluginFrameworkDir="${PluginPath}/Contents/Frameworks" +PluginPluginsDir="${PluginPath}/Contents/Plugins" +PluginQmlDir="${PluginPath}/Contents/Resources/qml" + +# Move all Viewer frameworks into plugin, then move Sparkle back to Viewer +cp -fRP "${FrameworkDir}/"* "${PluginFrameworkDir}/" +rm -rf "${FrameworkDir}" +mkdir -p "${FrameworkDir}" +mv "${PluginFrameworkDir}/Sparkle.framework" "${FrameworkDir}/" + +# Move Viewer Qt plugins into plugin +if [ -d "${AppDir}/Contents/Plugins" ]; then - exitWithError "Deploy Qt dependencies (exporter qml) failed" + mkdir -p "${PluginPluginsDir}" + cp -fRP "${AppDir}/Contents/Plugins/"* "${PluginPluginsDir}/" + rm -rf "${AppDir}/Contents/Plugins" fi -# 3.2.1 Copy Sparkle -printStep "Copy Sparkle.framework" -FrameworkDir="${AppDir}/Contents/Frameworks" -SparklePath="${SourceDir}/vendor/sparkle/mac/Sparkle.framework" -cp -f -R -P "${SparklePath}" "${FrameworkDir}" - -# 3.2.2 Merge and copy ffmovie -printStep "Merge ffmovie.dylib" -x64FfmoviePath="${SourceDir}/vendor/ffmovie/mac/x64/libffmovie.dylib" -arm64FfmoviePath="${SourceDir}/vendor/ffmovie/mac/arm64/libffmovie.dylib" -FfmoviePath="${FrameworkDir}/libffmovie.dylib" -lipo -create "${x64FfmoviePath}" "${arm64FfmoviePath}" -output "${FfmoviePath}" -if [ $? -ne 0 ]; +# Move Viewer QML resources into plugin +if [ -d "${ResourcesDir}/qml" ]; then - exitWithError "Failed to merge ffmovie universal dylib at ${FfmoviePath}" + mkdir -p "${PluginQmlDir}" + cp -fRP "${ResourcesDir}/qml/"* "${PluginQmlDir}/" + rm -rf "${ResourcesDir}/qml" fi -# 3.3 Obtain public and private keys +# 3.4 Obtain public and private keys printStep "Obtain signing keys" -# 3.3.1 Obtain DSA public and private keys SignForUpdate=false DSAPublicKey="" DSAPrivateKey="" @@ -321,7 +375,6 @@ then logInfo "Get DSAPrivateKey: ${DSAPrivateKey}" fi -# 3.3.2 Obtain EDDSA public and private keys EDDSAPublicKey="" EDDSAPrivateKey="" if declare -F GetEDDSAPublicKeyPath > /dev/null; @@ -341,90 +394,37 @@ then SignForUpdate=true fi -# 3.4 Copy resources +# 3.5 Copy resources printStep "Copy resources" -ContentsDir="${AppDir}/Contents" -PlistPath="${SourceDir}/package/templates/Info.plist" -cp -f "${PlistPath}" "${ContentsDir}" - -ResourcesDir="${AppDir}/Contents/Resources" -cp -f "${SourceDir}/assets/images/appIcon.icns" "${ResourcesDir}" -cp -f "${SourceDir}/assets/images/pagIcon.icns" "${ResourcesDir}" if [ -n "${DSAPublicKey}" ] && [ -f "${DSAPublicKey}" ]; then cp -f "${DSAPublicKey}" "${ResourcesDir}" fi -# 3.5 Merge PAGExporter and copy related tools -printStep "Merge PAGExporter" - -# 3.5.1 Merge PAGExporter -PluginPath="${ResourcesDir}/PAGExporter.plugin" -PluginExePath="${PluginPath}/Contents/MacOS/PAGExporter" -x86_64PluginPath="${x86_64BuildDirForPlugin}/PAGExporter.plugin" -arm64PluginPath="${arm64BuildDirForPlugin}/PAGExporter.plugin" -x86_64PluginExePath="${x86_64PluginPath}/Contents/MacOS/PAGExporter" -arm64PluginExePath="${arm64PluginPath}/Contents/MacOS/PAGExporter" - -# Remove absolute build rpaths from each architecture before merging -cleanAbsoluteRpaths "${x86_64PluginExePath}" -cleanAbsoluteRpaths "${arm64PluginExePath}" - -cp -fr "${x86_64PluginPath}" "${PluginPath}" -lipo -create "${x86_64PluginExePath}" "${arm64PluginExePath}" -output "${PluginExePath}" - -# 3.5.1.1 Generate dSYM files for PAGExporter -printStep "Generate dSYM for PAGExporter" -generateUniversalDsym "PAGExporter" "${x86_64BuildDirForPlugin}" "${arm64BuildDirForPlugin}" "${BuildDir}" "${x86_64PluginExePath}" "${arm64PluginExePath}" - -# 3.5.2 Obtain the dependencies of PAGExporter -printStep "Deploy PAGExporter Qt dependencies" -"${Deployqt}" "${PluginPath}" -qmldir="${PluginSourceDir}/assets/qml" -if [ $? -ne 0 ]; -then - exitWithError "Obtain the dependencies of PAGExporter failed" -fi -# Remove libraries from plugin that already exist in the main app (to avoid overwriting universal with single-arch) -if [ -d "${PluginPath}/Contents/Frameworks" ]; -then - for existingLib in "${FrameworkDir}"/*.dylib; do - libName=$(basename "${existingLib}") - rm -f "${PluginPath}/Contents/Frameworks/${libName}" - done - # Copy remaining plugin Frameworks into the main app. - # Use process substitution so exitWithError aborts the whole script; - # find -exec swallows cp failures and a piped while runs in a subshell. - while IFS= read -r -d '' Item; do - if ! cp -fRP "${Item}" "${AppDir}/Contents/Frameworks/"; - then - exitWithError "Failed to copy plugin framework item: ${Item}" - fi - done < <(find "${PluginPath}/Contents/Frameworks" -mindepth 1 -maxdepth 1 -print0) -fi -if [ -d "${PluginPath}/Contents/Plugins" ]; -then - cp -fRP "${PluginPath}/Contents/Plugins/"* "${AppDir}/Contents/Plugins/" -fi -rm -rf "${PluginPath}/Contents/Frameworks" -rm -rf "${PluginPath}/Contents/Plugins" -rm -rf "${PluginPath}/Contents/Resources/qml" - -# 3.5.3 Copy related tools -printStep "Copy tools and scripts" +# 3.5.1 Copy tools +printStep "Copy tools" EncoderToolsPath="${EncoderToolBuildDir}/Release/H264EncoderTools" cp -f "${EncoderToolsPath}" "${ResourcesDir}" -cp -f "${SourceDir}/qttools/copy_qt_resource.sh" "${ResourcesDir}/" cp -f "${SourceDir}/qttools/delete_qt_resource.sh" "${ResourcesDir}/" -cp -f "${SourceDir}/qttools/replace_qt_path.sh" "${ResourcesDir}/" -# 3.5.5 Generate qt.conf for PAGExporter plugin -printStep "Generate qt.conf for PAGExporter" -PLUGIN_RESOURCES_DIR="${ResourcesDir}/PAGExporter.plugin/Contents/Resources" +# 3.5.2 Generate qt.conf for PAGExporter plugin +printStep "Generate qt.conf" +PLUGIN_RESOURCES_DIR="${PluginPath}/Contents/Resources" PLUGIN_QT_CONF="${PLUGIN_RESOURCES_DIR}/qt.conf" mkdir -p "${PLUGIN_RESOURCES_DIR}" cat > "${PLUGIN_QT_CONF}" << 'EOF' [Paths] -Prefix = /Library/Application Support/PAGExporter +Plugins = PlugIns +Imports = Resources/qml +QmlImports = Resources/qml +EOF + +# Generate qt.conf for PAGViewer (points to PAGExporter plugin for Qt resources) +# Plugins/Imports/QmlImports are relative to the Prefix directory. +VIEWER_QT_CONF="${ResourcesDir}/qt.conf" +cat > "${VIEWER_QT_CONF}" << 'EOF' +[Paths] +Prefix = Resources/PAGExporter.plugin/Contents Plugins = PlugIns Imports = Resources/qml QmlImports = Resources/qml @@ -447,8 +447,11 @@ then /usr/libexec/Plistbuddy -c "Set SUPublicDSAKeyFile ${DSAPublicKeyName}" "${ContentsDir}/Info.plist" \ || exitWithError "Failed to update SUPublicDSAKeyFile in ${ContentsDir}/Info.plist" fi -/usr/libexec/Plistbuddy -c "Set SUPublicEDKey ${SUPublicEDKey}" "${ContentsDir}/Info.plist" \ - || exitWithError "Failed to update SUPublicEDKey in ${ContentsDir}/Info.plist" +if [ -n "${SUPublicEDKey}" ]; +then + /usr/libexec/Plistbuddy -c "Set SUPublicEDKey ${SUPublicEDKey}" "${ContentsDir}/Info.plist" \ + || exitWithError "Failed to update SUPublicEDKey in ${ContentsDir}/Info.plist" +fi /usr/libexec/Plistbuddy -c "Set CFBundleVersion ${AppVersion}" "${PluginPath}/Contents/Info.plist" \ || exitWithError "Failed to update CFBundleVersion in ${PluginPath}/Contents/Info.plist" @@ -459,14 +462,12 @@ fi # 3.7 Set rpath printStep "Set rpath" -install_name_tool -delete_rpath "${QtPath}/lib" "${ExePath}" 2>/dev/null || true -install_name_tool -delete_rpath "${SourceDir}/vendor/sparkle/mac" "${ExePath}" 2>/dev/null || true +cleanAbsoluteRpaths "${ExePath}" install_name_tool -add_rpath "@executable_path/../Frameworks" "${ExePath}" 2>/dev/null || true +install_name_tool -add_rpath "@executable_path/../Resources/PAGExporter.plugin/Contents/Frameworks" "${ExePath}" 2>/dev/null || true -install_name_tool -delete_rpath "${QtPath}/lib" "${PluginExePath}" 2>/dev/null || true -install_name_tool -add_rpath "@executable_path/../Frameworks" "${PluginExePath}" 2>/dev/null || true +cleanAbsoluteRpaths "${PluginExePath}" install_name_tool -add_rpath "@loader_path/../Frameworks" "${PluginExePath}" 2>/dev/null || true -install_name_tool -add_rpath "/Library/Application Support/PAGExporter/Frameworks" "${PluginExePath}" 2>/dev/null || true # 4 Sign & Notarize printSection 4 "Sign & Notarize" @@ -492,10 +493,8 @@ then xattr -rc "${AppDir}" xattr -rc "${PluginPath}" - # Sign all dylibs in Frameworks/ directory first. - # Use process substitution so exitWithError aborts the whole script; - # a piped `while` would only exit the subshell. - logInfo "Signing all libraries in Frameworks/..." + # Sign all dylibs in Viewer Frameworks and PAGExporter plugin. + logInfo "Signing all libraries..." while IFS= read -r dylib; do SIGN_OUTPUT=$(codesign --force --timestamp --options "runtime" --sign "${SignCertName}" "${dylib}" 2>&1) SIGN_STATUS=$? @@ -504,7 +503,7 @@ then echo "${SIGN_OUTPUT}" exitWithError "Failed to sign dylib: ${dylib}" fi - done < <(find "${FrameworkDir}" -name "*.dylib" -type f) + done < <(find "${FrameworkDir}" "${PluginPath}/Contents/Frameworks" -name "*.dylib" -type f 2>/dev/null) # Sign standalone executables not inside a bundle logInfo "Signing: ${ResourcesDir}/H264EncoderTools" diff --git a/viewer/qttools/copy_qt_resource.sh b/viewer/qttools/copy_qt_resource.sh deleted file mode 100755 index 03c4737f69..0000000000 --- a/viewer/qttools/copy_qt_resource.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash - -# Shared directory for PAGExporter Qt resources (outside plugin bundle to preserve code signature) -SHARED_QT_DIR="/Library/Application Support/PAGExporter" - -# Locate PAGViewer.app -PAGVIEWER_APP="" -if [ -d "/Applications/PAGViewer.app" ]; then - PAGVIEWER_APP="/Applications/PAGViewer.app" -elif [ -d "$HOME/Applications/PAGViewer.app" ]; then - PAGVIEWER_APP="$HOME/Applications/PAGViewer.app" -else - PAGVIEWER_APP=$(mdfind "kMDItemDisplayName == 'PAGViewer.app' && kMDItemContentType == 'com.apple.application-bundle'" | head -n 1) -fi - -if [ -z "$PAGVIEWER_APP" ] || [ ! -d "$PAGVIEWER_APP" ]; then - echo "Error: PAGViewer.app not found!" - echo "PAGExporter requires PAGViewer to be installed first." - echo "Please install PAGViewer.app in /Applications/ or ~/Applications/" - exit 1 -fi - -echo "Found PAGViewer: $PAGVIEWER_APP" - -# Verify PAGViewer has the required Qt resources -VIEWER_FRAMEWORKS="$PAGVIEWER_APP/Contents/Frameworks" -if [ ! -d "$VIEWER_FRAMEWORKS" ] || [ -z "$(ls -A "$VIEWER_FRAMEWORKS"/Qt*.framework 2>/dev/null)" ]; then - echo "Error: PAGViewer installation appears incomplete (missing Qt frameworks)" - echo "Please reinstall PAGViewer.app" - exit 1 -fi - -# Remove existing shared directory if exists -if [ -d "$SHARED_QT_DIR" ]; then - echo "Removing existing shared Qt directory..." - sudo rm -rf "$SHARED_QT_DIR" -fi - -# Create shared Qt directory structure -echo "Creating shared Qt directory: $SHARED_QT_DIR" -sudo mkdir -p "$SHARED_QT_DIR/Frameworks" -sudo mkdir -p "$SHARED_QT_DIR/PlugIns" -sudo mkdir -p "$SHARED_QT_DIR/Resources" - -# Copy Qt frameworks -echo "Copying Qt frameworks..." -for framework in "$VIEWER_FRAMEWORKS"/Qt*.framework; do - if [ -d "$framework" ]; then - frameworkName=$(basename "$framework") - echo " - $frameworkName" - sudo cp -R "$framework" "$SHARED_QT_DIR/Frameworks/" - fi -done - -# Copy Qt plugins -echo "Copying Qt plugins..." -VIEWER_PLUGINS="$PAGVIEWER_APP/Contents/PlugIns" -if [ -d "$VIEWER_PLUGINS" ]; then - for plugin in "$VIEWER_PLUGINS"/*; do - if [ -d "$plugin" ]; then - pluginName=$(basename "$plugin") - echo " - $pluginName" - sudo cp -R "$plugin" "$SHARED_QT_DIR/PlugIns/" - fi - done -fi - -# Copy QML resources -VIEWER_QML="$PAGVIEWER_APP/Contents/Resources/qml" -if [ -d "$VIEWER_QML" ]; then - echo "Copying QML resources..." - sudo cp -R "$VIEWER_QML" "$SHARED_QT_DIR/Resources/" -fi - -# Copy Qt translation files -echo "Copying Qt translations..." -VIEWER_RESOURCES="$PAGVIEWER_APP/Contents/Resources" -for resource in "$VIEWER_RESOURCES"/*.qm; do - if [ -f "$resource" ]; then - resourceName=$(basename "$resource") - echo " - $resourceName" - sudo cp "$resource" "$SHARED_QT_DIR/Resources/" - fi -done - -# Copy ffmovie library (required by PAGExporter) -echo "Copying ffmovie library..." -FFMOVIE_LIB="$VIEWER_FRAMEWORKS/libffmovie.dylib" -if [ -f "$FFMOVIE_LIB" ]; then - echo " - libffmovie.dylib" - sudo cp "$FFMOVIE_LIB" "$SHARED_QT_DIR/Frameworks/" -else - echo "Warning: libffmovie.dylib not found in PAGViewer" -fi - -echo "Qt resources copied to shared directory: $SHARED_QT_DIR" diff --git a/viewer/qttools/replace_qt_path.sh b/viewer/qttools/replace_qt_path.sh deleted file mode 100755 index 0f5daeb6fb..0000000000 --- a/viewer/qttools/replace_qt_path.sh +++ /dev/null @@ -1,101 +0,0 @@ -AE_PLUGIN_PATH='/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore' -AE_EXPORTER_PATH="$AE_PLUGIN_PATH/PAGExporter.plugin" -QT_FRAMEWORK_DIR="$AE_EXPORTER_PATH/Contents/Frameworks" -TARGET_QML_DIR="$AE_EXPORTER_PATH/Contents/Resources/qml" - -# Replace paths in executable/library files -function startReplacePathInExecuteFile(){ - executeName=$1 - isQML="$2" - currentPath=`pwd` - - otool -L $executeName - for linkPath in `otool -L $executeName | tr " " "\?"` - do - # Replace executable paths - if [[ "$linkPath" =~ ^@executable_path/\.\./Frameworks ]]; then - oldPath=`echo "$linkPath" | grep -e "^@executable_path/\.\./Frameworks/[A-Z|a-z|0-9|/|\.]*" -o` - newPath=`echo "$oldPath" | sed -e "s/^@executable_path\/\.\.\/Frameworks//"` - newPath="@rpath$newPath" - install_name_tool -change "$oldPath" "$newPath" $executeName - fi - done - - # Replace library ID - idName=`otool -l $executeName | grep -A 10 LC_ID_DYLIB | grep name` - if [ -n "$idName" ]; then - idName=`echo ${idName#*name }` - idName=`echo ${idName% (*}` - - if [ "$isQML" == "true" ]; then - oldIdName=`echo ${idName#*qml}` - newIdName="@rpath$oldIdName" - else - oldIdName=`echo "$idName" | grep -e "^@executable_path/\.\./Frameworks/[A-Z|a-z|0-9|/|\ - .]*" -o` - newIdName=`echo "$oldIdName" | sed -e "s/^@executable_path\/\.\.\/Frameworks//"` - newIdName="@rpath$newIdName" - fi - - install_name_tool -id "$newIdName" $executeName - fi - - # Remove old rpath - install_name_tool -delete_rpath "@executable_path/../Frameworks" $executeName 2>/dev/null || true - - # Add new rpath - if [ "$isQML" == "true" ]; then - install_name_tool -add_rpath "$TARGET_QML_DIR" $executeName - install_name_tool -add_rpath "$QT_FRAMEWORK_DRI" $executeName - else - install_name_tool -add_rpath "$QT_FRAMEWORK_DRI" $executeName - fi -} - -# Replace paths in Qt frameworks -function replacePathInFrameworks(){ - cd "$1" - for file in * - do - if [ "${file##*.}"x = "framework"x ] - then - executeName=${file%.*} - cd $file - startReplacePathInExecuteFile "$executeName" - cp -f "$executeName" "Versions/A/" - cp -f "$executeName" "Versions/Current/" - cd .. - fi - done -} - -# Replace paths in PAGExporter main executable -replacePathInExporter(){ - cd "$1/Contents/MacOS" - executeName="PAGExporter" - startReplacePathInExecuteFile "$executeName" -} - -# Replace paths in dylib files recursively -replacePathInDylib(){ - cd "$1" - for file in * - do - if [ -d "$file" ]; then - replacePathInDylib $file $2 - elif [ "${file##*.}"x = "dylib"x ]; then - startReplacePathInExecuteFile "$file" $2 - fi - done - cd .. -} - -ExporterPath="$1" -ExporterFrameWorkPath="$ExporterPath/Contents/Frameworks" -ExporterPluginPath="$ExporterPath/Contents/PlugIns" -ExporterResourcePath="$ExporterPath/Contents/Resources/qml" - -replacePathInFrameworks "$ExporterFrameWorkPath" -replacePathInDylib "$ExporterPluginPath" -replacePathInDylib "$ExporterResourcePath" true -replacePathInExporter "$ExporterPath" diff --git a/viewer/src/platform/PluginInstaller.h b/viewer/src/platform/PluginInstaller.h index 3a70405b89..43e12f4a50 100644 --- a/viewer/src/platform/PluginInstaller.h +++ b/viewer/src/platform/PluginInstaller.h @@ -134,7 +134,6 @@ class PluginInstaller : public QObject { bool copyUserPluginWithRetry(const QString& plugin, int maxRetries = 5) const; - void CopyQtResource(char cmd[], int cmdSize) const; void DeleteQtResource(char cmd[], int cmdSize) const; struct Version { diff --git a/viewer/src/platform/mac/PluginInstaller.mm b/viewer/src/platform/mac/PluginInstaller.mm index 622f2c87b8..2e88813e4a 100644 --- a/viewer/src/platform/mac/PluginInstaller.mm +++ b/viewer/src/platform/mac/PluginInstaller.mm @@ -311,12 +311,6 @@ QStringList detectSpecialVersion() { } } - char qtResourceCmd[cmdBufSize] = {0}; - CopyQtResource(qtResourceCmd, sizeof(qtResourceCmd)); - if (strlen(qtResourceCmd) > 0) { - adminCommands << QString::fromUtf8(qtResourceCmd).trimmed(); - } - if (adminCommands.isEmpty()) { return userPluginSuccess; } @@ -353,14 +347,6 @@ QStringList detectSpecialVersion() { return executeWithPrivileges(fullCommand); } -void PluginInstaller::CopyQtResource(char cmd[], int cmdSize) const { - NSString* copyQtShellPath = [[NSBundle mainBundle] pathForResource:@"copy_qt_resource" - ofType:@"sh"]; - if (copyQtShellPath != nil) { - snprintf(cmd + strlen(cmd), cmdSize - strlen(cmd), "sh '%s'\n", [copyQtShellPath UTF8String]); - } -} - void PluginInstaller::DeleteQtResource(char cmd[], int cmdSize) const { NSString* deleteShellPath = [[NSBundle mainBundle] pathForResource:@"delete_qt_resource" ofType:@"sh"];