Skip to content

Swift

Swift #94

Workflow file for this run

name: Swift
on:
workflow_call:
workflow_dispatch:
jobs:
build:
name: build-${{ matrix.sdk }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- sdk: macosx
runs-on: macos-14
platform: macOS
xcode: '15.2'
destination: platform=macOS
- sdk: iphoneos
runs-on: macos-14
platform: iOS
xcode: '15.2'
destination: generic/platform=iOS
permissions:
contents: read
id-token: 'write'
defaults:
run:
working-directory: ./swift/apple
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-rust
with:
targets: aarch64-apple-darwin aarch64-apple-ios x86_64-apple-darwin
- uses: actions/cache/restore@v4
name: Restore Swift DerivedData Cache
id: cache
with:
path: ~/Library/Developer/Xcode/DerivedData
key: ${{ matrix.runs-on }}-${{ hashFiles('swift/*', 'rust/**/*.rs', 'rust/**/*.toml', 'rust/**/*.lock}') }}
- name: Install the Apple build certificate and provisioning profile
env:
BUILD_CERT: ${{ secrets.APPLE_BUILD_CERTIFICATE_BASE64 }}
BUILD_CERT_PASS: ${{ secrets.APPLE_BUILD_CERTIFICATE_P12_PASSWORD }}
INSTALLER_CERT: ${{ secrets.APPLE_MAC_INSTALLER_CERTIFICATE_BASE64 }}
INSTALLER_CERT_PASS: ${{ secrets.APPLE_MAC_INSTALLER_CERTIFICATE_P12_PASSWORD }}
KEYCHAIN_PASS: ${{ secrets.APPLE_RUNNER_KEYCHAIN_PASSWORD }}
IOS_APP_PP: ${{ secrets.APPLE_IOS_APP_PROVISIONING_PROFILE }}
IOS_NE_PP: ${{ secrets.APPLE_IOS_NE_PROVISIONING_PROFILE }}
MACOS_APP_PP: ${{ secrets.APPLE_MACOS_APP_PROVISIONING_PROFILE }}
MACOS_NE_PP: ${{ secrets.APPLE_MACOS_NE_PROVISIONING_PROFILE }}
run: |
BUILD_CERT_PATH=$RUNNER_TEMP/build_certificate.p12
INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.cer
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
PP_PATH=~/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PP_PATH"
# import certificate and provisioning profiles from secrets
echo -n "$BUILD_CERT" | base64 --decode -o $BUILD_CERT_PATH
# Matrix won't let us access secrets (for good reason), so use an explicit conditional here instead
if [ "${{ matrix.platform }}" = "iOS" ]; then
echo -n "$IOS_APP_PP" | base64 --decode -o "$PP_PATH"/app.mobileprovision
echo -n "$IOS_NE_PP" | base64 --decode -o "$PP_PATH"/ne.mobileprovision
elif [ "${{ matrix.platform }}" = "macOS" ]; then
echo -n "$MACOS_APP_PP" | base64 --decode -o "$PP_PATH"/app.provisionprofile
echo -n "$MACOS_NE_PP" | base64 --decode -o "$PP_PATH"/ne.provisionprofile
# Submission to the macOS app store requires an installer package
# which must be signed separately.
echo -n "$INSTALLER_CERT" | base64 --decode -o $INSTALLER_CERT_PATH
else
echo "Platform not supported"
exit 1
fi
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASS" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASS" $KEYCHAIN_PATH
# import certificate to keychain
security import $BUILD_CERT_PATH -P "$BUILD_CERT_PASS" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
if [ "${{ matrix.platform }}" = "macOS" ]; then
security import $INSTALLER_CERT_PATH -P "$INSTALLER_CERT_PASS" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
fi
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Build and sign app
id: build
env:
# Build universal binary
ONLY_ACTIVE_ARCH: no
# Needed because `productbuild` doesn't support picking this up automatically like Xcode does
INSTALLER_CODE_SIGN_IDENTITY: "3rd Party Mac Developer Installer: Firezone, Inc. (47R2M6779T)"
# mark:next-apple-version
FIREZONE_PACKAGE_VERSION: 1.0.6
run: |
# Use the same Xcode version as development
sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
# Copy xcconfig
cp Firezone/xcconfig/release.xcconfig Firezone/xcconfig/config.xcconfig
# App Store Connect requires a new build version on each upload and it must be an integer.
# See https://developer.apple.com/documentation/xcode/build-settings-reference#Current-Project-Version
seconds_since_epoch=$(date +%s)
sed -i '' "s/CURRENT_PROJECT_VERSION = [0-9]/CURRENT_PROJECT_VERSION = $seconds_since_epoch/" \
Firezone.xcodeproj/project.pbxproj
# Unfortunately the macOS app requires an installer package to make it into the App Store,
# while iOS requires an ipa. The process for building each of these is slightly different.
if [ "${{ matrix.platform }}" = "iOS" ]; then
# Build archive
xcodebuild archive \
-skipMacroValidation \
-archivePath $RUNNER_TEMP/Firezone.xcarchive \
-configuration Release \
-scheme Firezone \
-sdk ${{ matrix.sdk }} \
-destination '${{ matrix.destination }}'
# Export IPA
xcodebuild \
-exportArchive \
-archivePath $RUNNER_TEMP/Firezone.xcarchive \
-exportPath $RUNNER_TEMP/ \
-exportOptionsPlist Firezone/ExportOptions.plist
# Save resulting file to use for upload
echo "app_bundle=$RUNNER_TEMP/Firezone.ipa" >> "$GITHUB_OUTPUT"
elif [ "${{ matrix.platform }}" = "macOS" ]; then
# Build app bundle
xcodebuild build \
-skipMacroValidation \
-configuration Release \
-scheme Firezone \
-sdk ${{ matrix.sdk }} \
-destination '${{ matrix.destination }}'
# Move it from randomized build output dir to somewhere we can find it
mv ~/Library/Developer/Xcode/DerivedData/Firezone-*/Build/Products/Release/Firezone.app $RUNNER_TEMP/.
# Create signed installer pkg
productbuild \
--sign "${{ env.INSTALLER_CODE_SIGN_IDENTITY }}" \
--component $RUNNER_TEMP/Firezone.app /Applications $RUNNER_TEMP/Firezone.pkg
# Save resulting file to use for upload
echo "app_bundle=$RUNNER_TEMP/Firezone.pkg" >> "$GITHUB_OUTPUT"
else
echo "Unsupported platform"
exit 1
fi
- name: Upload build to App Store Connect
if: ${{ github.event_name == 'workflow_dispatch' || (github.ref == 'refs/heads/main' && contains(github.event.head_commit.modified, 'elixir/VERSION')) }}
env:
ISSUER_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_ISSUER_ID }}
API_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
API_KEY: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY }}
run: |
# set up private key from env
mkdir -p ~/private_keys
echo "$API_KEY" > ~/private_keys/AuthKey_$API_KEY_ID.p8
# Submit app to App Store Connect
xcrun altool \
--upload-app \
-f ${{ steps.build.outputs.app_bundle }} \
-t ${{ matrix.platform }} \
--apiKey $API_KEY_ID \
--apiIssuer $ISSUER_ID
- uses: actions/cache/save@v4
if: ${{ steps.cache.outputs.cache-hit != 'true'}}
name: Save Swift DerivedData Cache
with:
path: ~/Library/Developer/Xcode/DerivedData
# Swift benefits heavily from build cache, so aggressively write a new one
# on each build on `main` and attempt to restore it in PR builds with broader restore-key.
key: ${{ steps.cache.outputs.cache-primary-key }}