mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-22 20:23:04 +00:00
df02aae263
Also, update the Xcode versions for CI builds.
850 lines
32 KiB
Bash
Executable file
850 lines
32 KiB
Bash
Executable file
#!/bin/bash
|
|
# vim: tw=100:
|
|
|
|
# This file is part of Background Music.
|
|
#
|
|
# Background Music is free software: you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation, either version 2 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# Background Music is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Background Music. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
# build_and_install.sh
|
|
#
|
|
# Copyright © 2016-2020 Kyle Neideck
|
|
# Copyright © 2016 Nick Jacques
|
|
#
|
|
# Builds and installs BGMApp, BGMDriver and BGMXPCHelper. Requires xcodebuild and Xcode.
|
|
#
|
|
# Don't let the length of this script scare you away from building/installing without it (using
|
|
# either Xcode or xcodebuild). 90% of this code is for error handling, logging, user friendliness,
|
|
# etc. See MANUAL-INSTALL.md, DEVELOPING.md and BGMDriver/BGMDriver/quick_install.sh.
|
|
#
|
|
|
|
# Safe mode
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
# Subshells and functions inherit the ERR trap
|
|
set -o errtrace
|
|
|
|
# Go to the project directory.
|
|
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
|
|
|
error_handler() {
|
|
LAST_COMMAND="$3" LAST_COMMAND_EXIT_STATUS="$2"
|
|
|
|
# Log the error.
|
|
echo "Failure in $0 at line $1. The last command was (probably)" >> ${LOG_FILE}
|
|
echo " ${LAST_COMMAND}" >> ${LOG_FILE}
|
|
echo "which exited with status ${LAST_COMMAND_EXIT_STATUS}." >> ${LOG_FILE}
|
|
echo "Error message: ${ERROR_MSG}" >> ${LOG_FILE}
|
|
echo >> ${LOG_FILE}
|
|
|
|
# Scrub username from log (and also real name just in case).
|
|
sed -i'tmp' "s/$(whoami)/[username removed]/g" ${LOG_FILE}
|
|
sed -i'tmp' "s/$(id -F)/[name removed]/g" ${LOG_FILE}
|
|
rm "${LOG_FILE}tmp"
|
|
|
|
# Print an error message.
|
|
echo "$(tput setaf 9)ERROR$(tput sgr0): Install failed at line $1 with the message:" >&2
|
|
echo -e "${ERROR_MSG}" >&2
|
|
echo >&2
|
|
echo "Feel free to report this. If you do, you'll probably want to include the" \
|
|
"build_and_install.log file from this directory ($(pwd)). But quickly skim through it" \
|
|
"first to check that it doesn't include any personal information. It shouldn't, but this" \
|
|
"is alpha software so you never know." >&2
|
|
echo >&2
|
|
echo "To try building and installing without this build script, see MANUAL-INSTALL.md." >&2
|
|
echo >&2
|
|
echo "You can also try ignoring compiler warnings with: $0 -w" >&2
|
|
|
|
echo >&2
|
|
echo "Error details:" >&2
|
|
echo "Line $1. The last command was (probably)" >&2
|
|
echo " ${LAST_COMMAND}" >&2
|
|
echo "which exited with status ${LAST_COMMAND_EXIT_STATUS}." >&2
|
|
echo >&2
|
|
|
|
# Finish logging debug info if the script fails early.
|
|
if ! [[ -z ${LOG_DEBUG_INFO_TASK_PID:-} ]]; then
|
|
wait ${LOG_DEBUG_INFO_TASK_PID}
|
|
fi
|
|
}
|
|
|
|
enable_error_handling() {
|
|
if [[ -z ${CONTINUE_ON_ERROR} ]] || [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then
|
|
set -e
|
|
# TODO: The version of Bash that ships with OSX only gives you the line number of the
|
|
# function the error occurred in -- not the line the error occurred on. There are a
|
|
# few solutions suggested on various websites, but none of them work.
|
|
trap 'error_handler ${LINENO} $? "${BASH_COMMAND}"' ERR
|
|
fi
|
|
}
|
|
|
|
disable_error_handling() {
|
|
set +e
|
|
trap - ERR
|
|
}
|
|
|
|
# Build for release by default. Use -d for a debug build.
|
|
CONFIGURATION=Release
|
|
#CONFIGURATION=Debug
|
|
|
|
XCODEBUILD_ACTION="install"
|
|
|
|
# The default is to clean before installing because we want the log file to have roughly the same
|
|
# information after every build.
|
|
CLEAN=clean
|
|
|
|
XCODEBUILD_OPTIONS=""
|
|
|
|
CONTINUE_ON_ERROR=0
|
|
|
|
# Update .gitignore if you change this.
|
|
LOG_FILE=build_and_install.log
|
|
|
|
# Empty the log file
|
|
echo -n > ${LOG_FILE}
|
|
|
|
COREAUDIOD_PLIST="/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist"
|
|
|
|
# Output locations for installing. These are overwritten later if building or archiving.
|
|
#
|
|
# TODO: Should (can?) we use xcodebuild to get these from the Xcode project rather than duplicating
|
|
# them?
|
|
APP_PATH="/Applications"
|
|
APP_DIR="Background Music.app"
|
|
DRIVER_PATH="/Library/Audio/Plug-Ins/HAL"
|
|
DRIVER_DIR="Background Music Device.driver"
|
|
# XPC_HELPER_OUTPUT_PATH is set below because it depends on the system (when installing).
|
|
XPC_HELPER_DIR="BGMXPCHelper.xpc"
|
|
|
|
# The root output directory when archiving.
|
|
ARCHIVES_DIR="archives"
|
|
|
|
GENERAL_ERROR_MSG="Internal script error. Probably a bug in this script."
|
|
BUILD_FAILED_ERROR_MSG="A build command failed. Probably a compilation error."
|
|
BGMAPP_FAILED_TO_START_ERROR_MSG="Background Music (${APP_PATH}/${APP_DIR}) didn't seem to start \
|
|
up. It might just be taking a while.
|
|
|
|
If it didn't install correctly, you'll need to open the Sound control panel in System Preferences \
|
|
and change your output device at least once. Your sound probably won't work until you do. (Or you \
|
|
restart your computer.)
|
|
|
|
If you only have one device, you can create a temporary one by opening \
|
|
\"/Applications/Utilities/Audio MIDI Setup.app\", clicking the plus button and choosing \"Create \
|
|
Multi-Output Device\"."
|
|
ERROR_MSG="${GENERAL_ERROR_MSG}"
|
|
|
|
XCODEBUILD="/usr/bin/xcodebuild"
|
|
if ! [[ -x "${XCODEBUILD}" ]]; then
|
|
XCODEBUILD=$(which xcodebuild || true)
|
|
fi
|
|
# This check is last because it takes 10 seconds or so if it fails.
|
|
if ! [[ -x "${XCODEBUILD}" ]]; then
|
|
XCODEBUILD="$(/usr/bin/xcrun --find xcodebuild 2>>${LOG_FILE} || true)"
|
|
fi
|
|
|
|
RECOMMENDED_MIN_XCODE_VERSION=8
|
|
|
|
usage() {
|
|
echo "Usage: $0 [options]" >&2
|
|
echo -e "\t-n Don't clean before building/installing." >&2
|
|
echo -e "\t-d Debug build. (Release is the default.)" >&2
|
|
echo -e "\t-a Build and archive, don't install. See Xcode docs for info about archiving." >&2
|
|
echo -e "\t-b Build only, don't install." >&2
|
|
echo -e "\t-w Ignore compiler warnings. (They're treated as errors by default.)" >&2
|
|
echo -e "\t-x [options] Extra options to pass to xcodebuild." >&2
|
|
echo -e "\t-c Continue on script errors. Might not be safe." >&2
|
|
echo -e "\t-h Print this usage statement." >&2
|
|
exit 1
|
|
}
|
|
|
|
bold_face() {
|
|
echo $(tput bold)$*$(tput sgr0)
|
|
}
|
|
|
|
# Takes a PID and returns 0 if the process is running.
|
|
is_alive() {
|
|
kill -0 $1 > /dev/null 2>&1 && return 0 || return 1
|
|
}
|
|
|
|
# Shows a "..." animation until the previous command finishes. Shows an error message and exits the
|
|
# script if the command fails. The return value will be the exit status of the command.
|
|
#
|
|
# Params:
|
|
# - The error message to show if the previous command fails.
|
|
# - An optional timeout in seconds.
|
|
show_spinner() {
|
|
disable_error_handling
|
|
|
|
local PREV_COMMAND_PID=$!
|
|
|
|
# Get the previous command as a string, with variables resolved. Assumes that if the command has
|
|
# a child process we just want the text of the child process's command. (And that it only has
|
|
# one child.)
|
|
local CHILD_PID=$(pgrep -P ${PREV_COMMAND_PID} | head -n1 || echo ${PREV_COMMAND_PID})
|
|
local PREV_COMMAND_STRING=$(ps -o command= ${CHILD_PID})
|
|
local TIMEOUT=${2:-0}
|
|
|
|
exec 3>&1 # Creates an alias so the following subshell can print to stdout.
|
|
DID_TIMEOUT=$(
|
|
I=1
|
|
while (is_alive ${PREV_COMMAND_PID}) && \
|
|
([[ ${TIMEOUT} -lt 1 ]] || [[ $I -lt ${TIMEOUT} ]])
|
|
do
|
|
printf '.' >&3
|
|
sleep 1
|
|
# Erase after we've printed three dots. (\b is backspace.)
|
|
[[ $((I % 3)) -eq 0 ]] && printf '\b\b\b \b\b\b' >&3
|
|
((I++))
|
|
done
|
|
if [[ $I -eq ${TIMEOUT} ]]; then
|
|
kill ${PREV_COMMAND_PID} >> ${LOG_FILE} 2>&1
|
|
echo 1
|
|
else
|
|
echo 0
|
|
fi)
|
|
exec 3<&- # Close the file descriptor.
|
|
|
|
wait ${PREV_COMMAND_PID}
|
|
local EXIT_STATUS=$?
|
|
|
|
# Clean up the dots.
|
|
printf '\b\b\b \b\b\b'
|
|
|
|
# Print an error message if the command fails.
|
|
# (wait returns 127 if the process has already exited.)
|
|
if [[ ${EXIT_STATUS} -ne 0 ]] && [[ ${EXIT_STATUS} -ne 127 ]]; then
|
|
ERROR_MSG="$1"
|
|
if [[ ${DID_TIMEOUT} -ne 0 ]]; then
|
|
ERROR_MSG+="\n\nCommand timed out after ${TIMEOUT} seconds."
|
|
fi
|
|
|
|
error_handler ${LINENO} ${EXIT_STATUS} "${PREV_COMMAND_STRING}"
|
|
|
|
if [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then
|
|
exit ${EXIT_STATUS}
|
|
fi
|
|
fi
|
|
|
|
enable_error_handling
|
|
|
|
return ${EXIT_STATUS}
|
|
}
|
|
|
|
parse_options() {
|
|
while getopts ":ndabwx:ch" opt; do
|
|
case $opt in
|
|
n)
|
|
CLEAN=""
|
|
;;
|
|
d)
|
|
CONFIGURATION="Debug"
|
|
;;
|
|
a)
|
|
# The "archive" action makes a build for distribution. It's the same as the archive
|
|
# option in Xcode. It won't install.
|
|
XCODEBUILD_ACTION="archive"
|
|
# The dirs xcodebuild will put the archives in.
|
|
APP_PATH="$ARCHIVES_DIR/BGMApp.xcarchive"
|
|
XPC_HELPER_OUTPUT_PATH="$ARCHIVES_DIR/BGMXPCHelper.xcarchive"
|
|
DRIVER_PATH="$ARCHIVES_DIR/BGMDriver.xcarchive"
|
|
;;
|
|
b)
|
|
# Just build; don't install.
|
|
XCODEBUILD_ACTION="build"
|
|
# The dirs xcodebuild will build in.
|
|
# TODO: If these dirs were created by running this script without -b, they'll be
|
|
# owned by root and xcodebuild will fail.
|
|
APP_PATH="./BGMApp/build"
|
|
XPC_HELPER_OUTPUT_PATH="./BGMApp/build"
|
|
DRIVER_PATH="./BGMDriver/build"
|
|
;;
|
|
w)
|
|
# TODO: What if they also pass their own OTHER_CFLAGS with -x?
|
|
XCODEBUILD_OPTIONS="${XCODEBUILD_OPTIONS} OTHER_CFLAGS=\"-Wno-error\""
|
|
;;
|
|
x)
|
|
XCODEBUILD_OPTIONS="$OPTARG"
|
|
;;
|
|
c)
|
|
CONTINUE_ON_ERROR=1
|
|
echo "$(tput setaf 11)WARNING$(tput sgr0): Ignoring errors."
|
|
disable_error_handling
|
|
;;
|
|
h)
|
|
usage
|
|
;;
|
|
\?)
|
|
echo "Invalid option: -$OPTARG" >&2
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# check_xcode return codes
|
|
CHECK_XCODE_NO_ERR=0
|
|
CHECK_XCODE_ERR_NO_CLTOOLS=1
|
|
CHECK_XCODE_ERR_NO_XCODE=2
|
|
CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE=3
|
|
CHECK_XCODE_ERR_LICENSE_NOT_ACCEPTED=4
|
|
CHECK_XCODE_ERR_OLD_VERSION=5
|
|
|
|
# Checks if $XCODEBUILD is a usable xcodebuild. Exits with one of the status codes above.
|
|
check_xcode() {
|
|
local EXIT_CODE=${CHECK_XCODE_NO_ERR}
|
|
|
|
# First, check xcodebuild exists on the system an is an executable.
|
|
if ! [[ -x "${XCODEBUILD}" ]] || ! /usr/bin/xcode-select --print-path &>/dev/null || \
|
|
(! [[ -e /Library/Developer/CommandLineTools/usr/bin/git ]] && \
|
|
! pkgutil --pkg-info=com.apple.pkg.CLTools_Executables &>/dev/null); then
|
|
EXIT_CODE=${CHECK_XCODE_ERR_NO_CLTOOLS}
|
|
fi
|
|
|
|
# Check that Xcode is installed, not just the command line tools.
|
|
if [[ "${XCODE_VERSION}" == "-1" ]]; then
|
|
((EXIT_CODE+=2))
|
|
fi
|
|
|
|
# Check they've already accepted the Xcode license. This code is mostly copied from
|
|
# Homebrew/Library/Homebrew/brew.sh.
|
|
disable_error_handling
|
|
|
|
local XCRUN_OUTPUT # (Declared local before assigning so we can get $?.)
|
|
XCRUN_OUTPUT="$(/usr/bin/xcrun clang 2>&1)"
|
|
local XCRUN_STATUS="$?"
|
|
if [[ ${EXIT_CODE} -eq 0 ]] && \
|
|
[[ "${XCRUN_STATUS}" -ne 0 ]] && \
|
|
( [[ "${XCRUN_OUTPUT}" = *license* ]] || [[ "${XCRUN_OUTPUT}" = *licence* ]] ); then
|
|
EXIT_CODE=${CHECK_XCODE_ERR_LICENSE_NOT_ACCEPTED}
|
|
fi
|
|
|
|
enable_error_handling
|
|
|
|
# Version check.
|
|
local XCODE_MAJOR_VERSION="$(echo ${XCODE_VERSION} | sed 's/\..*$//g')"
|
|
if [[ ${EXIT_CODE} -eq 0 ]] && \
|
|
[[ "${XCODE_MAJOR_VERSION}" -lt ${RECOMMENDED_MIN_XCODE_VERSION} ]]
|
|
then
|
|
EXIT_CODE=${CHECK_XCODE_ERR_OLD_VERSION}
|
|
fi
|
|
|
|
exit ${EXIT_CODE}
|
|
}
|
|
|
|
# Expects CHECK_XCODE_TASK_PID to be set and error handling to be disabled. Returns 1 if we need to
|
|
# check for Xcode again.
|
|
handle_check_xcode_result() {
|
|
if [[ -z ${HANDLED_CHECK_XCODE_RESULT:-} ]]; then
|
|
HANDLED_CHECK_XCODE_RESULT=1
|
|
# Wait for the Xcode checks to finish.
|
|
wait ${CHECK_XCODE_TASK_PID}
|
|
CHECK_XCODE_TASK_STATUS=$?
|
|
|
|
# If there was a problem with Xcode/xcodebuild, print the error message and exit.
|
|
if [[ ${CHECK_XCODE_TASK_STATUS} -ne ${CHECK_XCODE_NO_ERR} ]]; then
|
|
handle_check_xcode_failure ${CHECK_XCODE_TASK_STATUS}
|
|
local STATUS=$?
|
|
return ${STATUS}
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Returns 1 if we need to check for Xcode again. Expects error handling to be disabled.
|
|
#
|
|
# Params:
|
|
# - The exit code of check_xcode.
|
|
handle_check_xcode_failure() {
|
|
local CONTINUE=${CONTINUE_ON_ERROR}
|
|
|
|
# No command line tools
|
|
if [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS} ]] || \
|
|
[[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE} ]]; then
|
|
echo "$(tput setaf 9)ERROR$(tput sgr0): The Xcode Command Line Tools don't seem to be" \
|
|
"installed on your system." >&2
|
|
echo >&2
|
|
echo "If you have Xcode installed, you should be able to install them with" >&2
|
|
echo " sudo /usr/bin/xcode-select --install" >&2
|
|
echo "If not, you'll need to install Xcode (~9GB), because xcodebuild no longer works" \
|
|
"without it." >&2
|
|
echo >&2
|
|
fi
|
|
|
|
# No Xcode (that the command line tools are aware of)
|
|
if [[ $1 -eq ${CHECK_XCODE_ERR_NO_XCODE} ]] || \
|
|
[[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE} ]]; then
|
|
if ! handle_check_xcode_failure_no_xcode $1; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# They need to agree to the Xcode license.
|
|
if [[ $1 -eq ${CHECK_XCODE_ERR_LICENSE_NOT_ACCEPTED} ]]; then
|
|
echo "$(tput setaf 9)ERROR$(tput sgr0): You need to agree to the Xcode license before you" \
|
|
"can build Background Music. Run this command and then try again:" >&2
|
|
echo " sudo ${XCODEBUILD} -license" >&2
|
|
echo >&2
|
|
fi
|
|
|
|
# Xcode version is probably too old.
|
|
if [[ $1 -eq ${CHECK_XCODE_ERR_OLD_VERSION} ]]; then
|
|
echo "$(tput setaf 11)WARNING$(tput sgr0): Your version of Xcode (${XCODE_VERSION}) may" \
|
|
"not be recent enough to build Background Music. If you have a newer version" \
|
|
"installed, you can set the Xcode command line tools to use it with" >&2
|
|
echo " sudo /usr/bin/xcode-select --switch /the/path/to/your/Xcode.app" >&2
|
|
echo >&2
|
|
CONTINUE=1
|
|
fi
|
|
|
|
# Try to find Xcode and print a more useful error message.
|
|
if [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS} ]] || \
|
|
[[ $1 -eq ${CHECK_XCODE_ERR_NO_XCODE} ]] || \
|
|
[[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE} ]] || \
|
|
[[ $1 -eq ${CHECK_XCODE_ERR_OLD_VERSION} ]]; then
|
|
echo "Searching for Xcode installations..." >&2
|
|
echo >&2
|
|
XCODE_PATHS=$(mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode' || \
|
|
kMDItemCFBundleIdentifier == 'com.apple.Xcode'")
|
|
|
|
if [[ "${XCODE_PATHS}" != "" ]]; then
|
|
echo "It looks like you have Xcode installed to:" >&2
|
|
echo "${XCODE_PATHS}" >&2
|
|
else
|
|
echo "None found." >&2
|
|
fi
|
|
fi
|
|
|
|
# Exit with an error status, unless we only printed a warning or were told to continue anyway.
|
|
if [[ ${CONTINUE} -eq 0 ]]; then
|
|
exit $1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Returns 1 if we need to check for Xcode again. Expects error handling to be disabled.
|
|
#
|
|
# Params:
|
|
# - The exit code of check_xcode.
|
|
handle_check_xcode_failure_no_xcode() {
|
|
# The problem could be that they have Xcode installed, but the command line tools can't find
|
|
# it.
|
|
|
|
# Just check for Xcode in the default location at first, since searching with mdfind can
|
|
# take a while.
|
|
if [[ $1 -eq ${CHECK_XCODE_ERR_NO_XCODE} ]] && [[ -d /Applications/Xcode.app ]]; then
|
|
echo "It looks like you have Xcode installed to /Applications/Xcode.app, but the" \
|
|
"Xcode command line tools aren't set to use it. Try" >&2
|
|
echo " sudo /usr/bin/xcode-select --switch /Applications/Xcode.app" >&2
|
|
echo "and then run this installer again." >&2
|
|
else
|
|
echo "$(tput setaf 9)ERROR$(tput sgr0): Unfortunately, Xcode (~9GB) is required to" \
|
|
"build Background Music, but ${XCODEBUILD} doesn't appear to be usable. If you" \
|
|
"do have Xcode installed, try telling the Xcode command line tools where to find" \
|
|
"it with" >&2
|
|
echo " sudo /usr/bin/xcode-select --switch /the/path/to/your/Xcode.app" >&2
|
|
echo "and then running this installer again." >&2
|
|
fi
|
|
echo >&2
|
|
|
|
# Explain how to revert the command we suggested.
|
|
local DEV_DIR_PATH # (Declared local before assigning so we can get $?.)
|
|
DEV_DIR_PATH=$(/usr/bin/xcode-select --print-path 2>/dev/null)
|
|
local XCSELECT_STATUS="$?"
|
|
if [[ "${XCSELECT_STATUS}" -eq 0 ]] && [[ "${DEV_DIR_PATH}" != "" ]]; then
|
|
echo "If you want to change it back afterwards for some reason, use" >&2
|
|
echo " sudo /usr/bin/xcode-select --switch ${DEV_DIR_PATH}" >&2
|
|
fi
|
|
echo >&2
|
|
|
|
# Print the error message from xcodebuild.
|
|
local OUTPUT_MSG="Output from ${XCODEBUILD}: ------------------------------------------"
|
|
echo "${OUTPUT_MSG}" >&2
|
|
"${XCODEBUILD}" -version >&2 || true
|
|
echo "${OUTPUT_MSG}" | tr '[:print:]' - >&2
|
|
echo >&2
|
|
|
|
# Offer to switch if it will probably work.
|
|
if [[ -d /Applications/Xcode.app ]]; then
|
|
local SWITCH_CMD="sudo xcode-select --switch /Applications/Xcode.app"
|
|
read -p "Try \"$(bold_face ${SWITCH_CMD})\" now (y/N)? " TRY_SWITCH
|
|
if [[ "${TRY_SWITCH}" == "y" ]] || [[ "${TRY_SWITCH}" == "Y" ]]; then
|
|
echo
|
|
echo "+ ${SWITCH_CMD}"
|
|
eval ${SWITCH_CMD}
|
|
local XCS_STATUS=$?
|
|
if [[ ${XCS_STATUS} -eq 0 ]]; then
|
|
# Return that we should call check_xcode again to see if it worked.
|
|
echo
|
|
echo Seemed to work. Trying to start installation again...
|
|
echo
|
|
return 1
|
|
else
|
|
echo >&2
|
|
echo "\"$(bold_face ${SWITCH_CMD})\" failed with exit status ${XCS_STATUS}." >&2
|
|
exit ${XCS_STATUS}
|
|
fi
|
|
fi
|
|
echo
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
log_debug_info() {
|
|
# Log some environment details, version numbers, etc. This takes a while, so we do it in the
|
|
# background.
|
|
|
|
(disable_error_handling
|
|
echo "Background Music Build Log" >> ${LOG_FILE}
|
|
echo "----" >> ${LOG_FILE}
|
|
echo "Build script args: $*" >> ${LOG_FILE}
|
|
echo "System details:" >> ${LOG_FILE}
|
|
|
|
sw_vers >> ${LOG_FILE} 2>&1
|
|
# The same as uname -a, except without printing the nodename (for privacy).
|
|
uname -mrsv >> ${LOG_FILE} 2>&1
|
|
|
|
/bin/bash --version >> ${LOG_FILE} 2>&1
|
|
/usr/bin/env python --version >> ${LOG_FILE} 2>&1
|
|
|
|
echo "On git branch: $(git rev-parse --abbrev-ref HEAD 2>&1)" >> ${LOG_FILE}
|
|
echo "Most recent commit: $(git rev-parse HEAD 2>&1)" \
|
|
"(\"$(git show -s --format=%s HEAD 2>&1)\")" >> ${LOG_FILE}
|
|
|
|
echo "Using xcodebuild: ${XCODEBUILD}" >> ${LOG_FILE}
|
|
echo "Using BGMXPCHelper output path: ${XPC_HELPER_OUTPUT_PATH}" >> ${LOG_FILE}
|
|
|
|
xcode-select --version >> ${LOG_FILE} 2>&1
|
|
echo "Xcode path: $(xcode-select --print-path 2>&1)" >> ${LOG_FILE}
|
|
echo "Xcode version:" >> ${LOG_FILE}
|
|
xcodebuild -version >> ${LOG_FILE} 2>&1
|
|
echo "Xcode SDKs:" >> ${LOG_FILE}
|
|
xcodebuild -showsdks >> ${LOG_FILE} 2>&1
|
|
xcrun --version >> ${LOG_FILE} 2>&1
|
|
echo "Clang version:" >> ${LOG_FILE}
|
|
$(/usr/bin/xcrun --find clang 2>&1) --version >> ${LOG_FILE} 2>&1
|
|
|
|
echo "launchctl version: $(launchctl version 2>&1)" >> ${LOG_FILE}
|
|
echo "----" >> ${LOG_FILE}) &
|
|
|
|
LOG_DEBUG_INFO_TASK_PID=$!
|
|
}
|
|
|
|
# Cleans the build products and intermediate files for a build scheme.
|
|
#
|
|
# Params:
|
|
# - The Xcode build scheme to clean, e.g. "Background Music Device".
|
|
clean() {
|
|
if [[ "${CLEAN}" != "" ]]; then
|
|
${SUDO} "${XCODEBUILD}" -scheme "$1" \
|
|
-configuration ${CONFIGURATION} \
|
|
BUILD_DIR=./build \
|
|
${CLEAN} >> ${LOG_FILE} 2>&1
|
|
fi
|
|
}
|
|
|
|
# Register our handler so we can print a message and clean up if there's an error.
|
|
enable_error_handling
|
|
|
|
parse_options "$@"
|
|
|
|
# Warn if running as root.
|
|
if [[ $(id -u) -eq 0 ]]; then
|
|
echo "$(tput setaf 11)WARNING$(tput sgr0): This script is not intended to be run as root. Run" \
|
|
"it normally and it'll sudo when it needs to." >&2
|
|
fi
|
|
|
|
# Print initial message.
|
|
if [[ "${XCODEBUILD_ACTION}" == "install" ]]; then
|
|
XPC_HELPER_OUTPUT_PATH="$(BGMApp/BGMXPCHelper/safe_install_dir.sh)"
|
|
|
|
echo "$(bold_face About to install Background Music). Please pause all audio, if you can."
|
|
[[ "${CONFIGURATION}" == "Debug" ]] && echo "Debug build."
|
|
echo
|
|
echo "This script will install:"
|
|
echo " - ${APP_PATH}/${APP_DIR}"
|
|
echo " - ${DRIVER_PATH}/${DRIVER_DIR}"
|
|
echo " - ${XPC_HELPER_OUTPUT_PATH}/${XPC_HELPER_DIR}"
|
|
echo " - /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
|
|
echo
|
|
elif [[ "${XCODEBUILD_ACTION}" == "archive" ]]; then
|
|
echo "$(bold_face Building and archiving Background Music...)"
|
|
echo
|
|
else
|
|
echo "$(bold_face Building Background Music...)"
|
|
echo
|
|
fi
|
|
|
|
# Make sure Xcode and the command line tools are installed and recent enough.
|
|
# This sets XCODE_VERSION to major.minor, e.g. 8.3, or -1 if Xcode isn't installed.
|
|
XCODE_VERSION=$((${XCODEBUILD} -version 2>/dev/null || echo 'V -1') | head -n 1 | awk '{ print $2 }')
|
|
check_xcode &
|
|
CHECK_XCODE_TASK_PID=$!
|
|
|
|
if [[ "${XCODEBUILD_ACTION}" == "install" ]]; then
|
|
read -p "Continue (y/N)? " CONTINUE_INSTALLATION
|
|
|
|
if [[ "${CONTINUE_INSTALLATION}" != "y" ]] && [[ "${CONTINUE_INSTALLATION}" != "Y" ]]; then
|
|
echo "Installation cancelled."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# If the check_xcode process has already finished, we can check the result early.
|
|
NEED_TO_HANDLE_CHECK_XCODE_RESULT=1
|
|
if ! is_alive ${CHECK_XCODE_TASK_PID}; then
|
|
disable_error_handling
|
|
|
|
handle_check_xcode_result
|
|
NEED_TO_HANDLE_CHECK_XCODE_RESULT=$?
|
|
|
|
enable_error_handling
|
|
fi
|
|
|
|
# Update the user's sudo timestamp if we're going to need to sudo at some point. This prompts the
|
|
# user for their password.
|
|
if [[ "${XCODEBUILD_ACTION}" == "install" ]]; then
|
|
# Don't call sudo -v if this is a Travis CI build.
|
|
if ([[ -z ${TRAVIS:-} ]] || [[ "${TRAVIS}" != true ]]) && ! sudo -v; then
|
|
echo "$(tput setaf 9)ERROR$(tput sgr0): This script must be run by a user with" \
|
|
"administrator (sudo) privileges." >&2
|
|
exit 1
|
|
fi
|
|
echo
|
|
fi
|
|
|
|
while [[ ${NEED_TO_HANDLE_CHECK_XCODE_RESULT} -ne 0 ]]; do
|
|
disable_error_handling
|
|
|
|
handle_check_xcode_result
|
|
NEED_TO_HANDLE_CHECK_XCODE_RESULT=$?
|
|
|
|
enable_error_handling
|
|
done
|
|
|
|
log_debug_info "$@"
|
|
|
|
# Set some variables that control the compilation commands below.
|
|
if [[ "${XCODEBUILD_ACTION}" == "install" ]]; then
|
|
SUDO="sudo"
|
|
ACTIONING="Installing"
|
|
DSTROOT_ARG="DSTROOT=/"
|
|
elif [[ "${XCODEBUILD_ACTION}" == "archive" ]]; then
|
|
SUDO=""
|
|
ACTIONING="Building and archiving"
|
|
DSTROOT_ARG=""
|
|
else
|
|
# No need to sudo if we're only building.
|
|
SUDO=""
|
|
ACTIONING="Building"
|
|
DSTROOT_ARG=""
|
|
fi
|
|
|
|
# Enable AddressSanitizer in debug builds to catch memory bugs. Allow ENABLE_ASAN to be set as an
|
|
# environment variable by only setting it here if it isn't already set. (Used by package.sh.)
|
|
if [[ "${CONFIGURATION}" == "Debug" ]]; then
|
|
ENABLE_ASAN="${ENABLE_ASAN:-YES}"
|
|
else
|
|
ENABLE_ASAN="${ENABLE_ASAN:-NO}"
|
|
fi
|
|
|
|
enableUBSanArg() {
|
|
if [[ "${ENABLE_UBSAN+}" != "" ]]; then
|
|
echo "-enableUndefinedBehaviorSanitizer"
|
|
echo "$ENABLE_UBSAN"
|
|
fi
|
|
}
|
|
|
|
# Clean all projects. Done separately to workaround what I think is a bug in Xcode 10.0. If you just
|
|
# add "clean" to the other xcodebuild commands, they seem to fail because of the DSTROOT="/" arg.
|
|
if [[ "${CLEAN}" != "" ]]; then
|
|
# Disable the -e shell option and error trap for build commands so we can handle errors
|
|
# differently.
|
|
(disable_error_handling
|
|
clean "Background Music Device"
|
|
clean "PublicUtility"
|
|
clean "BGMXPCHelper"
|
|
clean "Background Music"
|
|
# Also delete the build dirs as files/dirs left in them can make the install step fail and,
|
|
# if you're using Xcode 10, the commands above will have cleaned the DerivedData dir but not
|
|
# the build dirs. I think this is a separate Xcode bug. See
|
|
# <http://www.openradar.me/40906897>.
|
|
${SUDO} /bin/rm -rf BGMDriver/build BGMApp/build >> ${LOG_FILE} 2>&1) &
|
|
|
|
echo "Cleaning"
|
|
show_spinner "Clean command failed. Try deleting the directories BGMDriver/build and \
|
|
BGMApp/build manually and running '$0 -n' to skip the cleaning step."
|
|
fi
|
|
|
|
# Prints the -archivePath option if we're archiving (i.e. making a .xcarchive). Does nothing if not.
|
|
# Params:
|
|
# - The name for the archive. The .xcarchive extension will be added.
|
|
archivePath() {
|
|
if [[ "${XCODEBUILD_ACTION}" == "archive" ]]; then
|
|
echo "-archivePath"
|
|
echo "$ARCHIVES_DIR/$1"
|
|
fi
|
|
}
|
|
|
|
# Prints the INSTALL_OWNER and INSTALL_GROUP arguments to use for the xcodebuild commands.
|
|
ownershipArgs() {
|
|
if [[ "${XCODEBUILD_ACTION}" != "install" ]]; then
|
|
# Stop xcodebuild from trying to chown the files in the archive to root when making an
|
|
# archive, so we don't need to use sudo.
|
|
echo "INSTALL_OWNER="
|
|
echo "INSTALL_GROUP="
|
|
fi
|
|
}
|
|
|
|
# BGMDriver
|
|
|
|
echo "[1/3] ${ACTIONING} the virtual audio device $(bold_face ${DRIVER_DIR}) to" \
|
|
"$(bold_face ${DRIVER_PATH})" \
|
|
| tee -a ${LOG_FILE}
|
|
|
|
(disable_error_handling
|
|
# Build and, if requested, archive or install BGMDriver.
|
|
${SUDO} "${XCODEBUILD}" -scheme "Background Music Device" \
|
|
-configuration ${CONFIGURATION} \
|
|
-enableAddressSanitizer ${ENABLE_ASAN} \
|
|
$(enableUBSanArg) \
|
|
$(archivePath BGMDriver) \
|
|
BUILD_DIR=./build \
|
|
RUN_CLANG_STATIC_ANALYZER=0 \
|
|
$(ownershipArgs) \
|
|
${DSTROOT_ARG} \
|
|
${XCODEBUILD_OPTIONS} \
|
|
"${XCODEBUILD_ACTION}" >> ${LOG_FILE} 2>&1) &
|
|
|
|
show_spinner "${BUILD_FAILED_ERROR_MSG}"
|
|
|
|
# BGMXPCHelper
|
|
|
|
echo "[2/3] ${ACTIONING} $(bold_face ${XPC_HELPER_DIR}) to $(bold_face ${XPC_HELPER_OUTPUT_PATH})" \
|
|
| tee -a ${LOG_FILE}
|
|
|
|
xpcHelperInstallPathArg() {
|
|
if [[ "${XCODEBUILD_ACTION}" == "install" ]]; then
|
|
echo "INSTALL_PATH=${XPC_HELPER_OUTPUT_PATH}"
|
|
fi
|
|
}
|
|
|
|
# The Xcode project file is configured so that xcodebuild will call post_install.sh after it
|
|
# finishes building. It calls post_install.sh with the ACTION env var set to "install" when
|
|
# XCODEBUILD_ACTION is set to "archive" here. The REAL_ACTION arg in this command is a workaround
|
|
# that lets post_install.sh know when we're archiving.
|
|
(disable_error_handling
|
|
${SUDO} "${XCODEBUILD}" -scheme BGMXPCHelper \
|
|
-configuration ${CONFIGURATION} \
|
|
-enableAddressSanitizer ${ENABLE_ASAN} \
|
|
$(enableUBSanArg) \
|
|
$(archivePath BGMXPCHelper) \
|
|
BUILD_DIR=./build \
|
|
RUN_CLANG_STATIC_ANALYZER=0 \
|
|
$(xpcHelperInstallPathArg) \
|
|
$(ownershipArgs) \
|
|
REAL_ACTION="${XCODEBUILD_ACTION}" \
|
|
${DSTROOT_ARG} \
|
|
${XCODEBUILD_OPTIONS} \
|
|
"${XCODEBUILD_ACTION}" >> ${LOG_FILE} 2>&1) &
|
|
|
|
show_spinner "${BUILD_FAILED_ERROR_MSG}"
|
|
|
|
# BGMApp
|
|
|
|
echo "[3/3] ${ACTIONING} $(bold_face ${APP_DIR}) to $(bold_face ${APP_PATH})" \
|
|
| tee -a ${LOG_FILE}
|
|
|
|
(disable_error_handling
|
|
${SUDO} "${XCODEBUILD}" -scheme "Background Music" \
|
|
-configuration ${CONFIGURATION} \
|
|
-enableAddressSanitizer ${ENABLE_ASAN} \
|
|
$(enableUBSanArg) \
|
|
$(archivePath BGMApp) \
|
|
BUILD_DIR=./build \
|
|
RUN_CLANG_STATIC_ANALYZER=0 \
|
|
$(ownershipArgs) \
|
|
${DSTROOT_ARG} \
|
|
${XCODEBUILD_OPTIONS} \
|
|
"${XCODEBUILD_ACTION}" >> ${LOG_FILE} 2>&1) &
|
|
|
|
show_spinner "${BUILD_FAILED_ERROR_MSG}"
|
|
|
|
if [[ "${XCODEBUILD_ACTION}" == "install" ]]; then
|
|
# Fix Background Music.app owner/group.
|
|
#
|
|
# We have to run xcodebuild as root to install BGMXPCHelper because it installs to directories
|
|
# owned by root. But that means the build directory gets created by root, and since BGMApp uses
|
|
# the same build directory we have to run xcodebuild as root to install BGMApp as well.
|
|
#
|
|
# TODO: Can't we just chown -R the build dir before we install BGMApp? Then we wouldn't have to
|
|
# install BGMApp as root. (But maybe still handle the unlikely case of APP_PATH not being
|
|
# user-writable.)
|
|
sudo chown -R "$(whoami):admin" "${APP_PATH}/${APP_DIR}"
|
|
|
|
# Fix the build directories' owner/group. This is mainly so the whole source directory can be
|
|
# deleted easily after installing.
|
|
sudo chown -R "$(whoami):admin" "BGMApp/build" "BGMDriver/build"
|
|
|
|
# Restart coreaudiod.
|
|
|
|
echo "Restarting coreaudiod to load the virtual audio device." \
|
|
| tee -a ${LOG_FILE}
|
|
|
|
# The extra or-clauses are fallback versions of the command that restarts coreaudiod. Apparently
|
|
# some of these commands don't work with older versions of launchctl, so I figure there's no
|
|
# harm in trying a bunch of different ways (which should all work).
|
|
(sudo launchctl kickstart -k system/com.apple.audio.coreaudiod &>/dev/null || \
|
|
sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \
|
|
sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \
|
|
sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \
|
|
sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \
|
|
(sudo launchctl unload "${COREAUDIOD_PLIST}" &>/dev/null && \
|
|
sudo launchctl load "${COREAUDIOD_PLIST}" &>/dev/null) || \
|
|
sudo killall coreaudiod &>/dev/null) && \
|
|
sleep 5
|
|
|
|
# Invalidate sudo ticket
|
|
sudo -k
|
|
|
|
# Open BGMApp. We have to change the default audio device after restarting coreaudiod and this
|
|
# is the easiest way.
|
|
echo "Launching Background Music."
|
|
|
|
ERROR_MSG="${BGMAPP_FAILED_TO_START_ERROR_MSG}"
|
|
open "${APP_PATH}/${APP_DIR}"
|
|
|
|
# Ignore script errors from this point.
|
|
disable_error_handling
|
|
|
|
# Wait up to 5 seconds for Background Music to start.
|
|
(trap 'exit 1' TERM
|
|
while ! (ps -Ao ucomm= | grep 'Background Music' > /dev/null); do
|
|
sleep 1
|
|
done) &
|
|
show_spinner "${BGMAPP_FAILED_TO_START_ERROR_MSG}" 5
|
|
|
|
echo "Done."
|
|
elif [[ "${XCODEBUILD_ACTION}" == "archive" ]]; then
|
|
# Copy the dSYMs (debug symbols) into the correct directories in the archives. I haven't been
|
|
# able to figure out why Xcode isn't doing this automatically.
|
|
cp -r "BGMDriver/build/Release/Background Music Device.driver.dSYM" "$DRIVER_PATH/dSYMs"
|
|
cp -r "BGMApp/build/Release/BGMXPCHelper.xpc.dSYM" "$XPC_HELPER_OUTPUT_PATH/dSYMs"
|
|
mv "$APP_PATH/Products/Applications/Background Music.app/Contents/MacOS/Background Music.dSYM" \
|
|
"$APP_PATH/dSYMs"
|
|
fi
|
|
|