feat(macos): build a signed .app bundle in a .dmg (#4759)

This commit is contained in:
Andy Grundman
2026-03-03 23:30:53 -05:00
committed by GitHub
parent b000d43883
commit 423a864ee3
22 changed files with 660 additions and 112 deletions
+3 -1
View File
@@ -142,6 +142,8 @@ include_directories(
"${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet/include"
"${CMAKE_SOURCE_DIR}/third-party/nanors"
"${CMAKE_SOURCE_DIR}/third-party/nanors/deps/obl"
${OPENSSL_INCLUDE_DIR}
${Opus_INCLUDE_DIR}
${FFMPEG_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} # has to be the last, or we get runtime error on macOS ffmpeg encoder
)
@@ -152,7 +154,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
enet
libdisplaydevice::display_device
nlohmann_json::nlohmann_json
opus
${Opus_LIBRARY}
${FFMPEG_LIBRARIES}
${Boost_LIBRARIES}
${OPENSSL_LIBRARIES}
+11 -1
View File
@@ -2,6 +2,14 @@
add_compile_definitions(SUNSHINE_PLATFORM="macos")
if (SUNSHINE_BUILD_HOMEBREW)
set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}")
else()
# Bundle layout for macOS app builds
set(SUNSHINE_ASSETS_DIR "${CMAKE_PROJECT_NAME}.app/Contents/Resources/assets")
set(SUNSHINE_ASSETS_DIR_DEF "../Resources/assets")
endif()
set(MACOS_LINK_DIRECTORIES
/opt/homebrew/lib
/opt/local/lib
@@ -26,7 +34,9 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${FOUNDATION_LIBRARY}
${VIDEO_TOOLBOX_LIBRARY})
set(APPLE_PLIST_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist")
set(APPLE_PLIST_TEMPLATE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/build/Info.plist.in")
set(APPLE_PLIST_FILE "${CMAKE_BINARY_DIR}/Info.plist")
configure_file("${APPLE_PLIST_TEMPLATE}" "${APPLE_PLIST_FILE}" @ONLY)
set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.h"
+1 -1
View File
@@ -5,6 +5,6 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CURL_LIBRARIES})
# add install prefix to assets path if not already there
if(NOT SUNSHINE_ASSETS_DIR MATCHES "^${CMAKE_INSTALL_PREFIX}")
if(NOT APPLE AND NOT SUNSHINE_ASSETS_DIR MATCHES "^${CMAKE_INSTALL_PREFIX}")
set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}")
endif()
+107
View File
@@ -0,0 +1,107 @@
# Copyright 2019-2022, Collabora, Ltd.
#
# SPDX-License-Identifier: BSL-1.0
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
#
# Original Author:
# 2019-2022 Rylie Pavlik <rylie.pavlik@collabora.com> <rylie@ryliepavlik.com>
#[[.rst:
FindOpus
---------------
Find the opus codec library.
Targets
^^^^^^^
If successful, the following imported target is created
* ``Opus::opus``
Cache variables
^^^^^^^^^^^^^^^
The following cache variable may also be set to assist/control the operation of this module:
``Opus_ROOT_DIR``
The root to search for opus.
#]]
set(Opus_ROOT_DIR # cmake-lint: disable=C0103
"${Opus_ROOT_DIR}"
CACHE PATH "Root to search for opus")
# Todo: handle in-tree/fetch-content builds?
if(NOT OPUS_FOUND)
# Look for a CMake config file
find_package(Opus QUIET NO_MODULE)
endif()
if(TARGET opus)
# for fetch content/in tree
set(Opus_LIBRARY opus) # cmake-lint: disable=C0103
endif()
if(NOT ANDROID)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
set(_old_prefix_path "${CMAKE_PREFIX_PATH}")
# So pkg-config uses Opus_ROOT_DIR too.
if(Opus_ROOT_DIR)
list(APPEND CMAKE_PREFIX_PATH ${Opus_ROOT_DIR})
endif()
pkg_check_modules(PC_opus QUIET opus)
# Restore
set(CMAKE_PREFIX_PATH "${_old_prefix_path}")
endif()
endif()
find_path(
Opus_INCLUDE_DIR
NAMES opus/opus.h
PATHS ${Opus_ROOT_DIR}
HINTS ${PC_opus_INCLUDE_DIRS} ${OPUS_INCLUDE_DIR} ${OPUS_INCLUDE_DIRS}
PATH_SUFFIXES include)
find_library(
Opus_LIBRARY
NAMES opus
PATHS ${Opus_ROOT_DIR}
HINTS ${PC_opus_LIBRARY_DIRS}
PATH_SUFFIXES lib)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Opus REQUIRED_VARS Opus_LIBRARY
Opus_INCLUDE_DIR)
if(Opus_FOUND)
if(NOT TARGET Opus::opus)
if(TARGET ${Opus_LIBRARY})
# we want an alias
add_library(Opus::opus ALIAS ${Opus_LIBRARY})
else()
# we want an imported target
add_library(Opus::opus UNKNOWN IMPORTED)
set_target_properties(
Opus::opus
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${Opus_INCLUDE_DIR}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION ${Opus_LIBRARY})
endif()
endif()
mark_as_advanced(Opus_INCLUDE_DIR Opus_LIBRARY)
endif()
mark_as_advanced(Opus_ROOT_DIR)
include(FeatureSummary)
set_package_properties(
Opus PROPERTIES
URL "https://opus-codec.org/"
DESCRIPTION
"The reference library implementation for the audio codec of the same name."
)
+7 -1
View File
@@ -1,6 +1,10 @@
# load common dependencies
# this file will also load platform specific dependencies
# Resolve OpenSSL before subprojects run their own find_package(OpenSSL) calls.
# This ensures a user-provided OPENSSL_ROOT_DIR is honored consistently.
find_package(OpenSSL REQUIRED)
# boost, this should be before Simple-Web-Server as it also depends on boost
include(dependencies/Boost_Sunshine)
@@ -17,7 +21,6 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice")
# common dependencies
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
pkg_check_modules(CURL REQUIRED libcurl)
@@ -29,6 +32,9 @@ include_directories(SYSTEM ${MINIUPNP_INCLUDE_DIRS})
# ffmpeg pre-compiled binaries
include("${CMAKE_MODULE_PATH}/dependencies/ffmpeg.cmake")
# Opus
include("${CMAKE_MODULE_PATH}/dependencies/FindOpus.cmake")
# platform specific dependencies
if(WIN32)
include("${CMAKE_MODULE_PATH}/dependencies/windows.cmake")
+105 -19
View File
@@ -1,25 +1,111 @@
# macos specific packaging
# todo - bundle doesn't produce a valid .app use cpack -G DragNDrop
set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}")
set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}")
set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns")
# set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine")
if (SUNSHINE_BUILD_HOMEBREW)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
DESTINATION "${SUNSHINE_ASSETS_DIR}")
if(SUNSHINE_PACKAGE_MACOS) # todo
set(MAC_PREFIX "${CMAKE_PROJECT_NAME}.app/Contents")
set(INSTALL_RUNTIME_DIR "${MAC_PREFIX}/MacOS")
# copy assets to build directory, for running without install
file(COPY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
DESTINATION "${CMAKE_BINARY_DIR}/assets")
else()
# .app build
set(APPLE_CODESIGN_IDENTITY "" CACHE STRING "Codesign identity, e.g. 'Developer ID Application: Name (TEAMID)'")
# Build an .app
set(CMAKE_MACOSX_BUNDLE YES)
set(MAC_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app")
set(MAC_BUNDLE_CONTENTS "${MAC_BUNDLE_NAME}/Contents")
set(MAC_BUNDLE_RESOURCES "${MAC_BUNDLE_CONTENTS}/Resources")
install(TARGETS sunshine
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime)
else()
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh"
DESTINATION "${SUNSHINE_ASSETS_DIR}")
endif()
BUNDLE DESTINATION .
COMPONENT Runtime)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
DESTINATION "${SUNSHINE_ASSETS_DIR}")
# copy assets to build directory, for running without install
file(COPY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
DESTINATION "${CMAKE_BINARY_DIR}/assets")
install(FILES "${APPLE_PLIST_FILE}"
DESTINATION "${MAC_BUNDLE_CONTENTS}"
COMPONENT Runtime)
install(FILES "${PROJECT_SOURCE_DIR}/src_assets/macos/build/sunshine.icns"
DESTINATION "${MAC_BUNDLE_RESOURCES}"
COMPONENT Runtime)
# macOS-specific assets (apps.json, etc.)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
DESTINATION "${MAC_BUNDLE_RESOURCES}/assets"
COMPONENT Runtime
PATTERN ".DS_Store" EXCLUDE
PATTERN "._*" EXCLUDE)
# Pull in non-system dylibs for a self-contained .app
install(CODE "
set(_app \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}.app\")
message(STATUS \"Running fixup_bundle for: \${_app}\")
include(BundleUtilities)
set(BU_CHMOD_BUNDLE_ITEMS TRUE)
fixup_bundle(\"\${_app}\" \"\" \"\")
# Remove Finder/resource-fork metadata that breaks codesign.
execute_process(COMMAND /usr/bin/xattr -rc \"\${_app}\")
message(STATUS \"removing any existing signatures\")
execute_process(COMMAND /usr/bin/codesign
--remove-signature --force --deep
\"\${_app}\"
RESULT_VARIABLE rc
)
if(NOT rc EQUAL 0)
message(FATAL_ERROR \"codesign failed to remove existing signatures\")
endif()
# SHOULD_SIGN is set only when publish_release is true or when manually building
if(\$ENV{SHOULD_SIGN} STREQUAL \"true\")
# Sign anything inside Contents/Frameworks
set(_fw_dir \"\${_app}/Contents/Frameworks\")
if(EXISTS \"\${_fw_dir}\")
file(GLOB_RECURSE _sign_items
\"\${_fw_dir}/*.framework\"
\"\${_fw_dir}/*.dylib\"
)
foreach(item IN LISTS _sign_items)
execute_process(COMMAND /usr/bin/codesign --verbose=2
--sign \"${APPLE_CODESIGN_IDENTITY}\" \"\${item}\"
--force --timestamp --options=runtime
RESULT_VARIABLE rc2
)
if(NOT rc2 EQUAL 0)
message(FATAL_ERROR \"codesign failed while signing library: \${item}\")
endif()
endforeach()
endif()
# Sign the app last
execute_process(COMMAND /usr/bin/codesign --verbose=2
--sign \"${APPLE_CODESIGN_IDENTITY}\" \"\${_app}\"
--force --timestamp --options=runtime
RESULT_VARIABLE rc3
)
if(NOT rc3 EQUAL 0)
message(FATAL_ERROR \"codesign failed while signing .app\")
endif()
# Verify
execute_process(COMMAND /usr/bin/codesign --verify --deep --strict --verbose=2 \"\${_app}\"
RESULT_VARIABLE rc4
)
if(NOT rc4 EQUAL 0)
message(FATAL_ERROR \"codesign --verify failed\")
endif()
endif()
" COMPONENT Runtime)
# DragNDrop
set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}")
set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}")
set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/src_assets/macos/build/sunshine.icns")
set(CPACK_PACKAGING_INSTALL_PREFIX "/")
set(CPACK_DMG_BACKGROUND_IMAGE "${PROJECT_SOURCE_DIR}/src_assets/macos/build/sunshine-background-72dpi.jpg")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${PROJECT_SOURCE_DIR}/src_assets/macos/build/dmg-finder-layout.applescript")
endif()
+2 -2
View File
@@ -1,8 +1,8 @@
# unix specific packaging
# put anything here that applies to both linux and macos
# return here if building a macos package
if(SUNSHINE_PACKAGE_MACOS)
# return here if building a macos .app
if(APPLE AND NOT SUNSHINE_BUILD_HOMEBREW)
return()
endif()
+5
View File
@@ -1,5 +1,10 @@
if (WIN32)
elseif (APPLE)
if (NOT SUNSHINE_BUILD_HOMEBREW)
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
set(CMAKE_INSTALL_RPATH "")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
endif()
elseif (UNIX)
include(GNUInstallDirs)
-2
View File
@@ -41,8 +41,6 @@ endif()
if(APPLE)
option(SUNSHINE_CONFIGURE_PORTFILE
"Configure macOS Portfile. Recommended to use with SUNSHINE_CONFIGURE_ONLY" OFF)
option(SUNSHINE_PACKAGE_MACOS
"Should only be used when creating a macOS package/dmg." OFF)
elseif(UNIX) # Linux
option(SUNSHINE_BUILD_APPIMAGE
"Enable an AppImage build." OFF)
+14 -4
View File
@@ -1,7 +1,11 @@
# common target definitions
# this file will also load platform specific macros
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
if(APPLE AND NOT SUNSHINE_BUILD_HOMEBREW)
add_executable(sunshine MACOSX_BUNDLE ${SUNSHINE_TARGET_FILES})
else()
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
endif()
foreach(dep ${SUNSHINE_TARGET_DEPENDENCIES})
add_dependencies(sunshine ${dep}) # compile these before sunshine
endforeach()
@@ -27,9 +31,15 @@ endif()
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
set_target_properties(sunshine PROPERTIES CXX_STANDARD 23
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR})
if(APPLE AND NOT SUNSHINE_BUILD_HOMEBREW)
# codesign on Mac won't sign an .app that uses a symlink
set_target_properties(sunshine PROPERTIES CXX_STANDARD 23)
else()
# symlink sunshine -> sunshine-PROJECT_VERSION
set_target_properties(sunshine PROPERTIES CXX_STANDARD 23
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR})
endif()
# CLion complains about unknown flags after running cmake, and cannot add symbols to the index for cuda files
if(CUDA_INHERIT_COMPILE_OPTIONS)
+23 -1
View File
@@ -1,4 +1,26 @@
# macos specific target definitions
target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${APPLE_PLIST_FILE})
if (SUNSHINE_BUILD_HOMEBREW)
target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${APPLE_PLIST_FILE})
else()
# .app build
set_target_properties(sunshine PROPERTIES
OUTPUT_NAME "${CMAKE_PROJECT_NAME}"
MACOSX_BUNDLE_BUNDLE_NAME "${CMAKE_PROJECT_NAME}"
MACOSX_BUNDLE_GUI_IDENTIFIER "${PROJECT_FQDN}"
MACOSX_BUNDLE_INFO_PLIST "${APPLE_PLIST_FILE}"
MACOSX_BUNDLE_ICON_FILE "sunshine.icns"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}")
# Populate bundle resources in the build tree for local runs.
set(_bundle_resources_dir "$<TARGET_FILE_DIR:sunshine>/../Resources")
add_custom_command(TARGET sunshine POST_BUILD
COMMENT "Copying bundle resources to build tree"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${_bundle_resources_dir}"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_BINARY_DIR}/assets" "${_bundle_resources_dir}/assets"
VERBATIM)
endif()
# Tell linker to dynamically load these symbols at runtime, in case they're unavailable:
target_link_options(sunshine PRIVATE -Wl,-U,_CGPreflightScreenCaptureAccess -Wl,-U,_CGRequestScreenCaptureAccess)