hammerspoon/scripts/build.sh

370 lines
13 KiB
Bash
Executable File

#!/bin/bash
# Hammerspoon build system
# Check if we're in a CI system
export IS_CI=${IS_CI:-0}
# Check if we're doing a nightly build
export IS_NIGHTLY=${IS_NIGHTLY:-0}
# Make it easy to fork us
export APP_NAME="${APP_NAME:-"Hammerspoon"}"
# Set some defaults that we'll override based on command line arguments
XCODE_SCHEME="Hammerspoon"
XCODE_CONFIGURATION="Debug"
XCCONFIG_FILE=""
UPLOAD_DSYM=0
BUILD_FOR_TESTING=0
KEYCHAIN_PROFILE="HAMMERSPOON_BUILDSH"
P12_FILE=""
NOTARIZATION_CREDS_FILE=""
TWITTER_ACCOUNT="_hammerspoon"
DEBUG=0
DOCS_JSON=1
DOCS_MD=1
DOCS_HTML=1
DOCS_SQL=1
DOCS_DASH=1
DOCS_LUASKIN=1
DOCS_LINT_ONLY=0
INSTALLDEPS_FULL=0
# Print out friendly command line usage information
function usage() {
echo "Usage $0 COMMAND [OPTIONS]"
echo "COMMANDS:"
echo " installdeps - Install all ${APP_NAME} build dependencies"
echo " clean - Erase build directory"
echo " build - Build ${APP_NAME}.app"
echo " test - Test ${APP_NAME}.app"
echo " validate - Validate signature/gatekeeper/entitlements"
echo " docs - Build documentation"
echo " keychain-prep - (CI Only) Prepare a new default Keychain with required secrets for signing/notarizing"
echo " keychain-post - (CI Only) Remove Keychain secrets"
echo " notarize - Notarize a ${APP_NAME}.app bundle with Apple (note that it must be signed first)"
echo " archive - Archive the build/notarization artifacts"
echo " release - Perform all the steps to upload a release"
echo ""
echo "GENERAL OPTIONS:"
echo " -h - Show this help"
echo " -d - Enable debugging"
echo ""
echo "BUILD OPTIONS:"
echo " -s - Hammerspoon build scheme (Default: Hammerspoon)"
echo " -c - Hammerspoon build configuration (Default: Debug)"
echo " -x - Use extra build settings from a .xcconfig file (Default: None)"
echo " -u - Upload debug symbols to crash reporting service (Default: No)"
echo " -e - Build for testing"
echo ""
echo "DOCS OPTIONS:"
echo "By default all docs are built. Only one of the following options can be supplied."
echo "If more than one is present, only the last one will have an effect"
echo " -j - Build only JSON documentation"
echo " -m - Build only Markdown documentation"
echo " -t - Build only HTML documentation"
echo " -q - Build only SQLite documentation"
echo " -a - Build only Dash documentation"
echo " -k - Build only LuaSkin documentation"
echo " -l - Only lint docs, don't build anything"
echo ""
echo "INSTALLDEPS OPTIONS:"
echo " -r - Install full dependencies required to complete a public release"
echo ""
echo "KEYCHAIN-PREP OPTIONS:"
echo "Note: This command is primarily for use in CI. For local builds, manually import your Apple signing certificate"
echo " and see the suggested xcrun command in NOTARIZATION OPTIONS below"
echo " -s - Hammerspoon build scheme (Default: Hammerspoon)"
echo " -c - Hammerspoon build configuration (Default: Debug)"
echo " -x - Use extrabuild settings from a .xcconfig file (Default: None)"
echo " -p - Import a .p12 containing the signing certificate (usually issued by Apple)"
echo " -o - Import Notarization credentials file. This should contain your developer"
echo " Apple ID and an App Specific Password for it, in the format:"
echo " NOTARIZATION_USERNAME=\"foo@bar.com\""
echo " NOTARIZATION_PASSWORD=\"abcd-1234-efgh-5678\""
echo " -y - Keychain profile name for notarization credentials (Default: HAMMERSPOON_BUILDSH)"
echo ""
echo "KEYCHAIN-POST OPTIONS:"
echo "Note: This command is primarily for use in CI. You should never run this locally, it will delete your keychain"
echo ""
echo "NOTARIZATION OPTIONS:"
echo "Note: The keychain profile must be set up ahead of time using your developer Apple ID account and Team ID:"
echo " xcrun notarytool store-credentials -v --apple-id APPLE_ID --team-id TEAM_ID --password APP_SPECIFIC_PASSWORD"
echo " -y - Keychain profile name (Default: HAMMERSPOON_BUILDSH)"
echo " -z - Path to a file to notarize (Default: build/Hammerspoon.app.zip"
echo ""
echo "RELEASE OPTIONS:"
echo " -w - Twitter account to announce release with (Default: _hammerspoon)"
echo ""
echo "ENVIRONMENT VARIABLES:"
echo " IS_CI - Set to 1 to enable CI behaviours (Default: 0)"
echo " GITHUB_USER - GitHub user/organization to upload releases to (Default: Hammerspoon)"
echo " GITHUB_REPO - GitHub repository to upload releases to (Default: hammerspoon)"
echo " SENTRY_ORG - Sentry organization to upload debugging symbols to (Default: hammerspoon)"
echo " SENTRY_PROJECT - Sentry project to upload debugging symbols to (Default hammerspoon)"
exit 2
}
# Fetch the COMMAND we should perform
OPERATION=${1:-unknown};shift
if [ "${OPERATION}" == "-h" ] || [ "${OPERATION}" == "--help" ]; then
usage
fi
#if [ "${OPERATION}" != "build" ] && [ "${OPERATION}" != "test" ] && [ "${OPERATION}" != "docs" ] && [ "${OPERATION}" != "installdeps" ] && [ "${OPERATION}" != "notarize" ] && [ "${OPERATION}" != "archive" ] && [ "${OPERATION}" != "release" ] && [ "${OPERATION}" != "clean" ] && [ "${OPERATION}" != "validate" ] && [ "${OPERATION}" != "keychain-prep" ] && [ "${OPERATION}" != "keychain-post" ] ; then
# usage
#fi;
# Parse the rest of any arguments
PARSED_ARGUMENTS=$(getopt ds:c:x:ujmtqakly:z:w:ep:o:r $*)
if [ $? != 0 ]; then
usage
fi
set -- $PARSED_ARGUMENTS
# Translate the parsed arguments into our defaults
for i
do
case "$i" in
-d)
DEBUG=1
shift;;
-s)
XCODE_SCHEME=${2}; shift
shift;;
-c)
XCODE_CONFIGURATION=${2}; shift
shift;;
-x)
XCCONFIG_FILE=${2}; shift
shift;;
-u)
UPLOAD_DSYM=1
shift;;
-e)
BUILD_FOR_TESTING=1
shift;;
-j)
# JSON can be built without any of the others
DOCS_MD=0
DOCS_HTML=0
DOCS_SQL=0
DOCS_DASH=0
DOCS_LUASKIN=0
DOCS_LINT_ONLY=0
shift;;
-m)
# Markdown requires JSON, so leave that enabled
DOCS_HTML=0
DOCS_SQL=0
DOCS_DASH=0
DOCS_LUASKIN=0
DOCS_LINT_ONLY=0
shift;;
-t)
# HTML requires JSON, so leave that enabled
DOCS_MD=0
DOCS_SQL=0
DOCS_DASH=0
DOCS_LUASKIN=0
DOCS_LINT_ONLY=0
shift;;
-q)
# SQLite requires JSON, so leave that enabled
DOCS_MD=0
DOCS_HTML=0
DOCS_DASH=0
DOCS_LUASKIN=0
DOCS_LINT_ONLY=0
shift;;
-a)
# Dash requires JSON, SQLite, LuaSkin and HTML
DOCS_MD=0
DOCS_LINT_ONLY=0
shift;;
-k)
# LuaSkin requires nothing else
DOCS_JSON=0
DOCS_MD=0
DOCS_HTML=0
DOCS_SQL=0
DOCS_DASH=0
DOCS_LINT_ONLY=0
shift;;
-l)
# Linting requires no other docs to be built
DOCS_JSON=0
DOCS_MD=0
DOCS_HTML=0
DOCS_SQL=0
DOCS_DASH=0
DOCS_LINT_ONLY=1
shift;;
-y)
KEYCHAIN_PROFILE=${2}; shift
shift;;
-z)
NOTARIZATION_FILE=${2}; shift
shift;;
-p)
P12_FILE="${2}"; shift
shift;;
-o)
NOTARIZATION_CREDS_FILE="${2}"; shift
shift;;
-w)
TWITTER_ACCOUNT=${2}; shift
shift;;
-r)
INSTALLDEPS_FULL=1
shift;;
--)
shift; break;;
esac
done
# If the user asked for debugging, print out some settings and enable bash tracing
if [ ${DEBUG} == 1 ]; then
echo "OPERATION is: ${OPERATION}"
echo "XCODE_SCHEME is: ${XCODE_SCHEME}"
echo "XCCODE_CONFIGURATION is: ${XCODE_CONFIGURATION}"
echo "XCCONFIG_FILE is: ${XCCONFIG_FILE:-None}"
echo "UPLOAD_DSYM is: ${UPLOAD_DSYM}"
echo "BUILD_FOR_TESTING is: ${BUILD_FOR_TESTING}"
echo "KEYCHAIN_PROFILE is: ${KEYCHAIN_PROFILE}"
echo "DEBUG is: ${DEBUG}"
echo "DOCS_JSON is: ${DOCS_JSON}"
echo "DOCS_MD is: ${DOCS_MD}"
echo "DOCS_HTML is: ${DOCS_HTML}"
echo "DOCS_SQL is: ${DOCS_SQL}"
echo "DOCS_DASH is: ${DOCS_DASH}"
echo "DOCS_LUASKIN is: ${DOCS_LUASKIN}"
echo "DOCS_LINT_ONLY is: ${DOCS_LINT_ONLY}"
# Enable script tracing, with timestamps
#export PS4='+\t '
set -x
fi
# Enable lots of safety
set -eu
set -o pipefail
# Export all the arguments we need later
export XCODE_SCHEME
export XCODE_CONFIGURATION
export XCCONFIG_FILE
export UPLOAD_DSYM
export BUILD_FOR_TESTING
export KEYCHAIN_PROFILE
export TWITTER_ACCOUNT
export DEBUG
export DOCS_JSON
export DOCS_MD
export DOCS_HTML
export DOCS_SQL
export DOCS_DASH
export DOCS_LINT_ONLY
export INSTALLDEPS_FULL
# Early sanity checks that we have everything we need, starting with Xcode as the default Developer path
if [[ "$(xcode-select -p)" != *"Xcode"* ]]; then
echo "ERROR: Your active Developer directory is not pointing at Xcode.app. You will need Xcode to build ${APP_NAME}."
echo "Current Developer path is: $(xcode-select -p)"
echo "You can change this with: sudo xcode-select -s /path/to/Xcode.app"
exit 1
fi
# Check for greadlink
export PATH="$PATH:/opt/homebrew/bin"
if [ "$(which greadlink)" == "" ]; then
echo "ERROR: Unable to find greadlink. Please run \"brew install coreutils\" and then \"$0 installdeps\""
exit 1
fi
# This silly which dancing is to ensure we don't trip over a zsh alias for 'grm' to 'git rm'
export RM ; RM="$(which -a grm | grep -v aliased | head -1) --one-file-system --preserve-root"
# Calculate some variables we need later
echo "Gathering info..."
export SCRIPT_NAME ; SCRIPT_NAME="$(basename "$0")"
export SCRIPT_HOME ; SCRIPT_HOME="$(dirname "$(greadlink -f "$0")")"
export HAMMERSPOON_HOME ; HAMMERSPOON_HOME="$(greadlink -f "${SCRIPT_HOME}/../")"
export WEBSITE_HOME ; WEBSITE_HOME="$(greadlink -f "${HAMMERSPOON_HOME}/../website")"
export BUILD_HOME="${HAMMERSPOON_HOME}/build"
export CI_ARTIFACTS_HOME="${HAMMERSPOON_HOME}/artifacts"
export HAMMERSPOON_BUNDLE_NAME="${APP_NAME}.app"
export HAMMERSPOON_BUNDLE_PATH="${BUILD_HOME}/${HAMMERSPOON_BUNDLE_NAME}"
export HAMMERSPOON_XCARCHIVE_PATH="${HAMMERSPOON_BUNDLE_PATH}.xcarchive"
export XCODE_BUILT_PRODUCTS_DIR ; XCODE_BUILT_PRODUCTS_DIR="$(xcodebuild -workspace Hammerspoon.xcworkspace -scheme "${XCODE_SCHEME}" -configuration "${XCODE_CONFIGURATION}" -destination "platform=macOS" -showBuildSettings | sort | uniq | grep ' BUILT_PRODUCTS_DIR =' | awk '{ print $3 }')"
export DOCS_SEARCH_DIRS=("Hammerspoon" "extensions/")
# Calculate private token variables
export TOKENPATH ; TOKENPATH="$(greadlink -f "${HAMMERSPOON_HOME}/..")"
export GITHUB_TOKEN_FILE="${TOKENPATH}/token-github-release"
export GITHUB_USER="${GITHUB_USER:-hammerspoon}"
export GITHUB_REPO="${GITHUB_REPO:-hammerspoon}"
export SENTRY_TOKEN_API_FILE="${TOKENPATH}/token-sentry-api"
export SENTRY_TOKEN_AUTH_FILE="${TOKENPATH}/token-sentry-auth"
export NOTARIZATION_TOKEN_FILE="${TOKENPATH}/token-notarization"
export NOTARIZATION_FILE="${NOTARIZATION_FILE:-}"
# Calculate options for xcbeautify
export XCB_OPTS=(-q)
if [ "${IS_CI}" == "1" ] || [ "${DEBUG}" == "1" ]; then
XCB_OPTS=()
fi
# Import our function library
# shellcheck source=scripts/libbuild.sh disable=SC1091
source "${SCRIPT_HOME}/libbuild.sh"
# Make sure our build directory exists
mkdir -p "${BUILD_HOME}"
if [ "${IS_CI}" == "1" ]; then
mkdir -p "${CI_ARTIFACTS_HOME}"
fi
# Figure out which COMMAND we have been tasked with performing, and go do it
case "${OPERATION}" in
"clean")
op_clean
;;
"build")
op_build
;;
"test")
op_test
;;
"validate")
op_validate
;;
"docs")
op_docs
;;
"installdeps")
op_installdeps
;;
"keychain-prep")
op_keychain_prep
;;
"keychain-post")
op_keychain_post
;;
"notarize")
op_notarize
;;
"archive")
op_archive
;;
"release")
op_release
;;
*)
echo "Unknown command: ${OPERATION}"
usage
;;
esac
exit 0