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
+181
View File
@@ -0,0 +1,181 @@
---
name: CI-macOS
permissions: {}
on:
workflow_call:
inputs:
publish_release:
required: true
type: string
release_commit:
required: true
type: string
release_version:
required: true
type: string
secrets:
# email address
APPLE_ID:
required: false
# 10-character Team ID
APPLE_TEAM_ID:
required: false
# app-specific password in APPLE_ID's account that must be named "notarytool"
# https://support.apple.com/en-us/102654
APPLE_NOTARYTOOL_PASSWORD:
required: false
# Developer ID Application: Full Name (TEAMIDHERE)
APPLE_CODESIGN_IDENTITY:
required: false
# pkcs12 export from Xcode in base64
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64:
required: false
# pkcs12 password added by Xcode export
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_PASSWORD:
required: false
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ inputs.release_version }}
COMMIT: ${{ inputs.release_commit }}
jobs:
build_dmg:
name: ${{ matrix.name }}
permissions:
contents: read
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: "macos-14"
name: "macOS-arm64"
- os: "macos-15-intel"
name: "macOS-x86_64"
steps:
- name: Install Apple certificate
uses: apple-actions/import-codesign-certs@b610f78488812c1e56b20e6df63ec42d833f2d14 # v6.0.0
if: inputs.publish_release == 'true'
with:
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }}
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_PASSWORD }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Install dependencies
timeout-minutes: 5
run: |
brew install --force \
cmake \
doxygen \
graphviz \
node \
pkgconf \
icu4c@78 \
miniupnpc \
openssl@3 \
opus
- name: Configure
env:
APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }}
run: |
mkdir -p build
cmake \
-B build \
-S . \
-DBUILD_WERROR=ON \
-DCMAKE_BUILD_TYPE=Release \
-DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3 2>/dev/null)" \
-DOpus_ROOT_DIR="$(brew --prefix opus 2>/dev/null)" \
-DSUNSHINE_PUBLISHER_NAME="${GITHUB_REPOSITORY_OWNER}" \
-DSUNSHINE_PUBLISHER_WEBSITE="https://app.lizardbyte.dev" \
-DSUNSHINE_PUBLISHER_ISSUE_URL="https://app.lizardbyte.dev/support" \
-DAPPLE_CODESIGN_IDENTITY="${APPLE_CODESIGN_IDENTITY}"
- name: Build
run: cmake --build build -j "$(sysctl -n hw.ncpu)"
- name: Package DMG
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_NOTARYTOOL_PASSWORD }}
APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }}
SHOULD_SIGN: ${{ inputs.publish_release }}
run: |
# build DMG and sign everything (see cmake/packaging/macos.cmake)
# cpack can rarely fail with "hdiutil: create failed - Resource busy"
# so let's allow 1 retry
if ! cpack -G DragNDrop --config build/CPackConfig.cmake; then
echo "cpack failed, retrying once with verbose..."
if ! cpack -G DragNDrop --config build/CPackConfig.cmake --verbose; then
echo "cpack failed again. Aborting."
exit 1
fi
fi
# Notarize
if [[ "${SHOULD_SIGN}" == "true" && -n "${APPLE_NOTARYTOOL_PASSWORD}" ]]; then
# Notarizing allows the signed .app to run on any Mac with no prompts.
# If you don't notarize, users must jump through the "Open Anyway" hoop as well as run
# `xattr -cr /Applications/Sunshine.app` to remove quarantine.
if [[ -n "${APPLE_NOTARYTOOL_PASSWORD}" ]]; then
xcrun notarytool submit build/cpack_artifacts/Sunshine.dmg \
--apple-id "${APPLE_ID}" \
--team-id "${APPLE_TEAM_ID}" \
--password "${APPLE_NOTARYTOOL_PASSWORD}" \
--wait
xcrun stapler staple -v build/cpack_artifacts/Sunshine.dmg
fi
fi
mkdir -p artifacts
mv build/cpack_artifacts/Sunshine.dmg \
artifacts/Sunshine-${{ matrix.name }}.dmg
- name: Test
id: test
working-directory: build/tests
run: ./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
- name: Generate gcov report
id: test_report
# any except canceled or skipped
if: >-
always() &&
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
working-directory: build
run: |
python -m pip install "../scripts[test]"
python -m gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
--xml-pretty \
-j "$(sysctl -n hw.ncpu)" \
-o coverage.xml
- name: Upload coverage artifact
if: >-
always() &&
(steps.test_report.outcome == 'success')
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: coverage-${{ matrix.name }}
path: |
build/coverage.xml
build/tests/test_results.xml
if-no-files-found: error
- name: Upload Artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: build-${{ matrix.name }}
path: artifacts/
if-no-files-found: error
+27
View File
@@ -89,6 +89,26 @@ jobs:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
GIT_EMAIL: ${{ secrets.GH_BOT_EMAIL }}
build-macos:
name: macOS
needs: release-setup
permissions:
contents: read
uses: ./.github/workflows/ci-macos.yml
with:
publish_release: ${{ needs.release-setup.outputs.publish_release }}
release_commit: ${{ needs.release-setup.outputs.release_commit }}
release_version: ${{ needs.release-setup.outputs.release_version }}
secrets:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_NOTARYTOOL_PASSWORD }}
APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }}
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64: >-
${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }}
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_PASSWORD: >-
${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_PASSWORD }}
build-linux:
name: Linux
needs: release-setup
@@ -161,6 +181,7 @@ jobs:
- build-linux
- build-archlinux
- build-linux-flatpak
- build-macos
- build-homebrew
- build-windows
permissions:
@@ -182,6 +203,12 @@ jobs:
- name: Archlinux
coverage: true
pr: true
- name: macOS-arm64
coverage: true
pr: true
- name: macOS-x86_64
coverage: true
pr: true
- name: Homebrew-macos-14
coverage: false
pr: true
+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)
+12 -2
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
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)
+68 -27
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env bash
# Note: This script is not used by CI, and is only for manually building/signing the .app.
# Changes made to this script should also be made in ci-macos.yml.
set -euo pipefail
# Default value for arguments
@@ -7,16 +9,18 @@ publisher_name="LizardByte"
publisher_website="https://app.lizardbyte.dev"
publisher_issue_url="https://app.lizardbyte.dev/support"
step="all"
build_docs="ON"
build_docs="OFF"
build_tests="ON"
build_type="Release"
build_system="Unix Makefiles"
sign_app="true"
# environment variables
BUILD_VER=""
# BUILD_VERSION should be empty or cmake will assume a CI build
BUILD_VERSION=""
BRANCH=$(git rev-parse --abbrev-ref HEAD)
COMMIT=$(git rev-parse --short HEAD)
export BUILD_VER
export BUILD_VERSION
export BRANCH
export COMMIT
@@ -38,9 +42,14 @@ function _usage() {
local exit_code=$1
cat <<EOF
This script installs the dependencies and builds the project.
The script is intended to be run on an Apple Silicon Mac,
but may work on Intel as well.
This script builds a macOS .app bundle packaged inside a .dmg.
If the environment variable APPLE_CODESIGN_IDENTITY is set, the app will be signed.
This must be a "Developer ID" identity.
For others to be able to open the .dmg, it must be notarized. Create a keychain profile named
"notarytool-password" based on the instructions at
https://developer.apple.com/documentation/security/customizing-the-notarization-workflow?language=objc
Usage:
$0 [options]
@@ -52,14 +61,17 @@ Options:
--publisher-website The URL of the publisher's website.
--publisher-issue-url The URL of the publisher's support site or issue tracker.
If you provide a modified version of Sunshine, we kindly request that you use your own url.
--step Which step(s) to run: deps, cmake, build, or all (default: all)
--step=STEP Which step(s) to run: deps, cmake, build, dmg, or all (default: all)
--debug Build in debug mode.
--skip-docs Don't build docs.
--build-docs Build docs.
--skip-tests Don't build the test suite.
--skip-codesign Don't sign/notarize the bundle.
Steps:
deps Install dependencies only
cmake Run cmake configure only
build Build the project only
dmg Create a DMG package
all Run all steps (default)
EOF
@@ -79,19 +91,25 @@ function run_step_cmake() {
# prepare CMAKE args
cmake_args=(
"-B=build"
"-G=${build_system}"
"-S=."
"-DCMAKE_BUILD_TYPE=${build_type}"
"-DBUILD_WERROR=ON"
"-DHOMEBREW_ALLOW_FETCHCONTENT=ON"
"-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3 2>/dev/null)"
"-DSUNSHINE_ASSETS_DIR=sunshine/assets"
"-DSUNSHINE_BUILD_HOMEBREW=ON"
"-DSUNSHINE_ENABLE_TRAY=ON"
"-DBUILD_DOCS=${build_docs}"
"-DBOOST_USE_STATIC=OFF"
"-DBUILD_TESTS=${build_tests}"
"-DBUILD_WERROR=ON"
"-DCMAKE_BUILD_TYPE=${build_type}"
"-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3 2>/dev/null)"
"-DOpus_ROOT_DIR=$(brew --prefix opus 2>/dev/null)"
"-DSUNSHINE_ENABLE_TRAY=ON"
)
if [[ -n "${sign_app}" ]]; then
if [[ -n "${APPLE_CODESIGN_IDENTITY:-}" ]]; then
cmake_args+=("-DAPPLE_CODESIGN_IDENTITY='${APPLE_CODESIGN_IDENTITY}'")
else
echo "Please set the APPLE_CODESIGN_IDENTITY environment variable or use --skip-codesign"
exit 1
fi
fi
# Publisher metadata
if [[ -n "$publisher_name" ]]; then
cmake_args+=("-DSUNSHINE_PUBLISHER_NAME='${publisher_name}'")
@@ -113,12 +131,26 @@ function run_step_cmake() {
function run_step_build() {
echo "Running step: Build"
make -C "${build_dir}" -j "${num_processors}"
cmake --build "${build_dir}" -j "${num_processors}"
return 0
}
echo "*** To complete installation, run:"
echo
echo " sudo make -C \"${build_dir}\" install"
echo " /usr/local/bin/sunshine"
function run_step_dmg() {
echo "Running step: Creating DMG package"
# This variable is needed by cmake/packaging/macos.cmake
SHOULD_SIGN=false
if [[ -n "${sign_app}" ]]; then
SHOULD_SIGN=true
fi
export SHOULD_SIGN
cpack -G DragNDrop --config "${build_dir}/CPackConfig.cmake" --verbose
if [[ -n "${sign_app}" ]]; then
xcrun notarytool submit "${build_dir}/cpack_artifacts/Sunshine.dmg" --keychain-profile "notarytool-password" --wait
xcrun stapler staple -v "${build_dir}/cpack_artifacts/Sunshine.dmg"
fi
return 0
}
@@ -133,14 +165,17 @@ function run_install() {
build)
run_step_build
;;
dmg)
run_step_dmg
;;
all)
run_step_deps
run_step_cmake
run_step_build
run_step_dmg
;;
*)
echo "Invalid step: $step"
echo "Valid steps are: deps, cmake, build, all"
echo "Valid steps are: deps, cmake, build, dmg, all"
exit 1
;;
esac
@@ -172,8 +207,14 @@ while getopts ":h-:" opt; do
debug)
build_type="Debug"
;;
skip-docs)
build_docs="OFF"
build-docs)
build_docs="ON"
;;
skip-tests)
build_tests="OFF"
;;
skip-codesign)
sign_app=""
;;
*)
echo "Invalid option: --${OPTARG}" 1>&2
+24
View File
@@ -5,9 +5,14 @@
// standard includes
#include <codecvt>
#include <csignal>
#include <filesystem>
#include <fstream>
#include <iostream>
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
// local includes
#include "confighttp.h"
#include "display_device.h"
@@ -116,6 +121,25 @@ void mainThreadLoop(const std::shared_ptr<safe::event_t<bool>> &shutdown_event)
}
int main(int argc, char *argv[]) {
#ifdef __APPLE__
// Bundle assets are referenced relative to the executable
// (e.g. ../Resources/assets), so anchor cwd to Contents/MacOS.
{
char executable[2048];
uint32_t size = sizeof(executable);
if (_NSGetExecutablePath(executable, &size) == 0) {
std::error_code ec;
auto exec_dir = std::filesystem::weakly_canonical(std::filesystem::path {executable}, ec).parent_path();
if (!ec) {
std::filesystem::current_path(exec_dir, ec);
}
if (ec) {
std::cerr << "Failed to set working directory to executable path: " << ec.message() << '\n';
}
}
}
#endif
lifetime::argv = argv;
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>dev.lizardbyte.sunshine</string>
<key>CFBundleName</key>
<string>Sunshine</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app requires access to your microphone to stream audio.</string>
</dict>
</plist>
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>@PROJECT_FQDN@</string>
<key>CFBundleName</key>
<string>@CMAKE_PROJECT_NAME@</string>
<key>CFBundleDisplayName</key>
<string>@CMAKE_PROJECT_NAME@</string>
<key>CFBundleExecutable</key>
<string>@CMAKE_PROJECT_NAME@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>@PROJECT_VERSION@</string>
<key>CFBundleVersion</key>
<string>@PROJECT_VERSION@</string>
<key>CFBundleIconFile</key>
<string>sunshine.icns</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>NSMicrophoneUsageDescription</key>
<string>@CMAKE_PROJECT_NAME@ requires access to your microphone to stream audio.</string>
<key>NSBonjourServices</key>
<array>
<string>_nvstream._tcp</string>
</array>
</dict>
</plist>
+52
View File
@@ -0,0 +1,52 @@
# Based on https://gitlab.kitware.com/cmake/cmake/-/blob/master/Packaging/CMakeDMGSetup.scpt
on run argv
set image_name to item 1 of argv
tell application "Finder"
tell disk image_name
-- wait for the image to finish mounting
set open_attempts to 0
repeat while open_attempts < 4
try
open
delay 1
set open_attempts to 5
close
on error errStr number errorNumber
set open_attempts to open_attempts + 1
delay 10
end try
end repeat
delay 5
-- open the image the first time and save a DS_Store with just
-- background and icon setup
open
set current view of container window to icon view
set theViewOptions to the icon view options of container window
set background picture of theViewOptions to file ".background:background.jpg"
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 128
set text size of theViewOptions to 16
close
-- next setup the position of the app and Applications symlink
-- plus hide all the window decoration
open
update without registering applications
tell container window
set sidebar width to 0
set statusbar visible to false
set toolbar visible to false
set the bounds to { 400, 100, 900, 465 }
set position of item "Sunshine.app" to { 133, 200 }
set position of item "Applications" to { 378, 200 }
end tell
update without registering applications
close
end tell
delay 1
end tell
end run
Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.
-41
View File
@@ -1,41 +0,0 @@
#!/bin/bash -e
# note: this file was used to remove files when using the pkg/dmg, it is no longer used, but left for reference
set -e
package_name=org.macports.Sunshine
echo "Removing files now..."
FILES=$(pkgutil --files $package_name --only-files)
for file in ${FILES}; do
file="/$file"
echo "removing: $file"
rm -f "$file"
done
echo "Removing directories now..."
DIRECTORIES=$(pkgutil --files org.macports.Sunshine --only-dirs)
for dir in ${DIRECTORIES}; do
dir="/$dir"
echo "Checking if empty directory: $dir"
# check if directory is empty... could just use ${DIRECTORIES} here if pkgutils added the `/` prefix
empty_dir=$(find "$dir" -depth 0 -type d -empty)
# remove the directory if it is empty
if [[ $empty_dir != "" ]]; then # prevent the loop from running and failing if no directories found
# shellcheck disable=SC2066 # don't split words as we already know this will be a single directory
for i in "${empty_dir}"; do
echo "Removing empty directory: ${i}"
rmdir "${i}"
done
fi
done
echo "Forgetting Sunshine..."
pkgutil --forget $package_name
echo "Sunshine has been uninstalled..."
BIN
View File
Binary file not shown.