#!/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 ", 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=" 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: # : 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