#!/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 . # # build_and_install.sh # # Copyright © 2016 Kyle Neideck # # Builds and installs BGMApp, BGMDriver and BGMXPCHelper. Requires xcodebuild. # # Safe mode set -euo pipefail IFS=$'\n\t' # General error message set -o errtrace general_error() { echo "$(tput setaf 1)ERROR$(tput sgr0): Install script failed at line $1. This is probably a" \ "bug in the script. Feel free to report it." >&2 } trap 'general_error ${LINENO}' ERR # Build for release by default. # TODO: Add an option to use the debug configuration? CONFIGURATION=Release #CONFIGURATION=Debug # Update .gitignore if you change this. LOG_FILE=build_and_install.log 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. # # Takes an optional timeout in seconds. The return value will be the exit status of the command. show_spinner() { 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=${1:-0} (I=1; while (is_alive ${PREV_COMMAND_PID}) && ([[ ${TIMEOUT} -lt 1 ]] || [[ $I -lt ${TIMEOUT} ]]); do printf '.'; sleep 1; # Erase after we've printed three dots. (\b is backspace.) [[ $(($I % 3)) -eq 0 ]] && printf '\b\b\b \b\b\b'; I=$(($I + 1)); done) & set +e wait ${PREV_COMMAND_PID} local EXIT_STATUS=$? set -e # Clean up the dots. printf '\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 echo "$(tput setaf 1)ERROR$(tput sgr0): Build step failed. See ${LOG_FILE} for details." >&2 echo "Failed command:" >&2 echo " ${PREV_COMMAND_STRING}" >&2 exit ${EXIT_STATUS} fi return ${EXIT_STATUS} } # Check for xcodebuild. if [[ "$(which xcodebuild)" == "" ]]; then echo "$(tput setaf 1)ERROR$(tput sgr0): Can't find xcodebuild in your \$PATH." >&2 echo >&2 echo "If you have Xcode installed, you should be able to install the command line developer" \ "tools, including xcodebuild, with" >&2 echo " xcode-select --install" >&2 echo "If not, you'll need to install Xcode (~9GB), because xcodebuild no longer works without" \ "it." >&2 # Disable error handlers. trap - ERR set +e # Check for Xcode. XCODE_PATH=$(which xcode-select > /dev/null && xcode-select --print-path) XCODE_PATH=${XCODE_PATH%/Contents/Developer} if [[ "${XCODE_PATH}" == "" ]] && [[ -d /Applications/Xcode.app ]]; then XCODE_PATH="/Applications/Xcode.app" fi if [[ "${XCODE_PATH}" == "" ]] && [[ -d ~/Applications/Xcode.app ]]; then XCODE_PATH="~/Applications/Xcode.app" fi if [[ "${XCODE_PATH}" != "" ]]; then echo >&2 echo "It looks like you have Xcode installed to ${XCODE_PATH}" >&2 fi exit 1 fi # Go to the project directory. cd "$( dirname "${BASH_SOURCE[0]}" )" # BGMDriver echo "Installing the virtual audio device $(bold_face Background Music Device.driver) to" \ "$(bold_face /Library/Audio/Plug-Ins/HAL)." \ | tee ${LOG_FILE} sudo -v # Disable the -e shell option here so we can handle the error differently. (set +e; sudo xcodebuild -project BGMDriver/BGMDriver.xcodeproj \ -target "Background Music Device" \ -configuration ${CONFIGURATION} \ RUN_CLANG_STATIC_ANALYZER=0 \ DSTROOT="/" \ install >> ${LOG_FILE} 2>&1) & show_spinner # BGMXPCHelper XPC_HELPER_INSTALL_DIR=$(BGMApp/BGMXPCHelper/safe_install_dir.sh) echo "Installing $(bold_face BGMXPCHelper.xpc) to $(bold_face ${XPC_HELPER_INSTALL_DIR})." \ | tee -a ${LOG_FILE} (set +e; sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \ -target BGMXPCHelper \ -configuration ${CONFIGURATION} \ RUN_CLANG_STATIC_ANALYZER=0 \ DSTROOT="/" \ INSTALL_PATH="${XPC_HELPER_INSTALL_DIR}" \ install >> ${LOG_FILE} 2>&1) & show_spinner # BGMApp echo "Installing $(bold_face Background Music.app) to $(bold_face /Applications)." \ | tee -a ${LOG_FILE} (set +e; sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \ -target "Background Music" \ -configuration ${CONFIGURATION} \ RUN_CLANG_STATIC_ANALYZER=0 \ DSTROOT="/" \ install >> ${LOG_FILE} 2>&1) & show_spinner # 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.) sudo chown -R $(whoami):admin "/Applications/Background Music.app" # Restart coreaudiod. echo "Restarting coreaudiod to load BGMDriver." \ | tee -a ${LOG_FILE} sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod # Open BGMApp. # I'd rather not open BGMApp here, or at least ask first, but you have to change your default audio # device after restarting coreaudiod and this is the easiest way. echo "Launching Background Music." open "/Applications/Background Music.app" # Wait up to 5 seconds for Background Music to start. (while [[ "$(ps -u $(whoami) -o ucomm= | grep 'Background Music')" == "" ]]; do sleep 1; done) & show_spinner 5 echo "Done."