Skip to content

Build: Build and sign application budle for macOS #4308

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 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
141 changes: 66 additions & 75 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: continuous-integration

on:
push:
Expand All @@ -11,87 +11,78 @@ defaults:
shell: bash

jobs:
# This workflow contains a single job called "build"
build:
name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.QT_API }}"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-20.04
python-version: "3.10"
QT_API: PySide6
with_opencl: false
#- os: ubuntu-22.04
# python-version: "3.11"
# QT_API: PyQt6
# with_opencl: true
#- os: ubuntu-22.04
# python-version: "3.13"
# QT_API: PySide6
# with_opencl: true
# build_windows_installer:
# name: Build the Windows installer
# runs-on: windows-2022
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-python@v5
# with:
# python-version: "3.11"
# cache: "pip"
# - name: Install silx
# run: pip install .[full,test]
# - name: Install pyinstaller
# # Install PyInstaller from source and compile bootloader.
# env:
# PYINSTALLER_COMPILE_BOOTLOADER: "1"
# PYINSTALLER_BOOTLOADER_WAF_ARGS: "--msvc_target=x64"
# run: pip install pyinstaller --no-binary pyinstaller
# - name: Build the package with all dependencies
# run: |
# cd package/windows
# pyinstaller pyinstaller.spec
# - uses: actions/upload-artifact@v4
# with:
# name: windows-installer
# path: |
# ./package/windows/artifacts/silx-*.exe
# ./package/windows/artifacts/silx-*.zip

#- os: macos-13
# python-version: "3.12"
# QT_API: PyQt5
# with_opencl: true
- os: macos-13
python-version: "3.13"
QT_API: PyQt6
with_opencl: true
#- os: macos-13
# python-version: "3.10"
# QT_API: PySide6
# with_opencl: true

#- os: windows-latest
# python-version: "3.13"
# QT_API: PyQt5
# with_opencl: false
#- os: windows-latest
# python-version: "3.10"
# QT_API: PyQt6
# with_opencl: false
- os: windows-latest
python-version: "3.12"
QT_API: PyQt5
with_opencl: false
# test_windows_installer:
# needs: [build_windows_installer]
# name: Test the Windows installer
# runs-on: windows-2022
# steps:
# - uses: actions/download-artifact@v4
# with:
# name: windows-installer
# - name: Unzip
# run: 7z x silx-*.zip
# - name: Test
# run: |
# cd silx
# ./silx-view.exe --help
# ./silx.exe --help

build_and_notarize_macos_app:
name: Build the macOS app
runs-on: macos-13
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
python-version: "3.11"
cache: "pip"

- uses: ./.github/actions/setup-system

- name: Install build dependencies
run: |
pip install --upgrade --pre build cython setuptools wheel
pip list

- name: Build
- name: Install silx
run: pip install .[full]
- name: Install pyinstaller
# Install PyInstaller from source and compile bootloader.
env:
MACOSX_DEPLOYMENT_TARGET: "10.9"
run: |
python -m build --no-isolation
ls dist

- name: Install
run: |
pip install -r ci/requirements-pinned.txt
pip install --pre "${{ matrix.QT_API }}"
pip install --pre "$(ls dist/silx*.whl)[full,test]"
python ./ci/info_platform.py
pip list

- name: Test
PYINSTALLER_COMPILE_BOOTLOADER: "1"
run: pip install pyinstaller --no-binary pyinstaller
- name: Build the package with all dependencies
env:
QT_API: ${{ matrix.QT_API }}
SILX_TEST_LOW_MEM: "False"
SILX_OPENCL: ${{ matrix.with_opencl && 'True' || 'False' }}
ACTIONS_STEP_DEBUG: true
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD }}
CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.APPPLICATION_SPECIFIC_PASSWORD }}
run: |
python -c "import silx.test, sys; sys.exit(silx.test.run_tests(verbosity=1, args=['--qt-binding=${{ matrix.QT_API }}']));"
cd package/windows
pyinstaller pyinstaller.spec
- uses: actions/upload-artifact@v4
with:
name: macos-app
Binary file added package/desktop/silx.icns
Binary file not shown.
Binary file added package/windows/DS_Store
Binary file not shown.
Binary file added package/windows/background.pdf
Binary file not shown.
55 changes: 55 additions & 0 deletions package/windows/codesign.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash

