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 26 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
26 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
e343a79
Add iOS target to GHA
cboulay Jul 26, 2025
6024619
Move Apple GHA to its own script
cboulay Jul 26, 2025
434d792
Apple GHA - refactor to reuse certification setup across build and de…
cboulay Jul 26, 2025
b548b4d
Apple GHA - don't run tests for iOS target
cboulay Jul 26, 2025
f80eaf1
Fixup iOS builds
cboulay Jul 27, 2025
9272fdd
Disable notarization of xcframework
cboulay Jul 27, 2025
1c62fba
Add missing argument to iOS builds
cboulay Jul 27, 2025
619e2bd
Add missing step to iOS package and deploy
cboulay Jul 27, 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
42 changes: 42 additions & 0 deletions .github/actions/install-apple-certs/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: 'Install Apple Certificates'
description: 'Installs Apple signing and notarization certificates and sets up the keychain'
inputs:
MACOS_CERTIFICATE_APP:
required: true
MACOS_CERTIFICATE_INST:
required: true
MACOS_CERTIFICATE_PWD:
required: true
MACOS_CI_KEYCHAIN_PWD:
required: true
runs:
using: "composite"
steps:
- name: Install certificates and provisioning profiles
shell: bash
run: |
# Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain
security create-keychain -p "${{ inputs.MACOS_CI_KEYCHAIN_PWD }}" $KEYCHAIN_PATH
security default-keychain -s $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "${{ inputs.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 "${{ inputs.MACOS_CERTIFICATE_APP }}" | base64 --decode -o $CERTIFICATE_PATH_APP
echo -n "${{ inputs.MACOS_CERTIFICATE_INST }}" | base64 --decode -o $CERTIFICATE_PATH_INST
# ... to keychain
security import $CERTIFICATE_PATH_APP -P "${{ inputs.MACOS_CERTIFICATE_PWD }}" -k $KEYCHAIN_PATH -A -t cert -f pkcs12
security import $CERTIFICATE_PATH_INST -P "${{ inputs.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 "${{ inputs.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
217 changes: 217 additions & 0 deletions .github/workflows/apple.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
name: Apple CI

on:
push:
branches:
- main
- dev
tags: ['*']
paths:
- '**'
- '!docs/**'
- '!.github/**'
- '.github/workflows/apple.yml'
pull_request:
release:
types: ['created']
workflow_dispatch:
inputs:
cmakeextra:
description: 'Extra CMake options'
required: false
default: ''

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

defaults:
run:
shell: bash

jobs:
build:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
- {name: "macOS-latest", os: "macOS-latest", cmake_extra: "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DLSL_UNITTESTS=ON -DLSL_BENCHMARKS=ON" }
- {name: "iOS", os: "macOS-latest", cmake_extra: "-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake -DPLATFORM=OS64" }

steps:
- uses: actions/checkout@v4

- name: Install certificates and provisioning profiles
uses: ./.github/actions/install-apple-certs
with:
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 }}

- name: Configure CMake
env:
APPLE_DEVELOPMENT_TEAM: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: |
cmake --version
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=${PWD}/install \
-DCPACK_PACKAGE_DIRECTORY=${PWD}/package \
-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" \
-DLSL_FRAMEWORK=ON \
-Dlslgitrevision=${{ github.sha }} \
-Dlslgitbranch=${{ github.ref }} \
${{ matrix.config.cmake_extra }} \
${{ github.event.inputs.cmakeextra }}
echo ${PWD}

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

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

- name: test install using examples
if: matrix.config.name != 'iOS'
run: |
# Test that the in-tree install was successful by building the examples
cmake -S examples -B examples/build \
-DLSL_INSTALL_ROOT=${PWD}/install \
-DCMAKE_INSTALL_PREFIX=examples/build/install \
-DLSL_COMFY_DEFAULTS=ON \
${{ matrix.config.cmake_extra }} \
${{ github.event.inputs.cmakeextra }}
cmake --build examples/build --target install --config Release -j
./examples/build/install/bin/HandleMetaData

