Skip to content

macOS Specify numpy version on mac/win builds. #105

macOS Specify numpy version on mac/win builds.

macOS Specify numpy version on mac/win builds. #105

name: Build
run-name: macOS ${{ github.event.pull_request.title }} ${{ github.event.head_commit.message }}
# see https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
# installer is always built with a specific version of Python
os: [macos-12]
python-version: ["3.12"]
architecture: [x86_64]
include:
- os: macos-14
python-version: "3.12"
architecture: arm64
env:
# define environment and configuration variables here.
# app_name is how the application will be named on the disk image.
# product_name is the name of the disk image (.dmg) that gets built.
# python_version specifies which version of Python to use.
# this should be regularly updated to the latest usable version.
app_name: "Nion Swift 16"
product_name: NionSwift-16.11.0
python_version: 3.12.4
# version of nionui-tool to use as basis of tool build
tool_source_version: 0.4.24
# qt_version, qt_platform specify which version of Qt to use.
# these should be regularly updated to the latest usable version.
# these are only used if building the tool from source.
qt_version: 6.7.2
qt_platform: macos
# numpy version
numpy_version: 1.26.4
steps:
- uses: actions/checkout@v4
# first set up Python
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -c "import sys; print(sys.version)"
# next build the installer (it's a disk image with the application).
- name: Build
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
MACOS_DEVELOPER_TEAM_ID: ${{ secrets.MACOS_DEVELOPER_TEAM_ID }}
run: |
# NOTE: not building from source fails to codesign because the framework soft links aren't preserved.
# TODO: fix installed tool builds.
# for now, build from source always but use a specific source version if desired.
build_from_source=true
do_codesign=true
# the first step in the build process is to create a virtual environment to be
# used by the installation scripts and activate it. also update pip.
mkdir venv
python -m venv venv/installer
source venv/installer/bin/activate
python -m pip install --upgrade pip
# grab nionswift-tool for splash and toml files
git clone https://github.com/nion-software/nionswift-tool.git --branch $tool_source_version --single-branch
# OPTIONAL: build tool from source
# next, install Qt using the aqt tool. only install the required Qt modules.
# then configure the Qt6_DIR variable (used by cmake), install numpy (used by
# cmake). then git clone the tool source code. finally run cmake.
if $build_from_source; then
pip install aqtinstall
mkdir Qt
echo "Installing Qt $qt_version ($qt_platform)"
python -m aqt install-qt --outputdir Qt mac desktop $qt_version clang_64
ls Qt
export Qt6=`pwd`/Qt/$qt_version/$qt_platform
export Qt6_DIR="$Qt6/lib/cmake/Qt6"
# see https://bugreports.qt.io/browse/QTBUG-97615
export CMAKE_PREFIX_PATH="$Qt6"
pip install numpy==$numpy_version
git clone https://github.com/nion-software/nionui-tool.git
pushd nionui-tool/launcher
PYTHON=`python -c "import sys; print(sys.executable, end='')"`
cmake CMakeLists.txt -DPython3_EXECUTABLE="$PYTHON"
cmake --build . --config Release
popd
fi
# now install a fresh Python from python.org. use a version from the earliest supported target OS.
# on macOS, manually extract Python from the pkg. then run mac_fix_framework to fix dylib references.
# the default Python assumes it is installed in /Library/Frameworks. However, the tool application
# needs to be relocatable. So mac_fix_frameworks adjusts all of the dylibs to be located relative
# to the tool executable (rpath).
# finally copy the extracted Python to the distribution directory, create a new environment (python-nionswift),
# and activate the environment.
curl -O https://www.python.org/ftp/python/$python_version/python-$python_version-macos11.pkg
# print md5 to be used for verification if desired.
md5 python-$python_version-macos11.pkg
mkdir python-$python_version-macos11
xar -xf python-$python_version-macos11.pkg -C python-$python_version-macos11
rm -rf Python.framework; mkdir Python.framework
tar -zxf python-$python_version-macos11/Python_Framework.pkg/Payload -C Python.framework
echo "Running fix"
python mac_fix_framework.py Python.framework/
python3.12 -m venv python-nionswift
source python-nionswift/bin/activate
# install latest version of nionswift (UNUSED EXCEPT FOR DEBUGGING)
# git clone https://github.com/nion-software/nionutils.git
# git clone https://github.com/nion-software/niondata.git
# git clone https://github.com/nion-software/nionui.git
# git clone https://github.com/nion-software/nionswift.git
# git clone https://github.com/nion-software/nionswift-io.git
# git clone https://github.com/nion-software/nionswift-instrumentation-kit.git
# git clone https://github.com/nion-software/eels-analysis.git nionswift-eels-analysis
# git clone https://github.com/nion-software/nionswift-usim.git
# python -m pip install ./nionutils
# python -m pip install ./niondata
# python -m pip install ./nionui
# python -m pip install ./nionswift
# python -m pip install ./nionswift-instrumentation-kit
# python -m pip install ./nionswift-eels-analysis
# python -m pip install ./nionswift-usim
# install the latest released version in the activated Python environment.
python -m pip install nionswift nionswift-tool nionswift-eels-analysis nionswift-usim
# deactivate the environment since everything is installed.
deactivate
# OPTIONAL: build tool from source
# copy the application built in the last step to the distribution directory.
rm -rf dist; mkdir -p dist/
if $build_from_source; then
cp -R "nionui-tool/launcher/build/Nion UI Launcher.app" "dist/$app_name.app"
mv "dist/$app_name.app/Contents/MacOS/Nion UI Launcher" "dist/$app_name.app/Contents/MacOS/Nion Swift"
cp "python-nionswift/bin/Nion Swift.app/Contents/Info.plist" "dist/$app_name.app/Contents/Info.plist"
else
cp -R "python-nionswift/bin/Nion Swift.app" "dist/$app_name.app"
fi
# configure the environment by copying site-packages, the toolconfig.toml file, application icon,
# and splash screen.
# strip arm64 leaving only native (for now)
ditto "dist/$app_name.app/Contents/MacOS/Nion Swift" --arch ${{ matrix.architecture }} "dist/$app_name.app/Contents/MacOS/Nion Swift ${{ matrix.architecture }}"
mv "dist/$app_name.app/Contents/MacOS/Nion Swift ${{ matrix.architecture }}" "dist/$app_name.app/Contents/MacOS/Nion Swift"
# copy the Python framework. must go after copying the app.
mv Python.framework "dist/$app_name.app/Contents/Frameworks"
# site packages need to go in the right spot.
cp -R python-nionswift/lib/python3.12/site-packages/* "dist/$app_name.app/Contents/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages"
# config files for the tool. to allow codesigning, toolconfig is put into Resources.
cp mac_toolconfig.toml "dist/$app_name.app/Contents/Resources/toolconfig.toml"
cp "nionswift-tool/launcher/Graphics/MacIcon.icns" "dist/$app_name.app/Contents/Resources/"
cp "nionswift-tool/launcher/Artwork/splash_600x400.png" "dist/$app_name.app/Contents/Resources/splash.png"
if $do_codesign; then
# codesign
echo -n "$MACOS_CERTIFICATE" | base64 -d > certificate.p12
echo "Create keychain"
security create-keychain -p "$MACOS_KEYCHAIN_PASSWORD" build.keychain
echo "Set default keychain"
security default-keychain -s build.keychain
echo "Unlock keychain"
security unlock-keychain -p "$MACOS_KEYCHAIN_PASSWORD" build.keychain
echo "Import certificate (disabled)"
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
echo "Find identity"
security find-identity -v
echo "Set key partition list (avoids dialog)"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PASSWORD" build.keychain
echo "Code sign"
# remove files that will not be signed and have can be regenerated
find "dist/$app_name.app" -name "__pycache__" -exec rm -rf {} \; -prune
find "dist/$app_name.app" -name "*.pyc" -exec rm -rf {} \; -prune
# sign files from the inside out. first ensure that dylibs are executable. then find all executable files
# and sign them. then sign apps within frameworks. then sign frameworks. then sign the app itself.
find "dist/$app_name.app" -name "*.dylib" -exec chmod +x {} \;
find "dist/$app_name.app" -type f -perm +111 -and -not -name "*.py" -exec /usr/bin/codesign -s "$MACOS_DEVELOPER_TEAM_ID" {} --options runtime --timestamp --deep --force --verbose \;
find "dist/$app_name.app/Contents/Frameworks" -name "*.app" -exec /usr/bin/codesign -s "$MACOS_DEVELOPER_TEAM_ID" {} --options runtime --timestamp --deep --force --verbose \;
find "dist/$app_name.app" -name "*.framework" -exec /usr/bin/codesign -s "$MACOS_DEVELOPER_TEAM_ID" {} --options runtime --timestamp --deep --force --verbose \;
/usr/bin/codesign -s "$MACOS_DEVELOPER_TEAM_ID" "dist/$app_name.app" --options runtime --timestamp --deep --force --verbose
# self signing for testing
# /usr/bin/codesign -s - "dist/$app_name.app" --deep --force --verbose
rm certificate.p12
fi
# finally, create the disk image
echo "Create disk image ${{ matrix.architecture }}"
hdiutil create tmp.dmg -ov -volname "$app_name Install ${{ matrix.architecture }}" -fs APFS -srcfolder "dist/"
hdiutil convert tmp.dmg -format UDZO -o "$product_name-${{ matrix.architecture }}.dmg"
# notarize
- name: Notarize DMG
uses: cocoalibs/xcode-notarization-action@v1
with:
app-path: "$product_name-${{ matrix.architecture }}.dmg"
apple-id: ${{ secrets.MACOS_DEVELOPER_APPLE_ID }}
password: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
team-id: ${{ secrets.MACOS_DEVELOPER_TEAM_ID }}
xcode-path: /Applications/Xcode.app
# finally, upload the artifacts of the build so that they are available on the GitHub action page.
- name: Upload Product DMG
uses: actions/upload-artifact@v4
with:
name: ${{ env.product_name }}-${{ matrix.architecture }}.dmg
path: ${{ env.product_name }}-${{ matrix.architecture }}.dmg