# The script codesigns the application bundle.

# Exit immediately if a command exits with a non-zero status
set -e
# Print commands and their arguments as they are executed (debug mode)
set -x

log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') INFO [codesign] $1"
}

log "Setting the required environment variables."
APP_NAME="silx-view"
ROOT="${PWD}"
APP_PATH="${ROOT}/dist/${APP_NAME}.app"
KEYCHAIN_PATH="${ROOT}/notarize.keychain-db"
CERTIFICATE_PATH="${ROOT}/certificate.p12"

log "Creating a temporary keychain."
security create-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}"
security set-keychain-settings -lut 21600 "${KEYCHAIN_PATH}"
security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}"

log "Importing the certificate from the base64 string."
echo "${CERTIFICATE_BASE64}" | base64 --decode -o "${CERTIFICATE_PATH}"

log "Importing the certificate to the keychain."
security import "${CERTIFICATE_PATH}" \
-P "${CERTIFICATE_PASSWORD}" \
-A -t cert -f pkcs12 \
-k "${KEYCHAIN_PATH}"

log "Configuring keychain access control for codesigning without UI prompts."
security set-key-partition-list \
-S apple-tool:,apple: \
-k "${KEYCHAIN_PASSWORD}" \
"${KEYCHAIN_PATH}"

security find-certificate ${ROOT}/notarize.keychain-db

log "Codesigning the application bundle."
# --sign "Developer ID Application: MARIUS SEPTIMIU RETEGAN (${APPLE_TEAM_ID})"
codesign --verbose --force --deep --options=runtime \
--entitlements ./entitlements.plist \
--keychain "${KEYCHAIN_PATH}" \
--timestamp "${APP_PATH}" \
--sign \""Developer ID Application: MARIUS SEPTIMIU RETEGAN (2YU2GQDPHY)\""

log "Removing the certificate file and keychain."
rm "${CERTIFICATE_PATH}"
security delete-keychain "${KEYCHAIN_PATH}"

log "Codesigning completed successfully."
125 changes: 125 additions & 0 deletions package/windows/create-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env bash

# The script creates a DMG file for the application bundle.

# Exit immediately if a command exits with a non-zero status
set -e
# Print commands and their arguments as they are executed (debug mode)
set -x

log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') INFO [create-dmg] $1"
}

log "Setting environment variables."
APP_NAME="silx-view"
ROOT="${PWD}"
APP="${ROOT}/dist/${APP_NAME}.app"
RESOURCES="${ROOT}" # The path to resources (volume icon, background, ...)."

log "Setting derived environment variables."
ARTIFACTS="${ROOT}/artifacts"
TEMPLATE="${ARTIFACTS}/template"
TEMPLATE_DMG="${ARTIFACTS}/template.dmg"
APP_DMG="${ARTIFACTS}/${APP_NAME}.dmg"

SLEEP_INTERVAL=5

log "Checking if artifacts folder exists."
[ -d "${ARTIFACTS}" ] && rm -rf "${ARTIFACTS}"

log "Creating the artifacts folder."
mkdir -p "${ARTIFACTS}"

log "Removing any previous images."
if [[ -e "${TEMPLATE}" ]]; then rm -rf "${TEMPLATE}"; fi
if [[ -e "${APP_DMG}" ]]; then rm -rf "${APP_DMG}"; fi

log "Copying required files."
mkdir -p "${TEMPLATE}/.background"
cp -a "${RESOURCES}/background.pdf" "${TEMPLATE}/.background/background.pdf"
cp -a "${RESOURCES}/silx.icns" "${TEMPLATE}/.VolumeIcon.icns"
cp -a "${RESOURCES}/DS_Store" "${TEMPLATE}/.DS_Store"
cp -a "${APP}" "${TEMPLATE}/${APP_NAME}.app"
ln -s "/Applications/" "${TEMPLATE}/Applications"

