Skip to content

Commit

Permalink
Merge pull request #380 from christofmuc/features/code_signing
Browse files Browse the repository at this point in the history
Add embedded Python and code signing for MacOS
  • Loading branch information
christofmuc authored Jan 3, 2025
2 parents 7ce7e58 + 14c12f3 commit c691a99
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 73 deletions.
35 changes: 30 additions & 5 deletions .github/workflows/builds-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,41 @@ jobs:
with:
python-version: '3.12'

- name: CMake configure
- name: Import Developer Certificate
run: |
echo "$MACOS_CERTIFICATE_BASE64" | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_TEMPORARY_KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -lut 3600 build.keychain
security unlock-keychain -p "$MACOS_TEMPORARY_KEYCHAIN_PASSWORD" build.keychain
security list-keychains -d user -s build.keychain $(security list-keychains -d user | sed s/\"//g)
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$MACOS_TEMPORARY_KEYCHAIN_PASSWORD" build.keychain
security find-identity -v
env:
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
MACOS_TEMPORARY_KEYCHAIN_PASSWORD: $ {{ secrets.MACOS_TEMPORARY_KEYCHAIN_PASSWORD }}

- name: Build the MacOS DMG
shell: bash
run: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_PREFIX_PATH=`brew --prefix icu4c`
- name: CMake build
run: cmake --build build --target package --parallel
export BUILD_TYPE=RelWithDebInfo
export BUILD_DIR=./build
security unlock-keychain -p "$MACOS_TEMPORARY_KEYCHAIN_PASSWORD" build.keychain
security find-identity -p codesigning -v
security find-certificate -c "$APPLE_DEVELOPER_IDENTITY"
make
make apple
env:
APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID}}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
TEAM_ID: ${{ secrets.TEAM_ID }}
MACOS_TEMPORARY_KEYCHAIN_PASSWORD: $ {{ secrets.MACOS_TEMPORARY_KEYCHAIN_PASSWORD }}

- name: Archive DMG artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: DMG image
path: |
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ set(JSON_BuildTests OFF CACHE INTERNAL "")
add_subdirectory(third_party/json)
option(JSON_VALIDATOR_INSTALL "" OFF)
add_subdirectory(third_party/json-schema-validator)
option(FMT_INSTALL "" OFF)
add_subdirectory(third_party/fmt)
option(SPDLOG_FMT_EXTERNAL "" ON)
add_subdirectory(third_party/spdlog)
Expand Down
102 changes: 102 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# MAC OS ONLY!
#
# This Makefile is only there to help building the MacOS DMG files, codesign them, notarize and staple
# and verify all of this. If you are using a normal, sane OS, you can ignore this Makefile
# and do the regular CMake process. Checkout the .github/workflows files for the authoritative way to build
# for your platform
#

BUILD_DIR?=.builds/universal_again
BUILD_TYPE?=Debug

# Determine the version the same way cmake does
VERSION=$(shell cmake -P The-Orm/gitversion.cmake 2>&1 >/dev/null)

# Make sure to setup a Python that matches the universal build/fat binary or the architecture build
# This can be really messy if you have - like me - multiple versions of Python installed on the Mac.
# Closely watch the build output to detect and inconsistencies during configuration, linking, or bundle fixing!
# Worst case, uninstall all Pythons except the one you want to use.
PYTHON_SOURCE=/Library/Frameworks/Python.framework/Versions/3.12

# Setup variables for the various build artifacts and their names
KNOBKRAFT=KnobKraft_Orm-$(VERSION)-Darwin
KNOBKRAFT_APP=$(BUILD_DIR)/The-Orm/KnobKraftOrm.app
KNOBKRAFT_DMG=$(BUILD_DIR)/$(KNOBKRAFT).dmg
KNOBKRAFT_MOUNT=/Volumes/$(KNOBKRAFT)
KNOBKRAFT_MOUNTED_APP=$(KNOBKRAFT_MOUNT)/KnobKraftOrm.app

# Some more paths
PYTHON_TO_USE=$(PYTHON_SOURCE)/bin/python3


all: configure build sign-dmg verify-signed

apple: notarize staple verify-notarization

configure:
@echo "Configuring build for type $(BUILD_TYPE) in directory $(BUILD_DIR), using Python from $(PYTHON_TO_USE)"
cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DPYTHON_EXECUTABLE=$(PYTHON_TO_USE) -DCODESIGN_CERTIFICATE_NAME="$(APPLE_DEVELOPER_IDENTITY)"

.PHONY: build
build $(KNOBKRAFT_DMG):
cmake --build $(BUILD_DIR) --target package --parallel $(shell sysctl -n hw.ncpu)

$(KNOBKRAFT_MOUNT): $(KNOBKRAFT_DMG)
hdiutil detach $@ ; true
yes | PAGER=cat hdiutil attach $<

attach: $(KNOBKRAFT_MOUNT)

