ci: Add standalone macOS build support#7581
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
3a44ebc to
0a07ac0
Compare
0a07ac0 to
494ec47
Compare
494ec47 to
c1c55cf
Compare
77ea1cc to
0ddaa11
Compare
0ddaa11 to
af801b9
Compare
af801b9 to
b36d45c
Compare
6ecae4e to
1fdb04e
Compare
0f28470 to
9dc8548
Compare
322094a to
ce58771
Compare
| name: build-${{ matrix.sdk }} | ||
| runs-on: ${{ matrix.runs-on }} | ||
| name: ${{ matrix.job_name }} | ||
| runs-on: macos-15 |
There was a problem hiding this comment.
This was updated to match our development machines which reduces the build configuration drift between the two.
| contents: read | ||
| id-token: "write" | ||
| env: | ||
| XCODE_VERSION: "16.2" |
There was a problem hiding this comment.
Updated to be a recent version. There were a few subtle differences in how provisioning profiles and certs are stored/loaded in Xcode 16 that will be noted where appropriate.
| - job_name: build-ios | ||
| rust-targets: aarch64-apple-ios | ||
| build-script: scripts/build/ios-appstore.sh | ||
| ARTIFACT_PATH: "./Firezone.ipa" | ||
| upload-script: scripts/upload/app-store-connect.sh | ||
|
|
||
| - job_name: build-macos-appstore | ||
| rust-targets: aarch64-apple-darwin x86_64-apple-darwin | ||
| build-script: scripts/build/macos-appstore.sh | ||
| ARTIFACT_PATH: "./Firezone.pkg" | ||
| upload-script: scripts/upload/app-store-connect.sh | ||
|
|
||
| - job_name: build-macos-standalone | ||
| rust-targets: aarch64-apple-darwin x86_64-apple-darwin | ||
| build-script: scripts/build/macos-standalone.sh | ||
| # mark:next-apple-version | ||
| ARTIFACT_PATH: "./firezone-macos-client-1.4.0.dmg" | ||
| NOTARIZE: "${{ (github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main') && 'true' || 'false' }}" | ||
| # mark:next-apple-version | ||
| upload-script: scripts/upload/github.sh | ||
| RELEASE_NAME: macos-client-1.4.0 |
There was a problem hiding this comment.
This was refactored to be a matrix for each distributable, with the upload process carved out into another script.
secrets are not available from the strategy context, otherwise I would have added them here and not in the build steps below.
| exit 1 | ||
| fi | ||
| - name: Upload build to App Store Connect | ||
| IOS_APP_PROVISIONING_PROFILE: "${{ secrets.APPLE_IOS_APP_PROVISIONING_PROFILE }}" |
There was a problem hiding this comment.
Not every matrix job requires every secret, but doing it like this simplifies the build scripts.
The overall goal here was to not require any ENV vars when building locally, and override appropriate ones from CI if we're building on a runner.
There was a problem hiding this comment.
Embedding this was somewhat of a pain, but I think it's important. Many Mac users simply try to (mistakenly) run their apps directly from the readonly disk image.
For many apps that works just fine, but for ours it won't due to the network extension. So I spent some time educating the user just in case, to avoid future support burden here.
| # xcrun altool requires private keys to be files in a specific naming format | ||
| API_PRIVATE_KEYS_DIR=${API_PRIVATE_KEYS_DIR:-$RUNNER_TEMP} | ||
| PRIVATE_KEY_PATH="$API_PRIVATE_KEYS_DIR/AuthKey_$API_KEY_ID.p8" | ||
| echo -n "$API_KEY" | base64 --decode -o "$PRIVATE_KEY_PATH" |
There was a problem hiding this comment.
Slight change here to use a base64-encoded version of the API key. It is already in a PEM format, but the downstream build-rust.sh script was barfing on its contained newlines, so this fixes that.
There was a problem hiding this comment.
Could also reuse that utility decode function used in lib.sh when building. Not sure if it is worth consolidating though.
There was a problem hiding this comment.
We do this from quite a few other places in CI. I'll look into reusing this script for those.
| xcuserdata/ | ||
| **/*.xcuserstate | ||
|
|
||
| Firezone/xcconfig/config.xcconfig |
There was a problem hiding this comment.
Ok so this change simplifies things nicely. Instead of defining multiple xcconfigs and moving them into place just before build, we simply specify the build vars on the command directly. So we can now commit a single xcconfig and use that for development and the release build scripts will override what vars they need.
There was a problem hiding this comment.
Simplifications from the removal of xcconfigs.
There was a problem hiding this comment.
This file has been inlined in the ios build script.
There was a problem hiding this comment.
I've seen it for several MacOS apps to have a somewhat playful thing here. I think JetBrains for example has something like:
(<logo.png>).install()
It would probably be quite easy to just once-off hire a designer to come up with a cool idea here. Maybe a tunnel that you can drag the Firezone logo through?
There was a problem hiding this comment.
Hm yeah, though generating this is a little tricky and requires some trial and error since the window coordinates didn't seem to line up to Figma's pixel coordinates. I.e. getting the icons placed onto the PNG in the right place in the script might make the process a little cumbersome for an external designer.
I'll spend a few more minutes to see if I can make it a little snazzier.
| local profiles_path="$1" | ||
| local profile="$2" | ||
| local profile_file="$3" |
There was a problem hiding this comment.
| local profiles_path="$1" | |
| local profile="$2" | |
| local profile_file="$3" | |
| local profile="$1" | |
| local profiles_path="$2" | |
| local profile_file="$3" |
I think this order would make more sense, perhaps also don't build the path within the function?
| -t cert \ | ||
| -f pkcs12 \ | ||
| -k "$keychain_path" | ||
| security set-key-partition-list \ |
There was a problem hiding this comment.
Perhaps add this information as a source-code comment as to why we are doing this.
| rm "$cert_path" | ||
| } | ||
|
|
||
| function insert_build_timestamp() { |
There was a problem hiding this comment.
| function insert_build_timestamp() { | |
| function set_current_project_version() { |
There was a problem hiding this comment.
Apple calls these build version, updated this to set_project_build_version.
| GIT_SHA="$git_sha" \ | ||
| CODE_SIGN_STYLE=Manual \ | ||
| CODE_SIGN_IDENTITY="$code_sign_identity" \ | ||
| CONFIGURATION_BUILD_DIR="$temp_dir" \ | ||
| APP_PROFILE_ID="$app_profile_id" \ | ||
| NE_PROFILE_ID="$ne_profile_id" \ | ||
| ONLY_ACTIVE_ARCH=NO \ |
There was a problem hiding this comment.
Are these ENV variables? It is odd that they are defined as part of the build command and not at the very front like ENV vars for regular commands.
There was a problem hiding this comment.
Yes and no. Xcode will pass them as env vars to downstream scripts when appropriate, but they won't persist beyond that.
I thought this way reduced the chance these env vars can affect the productbuild command just below and others.
I'm probably a little traumatized from my experience with env vars and Xcode / cargo downstream effects 😆
| # Notarize disk image and embedded app | ||
| if [ "$notarize" = "true" ]; then | ||
| private_key_path="$temp_dir/firezone-api-key.p8" | ||
| echo -n "$API_KEY" | base64 --decode -o "$private_key_path" |
There was a problem hiding this comment.
This could reuse the other function in lib if you could just pass a single output path.
There was a problem hiding this comment.
Yeah I made this a base64_decode function for reuse. Probably a good idea since it deals with secrets and we don't want to fudge that up.
| private_key_path="$temp_dir/firezone-api-key.p8" | ||
| echo -n "$API_KEY" | base64 --decode -o "$private_key_path" | ||
|
|
||
| # Submit app bundle to be notarized. Can take a few minutes. |
There was a problem hiding this comment.
A few minutes? What does this do? Wait for manual approval of an Apple support person?
There was a problem hiding this comment.
Yes, the API is quite unfriendly: https://developer.apple.com/documentation/security/customizing-the-notarization-workflow#Check-the-status-of-your-request
the notarization process typically takes less than an hour.
I was thinking we can start with this and see how long the waits are. If they really take an hour we might need the notarization step to happen in a separate workflow or job.
| # xcrun altool requires private keys to be files in a specific naming format | ||
| API_PRIVATE_KEYS_DIR=${API_PRIVATE_KEYS_DIR:-$RUNNER_TEMP} | ||
| PRIVATE_KEY_PATH="$API_PRIVATE_KEYS_DIR/AuthKey_$API_KEY_ID.p8" | ||
| echo -n "$API_KEY" | base64 --decode -o "$PRIVATE_KEY_PATH" |
There was a problem hiding this comment.
Could also reuse that utility decode function used in lib.sh when building. Not sure if it is worth consolidating though.
There was a problem hiding this comment.
Maybe call this script github-release because we also upload stuff as GitHub artifacts.
| - job_name: build-ios | ||
| rust-targets: aarch64-apple-ios | ||
| build-script: scripts/build/ios-appstore.sh | ||
| ARTIFACT_PATH: "./Firezone.ipa" |
There was a problem hiding this comment.
So all env variables here are by convention upper-cased? Don't feel strongly about it but the convention seems a bit unnecessary.
Also would prefer all "scripts" to be grouped together.
The CI swift workflow needs to be updated to accommodate the macOS standalone build. This required a decent amount of refactoring to make the Apple build process more maintainable.
Unfortunately this PR ended up being a giant ball of yarn where pulling on one thread tended to unravel things elsewhere, since building the Apple artifacts involve multiple interconnected systems. Combined with the slow iteration of running in CI, I wasn't able to split this PR into easier to digest commits, so I've annotated the PR as much as I can to explain what's changed.
The good news is that Apple release artifacts can now be easily built from a developer's machine with simply
scripts/build/macos-standalone.sh. The only thing needed is the proper provisioning profiles and signing certs installed.Since this PR is so big already, I'll save the swift/apple/README.md updates for another PR.