- name: Codesign
run: |
if [[ "${{ matrix.config.name }}" == "macOS-latest" ]]; then
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
elif [[ "${{ matrix.config.name }}" == "iOS" ]]; then
codesign -vvv --force --deep --sign "$APPLE_CODE_SIGN_IDENTITY_APP" \
install/Frameworks/lsl.framework/lsl
codesign -vvv --force --deep --sign "$APPLE_CODE_SIGN_IDENTITY_APP" \
install/Frameworks/lsl.framework
echo "✅ Verifying binary signatures in install target..."
codesign -vvv --verify --deep --strict install/Frameworks/lsl.framework/lsl
codesign -vvv --verify --deep --strict install/Frameworks/lsl.framework
fi

- name: upload install dir
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.config.name }}
path: install

# run internal tests
- name: unit tests
if: matrix.config.name != 'iOS'
run: |
mkdir -p dumps
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@v4
with:
name: dumps-${{ matrix.config.name }}
path: dumps

package_and_deploy:
name: Package and Deploy
needs: build
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build-macOS-latest
path: build-macOS-latest
- uses: actions/download-artifact@v4
with:
name: build-iOS
path: build-iOS

- name: Install certificates and provisioning profiles
uses: ./.github/actions/install-apple-certs
with:
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 }}

- name: Package and Notarize macOS Installer
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 - build-macOS-latest/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 build-macOS-latest/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

- name: Create, Sign, and Notarize XCFramework
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: |
xcodebuild -create-xcframework \
-framework build-macOS-latest/install/Frameworks/lsl.framework \
-framework build-iOS/install/Frameworks/lsl.framework \
-output lsl.xcframework

codesign -vvv --force --deep --sign "$APPLE_CODE_SIGN_IDENTITY_APP" lsl.xcframework
echo "✅ Verifying binary signatures in XCFramework..."
codesign -vvv --verify --deep --strict lsl.xcframework

ditto -c -k --sequesterRsrc --keepParent lsl.xcframework lsl.xcframework.zip

- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: mac-packages
path: |
lsl.xcframework.zip
package/

- name: upload to release page
if: github.event_name == 'release'
env:
TOKEN: "token ${{ secrets.GITHUB_TOKEN }}"
TAG: ${{ github.event.release.tag_name }}
UPLOAD_URL: ${{ github.event.release.upload_url }}
run: |
UPLOAD_URL=${UPLOAD_URL%\{*} # remove "{name,label}" suffix
for pkg in lsl.xcframework.zip package/*.*; do
NAME=$(basename $pkg)
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
36 changes: 19 additions & 17 deletions .github/workflows/cppcmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: C/C++ CI

on:
push:
branches: ['*']
branches:
- main
- dev
tags: ['*']
paths:
- '**'
Expand Down Expand Up @@ -39,18 +41,10 @@ jobs:
- {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 }}

steps:
- uses: actions/checkout@v4
- name: set up build environment in container
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 }}

- name: Configure CMake
run: |
if [[ "${{ matrix.config.name }}" = ubuntu-2* ]]; then
Expand All @@ -67,9 +61,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,7 +80,7 @@ jobs:
${{ github.event.inputs.cmakeextra }}
cmake --build examples/build --target install --config Release -j
./examples/build/install/bin/HandleMetaData

- name: package
run: |
echo $GITHUB_REF
Expand All @@ -101,6 +99,7 @@ jobs:
fi
cmake -E remove_directory package/_CPack_Packages
cp testing/lslcfgs/default.cfg .

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

- name: print network config
run: |
which ifconfig && ifconfig
Expand All @@ -121,24 +121,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 Down
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: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ include(cmake/TargetLib.cmake) #
include(cmake/Installation.cmake) #
include(cmake/TargetOther.cmake)

if(LSL_UNITTESTS)
if(LSL_UNITTESTS AND NOT IOS)
add_subdirectory(testing)
endif()

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()
Loading
Loading