detach: $(KNOBKRAFT_MOUNT)/KnobKraftOrm.app
hdiutil detach $(KNOBKRAFT_MOUNT)

app-signed: attach
codesign --verify -v --strict $(KNOBKRAFT_MOUNTED_APP)

binary-signed: $(KNOBKRAFT_APP)
codesign --verify -v --strict $(KNOBKRAFT_APP)

dmg-signed: $(KNOBKRAFT_DMG)
codesign --verify -v --strict $<

verify-signed: binary-signed app-signed dmg-signed

sign-dmg: $(KNOBKRAFT_DMG)
codesign --force --verbose=2 --sign "$(TEAM_ID)" $<

show-dmg-signature: $(KNOBKRAFT_DMG)
codesign -dvvv $<

.PHONY: notarize
# https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution?language=objc
# https://scriptingosx.com/2021/07/notarize-a-command-line-tool-with-notarytool/
# https://melatonin.dev/blog/how-to-code-sign-and-notarize-macos-audio-plugins-in-ci/ (https://github.com/sudara/pamplejuce/tree/main)
notarize: $(KNOBKRAFT_DMG)
@xcrun notarytool submit $< \
--team-id $(TEAM_ID) \
--apple-id $(APPLE_ID) \
--password $(APPLE_APP_SPECIFIC_PASSWORD) \
--wait

staple: $(KNOBKRAFT_DMG)
xcrun stapler staple $<

verify-notarization: $(KNOBKRAFT_DMG)
xcrun spctl --assess --type open --context context:primary-signature --ignore-cache --verbose=2 $<
xcrun spctl --assess --type install --ignore-cache --verbose=2 $<

run-app: $(KNOBKRAFT_APP)
open $<

run-dmg: attach
open $(KNOBKRAFT_MOUNTED_APP)

debug-codesign:
cmake -DPYTHON_SOURCE=$(PYTHON_SOURCE) -DSIGN_DIRECTORY=`pwd`/$(KNOBKRAFT_APP) -DENTITLEMENTS_FILE=./The-Orm/Codesign.entitlements -DCODESIGN_CERTIFICATE_NAME="$(CODESIGN_CERTIFICATE_NAME)" -P cmake/codesign.cmake


kill:
killall KnobKraftOrm

realclean:
rm -rf $(BUILD_DIR)
75 changes: 34 additions & 41 deletions The-Orm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,17 @@

cmake_minimum_required(VERSION 3.14)

find_package(Git)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --dirty=-dev --long
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE "PROJECT_VERSION_FULL"
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "Project full version: ${PROJECT_VERSION_FULL}")
else()
set(PROJECT_VERSION_FULL "unknown")
endif()

# Break up the version, number of commits, and the dev tag into parts
string(REGEX REPLACE "^([0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" PROJECT_VERSION ${PROJECT_VERSION_FULL})
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+).*" "\\1" PROJECT_TWEAK_VERSION ${PROJECT_VERSION_FULL})
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+-[0-9]+-g[0-9a-f]+(.*)" "\\1" PROJECT_DEV_TAG ${PROJECT_VERSION_FULL})

# If there were no new commits, PROJECT_TWEAK_VERSION will contain a hash or "-dev", so we need to set it to 0 in that case.
if(PROJECT_TWEAK_VERSION MATCHES "-g[0-9a-f]+.*")
set(PROJECT_TWEAK_VERSION 0)
endif()

# Append the tweak version if it's not 0
if(NOT PROJECT_TWEAK_VERSION EQUAL 0)
set(PROJECT_VERSION ${PROJECT_VERSION}.${PROJECT_TWEAK_VERSION})
endif()
# Get the version from our sub cmakefile
execute_process(
COMMAND sh -c "cmake -P '${CMAKE_CURRENT_LIST_DIR}/gitversion.cmake' >/dev/null"
ERROR_VARIABLE PROJECT_VERSION
)
# Cleanup output
string(REGEX REPLACE "^[[:space:]]+|[[:space:]]+$" "" PROJECT_VERSION "${PROJECT_VERSION}")
string(REGEX REPLACE "\n$" "" PROJECT_VERSION "${PROJECT_VERSION}")
message(STATUS "Project version is ${PROJECT_VERSION}")

# Start project
project(KnobKraftOrm VERSION ${PROJECT_VERSION})

