From 96c513925422a9636b371d6cd80e0c775d17a6f1 Mon Sep 17 00:00:00 2001 From: Peter Ammon Date: Thu, 4 Jul 2024 19:02:01 -0700 Subject: [PATCH] Build, codesign, and notarize macOS packages in CI This adds a new workflow and script to build macOS packages in GitHub CI. It also adds some documentation for the process. --- .github/workflows/mac_codesign.yml | 35 +++++++++ build_tools/make_pkg.sh | 112 +++++++++++++++++++++++++---- doc_internal/mac-artifacts.md | 54 ++++++++++++++ 3 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/mac_codesign.yml create mode 100644 doc_internal/mac-artifacts.md diff --git a/.github/workflows/mac_codesign.yml b/.github/workflows/mac_codesign.yml new file mode 100644 index 000000000..501a7f230 --- /dev/null +++ b/.github/workflows/mac_codesign.yml @@ -0,0 +1,35 @@ +name: macOS build and codesign + +on: + workflow_dispatch: # Enables manual trigger from GitHub UI + +jobs: + build-and-code-sign: + runs-on: macos-latest + environment: macos-codesign + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.79 + - name: build-and-codesign + run: | + cargo install apple-codesign + mkdir -p "$FISH_ARTEFACT_PATH" + echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode > /tmp/app.p12 + echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode > /tmp/installer.p12 + echo "$MACOS_NOTARIZE_JSON" > /tmp/notarize.json + ./build_tools/make_pkg.sh -s -f /tmp/app.p12 -i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" -n -j /tmp/notarize.json + rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json + env: + MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }} + MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }} + MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }} + MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }} + # macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead + # of crates.io, so give this a try. It's also sometimes significantly faster on all platforms. + CARGO_NET_GIT_FETCH_WITH_CLI: true + FISH_ARTEFACT_PATH: /tmp/fish-built + - uses: actions/upload-artifact@v4 + with: + name: macOS Artefacts + path: /tmp/fish-built/* + if-no-files-found: error diff --git a/build_tools/make_pkg.sh b/build_tools/make_pkg.sh index 71e8f2d3b..7f293d65f 100755 --- a/build_tools/make_pkg.sh +++ b/build_tools/make_pkg.sh @@ -2,6 +2,48 @@ # Script to produce an OS X installer .pkg and .app(.zip) +usage() { + echo "Build macOS packages, optionally signing and notarizing them." + echo "Usage: $0 options" + echo "Options:" + echo " -s Enables code signing" + echo " -f Path to .p12 file for application signing" + echo " -i Path to .p12 file for installer signing" + echo " -p Password for the .p12 files (necessary to access the certificates)" + echo " -e (Optional) Path to an entitlements XML file" + echo " -n Enables notarization. This will fail if code signing is not also enabled." + echo " -j Path to JSON file generated with `rcodesign encode-app-store-connect-api-key` (required for notarization)" + echo + exit 1 +} + +set -x +set -e + +SIGN= +NOTARIZE= + +while getopts "sf:i:p:e:nj:" opt; do + case $opt in + s) SIGN=1;; + f) P12_APP_FILE=$(realpath "$OPTARG");; + i) P12_INSTALL_FILE=$(realpath "$OPTARG");; + p) P12_PASSWORD="$OPTARG";; + e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");; + n) NOTARIZE=1;; + j) API_KEY_FILE=$(realpath "$OPTARG");; + \?) usage;; + esac +done + +if [ -n "$SIGN" ] && ([ -z "$P12_APP_FILE" ] || [-z "$P12_INSTALL_FILE"] || [ -z "$P12_PASSWORD" ]); then + usage +fi + +if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then + usage +fi + VERSION=$(git describe --always --dirty 2>/dev/null) if test -z "$VERSION" ; then echo "Could not get version from git" @@ -12,16 +54,8 @@ fi echo "Version is $VERSION" -set -x - -#Exit on error -set -e - -# Respect MAC_CODESIGN_ID, or default for ad-hoc. -# Note the :- means "or default" and the following - is the value. -MAC_CODESIGN_ID=${MAC_CODESIGN_ID:--} - PKGDIR=$(mktemp -d) +echo "$PKGDIR" SRC_DIR=$PWD OUTPUT_PATH=${FISH_ARTEFACT_PATH:-~/fish_built} @@ -30,14 +64,66 @@ mkdir -p "$PKGDIR/build" "$PKGDIR/root" "$PKGDIR/intermediates" "$PKGDIR/dst" # Pass FISH_USE_SYSTEM_PCRE2=OFF because a system PCRE2 on macOS will not be signed by fish, # and will probably not be built universal, so the package will fail to validate/run on other systems. -{ cd "$PKGDIR/build" && cmake -DMAC_INJECT_GET_TASK_ALLOW=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" -DWITH_GETTEXT=OFF -DFISH_USE_SYSTEM_PCRE2=OFF -DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' -DMAC_CODESIGN_ID="${MAC_CODESIGN_ID}" "$SRC_DIR" && make VERBOSE=1 -j 12 && env DESTDIR="$PKGDIR/root/" make install; } +{ cd "$PKGDIR/build" && cmake -DMAC_INJECT_GET_TASK_ALLOW=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" -DWITH_GETTEXT=OFF -DFISH_USE_SYSTEM_PCRE2=OFF -DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' "$SRC_DIR" && make VERBOSE=1 -j 12 && env DESTDIR="$PKGDIR/root/" make install; } + +if test -n "$SIGN"; then + echo "Signing executables" + ARGS=( + --p12-file "$P12_APP_FILE" + --p12-password "$P12_PASSWORD" + --code-signature-flags runtime + --for-notarization + ) + if [ -n "$ENTITLEMENTS_FILE" ]; then + ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE") + fi + for FILE in "$PKGDIR"/root/usr/local/bin/*; do + (set +x; rcodesign sign "${ARGS[@]}" "$FILE") + done +fi + pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg" productbuild --package-path "$PKGDIR/intermediates" --distribution "$SRC_DIR/build_tools/osx_distribution.xml" --resources "$SRC_DIR/build_tools/osx_package_resources/" "$OUTPUT_PATH/fish-$VERSION.pkg" -MAC_PRODUCTSIGN_ID=${MAC_PRODUCTSIGN_ID:--} -productsign --sign "${MAC_PRODUCTSIGN_ID}" "$OUTPUT_PATH/fish-$VERSION.pkg" "$OUTPUT_PATH/fish-$VERSION-signed.pkg" && mv "$OUTPUT_PATH/fish-$VERSION-signed.pkg" "$OUTPUT_PATH/fish-$VERSION.pkg" +if test -n "$SIGN"; then + echo "Signing installer" + ARGS=( + --p12-file "$P12_INSTALL_FILE" + --p12-password "$P12_PASSWORD" + --code-signature-flags runtime + --for-notarization + ) + (set +x; rcodesign sign "${ARGS[@]}" "$OUTPUT_PATH/fish-$VERSION.pkg") +fi # Make the app -{ cd "$PKGDIR/build" && make -j 12 signed_fish_macapp && zip -r "$OUTPUT_PATH/fish-$VERSION.app.zip" fish.app; } +cd "$PKGDIR/build" +make -j 12 fish_macapp +if test -n "$SIGN"; then + echo "Signing app" + ARGS=( + --p12-file "$P12_APP_FILE" + --p12-password "$P12_PASSWORD" + --code-signature-flags runtime + --for-notarization + ) + if [ -n "$ENTITLEMENTS_FILE" ]; then + ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE") + fi + (set +x; rcodesign sign "${ARGS[@]}" "fish.app") + +fi +mv "fish.app" "$OUTPUT_PATH/fish-$VERSION.app" +cd "$OUTPUT_PATH" + +# Maybe notarize. +if test -n "$NOTARIZE"; then + echo "Notarizing" + rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.pkg" + rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.app" +fi + +# Zip it up. +zip -r "fish-$VERSION.app.zip" "fish-$VERSION.app" && rm -Rf "fish-$VERSION.app" rm -rf "$PKGDIR" diff --git a/doc_internal/mac-artifacts.md b/doc_internal/mac-artifacts.md new file mode 100644 index 000000000..5b51a0838 --- /dev/null +++ b/doc_internal/mac-artifacts.md @@ -0,0 +1,54 @@ +# Building macOS Artifacts + +This describes how to build macOS artifacts as part of a release. These artifacts are uploaded to the GitHub release page, where they are discovered by the web site build script. + +Artifacts may be built locally or in CI. Using CI is preferred. + +> **Note** +> Only fish-shell administrations may create releases. Released macOS packages require code signing and notarization via private Apple developer keys, which are owned by @ridiculous_fish. These keys are stored in GitHub secrets. + +## Building in CI (GitHub Actions) + +macOS packages may be built in CI through a GitHub workflow. This requires a fish-shell administrator as it requires invoking secret code signing keys. + +Steps: + +1. Go to https://github.com/fish-shell/fish-shell/actions +2. On the left side, click on the "macOS build and codesign" Action. +3. On the right, click on the "Run workflow" button, select the branch or tag, and click Run Workflow. +4. **Reload the page**. This is necessary because the workflow will not appear until the page is reloaded. +5. Click on the new workflow. It should be in a Waiting state. +6. Click on Review Deployments, and approve and start the "deployment." +7. Once the workflow completes, expand the `actions/upload-artifact@v4` step in the logs. This should have an "Artifact download URL" - click it and download! + +## Building locally (no code signing) + +To build locally without notarizing and code signing, use the `build_tools/make_pkg.sh` script: + +``` +> ./build_tools/make_pkg.sh +``` + +Packages will be placed in `~/fish_built` by default. + +Note these packages will result in loud warnings or errors when others try to install them, because of the lack of code signing. + +## Building locally with code signing and notarization + +You will need the following: + +- The ".p12" certificate files for both "Developer ID Application" and "Developer ID Installer". + - (These can be generated via Export Certificate in Xcode) +- The password for these files (by convention, the same password is used for both). +- The JSON file generated via `rcodesign encode-app-store-connect-api-key`. + +An example run: + +``` +> ./build_tools/make_pkg.sh -s \ + -f fish-developer-id-application.p12 \ + -i fish-developer-id-installer.p12 \ + -p "$NOTARIZE_PASSWORD" \ + -n \ + -j notarize-data.json +```