Skip to content

Build for macOS

Build for macOS #486

Workflow file for this run

name: Build for macOS
on:
push:
branches:
- main
# PR branches that alter the build process should be prefixed with `build/`, so
# that this workflow runs.
- 'build/**'
schedule:
# Run this workflow every 8 hours to repackage the pre-built Drupal CMS site
# with the latest dependencies.
- cron: 0 */8 * * *
workflow_dispatch:
jobs:
# The PHP interpreter needs to have its own job so it can be built in parallel for all
# supported architectures, before being merged into a universal binary while building
# the app itself.
php:
name: PHP on ${{ matrix.os }}
strategy:
matrix:
os:
# x64, supported until 2027, at which point GitHub Actions will drop support for
# macOS on Intel.
# @see https://github.com/actions/runner-images/issues/13046
- macos-15-intel
- macos-latest # arm64
runs-on: ${{ matrix.os }}
env:
# The extensions and libraries needed to build PHP. These need to be variables so we can
# use them to generate a cache key.
# @see https://static-php.dev/en/guide/cli-generator.html
PHP_EXTENSIONS: bz2,ctype,curl,dom,filter,gd,iconv,mbstring,opcache,openssl,pcntl,pdo,pdo_sqlite,phar,posix,session,simplexml,sodium,sqlite3,tokenizer,xml,xmlwriter,yaml,zip,zlib
PHP_VERSION: 8.3
steps:
- uses: actions/checkout@v4
# Cache the built binary so we can skip the build steps if there is a cache hit.
- name: Generate cache key
run: |
CACHE_KEY=${{ runner.os }}-${{ runner.arch }}-$PHP_VERSION--$(echo $PHP_EXTENSIONS | tr ',' '-')
echo "CACHE_KEY=${CACHE_KEY}" >> $GITHUB_ENV
- id: cache
name: Cache PHP interpreter
uses: actions/cache@v4
with:
path: build/php-${{ runner.arch }}
key: php-${{ env.CACHE_KEY }}
- if: steps.cache.outputs.cache-hit != 'true'
name: "Set up PHP"
uses: shivammathur/setup-php@v2
with:
# Install PHP with the tools needed to build the interpreter, if necessary.
php-version: latest
tools: pecl, composer
extensions: curl, openssl, mbstring, tokenizer
ini-values: memory_limit=-1
- if: steps.cache.outputs.cache-hit != 'true'
name: Install dependencies and build PHP
run: |
brew install automake gzip
composer install
composer exec spc -- doctor
composer exec spc -- download --with-php=${{ env.PHP_VERSION }} --for-extensions=${{ env.PHP_EXTENSIONS }} --prefer-pre-built
composer exec spc -- build ${{ env.PHP_EXTENSIONS }} --build-cli --with-libs=freetype,libavif,libjpeg,libwebp --debug
mv ./buildroot/bin/php ./php-${{ runner.arch }}
env:
# Allows static-php-cli to download its many dependencies more smoothly.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: build
- name: Upload PHP binary
uses: actions/upload-artifact@v4
with:
name: php-${{ runner.arch }}
path: build/php-${{ runner.arch }}
retention-days: 1
app:
name: App
runs-on: macos-latest
needs:
- php
env:
# Don't publish by default. This is overridden for the release branch, below.
PUBLISH: never
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check out latest tag
if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
run: |
LATEST_TAG=$(git describe --tags --abbrev=0)
git checkout $LATEST_TAG
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
- name: Download PHP binaries
uses: actions/download-artifact@v4
with:
path: build
pattern: php-*
merge-multiple: true
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
tools: composer
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: latest
- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm install
# This was copied from the example in
# https://docs.github.com/en/actions/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development.
- name: Set up code signing
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Set up some useful variables.
CERTIFICATE_PATH=$RUNNER_TEMP/build.p12
PROVISION_PROFILE_PATH=$RUNNER_TEMP/build.provisionprofile
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Import the signing certificate and provisioning profile from our secrets.
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PROVISION_PROFILE_PATH
# Create a temporary keychain which will hold the signing certificate.
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Add the certificate to the keychain.
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Apply the provisioning profile.
# This path is based on what I found at https://stackoverflow.com/questions/45625347/xcode-provisioning-profiles-location#45642752
mkdir -p ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
cp $PROVISION_PROFILE_PATH ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
- name: Build app
run: |
composer run assets --working-dir=build
# Generate a universal binary of the PHP interpreter.
lipo -create ./build/php-X64 ./build/php-ARM64 -output ./bin/php
chmod +x ./bin/php
npx electron-vite build
- name: Prepare to publish
# If we're on a PR branch, we don't want Electron Builder to publish the app.
if: github.ref_name == 'main'
run: |
# Configure Electron Builder to notarize and publish.
echo "APPLE_ID=${{ secrets.APPLE_ID }}" >> $GITHUB_ENV
echo "APPLE_APP_SPECIFIC_PASSWORD=${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}" >> $GITHUB_ENV
echo "APPLE_TEAM_ID=${{ secrets.APPLE_TEAM_ID }}" >> $GITHUB_ENV
echo "PUBLISH=onTagOrDraft" >> $GITHUB_ENV
# Electron Builder needs a token to publish releases.
echo "GH_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV
# Pre-build the Drupal site.
npx electron . --root=drupal --no-server
find drupal -depth -type d -name tests -exec rm -r -f {} ';'
tar -c -z -f prebuilt.tar.gz --directory=drupal .
rm -r -f drupal
- name: Make application
run: npx electron-builder --universal --publish=${{ env.PUBLISH }}
# For manual testing, upload the final artifact if we're not in a release branch.
- name: Upload distributable
if: ${{ env.PUBLISH == 'never' }}
uses: actions/upload-artifact@v4
with:
name: app
path: dist/*.zip
retention-days: 7
- name: Update prebuilt Drupal code base
if: ${{ env.LATEST_TAG }}
uses: softprops/action-gh-release@v2
with:
files: |
dist/Drupal_CMS-macOS.zip
dist/Drupal_CMS-macOS.zip.blockmap
dist/latest-mac.yml
tag_name: ${{ env.LATEST_TAG }}