# Now you can use PROJECT_DEV_TAG in your CMake scripts or in your code to handle the "-dev" part.
Expand Down Expand Up @@ -186,6 +168,7 @@ ELSEIF(APPLE)
sqlite3
gin
spdlog::spdlog
pybind11::embed
)
SET_TARGET_PROPERTIES(KnobKraftOrm PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KnobKraft Orm ${KnobKraftOrm_VERSION}"
MACOSX_BUNDLE_ICON_FILE icon_orm.icns
Expand Down Expand Up @@ -302,21 +285,31 @@ IF(WIN32)
ENDIF()

IF(APPLE)
# This is supposed to build a relocatable macOS DMG installer when you specify the
# --target package
SET(MY_RELEASE_DIR ${CMAKE_BINARY_DIR}/macInstaller)
SET(APPS "\${CMAKE_INSTALL_PREFIX}/KnobKraftOrm.app")
SET(DIRS ${CMAKE_BINARY_DIR})
INSTALL(TARGETS KnobKraftOrm BUNDLE DESTINATION . COMPONENT Runtime)
INSTALL(CODE "include(BundleUtilities)
set(BU_CHMOD_BUNDLE_ITEMS TRUE)
fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\" IGNORE_ITEM \"Python\")
" COMPONENT Runtime)

# This section is supposed to build a relocatable macOS DMG installer when you specify the
# --target package

IF(CODESIGN_CERTIFICATE_NAME)
# We need to sign the files as a post build step. Doing this as install step doesn't help because
# CPack will already have created a copy and ignore our
get_filename_component(PYTHON_SOURCE_DIR ${PYTHON_EXECUTABLE} DIRECTORY)
add_custom_command(
TARGET KnobKraftOrm POST_BUILD
COMMAND "${CMAKE_COMMAND}"
-DPYTHON_SOURCE="${PYTHON_SOURCE_DIR}/../lib"
-DSIGN_DIRECTORY=$<TARGET_BUNDLE_DIR:KnobKraftOrm>
-DENTITLEMENTS_FILE="${CMAKE_CURRENT_LIST_DIR}/Codesign.entitlements"
-DCODESIGN_CERTIFICATE_NAME="${CODESIGN_CERTIFICATE_NAME}"
-P "$(CMAKE_SOURCE_DIR)/cmake/codesign.cmake"
)
ENDIF()

# This is the one and most important INSTALL directive, without it there is no Orm
INSTALL(TARGETS KnobKraftOrm BUNDLE DESTINATION .)

# Setup CPack variables
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon_orm.png")
set(CPACK_PACKAGE_VERSION ${KnobKraftOrm_VERSION})
set(CPACK_PACKAGE_VENDOR "Christof Ruch Beratungs UG (haftungsbeschraenkt)")
set(CPACK_PACKAGE_VENDOR "Christof Ruch")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/redist/agpl-3.0.txt")
#set(CPACK_RESOURCE_FILE_README "${CMAKE_PROJECT_DIR}/readme.md")
set(CPACK_COMMAND_HDIUTIL "${CMAKE_CURRENT_LIST_DIR}/hdiutil_repeat.sh")
Expand All @@ -328,7 +321,7 @@ IF(APPLE)
INCLUDE(CPack)
ELSE()
INSTALL(TARGETS KnobKraftOrm knobkraft-generic-adaptation
BUNDLE DESTINATION . COMPONENT Runtime
BUNDLE DESTINATION .
)
IF(UNIX)
SET(CPACK_BINARY_TGZ ON)
Expand Down
7 changes: 7 additions & 0 deletions The-Orm/Codesign.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?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>com.apple.security.app-sandbox</key><true/-->
</dict>
</plist>
17 changes: 17 additions & 0 deletions The-Orm/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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>
<!-- Application Identifier -->
<key>CFBundleIdentifier</key>
<string>com.knobkraft.orm</string>

<!-- Application Name -->
<key>CFBundleName</key>
<string>KnobKraftOrm</string>

<!-- Minimum System Version (macOS version) -->
<key>LSMinimumSystemVersion</key>
<string>10.12</string>
</dict>
</plist>
31 changes: 31 additions & 0 deletions The-Orm/gitversion.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

find_package(Git)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --dirty=-dev --long
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE "PROJECT_VERSION_FULL"
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
else()
set(PROJECT_VERSION_FULL "unknown")
endif()

# Break up the version, number of commits, and the dev tag into parts
string(REGEX REPLACE "^([0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" PROJECT_VERSION ${PROJECT_VERSION_FULL})
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+).*" "\\1" PROJECT_TWEAK_VERSION ${PROJECT_VERSION_FULL})
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+-[0-9]+-g[0-9a-f]+(.*)" "\\1" PROJECT_DEV_TAG ${PROJECT_VERSION_FULL})

# If there were no new commits, PROJECT_TWEAK_VERSION will contain a hash or "-dev", so we need to set it to 0 in that case.
if(PROJECT_TWEAK_VERSION MATCHES "-g[0-9a-f]+.*")
set(PROJECT_TWEAK_VERSION 0)
endif()

# Append the tweak version if it's not 0
if(NOT PROJECT_TWEAK_VERSION EQUAL 0)
set(PROJECT_VERSION ${PROJECT_VERSION}.${PROJECT_TWEAK_VERSION})
endif()
cmake_policy(SET CMP0140 NEW)
message(${PROJECT_VERSION})
#return(PROPAGATE ${PROJECT_VERSION})
Loading

0 comments on commit c691a99

Please sign in to comment.