Skip to content

Commit a78a443

Browse files
authored
Merge pull request #727 from openstudiocoalition/codesigning
Codesign on macOS
2 parents d4fbeda + 5cfb71b commit a78a443

11 files changed

+1644
-32
lines changed

.github/workflows/app_build.yml

Lines changed: 193 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ jobs:
2323
build:
2424
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
2525

26+
outputs:
27+
OS_APP_VERSION: ${{ steps.parse_cmake_versions.outputs.OS_APP_VERSION }}
28+
2629
runs-on: ${{ matrix.os }}
2730
continue-on-error: true
2831
strategy:
@@ -40,6 +43,7 @@ jobs:
4043
COMPRESSED_PKG_PATH: _CPack_Packages/Linux/TGZ
4144
QT_OS_NAME: linux
4245
QT_ARCH: gcc_64
46+
arch: x86_64
4347
- os: ubuntu-22.04
4448
SELF_HOSTED: false
4549
PLATFORM_NAME: Linux
@@ -49,6 +53,7 @@ jobs:
4953
COMPRESSED_PKG_PATH: _CPack_Packages/Linux/TGZ
5054
QT_OS_NAME: linux
5155
QT_ARCH: gcc_64
56+
arch: x86_64
5257
- os: windows-2022
5358
SELF_HOSTED: false
5459
PLATFORM_NAME: Windows
@@ -58,6 +63,7 @@ jobs:
5863
COMPRESSED_PKG_PATH: _CPack_Packages/win64/ZIP
5964
QT_OS_NAME: windows
6065
QT_ARCH: win64_msvc2019_64
66+
arch: x86_64
6167
- os: macos-13
6268
SELF_HOSTED: false
6369
PLATFORM_NAME: Darwin
@@ -69,6 +75,7 @@ jobs:
6975
SDKROOT: /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
7076
QT_OS_NAME: mac
7177
QT_ARCH: clang_64
78+
arch: x86_64
7279
- os: macos-arm64
7380
SELF_HOSTED: true
7481
PLATFORM_NAME: Darwin
@@ -80,6 +87,7 @@ jobs:
8087
SDKROOT: /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
8188
QT_OS_NAME: mac
8289
QT_ARCH: arm_64
90+
arch: arm64
8391

8492
steps:
8593

@@ -96,6 +104,7 @@ jobs:
96104
ruby-version: 3.2.2
97105

