blob: f28b7e7f355891bcb40803f1af5c2eea5da80c8a [file] [log] [blame]
#!/bin/bash
# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script is to:
# 1. create a new simulator by running "xcrun simctl create ..."
# 2. launch the created simulator by passing the ID to the simulator app,
# like: "/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator" -CurrentDeviceUDID "B647C213-110F-4A6B-827D-BD25313C2D1F"
# 3. install the target app on the created simulator by running
# "xcrun simctl install ..."
# 4. launch the target app on the created simulator by running
# "xcrun simctl launch <device> <app identifier> <args>", and get its PID. We
# pass in the env vars to the app by exporting the env vars adding the prefix
# "SIMCTL_CHILD_" in the calling environment.
# 5. check the app's PID periodically, exit the script when the app is not
# running.
# 6. when exit, will shutdown and delete the new created simulator.
#
# Note: the command "xcrun simctl launch ..." cannot return the app's output,
# so we pass in the StdRedirect.dylib as an $DYLD_INSERT_LIBRARIES, which
# could redirect the output to $GSTDERR and $GSTDOUT. Then we "tail -f" the
# file with the redirected content to show it on the console.
set -eu
if [[ "$(uname)" != Darwin ]]; then
echo "Cannot run iOS targets on a non-mac machine."
exit 1
fi
# Note: the sim_device and sdk_version might contain spaces, but they are already
# provided in quoted form in the template variables, so we should not quote them
# again here.
TEST_DEVICE_ID=$(xcrun simctl create TestDevice %sim_device% %sdk_version%)
function KillAllDevices() {
# Kill all running simulators.under Xcode 7+. The error message "No matching
# processes belonging to you were found" is expected when there's no running
# simulator.
pkill Simulator 2> /dev/null || true
}
# Kill the tail process (we redirect the app's output to a file and use tail to
# stream the file) when the app is not running.
# Default timeout is 600 secs. User could change it by running
# "export TIME_OUT=<new_timeout_in_secs>" before invoking "blaze run" command.
# $1: the PID of the app process
# $2: the PID of the tail process
function exit_when_app_not_running() {
local time_out=${TIME_OUT:-600}
local end_time=$(($(date +%s)+${time_out}))
while kill -0 "$1" &> /dev/null; do
if [[ $(date +%s) -gt $end_time ]]; then
break
fi
sleep 1
done
kill -9 "$2" &> /dev/null
}
# Wait until the given simualtor is booted.
# $1: the simulator ID to boot
function wait_for_sim_to_boot() {
i=0
while [ "${i}" -lt 60 ]; do
# The expected output of "xcrun simctl list" is like:
# -- iOS 8.4 --
# iPhone 5s (E946FA1C-26AB-465C-A7AC-24750D520BEA) (Shutdown)
# TestDevice (8491C4BC-B18E-4E2D-934A-54FA76365E48) (Booted)
# So if there's any booted simulator, $booted_device will not be empty.
local booted_device=$(xcrun simctl list devices | grep "$1" | grep "Booted" || true)
if [ -n "${booted_device}" ]; then
# Simulator is booted.
return
fi
sleep 1
i=$(($i+1))
done
echo "Failed to launch the simulator. The existing simulators are:"
xcrun simctl list
exit 1
}
# Clean up the given simulator.
# $1: the simulator ID
function CleanupSimulator() {
# Device may not have started up, so no guarantee shutdown is going to be good.
xcrun simctl shutdown "$1" 2> /dev/null || true
xcrun simctl delete "$1"
}
trap "CleanupSimulator ${TEST_DEVICE_ID}" EXIT
readonly STD_REDIRECT_DYLIB="$PWD/%std_redirect_dylib_path%"
readonly TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/bazel_temp.XXXXXX")
trap 'rm -rf "${TEMP_DIR}"' ERR EXIT
readonly APP_DIR="${TEMP_DIR}/extracted_app"
mkdir "${APP_DIR}"
KillAllDevices
# Get the xcode version number, like: 6.4.
# The expected output of "xcodebuild -version" is like:
# Xcode 6.4
# Build version 6E35b
#
# So the expected output of "xcodebuild -version|grep \"Xcode\"" is like:
# Xcode 6.4
#
# awk will pull out the version number, e.g. "6.4".
readonly XCODE_VERSION=$(xcodebuild -version | grep "Xcode" | awk '{ print $2 }')
if [[ "${XCODE_VERSION}" == 6.* ]]; then
simulator_name="iOS Simulator"
else
simulator_name="Simulator"
fi
# Get the developer path, like: /Applications/Xcode.app/Contents/Developer
readonly DEVELOPER_PATH=$(xcode-select -p)
# Launch the simulator.
"${DEVELOPER_PATH}/Applications/${simulator_name}.app/Contents/MacOS/${simulator_name}" -CurrentDeviceUDID "${TEST_DEVICE_ID}" &
wait_for_sim_to_boot "${TEST_DEVICE_ID}"
# Pass environment variables prefixed with "IOS_" to the simulator, replace the
# prefix with "SIMCTL_CHILD_". blaze/bazel adds "IOS_" to the env vars which
# will be passed to the app as prefix to differentiate from other env vars. We
# replace the prefix "IOS_" with "SIMCTL_CHILD_" here, because "simctl" only
# pass the env vars prefixed with "SIMCTL_CHILD_" to the app.
libs_to_insert="${STD_REDIRECT_DYLIB}"
while read -r envvar; do
if [[ "${envvar}" == IOS_* ]]; then
if [[ "${envvar}" == IOS_DYLD_INSERT_LIBRARIES=* ]]; then
libs_to_insert=SIMCTL_CHILD_"${envvar#IOS_}":"${libs_to_insert}"
else
export SIMCTL_CHILD_"${envvar#IOS_}"
fi
fi
done < <(env)
export SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${libs_to_insert}"
readonly RUN_LOG="${TEMP_DIR}/run.log"
touch "${RUN_LOG}"
export SIMCTL_CHILD_GSTDERR="${RUN_LOG}"
export SIMCTL_CHILD_GSTDOUT="${RUN_LOG}"
unzip -qq '%ipa_file%' -d "${APP_DIR}"
xcrun simctl install "$TEST_DEVICE_ID" "${APP_DIR}/Payload/%app_name%.app"
# Get the bundle ID of the app.
readonly BUNDLE_INFO_PLIST="${APP_DIR}/Payload/%app_name%.app/Info.plist"
readonly BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${BUNDLE_INFO_PLIST}")
USER_NAME=${USER:-"$(logname)"}
readonly SYSTEM_LOG="/Users/${USER_NAME}/Library/Logs/CoreSimulator/${TEST_DEVICE_ID}/system.log"
rm -f "${SYSTEM_LOG}"
# Launch the app. The expected output is:
# <bundle name, e.g. example.PrenotCalculatorBinary>: <pid of the app process>
IOS_PID=$(xcrun simctl launch "${TEST_DEVICE_ID}" "${BUNDLE_ID}" "$@")
# The awk command will abstract the pid of the app process.
IOS_PID=$(echo "${IOS_PID}" | awk '{ print $2 }')
echo "Start the app ${BUNDLE_ID} on ${TEST_DEVICE_ID}."
# Tail the file with the redirected outputs of the app.
tail -f "${RUN_LOG}" &
exit_when_app_not_running "${IOS_PID}" "$!"
# Wait for a while for the system.log to be updated.
sleep 5
if [ ! -f "${SYSTEM_LOG}" ];then
output=$(cat "${RUN_LOG}")
# If there's no system.log or output, might be a crash.
if [ -z "${output}" ];then
echo "no output or system.log"
exit 1
else
exit 0
fi
fi
# Check the system.log to see if there was an abnormal exit.
readonly ABNORMAL_EXIT_MSG=$(cat "${SYSTEM_LOG}" | \
grep "com.apple.CoreSimulator.SimDevice.${TEST_DEVICE_ID}.launchd_sim" | \
grep "Service exited with abnormal code")
if [ -n "${ABNORMAL_EXIT_MSG}" ]; then
echo "The app exited abnormally: ${ABNORMAL_EXIT_MSG}"
exit 1
fi