| #!/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 -t bazel_temp) |
| 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 |