98106
- name: Extract OSApp and OS SDK versions from CMakeLists.txt
107+
id: parse_cmake_versions
99108
shell: bash
100109
run: |
101110
# This both prints the variables and adds them to GITHUB_ENV
@@ -190,33 +199,65 @@ jobs:
190199
rm /usr/local/lib/pkgconfig/libcrypto.pc
191200
rm /usr/local/lib/pkgconfig/libssl.pc
192201
rm /usr/local/lib/pkgconfig/openssl.pc
193-
194-
echo "Installing IFW"
195-
curl -L -O https://download.qt.io/official_releases/qt-installer-framework/4.6.1/QtInstallerFramework-macOS-x64-4.6.1.dmg
196-
hdiutil attach -mountpoint ./qtfiw_installer QtInstallerFramework-macOS-x64-4.6.1.dmg
197-
echo "ls ./qtfiw_installer"
198-
ls ./qtfiw_installer
199-
echo "ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/"
200-
ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/
201-
echo "ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/"
202-
ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/
203-
echo "ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/MacOS"
204-
ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/MacOS
205-
echo "ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/Resources"
206-
ls ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/Resources
207-
208-
sudo ./qtfiw_installer/QtInstallerFramework-macOS-x64-4.6.1.app/Contents/MacOS/QtInstallerFramework-macOS-x64-4.6.1 --verbose --script ./ci/install_script_qtifw.qs
209-
ls ~
210-
ls ~/Qt/ || true
211-
ls ~/Qt/QtIFW-4.6.1 || true
212-
echo "~/Qt/QtIFW-4.6.1/bin/" >> $GITHUB_PATH
213202
fi;
214203
fi;
215204
216205
cmake --version
217206
ccache --show-config || true
218207
ccache --zero-stats || true
219208
209+
- name: "Configure for codesigning"
210+
if: runner.os == 'macOS'
211+
run: |
212+
set -x
213+
cd $RUNNER_TEMP
214+
mkdir codesigning && cd codesigning
215+
# ----- Create certificate files from secrets base64 -----
216+
echo "${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_BASE64 }}" | base64 --decode > certificate_application.p12
217+
echo "${{ secrets.MACOS_DEVELOPER_ID_INSTALLER_CERTIFICATE_P12_BASE64 }}" | base64 --decode > certificate_installer.p12
218+
219+
# ----- Configure Keychain -----
220+
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
221+
security create-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
222+
# Unlock it for 6 hours
223+
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
224+
security unlock-keychain -p "${{ secrets.MACOS_KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
225+
226+
# ----- Import certificates on Keychain -----
227+
security import certificate_application.p12 -P '${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_PASSWORD }}' -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
228+
security import certificate_installer.p12 -P '${{ secrets.MACOS_DEVELOPER_ID_INSTALLER_CERTIFICATE_P12_PASSWORD }}' -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
229+
security list-keychain -d user -s $KEYCHAIN_PATH
230+
security find-identity -vvvv $KEYCHAIN_PATH
231+
232+
# Add needed intermediary certificates
233+
brew list aria2 || brew install aria2
234+
aria2c https://www.apple.com/certificateauthority/AppleWWDRCAG2.cer
235+
aria2c https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
236+
security import AppleWWDRCAG2.cer -k $KEYCHAIN_PATH
237+
security import DeveloperIDG2CA.cer -k $KEYCHAIN_PATH
238+
security find-identity -vvvv $KEYCHAIN_PATH
239+
security find-identity -v -p codesigning
240+
241+
# Store AppConnect credentials
242+
echo "${{ secrets.NOTARIZATION_API_KEY }}" > AppConnect_Developer_API_Key.p8
243+
xcrun notarytool store-credentials OpenStudioApplication \
244+
--key AppConnect_Developer_API_Key.p8 \
245+
--key-id ${{ secrets.NOTARIZATION_API_TEAM_ID }} \
246+
--issuer ${{ secrets.NOTARIZATION_API_ISSUER_ID }} \
247+
--keychain $KEYCHAIN_PATH
248+
249+
cd .. && rm -Rf codesigning
250+
251+
# Download my patched QtIFW
252+
mkdir QtIFW && cd QtIFW
253+
aria2c https://github.com/jmarrec/QtIFW-fixup/releases/download/v5.0.0-dev-with-fixup/QtIFW-5.0.0-${{ matrix.arch }}.zip
254+
xattr -r -d com.apple.quarantine ./QtIFW-5.0.0-${{ matrix.arch }}.zip
255+
unzip QtIFW-5.0.0-${{ matrix.arch }}.zip
256+
rm -Rf ./*.zip
257+
chmod +x *
258+
./installerbase --version
259+
echo "$(pwd)" >> $GITHUB_PATH
260+
220261
- name: Install conan
221262
shell: bash
222263
run: |
@@ -487,12 +528,24 @@ jobs:
487528
-DANALYTICS_MEASUREMENT_ID:STRING=${{ secrets.ANALYTICS_MEASUREMENT_ID }} -Dopenstudio_DIR:PATH=$OS_DIR
488529
fi
489530
490-
cmake --preset conan-release -DQT_INSTALL_DIR:PATH=${{ env.QT_INSTALL_DIR }} \
531+
if [ "$RUNNER_OS" == "macOS" ]; then
532+
cmake --preset conan-release -DQT_INSTALL_DIR:PATH=${{ env.QT_INSTALL_DIR }} \
491533
-DBUILD_DOCUMENTATION:BOOL=${{ env.BUILD_DOCUMENTATION }} \
492534
-DBUILD_PACKAGE:BOOL=${{ env.BUILD_PACKAGE }} \
493535
-DCPACK_BINARY_TGZ:BOOL=ON \
494536
-DANALYTICS_API_SECRET:STRING=${{ secrets.ANALYTICS_API_SECRET }} \
495-
-DANALYTICS_MEASUREMENT_ID:STRING=${{ secrets.ANALYTICS_MEASUREMENT_ID }}
537+
-DANALYTICS_MEASUREMENT_ID:STRING=${{ secrets.ANALYTICS_MEASUREMENT_ID }} \
538+
-DCPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION:STRING="Developer ID Application: The Energy Coalition (UG9S5ZLM34)" \
539+
-DCPACK_CODESIGNING_NOTARY_PROFILE_NAME:STRING=OpenStudioApplication \
540+
-DCPACK_CODESIGNING_MACOS_IDENTIFIER:STRING=org.openstudiocoalition.OpenStudioApplication
541+
else
542+
cmake --preset conan-release -DQT_INSTALL_DIR:PATH=${{ env.QT_INSTALL_DIR }} \
543+
-DBUILD_DOCUMENTATION:BOOL=${{ env.BUILD_DOCUMENTATION }} \
544+
-DBUILD_PACKAGE:BOOL=${{ env.BUILD_PACKAGE }} \
545+
-DCPACK_BINARY_TGZ:BOOL=ON \
546+
-DANALYTICS_API_SECRET:STRING=${{ secrets.ANALYTICS_API_SECRET }} \
547+
-DANALYTICS_MEASUREMENT_ID:STRING=${{ secrets.ANALYTICS_MEASUREMENT_ID }}
548+
fi;
496549
cmake --build --preset conan-release --target package
497550
# Delete conan build and source folders
498551
conan cache clean --source --build --download --temp
@@ -555,6 +608,26 @@ jobs:
555608
name: OpenStudioApplication-${{ env.OS_APP_VERSION }}.${{ github.sha }}-${{ matrix.os }}.${{ env.COMPRESSED_EXT }}
556609
path: build/${{ matrix.COMPRESSED_PKG_PATH }}/*.${{ env.COMPRESSED_EXT }}
557610

611+
- name: Full Test Package signing for IFW and TGZ
612+
if: runner.os == 'macOS'
613+
working-directory: ./build
614+
shell: bash
615+
run: |
616+
begin_group() { echo -e "::group::\033[93m$1\033[0m"; }
617+
618+
begin_group "Full Check signature of _CPack_Packages for both IFW and TGZ"
619+
python ../developer/python/verify_signature.py --verbose --only-generator IFW .
620+
python ../developer/python/verify_signature.py --otool --otool-out-file otool_infos_cpack_tgz.json --verbose --only-generator TGZ .
621+
echo "::endgroup::"
622+
623+
- name: Upload otool info as artifact
624+
if: runner.os == 'macOS'
625+
uses: actions/upload-artifact@v4
626+
with:
627+
name: otool_infos_cpack_${{ matrix.os }}_${{ matrix.arch }}
628+
path: build/otool*json
629+
if-no-files-found: error
630+
558631
- name: Test
559632
working-directory: ./build
560633
shell: bash
@@ -628,3 +701,101 @@ jobs:
628701
/bin/rm OpenStudioApplication-*${{ env.COMPRESSED_EXT }} || true
629702
/bin/rm OpenStudioApplication-*${{ env.BINARY_EXT }} || true
630703
ls OpenStudioApplication-* || true
704+
705+
706+
test_package_macos:
707+
name: Test Built Package on macOS
708+
needs: build
709+
runs-on: ${{ matrix.os }}
710+
strategy:
711+
# fail-fast: Default is true, switch to false to allow one platform to fail and still run others
712+
fail-fast: false
713+
matrix:
714+
os: [macos-13, macos-14]
715+
include:
716+
- os: macos-13
717+
binary_os: macos-13
718+
SELF_HOSTED: false
719+
BINARY_EXT: dmg
720+
COMPRESSED_EXT: tar.gz
721+
arch: x86_64
722+
# This is the GHA hosted one
723+
- os: macos-14
724+
binary_os: macos-arm64
725+
SELF_HOSTED: false
726+
BINARY_EXT: dmg
727+
COMPRESSED_EXT: tar.gz
728+
arch: arm64
729+
730+
steps:
731+
- uses: actions/checkout@v4 # Still need code checked out to get testing scripts
732+
with:
733+
path: checkout
734+
735+
#- name: Gather Test Package from Artifacts
736+
# uses: actions/download-artifact@v4
737+
# with:
738+
# name: OpenStudioApplication-${{ needs.build.outputs.OS_APP_VERSION }}.${{ github.sha }}-${{ matrix.binary_os }}.${{ matrix.COMPRESSED_EXT }}
739+
# path: package
740+
741+
- name: Gather Dmg Package from Artifacts
742+
uses: actions/download-artifact@v4
743+
with:
744+
name: OpenStudioApplication-${{ needs.build.outputs.OS_APP_VERSION }}.${{ github.sha }}-${{ matrix.binary_os }}.${{ matrix.BINARY_EXT }}
745+
path: dmg
746+
747+
- name: Test Dmg Install and Package signing
748+
working-directory: ./dmg
749+
shell: bash
750+
run: |
751+
begin_group() { echo -e "::group::\033[93m$1\033[0m"; }
752+
753+
set -x
754+
755+
dmg=$(ls OpenStudioApplication-*.dmg)
756+
begin_group "Checking Signature of .dmg"
757+
spctl --assess --type open --context context:primary-signature -vvvv $dmg
758+
echo "::endgroup::"
759+
760+
begin_group "Mounting Dmg, and checking signature of installer app"
761+
mkdir temp_mount
762+
hdiutil attach -mountpoint ./temp_mount/ $dmg
763+
filename="${dmg%.*}"
764+
spctl --assess --type open --context context:primary-signature -vvvv ./temp_mount/$filename.app
765+
echo "::endgroup::"
766+
767+
begin_group "Installing"
768+
sudo ./temp_mount/$filename.app/Contents/MacOS/$filename --accept-licenses --default-answer --confirm-command --root $(pwd)/test_install install
769+
echo "::endgroup::"
770+
771+
begin_group "Quick Check signature of inner executables and binaries"
772+
codesign -dvvv ./test_install/lib/libopenstudiolib.dylib
773+
codesign -dvvv ./test_install/lib/libpythonengine.so
774+
codesign -dvvv ./test_install/lib/librubyengine.so
775+
codesign -dvvv ./test_install/EnergyPlus/energyplus
776+
codesign -dvvv ./test_install/EnergyPlus/libenergyplusapi.dylib
777+
codesign -dvvv ./test_install/EnergyPlus/libpython*.dylib
778+
codesign -dvvv ./test_install/EnergyPlus/ExpandObjects
779+
echo "::endgroup::"
780+
781+
begin_group "Full Check signature of installed DMG for all executables and resolve otool libraries"
782+
python ../checkout/developer/python/verify_signature.py --otool --otool-out-file otool_info_dmg.json --verbose --install test_install
783+
echo "::endgroup::"
784+
785+
begin_group "Running a simulation with python"
786+
./test_install/EnergyPlus/energyplus --help
787+
cur_v=$(python -c "import sys; sys.path.insert(0, './test_install/EnergyPlus'); from pyenergyplus.func import EnergyPlusVersion; v = EnergyPlusVersion(); print(f'{v.ep_version_major}.{v.ep_version_minor}.{v.ep_version_patch}')")
788+
aria2c https://raw.githubusercontent.com/NREL/EnergyPlus/v${cur_v}/testfiles/PythonPluginCustomSchedule.py
789+
aria2c https://raw.githubusercontent.com/NREL/EnergyPlus/v${cur_v}/testfiles/PythonPluginCustomSchedule.idf
790+
aria2c https://raw.githubusercontent.com/NREL/EnergyPlus/v${cur_v}/weather/USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw
791+
./test_install/EnergyPlus/energyplus -w USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw -d out PythonPluginCustomSchedule.idf
792+
echo "::endgroup::"
793+
794+
hdiutil detach ./temp_mount/
795+
796+
- name: Upload otool info as artifact
797+
uses: actions/upload-artifact@v4
798+
with:
799+
name: otool_info_dmg_${{ matrix.os }}_${{ matrix.arch }}
800+
path: dmg/otool*json
801+
if-no-files-found: error

CMake/CPackSignAndNotarizeDmg.cmake

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#[=======================================================================[.rst:
2+
CPackSignAndNotarizeDmg
3+
-----------------------
4+
5+
This file is meant to be used up as a ``CPACK_POST_BUILD_SCRIPTS``
6+
7+
It will run only on ``APPLE`` when the generator is ``IFW`` to codesign the resulting .dmg and notarize it.
8+
9+
To do so, it uses the `CodeSigning`_ functions :cmake:command:`codesign_files_macos`
10+
11+
It requires that this be set: :cmake:variable:`CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION`
12+
13+
And it will only notarize if this is set: :cmake:variable:`CPACK_CODESIGNING_NOTARY_PROFILE_NAME`
14+
15+
#]=======================================================================]
16+
message(STATUS "The message from ${CMAKE_CURRENT_LIST_FILE} and generator ${CPACK_GENERATOR}")
17+
message(STATUS "Built packages: ${CPACK_PACKAGE_FILES}")
18+
19+
if(APPLE AND CPACK_GENERATOR STREQUAL "IFW")
20+
21+
message(DEBUG "CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION=${CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION}")
22+
message(DEBUG "CPACK_CODESIGNING_NOTARY_PROFILE_NAME=${CPACK_CODESIGNING_NOTARY_PROFILE_NAME}")
23+
message(DEBUG "CPACK_IFW_PACKAGE_SIGNING_IDENTITY=${CPACK_IFW_PACKAGE_SIGNING_IDENTITY}")
24+
message(DEBUG "CPACK_CODESIGNING_MACOS_IDENTIFIER=${CPACK_CODESIGNING_MACOS_IDENTIFIER}")
25+
26+
include(${CMAKE_CURRENT_LIST_DIR}/CodeSigning.cmake)
27+
28+
if(NOT CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION)
29+
message(FATAL_ERROR "CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION is required, this should not have happened")
30+
endif()
31+
if(NOT CPACK_CODESIGNING_MACOS_IDENTIFIER)
32+
message(FATAL_ERROR "CPACK_CODESIGNING_MACOS_IDENTIFIER is required, this should not have happened")
33+
endif()
34+
35+
codesign_files_macos(
36+
FILES ${CPACK_PACKAGE_FILES}
37+
SIGNING_IDENTITY ${CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION}
38+
IDENTIFIER "${CPACK_CODESIGNING_MACOS_IDENTIFIER}.DmgInstaller"
39+
FORCE
40+
VERBOSE
41+
)
42+
43+
if(CPACK_CODESIGNING_NOTARY_PROFILE_NAME)
44+
notarize_files_macos(
45+
FILES ${CPACK_PACKAGE_FILES}
46+
NOTARY_PROFILE_NAME ${CPACK_CODESIGNING_NOTARY_PROFILE_NAME}
47+
STAPLE
48+
VERIFY
49+
VERBOSE
50+
)
51+
endif()
52+
53+
endif()

0 commit comments

Comments
 (0)