Skip to content

(APPLE) Build a Universal Framework, and add code signing and notarization #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: cboulay/cmake_cleanup
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0d8fcf2
mingw - Revert changes; back to windows-2019 which doesn't run; at le…
cboulay Jul 7, 2025
d453e75
Keep original order of cmake invocations as much as possible
cboulay Jul 7, 2025
c6779b1
Reorder lslobj cmake commands for slightly more logical grouping.
cboulay Jul 8, 2025
e1014b0
Cleanup CMake redundancies and use FILE_SET for headers -- only solut…
cboulay Jul 8, 2025
4c197bf
Add preliminary support for Xcode and building universal dylib
cboulay Jul 13, 2025
d1fa6d1
Initial support for Apple Frameworks
cboulay Jul 13, 2025
88c67dc
Move scripts into subfolder
cboulay Jul 13, 2025
c0ea96a
Remove set(CMAKE_MACOSX_RPATH ON) because that is already the default…
cboulay Jul 14, 2025
4a936ad
Fix framework install directory when not installing to the system.
cboulay Jul 14, 2025
25a8860
Remove vestigial docker references in CI scripts (docker was only use…
cboulay Jul 14, 2025
9531c43
First attempt at signing and notarizing framework in GitHub Actions. …
cboulay Jul 14, 2025
258d525
Fix some cmake errors that snuck in during rebase.
cboulay Jul 14, 2025
d7737ff
Fix Mac CMake argument for universal binary.
cboulay Jul 14, 2025
b924b08
FILE_SET is incompatible with frameworks. Revert back to target_inclu…
cboulay Jul 15, 2025
bb8650b
examples - find_package must search in Frameworks subdirectory if APPLE.
cboulay Jul 15, 2025
4c3e599
Add codesign step to GHA CI script.
cboulay Jul 15, 2025
2503db8
Fix typo
cboulay Jul 15, 2025
0a836c6
macOS packaging - auto version and change destination
cboulay Jul 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 105 additions & 20 deletions .github/workflows/cppcmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,52 @@ jobs:
fail-fast: false
matrix:
config:
- {name: "ubuntu-22.04", os: "ubuntu-22.04", cmake_extra: "-DLSL_BUNDLED_PUGIXML=OFF" }
- {name: "ubuntu-24.04", os: "ubuntu-24.04", cmake_extra: "-DLSL_BUNDLED_PUGIXML=OFF" }
- {name: "windows-x64", os: "windows-latest", cmake_extra: "-T v142,host=x86"}
- {name: "windows-32", os: "windows-latest", cmake_extra: "-T v142,host=x86 -A Win32"}
- {name: "macOS-latest", os: "macOS-latest"}

# runs all steps in the container configured in config.docker or as subprocesses when empty
container: ${{ matrix.config.docker }}
# - {name: "ubuntu-22.04", os: "ubuntu-22.04", cmake_extra: "-DLSL_BUNDLED_PUGIXML=OFF" }
# - {name: "ubuntu-24.04", os: "ubuntu-24.04", cmake_extra: "-DLSL_BUNDLED_PUGIXML=OFF" }
# - {name: "windows-x64", os: "windows-latest", cmake_extra: "-T v142,host=x86"}
# - {name: "windows-32", os: "windows-latest", cmake_extra: "-T v142,host=x86 -A Win32"}
- {name: "macOS-latest", os: "macOS-latest", cmake_extra: "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_OSX_ARCHITECTURES=\"x86_64;arm64\" -DLSL_FRAMEWORK=ON" }

steps:
- uses: actions/checkout@v4
- name: set up build environment in container

- name: Install certificates and provisioning profiles
if: matrix.config.os == 'macOS-latest'
env:
MACOS_CERTIFICATE_APP: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_INST: ${{ secrets.PROD_MACOS_CERTIFICATE_INST }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
set -x
apt update
apt install -y --no-install-recommends g++ git ninja-build file dpkg-dev lsb-release sudo curl cmake libpugixml-dev
if: ${{ matrix.config.docker }}
# Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH
security default-keychain -s $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH

# Import certificates from secrets ...
CERTIFICATE_PATH_APP=$RUNNER_TEMP/build_certificate_app.p12
CERTIFICATE_PATH_INST=$RUNNER_TEMP/build_certificate_inst.p12
echo -n "$MACOS_CERTIFICATE_APP" | base64 --decode -o $CERTIFICATE_PATH_APP
echo -n "$MACOS_CERTIFICATE_INST" | base64 --decode -o $CERTIFICATE_PATH_INST
# ... to keychain
security import $CERTIFICATE_PATH_APP -P "$MACOS_CERTIFICATE_PWD" -k $KEYCHAIN_PATH -A -t cert -f pkcs12
security import $CERTIFICATE_PATH_INST -P "$MACOS_CERTIFICATE_PWD" -k $KEYCHAIN_PATH -A -t cert -f pkcs12

# Set trusted partitions (groups of applications) that can access the keychain items
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH

# Get certificate identities into environment variables
CERT_IDENTITY_APP=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}')
echo "APPLE_CODE_SIGN_IDENTITY_APP=$CERT_IDENTITY_APP" >> $GITHUB_ENV
CERT_IDENTITY_INST=$(security find-identity -v -p basic $KEYCHAIN_PATH | grep "Developer ID Installer" | head -1 | awk -F'"' '{print $2}')
echo "APPLE_CODE_SIGN_IDENTITY_INST=$CERT_IDENTITY_INST" >> $GITHUB_ENV

- name: Configure CMake
env:
APPLE_DEVELOPMENT_TEAM: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: |
if [[ "${{ matrix.config.name }}" = ubuntu-2* ]]; then
sudo apt-get install -y --no-install-recommends libpugixml-dev
Expand All @@ -67,9 +96,13 @@ jobs:
-Dlslgitbranch=${{ github.ref }} \
${{ matrix.config.cmake_extra }} \
${{ github.event.inputs.cmakeextra }}
echo ${PWD}
echo ${PWD}

- name: make
run: cmake --build build --target install --config Release -j
run: cmake --build build --config Release -j

- name: make install
run: cmake --build build --config Release --target install

- name: test install using examples
run: |
Expand All @@ -82,8 +115,9 @@ jobs:
${{ github.event.inputs.cmakeextra }}
cmake --build examples/build --target install --config Release -j
./examples/build/install/bin/HandleMetaData

- name: package

- name: package (!macOS)
if: matrix.config.os != 'macOS-latest'
run: |
echo $GITHUB_REF
cmake --build build --target package --config Release -j
Expand All @@ -101,6 +135,49 @@ jobs:
fi
cmake -E remove_directory package/_CPack_Packages
cp testing/lslcfgs/default.cfg .

- name: Codesign (macOS)
if: matrix.config.os == 'macOS-latest'
run: |
codesign -vvv --force --deep --sign "$APPLE_CODE_SIGN_IDENTITY_APP" \
--entitlements lsl.entitlements --options runtime \
install/Frameworks/lsl.framework/Versions/A/lsl
codesign -vvv --force --deep --sign "$APPLE_CODE_SIGN_IDENTITY_APP" \
--entitlements lsl.entitlements --options runtime \
install/Frameworks/lsl.framework
echo "✅ Verifying binary signatures in install target..."
codesign -vvv --verify --deep --strict install/Frameworks/lsl.framework/Versions/A/lsl
codesign -vvv --verify --deep --strict install/Frameworks/lsl.framework

# CMake does a lousy job of creating .pkg files for macOS, so we do it manually
- name: package and notarize (macOS)
if: matrix.config.os == 'macOS-latest'
env:
APPLE_DEVELOPMENT_TEAM: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
APPLE_NOTARIZE_USERNAME: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
# Get the version number from the framework's Info.plist
VERSION=$(plutil -extract CFBundleShortVersionString xml1 -o - install/Frameworks/lsl.framework/Versions/A/Resources/Info.plist | sed -n 's/.*<string>\(.*\)<\/string>.*/\1/p')
echo "LSL_VERSION=$VERSION" >> $GITHUB_ENV

mkdir -p package
productbuild --sign "$APPLE_CODE_SIGN_IDENTITY_INST" \
--component install/Frameworks/lsl.framework \
/Library/Frameworks package/liblsl-${LSL_VERSION}-Darwin-universal.pkg
# Notarize the package
xcrun notarytool submit package/liblsl-${LSL_VERSION}-Darwin-universal.pkg \
--apple-id "$APPLE_NOTARIZE_USERNAME" \
--password "$APPLE_NOTARIZE_PASSWORD" \
--team-id "$APPLE_DEVELOPMENT_TEAM" \
--wait
# Staple the notarization ticket to the package
xcrun stapler staple package/liblsl-${LSL_VERSION}-Darwin-universal.pkg
# If notarization fails, you can get the history of notarization requests:
# xcrun notarytool history --apple-id "$APPLE_NOTARIZE_USERNAME" --password "$APPLE_NOTARIZE_PASSWORD" --team-id "$APPLE_DEVELOPMENT_TEAM"
# Then you can check the status of a specific request:
# xcrun notarytool log <request-id> --apple-id "$APPLE_NOTARIZE_USERNAME" --password "$APPLE_NOTARIZE_PASSWORD" --team-id "$APPLE_DEVELOPMENT_TEAM"

- name: upload install dir
uses: actions/upload-artifact@master
with:
Expand All @@ -112,6 +189,7 @@ jobs:
with:
name: pkg-${{ matrix.config.name }}
path: package

- name: print network config
run: |
which ifconfig && ifconfig
Expand All @@ -121,24 +199,26 @@ jobs:
ip route
ip -6 route
fi
# run internal tests, ignore test failures on docker (missing IPv6 connectivity)

# run internal tests
- name: unit tests
run: |
if [[ "${{ matrix.config.name }}" = ubuntu-2* ]]; then
ulimit -c unlimited
echo "$PWD/dumps/corefile-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
fi
mkdir -p dumps
install/bin/lsl_test_internal --order rand --wait-for-keypress never --durations yes || test ! -z "${{ matrix.config.docker }}"
install/bin/lsl_test_internal --order rand --wait-for-keypress never --durations yes
install/bin/lsl_test_exported --order rand --wait-for-keypress never --durations yes
timeout-minutes: 10

- name: upload dump
if: failure()
uses: actions/upload-artifact@master
with:
name: dumps-${{ matrix.config.name }}
path: dumps

- name: upload to release page
if: github.event_name == 'release'
env:
Expand All @@ -158,3 +238,8 @@ jobs:
MIME=$(file --mime-type $pkg|cut -d ' ' -f2)
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: $TOKEN" -H "Content-Type: $MIME" --data-binary @$pkg $UPLOAD_URL?name=$NAME
done

- name: Clean up keychain
if: matrix.config.os == 'macOS-latest'
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
2 changes: 1 addition & 1 deletion .github/workflows/mingw_static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
-Dlslgitrevision=${{ github.sha }} \
-Dlslgitbranch=${{ github.ref }} \
-DLSL_OPTIMIZATIONS=OFF \
-G Ninja
-G 'MSYS Makefiles'
- name: make
run: cmake --build build --target install --config Release -j --verbose
Expand Down
2 changes: 0 additions & 2 deletions cmake/CompilerSettings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,4 @@ endif()
# Platform-specific settings
if(WIN32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
elseif(APPLE)
set(CMAKE_MACOSX_RPATH ON)
endif()
39 changes: 39 additions & 0 deletions cmake/CreateFrameworkSymlinks.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This script is executed at install time.
# It was generated from CreateFrameworkSymlinks.cmake.in

set(FRAMEWORK_DIR "${CMAKE_INSTALL_PREFIX}/@CMAKE_INSTALL_FRAMEWORK_DIR@/lsl.framework")

message(STATUS "Executing configured symlink script.")
message(STATUS " -- Target Directory='${FRAMEWORK_DIR}'")

if(NOT EXISTS "${FRAMEWORK_DIR}")
message(FATAL_ERROR "Framework version directory does not exist. Cannot create symlink.")
endif()

message(STATUS " -- Framework version directory exists. Creating symlink...")

execute_process(
COMMAND ln -sf include Headers
WORKING_DIRECTORY "${FRAMEWORK_DIR}/Versions/A"
RESULT_VARIABLE result
ERROR_VARIABLE error
)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Failed to create Headers->include symlink: ${error}")
endif()

execute_process(
COMMAND ln -sf Versions/Current/Headers
WORKING_DIRECTORY "${FRAMEWORK_DIR}"
RESULT_VARIABLE result
ERROR_VARIABLE error
)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Failed to create <root>Headers->Versions/Current/Headers symlink: ${error}")
endif()

if(NOT result EQUAL 0)
message(FATAL_ERROR "Failed to create Headers symlink in framework: ${error}")
endif()

message(STATUS " -- Framework symlink created successfully.")
38 changes: 34 additions & 4 deletions cmake/Installation.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ include(CMakePackageConfigHelpers)
# Paths
if(LSL_UNIXFOLDERS)
include(GNUInstallDirs)
set(CMAKE_INSTALL_FRAMEWORK_DIR ${FRAMEWORK_DIR_DEFAULT} CACHE PATH "Install directory for frameworks on macOS")
else()
set(CMAKE_INSTALL_BINDIR LSL)
set(CMAKE_INSTALL_LIBDIR LSL)
set(CMAKE_INSTALL_INCLUDEDIR LSL/include)
set(CMAKE_INSTALL_FRAMEWORK_DIR LSL/Frameworks CACHE PATH "Install directory for frameworks on macOS")
endif()

# For Apple frameworks, we need to next the install directories within the framework.
if(APPLE AND LSL_FRAMEWORK)
# For the includes, this is insufficient. Later we will create more accessible symlinks.
set(LSL_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_FRAMEWORK_DIR}/lsl.framework/Versions/A/include)
set(LSL_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_FRAMEWORK_DIR}/lsl.framework/Resources/CMake)
else()
set(LSL_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
set(LSL_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/lsl)
endif()

# Generate a version file for the package.
Expand All @@ -30,20 +42,26 @@ install(TARGETS ${LSLTargets}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FRAMEWORK DESTINATION ${CMAKE_INSTALL_FRAMEWORK_DIR}
)
# Unfortunately, `INCLUDES DESTINATION` does not work.
# PUBLIC_HEADER does not work because it flattens the tree.
# FILE_SET is preferable but does not work with frameworks.
# So we are stuck manually specifying the headers to be installed.
install(DIRECTORY include/lsl DESTINATION ${LSL_INSTALL_INCLUDEDIR})
install(FILES include/lsl_c.h include/lsl_cpp.h DESTINATION ${LSL_INSTALL_INCLUDEDIR})

# Generate the LSLConfig.cmake file and mark it for installation
install(EXPORT LSLTargets
FILE LSLConfig.cmake
COMPONENT liblsl
NAMESPACE "LSL::"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LSL
DESTINATION ${LSL_CONFIG_INSTALL_DIR}
)
# A common alternative to installing the exported package config file is to generate it from a template.
#configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/lslConfig.cmake.in
# ${CMAKE_CURRENT_BINARY_DIR}/LSLConfig.cmake
# INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/lsl)
# INSTALL_DESTINATION ${LSL_CONFIG_INSTALL_DIR})
# If we use this method, then we need a corresponding install(FILES ...) command to install the generated file.

# Install the version file and the helper CMake script.
Expand All @@ -52,5 +70,17 @@ install(
cmake/LSLCMake.cmake
${CMAKE_CURRENT_BINARY_DIR}/LSLConfigVersion.cmake
COMPONENT liblsl
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LSL
DESTINATION ${LSL_CONFIG_INSTALL_DIR}
)

if(APPLE AND LSL_FRAMEWORK)
# Create symlinks for the framework. The variables we want to use to identify the symlink locations
# are not available at install time. Instead, we create a script during configuration time that will
# be run at install time to create the symlinks.
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/CreateFrameworkSymlinks.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/CreateFrameworkSymlinks.cmake
@ONLY
)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/CreateFrameworkSymlinks.cmake COMPONENT liblsl)
endif()
9 changes: 9 additions & 0 deletions cmake/ProjectOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
option(LSL_DEBUGLOG "Enable (lots of) additional debug messages" OFF)
option(LSL_UNIXFOLDERS "Use the unix folder layout for install targets" ON)
option(LSL_BUILD_STATIC "Build LSL as a static library." OFF)
option(LSL_FRAMEWORK "Build LSL as an Apple Framework (Mac only)" ON)
option(LSL_LEGACY_CPP_ABI "Build legacy C++ ABI into lsl-static" OFF)
option(LSL_OPTIMIZATIONS "Enable some more compiler optimizations" ON)
option(LSL_BUNDLED_BOOST "Use the bundled Boost by default" ON)
Expand All @@ -10,3 +11,11 @@ option(LSL_TOOLS "Build some experimental tools for in-depth tests" OFF)
option(LSL_UNITTESTS "Build LSL library unit tests" OFF)
option(LSL_FORCE_FANCY_LIBNAME "Add library name decorations (32/64/-debug)" OFF)
mark_as_advanced(LSL_FORCE_FANCY_LIBNAME)

# If we install to the system then we want the framework to land in
# `Library/Frameworks`, otherwise (e.g., Homebrew) we want `Frameworks`
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(FRAMEWORK_DIR_DEFAULT Library/Frameworks)
else()
set(FRAMEWORK_DIR_DEFAULT Frameworks)
endif()
Loading
Loading