#!/usr/bin/env bash ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../resources/build/builder-full.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/mac/mac.inc.sh" . "$KEYMAN_ROOT/resources/build/build-help.inc.sh" builder_describe "Builds Keyman for macOS." \ "@/core:mac" \ "@/resources/tools/check-markdown test:help" \ "clean" \ "configure" \ "build" \ "publish Publishes debug info to Sentry and builds DMG, .download_info for distribution" \ "test" \ "install Installs result of Keyman4MacIM locally." \ ":engine KeymanEngine4Mac" \ ":app Keyman4MacIM" \ ":help Online documentation" \ ":mcompile mnemonic layout recompiler- mac" \ ":testapp Keyman4Mac (test harness)" \ "--quick,-q Bypasses notarization for $(builder_term install)" builder_parse "$@" mac_verify_on_mac # Default is release build of Engine and (code-signed) Input Method if builder_is_debug_build; then CONFIG="Debug" CONFIG_TARGET=Pods-Keyman.debug.xcconfig else CONFIG="Release" CONFIG_TARGET=Pods-Keyman.release.xcconfig fi ### SET PATHS ### ENGINE_NAME="KeymanEngine4Mac" TESTAPP_NAME="Keyman4Mac" IM_NAME="Keyman4MacIM" XCODE_PROJ_EXT=".xcodeproj" PRODUCT_NAME="Keyman" KEYMAN_MAC_BASE_PATH="$KEYMAN_ROOT/mac" KME4M_BASE_PATH="$KEYMAN_MAC_BASE_PATH/$ENGINE_NAME" KMTESTAPP_BASE_PATH="$KEYMAN_MAC_BASE_PATH/$TESTAPP_NAME" KM4MIM_BASE_PATH="$KEYMAN_MAC_BASE_PATH/$IM_NAME" KME4M_PROJECT_PATH="$KME4M_BASE_PATH/$ENGINE_NAME$XCODE_PROJ_EXT" KMTESTAPP_PROJECT_PATH="$KMTESTAPP_BASE_PATH/$TESTAPP_NAME$XCODE_PROJ_EXT" KMIM_WORKSPACE_PATH="$KM4MIM_BASE_PATH/$IM_NAME.xcworkspace" PODS_FOLDER="/mac/Keyman4MacIM/Pods/Target Support Files/Pods-Keyman" builder_describe_outputs \ configure "$PODS_FOLDER/$CONFIG_TARGET" \ build:engine "/mac/$ENGINE_NAME/build/$CONFIG" \ build:app "/mac/$IM_NAME/build/$CONFIG" \ build:testapp "/mac/$TESTAPP_NAME/build/$CONFIG" ### DEFINE HELPER FUNCTIONS ### LOCALDEPLOY=false PREPRELEASE=false UPDATE_VERSION_IN_PLIST=true DO_CODESIGN=true CODESIGNING_SUPPRESSION="CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO" QUIET=false if builder_verbose; then BUILD_OPTIONS="" else BUILD_OPTIONS="-quiet" fi BUILD_ACTIONS="build" SKIP_BUILD=false UPLOAD_SENTRY=false # Import local environment variables for build # # /mac/localenv.sh can be used to define CERTIFICATE_ID, # APPSTORECONNECT_PROVIDER, APPSTORECONNECT_USERNAME, # APPSTORECONNECT_PASSWORD, DEVELOPMENT_TEAM variables; # see /mac/README.md for details. # if [[ -f "$THIS_SCRIPT_PATH/localenv.sh" ]]; then . "$THIS_SCRIPT_PATH/localenv.sh" fi BUILD_OPTIONS="-configuration $CONFIG $BUILD_OPTIONS PRODUCT_VERSION=$KEYMAN_VERSION" ### START OF THE BUILD ### execBuildCommand() { declare -r component="$1" shift declare -r cmnd="$*" builder_echo heading "Building $component:" "$cmnd" set +e local ret_code=0 eval "$cmnd" || ret_code=$? set -e mac_print_xcode_build_script_logs if [ $ret_code != 0 ]; then builder_die "Build of $component failed! Error: [$ret_code] when executing command: '$cmnd'" fi } do_clean ( ) { rm -rf "$KME4M_BASE_PATH/build" rm -rf "$KM4MIM_BASE_PATH/build" rm -rf "$KMTESTAPP_BASE_PATH/build" rm -rf "$KEYMAN_ROOT/mac/setup/Install Keyman.app" builder_heading "Cleaning pods folder (CocoaPods)" rm -rf "$PODS_FOLDER" builder_heading "Cleaning source (Carthage)" rm -rf "$KEYMAN_MAC_BASE_PATH/Carthage" # To consider: also mark for xcodebuild to do a clean build? # Though, shouldn't the above trigger this anyway? } do_configure ( ) { carthage bootstrap --use-xcframeworks pushd "$KM4MIM_BASE_PATH" > /dev/null pod update pod install popd > /dev/null } updatePlist() { if $UPDATE_VERSION_IN_PLIST ; then KM_PLIST="$1" APPNAME="$2" if [ ! -f "$KM_PLIST" ]; then builder_die "File not found: $KM_PLIST" fi local YEAR=`date "+%Y"` echo "Setting version and related fields to $KEYMAN_VERSION_WITH_TAG in $KM_PLIST" /usr/libexec/Plistbuddy -c "Set CFBundleVersion $KEYMAN_VERSION" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set CFBundleShortVersionString $KEYMAN_VERSION" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set :Keyman:SentryEnvironment $KEYMAN_VERSION_ENVIRONMENT" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set :Keyman:Tier $KEYMAN_TIER" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set :Keyman:VersionTag $KEYMAN_VERSION_TAG" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set :Keyman:VersionWithTag $KEYMAN_VERSION_WITH_TAG" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set :Keyman:VersionGitTag $KEYMAN_VERSION_GIT_TAG" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set :Keyman:VersionRelease $KEYMAN_VERSION_RELEASE" "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set CFBundleGetInfoString $APPNAME $KEYMAN_VERSION_WITH_TAG for macOS, Copyright © 2017-$YEAR SIL International." "$KM_PLIST" /usr/libexec/Plistbuddy -c "Set NSHumanReadableCopyright Copyright © 2017-$YEAR SIL International." "$KM_PLIST" fi } do_build_engine ( ) { ### Build Keyman Engine (kmx, ldml processor) ### execBuildCommand $ENGINE_NAME "xcodebuild -project \"$KME4M_PROJECT_PATH\" $BUILD_OPTIONS $BUILD_ACTIONS -scheme $ENGINE_NAME" } do_update_engine_metadata ( ) { execBuildCommand "$ENGINE_NAME dSYM file" "dsymutil \"$KME4M_BASE_PATH/build/$CONFIG/$ENGINE_NAME.framework/Versions/A/$ENGINE_NAME\" -o \"$KME4M_BASE_PATH/build/$CONFIG/$ENGINE_NAME.framework.dSYM\"" updatePlist "$KME4M_BASE_PATH/build/$CONFIG/$ENGINE_NAME.framework/Resources/Info.plist" "Keyman Engine" } do_build_app ( ) { ### Build Keyman.app (Input Method and Configuration app) ### builder_heading "Building help" build_help_html mac Keyman4MacIM/Keyman4MacIM/Help builder_heading "Building Keyman.app" execBuildCommand $IM_NAME "xcodebuild -workspace \"$KMIM_WORKSPACE_PATH\" $CODESIGNING_SUPPRESSION $BUILD_OPTIONS $BUILD_ACTIONS -scheme Keyman SYMROOT=\"$KM4MIM_BASE_PATH/build\"" } do_update_app_metadata ( ) { updatePlist "$KM4MIM_BASE_PATH/build/$CONFIG/Keyman.app/Contents/Info.plist" "Keyman" if builder_is_debug_build; then ENTITLEMENTS_FILE=Keyman.Debug.entitlements else ENTITLEMENTS_FILE=Keyman.entitlements # We need to re-sign the app after updating the plist file builder_if_release_build_level mac_codesign eval --force --sign $CERTIFICATE_ID --timestamp --verbose --preserve-metadata=identifier,entitlements "$KM4MIM_BASE_PATH/build/$CONFIG/Keyman.app/Contents/Frameworks/Sentry.framework" builder_if_release_build_level mac_codesign eval --force --sign $CERTIFICATE_ID --timestamp --verbose --preserve-metadata=identifier,entitlements "$KM4MIM_BASE_PATH/build/$CONFIG/Keyman.app/Contents/Frameworks/KeymanEngine4Mac.framework" builder_if_release_build_level mac_codesign eval --force --sign $CERTIFICATE_ID --timestamp --verbose -o runtime \ --entitlements "$KM4MIM_BASE_PATH/$ENTITLEMENTS_FILE" \ --requirements "'=designated => anchor apple generic and identifier \"\$self.identifier\" and ((cert leaf[field.1.2.840.113635.100.6.1.9] exists) or ( certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and certificate leaf[subject.OU] = \"$DEVELOPMENT_TEAM\" ))'" \ "$KM4MIM_BASE_PATH/build/$CONFIG/Keyman.app" fi } do_build_testapp() { ### Build test app / harness ### execBuildCommand $TESTAPP_NAME "xcodebuild -project \"$KMTESTAPP_PROJECT_PATH\" $BUILD_OPTIONS $BUILD_ACTIONS" updatePlist "$KMTESTAPP_BASE_PATH/build/$CONFIG/$TESTAPP_NAME.app/Contents/Info.plist" "Keyman Test App" } # In case both install & publish actions are specified, we should still only notarize once. DID_NOTARIZE=false do_notarize() { if [[ $DID_NOTARIZE == false ]]; then ### Validate notarization environment variables ### builder_heading "Notarizing app" if [ "${CODESIGNING_SUPPRESSION}" != "" ] && [ -z "${CERTIFICATE_ID+x}" ]; then builder_die "Notarization and signed executable is required for deployment, even locally. Specify CERTIFICATE_ID environment variable for custom certificate." fi if [ -z "${APPSTORECONNECT_PROVIDER+x}" ] || [ -z "${APPSTORECONNECT_USERNAME+x}" ] || [ -z "${APPSTORECONNECT_PASSWORD+x}" ]; then builder_die "Appstoreconnect Apple ID credentials must be configured in environment. See README.md for details." fi TARGET_PATH="$KM4MIM_BASE_PATH/build/$CONFIG" TARGET_APP_PATH="$TARGET_PATH/$PRODUCT_NAME.app" TARGET_ZIP_PATH="$TARGET_PATH/$PRODUCT_NAME.zip" # Note: get-task-allow entitlement must be *off* in our release build (to do this, don't include base entitlements in project build settings) # We may need to re-run the code signing if a custom certificate has been passed in if [ ! -z "${CERTIFICATE_ID+x}" ]; then builder_heading "Signing with custom certificate (CERTIFICATE_ID environment variable)." builder_if_release_build_level mac_codesign direct --force --options runtime --entitlements Keyman4MacIM/Keyman.entitlements --deep --sign "${CERTIFICATE_ID}" "$TARGET_APP_PATH" fi builder_heading "Zipping Keyman.app for notarization to $TARGET_ZIP_PATH" /usr/bin/ditto -c -k --keepParent "$TARGET_APP_PATH" "$TARGET_ZIP_PATH" builder_heading "Uploading Keyman.zip to Apple for notarization" mac_notarize "$TARGET_PATH" "$TARGET_ZIP_PATH" echo builder_heading "Attempting to staple notarization to Keyman.app" xcrun stapler staple "$TARGET_APP_PATH" || builder_die "stapler failed" DID_NOTARIZE=true fi } do_sentry() { # Upload symbols builder_heading "Uploading symbols to sentry.keyman.com" pushd "$KM4MIM_BASE_PATH" > /dev/null sentry-cli upload-dif "build/$CONFIG" popd > /dev/null } do_install() { if builder_is_ci_build; then builder_die "build.sh install should not be run on CI" elif ! builder_has_option --quick; then do_notarize else if [ "$(spctl --status)" == "assessments enabled" ]; then echo builder_warn "WARNING: Notarization is disabled but SecAssessment security policy is still active. Keyman will not run correctly." builder_warn " Disable SecAssessment with 'sudo spctl --master-disable' (or do notarized builds)" builder_die "Re-run without $(builder_term --quick) or disable SecAssessment." fi fi builder_heading "Attempting local deployment with command:" KM4MIM_APP_BASE_PATH="$KM4MIM_BASE_PATH/build/$CONFIG" builder_echo info "$KM4MIM_BASE_PATH/localdeploy.sh \"$KM4MIM_APP_BASE_PATH\"" "$KM4MIM_BASE_PATH/localdeploy.sh" "$KM4MIM_APP_BASE_PATH" } do_publish() { builder_if_release_build_level do_notarize builder_heading "Preparing files for release deployment..." ./setup/build.sh "${KM4MIM_BASE_PATH}/make-km-dmg.sh" local UPLOAD_PATH="${KM4MIM_BASE_PATH}/output/upload/${KEYMAN_VERSION}" write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION_FOR_FILENAME}.dmg" "Keyman4MacIM" dmg mac if builder_is_ci_build && builder_is_ci_build_level_release; then do_sentry fi } ### PROCESS COMMAND-LINE ARGUMENTS ### builder_run_action clean do_clean builder_run_action configure do_configure # Note: `xcodebuild test` rewrites info.plist, so we need to patch the metadata # after testing for use in install/publish, hence the multiple build:engine / # build:app runs builder_run_action build:engine do_build_engine builder_run_action test:engine execBuildCommand $ENGINE_NAME "xcodebuild -project \"$KME4M_PROJECT_PATH\" $BUILD_OPTIONS test -scheme $ENGINE_NAME" builder_run_action build:engine do_update_engine_metadata builder_run_action build:app do_build_app builder_run_action test:app execBuildCommand "$IM_NAME-tests" "xcodebuild test -workspace \"$KMIM_WORKSPACE_PATH\" $CODESIGNING_SUPPRESSION $BUILD_OPTIONS -scheme Keyman SYMROOT=\"$KM4MIM_BASE_PATH/build\"" builder_run_action test:help check-markdown "$KEYMAN_ROOT/mac/docs/help" builder_run_action build:app do_update_app_metadata builder_run_action build:testapp do_build_testapp builder_run_action install do_install builder_run_action publish do_publish