log "Creating a regular .fseventsd/no_log file."
mkdir "${TEMPLATE}/.fseventsd"
touch "${TEMPLATE}/.fseventsd/no_log"

log "Sleeping for a few seconds."
sleep "${SLEEP_INTERVAL}"

log "Creating the temporary disk image."
hdiutil create -verbose -format UDRW -volname "${APP_NAME}" -fs APFS \
-srcfolder "${TEMPLATE}" \
"${TEMPLATE_DMG}"

log "Sleeping for a few seconds."
sleep "${SLEEP_INTERVAL}"

log "Detaching the temporary disk image if still attached."
hdiutil detach -verbose "/Volumes/${APP_NAME}" -force || true

log "Attaching the temporary disk image in read/write mode."
MOUNT_OUTPUT=$(hdiutil attach -readwrite -noverify -noautoopen "${TEMPLATE_DMG}" | grep '^/dev/')
DEV_NAME=$(echo -n "${MOUNT_OUTPUT}" | head -n 1 | awk '{print $1}')
MOUNT_POINT=$(echo -n "${MOUNT_OUTPUT}" | tail -n 1 | awk '{print $3}')

log "Fixing permissions."
chmod -Rf go-w "${TEMPLATE}" || true

log "Hiding the background directory even more."
SetFile -a V "${MOUNT_POINT}/.background"

log "Seting the custom icon volume flag so that volume has nice icon."
SetFile -a C "${MOUNT_POINT}"

log "Moving the icons to the appropriate position relative to the background."
WINDOW_SIZE="{0, 0, 500, 300}"
APP_ICON_POSITION="{114, 124}"
APPLICATIONS_ICON_POSITION="{386, 124}"

osascript <<EOF
tell application "Finder"
tell disk "${APP_NAME}"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
set bounds of container window to ${WINDOW_SIZE}

set viewOptions to the icon view options of container window
set arrangement of viewOptions to not arranged
set icon size of viewOptions to 96
set background picture of viewOptions to file ".background:background.pdf"

set position of item "${APP_NAME}" to ${APP_ICON_POSITION}
set position of item "Applications" to ${APPLICATIONS_ICON_POSITION}

close
open
delay 1
update without registering applications
end tell
end tell
EOF

log "Sleeping for a few seconds."
sleep "${SLEEP_INTERVAL}"

log "Detaching the temporary disk image"
hdiutil detach "${DEV_NAME}" -force || true

log "Sleeping for a few seconds."
sleep "${SLEEP_INTERVAL}"

log "Converting the temporary image to a compressed image."
hdiutil convert "${TEMPLATE_DMG}" \
-format UDZO \
-imagekey zlib-level=9 \
-o "${APP_DMG}"

log "Cleaning up."
rm -rf "${TEMPLATE}"
rm -rf "${TEMPLATE_DMG}"
13 changes: 13 additions & 0 deletions package/windows/entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?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>
<!-- These are required for binaries built by PyInstaller -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
29 changes: 29 additions & 0 deletions package/windows/notarize.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash

# The script submits the application for notarization to Apple.

# Exit immediately if a command exits with a non-zero status
set -e
# Print commands and their arguments as they are executed (debug mode)
set -x

log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') INFO [notarize] $1"
}

log "Setting the required environment variables."
APP_NAME="silx-view"
ROOT="${PWD}"
APP_DMG="${ROOT}/artifacts/${APP_NAME}.dmg"

log "Submiting the application for notarization."
xcrun notarytool submit \
--apple-id "${APPLE_ID}" \
--team-id "${APPLE_TEAM_ID}" \
--password "${APPLICATION_SPECIFIC_PASSWORD}" \
--wait "${APP_DMG}"

log "Stapling the notarization ticket to the application bundle."
xcrun stapler staple "${APP_DMG}"

log "Notarization completed successfully."
Loading
Loading