| #!/bin/bash | |
| # Copyright 2017 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| function get_host_tools_dir { | |
| local -r build_dir="$(fx-build-dir-if-present && echo "${FUCHSIA_BUILD_DIR}")" | |
| local -r host_tools="${build_dir}/host-tools" | |
| if [[ -d "${build_dir}" && -d "${host_tools}" ]]; then | |
| echo "${host_tools}" | |
| fi | |
| } | |
| function get_exec_from_metadata { | |
| local -r metadata_file=$1 | |
| export PREBUILT_3P_DIR FUCHSIA_DIR HOST_PLATFORM | |
| export HOST_TOOLS_DIR="$(get_host_tools_dir)" | |
| awk -F ' *= *' -f - "${metadata_file}" <<'EOF' | |
| /^#### +EXECUTABLE */ { | |
| gsub(/\${PREBUILT_3P_DIR}/, ENVIRON["PREBUILT_3P_DIR"], $2); | |
| gsub(/\${FUCHSIA_DIR}/, ENVIRON["FUCHSIA_DIR"], $2); | |
| gsub(/\${HOST_TOOLS_DIR}/, ENVIRON["HOST_TOOLS_DIR"], $2); | |
| gsub(/\${HOST_PLATFORM}/, ENVIRON["HOST_PLATFORM"], $2); | |
| print $2; | |
| } | |
| EOF | |
| } | |
| function _relative { | |
| cmd="$1" | |
| if [[ "${cmd}" == *"${fuchsia_dir}"* ]]; then | |
| echo "//${cmd#${fuchsia_dir}/}" | |
| else | |
| echo "${cmd}" | |
| fi | |
| } | |
| function find_executable { | |
| local cmd_name="$1" | |
| local cmd_path="$(commands "${cmd_name}")" | |
| cmd_path="${cmd_path%.fx}" | |
| local fx_file_path="${cmd_path}.fx" | |
| if [[ ! -f "${fx_file_path}" ]]; then | |
| # no metadata, so let's try to find the file | |
| if [[ -z "${cmd_path}" ]]; then | |
| # no file in regular script directories, look in host_tools | |
| cmd_path="$(find_host_tools "${cmd_name}")" | |
| fi | |
| echo "${cmd_path}" | |
| if [[ ! -x "${cmd_path}" ]]; then | |
| return 1 | |
| fi | |
| else | |
| # there's metadata | |
| local from_metadata="$(get_exec_from_metadata "${fx_file_path}")" | |
| if [[ -n "${from_metadata}" ]]; then | |
| # EXECUTABLE is defined in the metadata, use it | |
| echo "${from_metadata}" | |
| if [[ -f "${cmd_path}" && ! "${cmd_path}" -ef "${from_metadata}" ]]; then | |
| fx-error "Invalid ${fx_file_path}: if both ${basename_exec} and EXECUTABLE metadata exist, they must point to the same file" | |
| return 1 | |
| fi | |
| else | |
| # no EXECUTABLE in the metadata, so return the cmd_path itself | |
| echo "${cmd_path}" | |
| if [[ ! -x "${cmd_path}" ]]; then | |
| return 1 | |
| fi | |
| fi | |
| fi | |
| } | |
| function find_execs_and_metadata { | |
| local cmd_name=$1 | |
| shift | |
| if [[ -z "${cmd_name}" ]]; then | |
| cmd_name="*" | |
| fi | |
| local dirs=() | |
| for d in "$@"; do | |
| if [[ -d "${d}" ]]; then | |
| dirs+=( "${d}" ) | |
| fi | |
| done | |
| if [[ ${#dirs[@]} -eq 0 ]]; then | |
| return 0 | |
| fi | |
| # run find assuming "-executable" is supported | |
| cmds="$( find "${dirs[@]}" -maxdepth 1 -type f \ | |
| \( -executable -name "${cmd_name}" \) -o \ | |
| \( -name "${cmd_name}.fx" \) 2>/dev/null )" | |
| if [[ $? -ne 0 ]]; then | |
| # assume that the error was caused because versions of find older than 4.3.0 | |
| # don't support -executable. Run with -perm +100 instead, which is not | |
| # supported in versions of find newer than 4.5.12, so it can't be used always. | |
| cmds="$( find "${dirs[@]}" -maxdepth 1 -type f \ | |
| \( -perm +100 -name "${cmd_name}" \) -o \ | |
| \( -name "${cmd_name}.fx" \) 2>/dev/null )" | |
| if [[ $? -ne 0 ]]; then | |
| { | |
| echo "ERROR: 'find' failed unexpectedly, please execute fx with '-x' and report a bug." | |
| echo "At least one of the commands below was expected to work:" | |
| echo 'find ' "${dirs[@]}" '-maxdepth 1 -type f \( -executable -name' "\"${cmd_name}\"" '\) -o \( -name ' "\"${cmd_name}.fx\"" '\)' | |
| echo 'find ' "${dirs[@]}" '-maxdepth 1 -type f \( -perm +100 -name' "\"${cmd_name}\"" '\) -o \( -name ' "\"${cmd_name}.fx\"" '\)' | |
| } >&2 | |
| exit 1 | |
| fi | |
| fi | |
| sort -u <<< "${cmds}" | |
| } | |
| function find_host_tools { | |
| local cmd_name=$1 | |
| local -r host_tools="$(get_host_tools_dir)" | |
| if [[ -z "${host_tools}" ]]; then | |
| return | |
| fi | |
| # get a list of non-host-tools commands and metadata files, separated by | |
| # semi-colon. | |
| cmds="$(commands | tr '\n' ';')" | |
| binaries=() | |
| # do not list a host tool if there's a subcommand script or an .fx metadata | |
| # file with the same name. | |
| for binary in $(find_execs_and_metadata "${cmd_name}" "${host_tools}"); do | |
| name="${binary##*/}" # remove path, equivalent to basename but faster | |
| # only return the binary if no {binary} or {binary}.fx as other commands | |
| if [[ "${cmds}" != */${name}.fx\;* && "${cmds}" != */${name}\;* ]]; then | |
| binaries+=( "${binary}" ) | |
| fi | |
| done | |
| echo "${binaries[@]}" | |
| } | |
| function commands { | |
| local cmd_name=$1 | |
| local dirs | |
| # handle "fx help vendor VENDOR COMMAND" | |
| if [[ "${cmd_name}" == "vendor" && $# -eq 3 ]]; then | |
| vendor=$2 | |
| cmd_name=$3 | |
| dirs=("${fuchsia_dir}"/vendor/${vendor}/scripts/devshell) | |
| else | |
| dirs=("${fuchsia_dir}"/tools/devshell{,/contrib} "${fuchsia_dir}"/vendor/*/scripts/devshell) | |
| fi | |
| find_execs_and_metadata "${cmd_name}" "${dirs[@]}" | |
| } | |
| function help_options { | |
| cat <<END | |
| fx help flags: fx help [OPTION] | |
| --no-contrib Hide contrib commands (see //tools/devshell/README.md) | |
| --deprecated Do not hide deprecated commands | |
| END | |
| } | |
| function help_global_options { | |
| cat <<END | |
| Global fx options: fx [OPTION] ${cmd} ... | |
| --dir=BUILD_DIR Path to the build directory to use when running COMMAND. | |
| -d=DEVICE_NAME Target a specific device. DEVICE_NAME may be a Fuchsia | |
| device name. Note: "fx set-device" can be used to set a | |
| default DEVICE_NAME for a BUILD_DIR. | |
| -i Iterative mode. Repeat the command whenever a file is | |
| modified under your Fuchsia directory, not including | |
| out/. | |
| -x Print commands and their arguments as they are executed. | |
| -xx Print extra logging of the fx tool itself (implies -x) | |
| END | |
| } | |
| function help_list_commands { | |
| local hide_contrib=0 | |
| local show_deprecated=0 | |
| while [[ $# -ne 0 ]]; do | |
| if [[ "$1" == "--deprecated" ]]; then | |
| show_deprecated=1 | |
| elif [[ "$1" == "--no-contrib" ]]; then | |
| hide_contrib=1 | |
| fi | |
| shift | |
| done | |
| # list all subcommands with summaries, grouped by categories | |
| commands | xargs awk \ | |
| -v hide_contrib=${hide_contrib} \ | |
| -v show_deprecated=${show_deprecated} \ | |
| -f "${fuchsia_dir}/scripts/fx-help.awk" | |
| # list host tools build artifacts without corresponding metadata | |
| host_tools="$(find_host_tools)" | |
| if [[ -n "${host_tools}" ]]; then | |
| echo "Host binaries produced by the build with no metadata (more info at //tools/devshell/README.md):" | |
| for i in ${host_tools}; do | |
| echo -n " " | |
| basename $i | |
| done | |
| fi | |
| help_options | |
| help_global_options | |
| } | |
| function help_command { | |
| local cmd="$@" | |
| local cmd_path="$(commands ${cmd} | head -1)" | |
| if [[ -z "${cmd_path}" ]]; then | |
| local cmd_path="$(find_host_tools ${cmd} | head -1)" | |
| if [[ -z "${cmd_path}" ]]; then | |
| echo "Command ${cmd} not found. Try \`fx help\`" | |
| else | |
| echo "'$(_relative "${cmd_path}")' is a host tool and no metadata" \ | |
| "was found. Try running \`fx ${cmd} -h\`" | |
| fi | |
| elif [[ $(file -b --mime "${cmd_path}" | cut -d / -f 1) == "text" ]]; then | |
| fx-print-command-help "${cmd_path}" | |
| help_global_options | |
| else | |
| echo "No help found. Try \`fx ${cmd} -h\`" | |
| fi | |
| } | |
| function usage { | |
| cat <<END | |
| usage: fx [--dir BUILD_DIR] [-d DEVICE_NAME] [-i] [-x] COMMAND [...] | |
| Run Fuchsia development commands. Must be run either from a directory | |
| that is contained in a Platform Source Tree or with the FUCHSIA_DIR | |
| environment variable set to the root of a Platform Source Tree. | |
| host shell extensions: (requires "source scripts/fx-env.sh") | |
| fx-update-path add useful tools to the PATH | |
| fx-set-prompt display the current configuration in the shell prompt | |
| END | |
| help_list_commands "$@" | |
| } | |
| fuchsia_dir="${FUCHSIA_DIR}" | |
| if [[ -z "${fuchsia_dir}" ]]; then | |
| # We walk the parent directories looking for .jiri_root rather than using | |
| # BASH_SOURCE so that we find the fuchsia_dir enclosing the current working | |
| # directory instead of the one containing this file in case the user has | |
| # multiple source trees and is picking up this file from another one. | |
| fuchsia_dir="$(pwd)" | |
| while [[ ! -d "${fuchsia_dir}/.jiri_root" ]]; do | |
| fuchsia_dir="$(dirname "${fuchsia_dir}")" | |
| if [[ "${fuchsia_dir}" == "/" ]]; then | |
| echo "Cannot find Platform Source Tree containing $(pwd)" | |
| exit 1 | |
| fi | |
| done | |
| fi | |
| declare -r vars_sh="${fuchsia_dir}/tools/devshell/lib/vars.sh" | |
| source "${vars_sh}" || exit $? | |
| declare -r metrics_sh="${fuchsia_dir}/tools/devshell/lib/metrics.sh" | |
| source "${metrics_sh}" || exit $? | |
| while [[ $# -ne 0 ]]; do | |
| case $1 in | |
| --config=*|--dir=*|-d=*) | |
| # Turn --switch=value into --switch value. | |
| arg="$1" | |
| shift | |
| set -- "${arg%%=*}" "${arg#*=}" "$@" | |
| continue | |
| ;; | |
| --config) | |
| fx-warn "DEPRECATION NOTICE: --config is deprecated, use --dir instead." | |
| sleep 5 | |
| if [[ $# -lt 2 ]]; then | |
| usage | |
| fx-error "Missing path to config file for --config argument" | |
| exit 1 | |
| fi | |
| shift # Removes --config. | |
| export _FX_BUILD_DIR="$(source "$1"; echo "${FUCHSIA_BUILD_DIR}")" | |
| ;; | |
| --dir) | |
| if [[ $# -lt 2 ]]; then | |
| usage | |
| fx-error "Missing path to build directory for --dir argument" | |
| exit 1 | |
| fi | |
| shift # Removes --dir. | |
| export _FX_BUILD_DIR="$1" | |
| if [[ "$_FX_BUILD_DIR" == //* ]]; then | |
| _FX_BUILD_DIR="${fuchsia_dir}/${_FX_BUILD_DIR#//}" | |
| fi | |
| ;; | |
| -d) | |
| if [[ $# -lt 2 ]]; then | |
| usage | |
| fx-error "Missing device name for -d argument" | |
| exit 1 | |
| fi | |
| shift # removes -d | |
| export FUCHSIA_DEVICE_NAME="$1" | |
| ;; | |
| -i) | |
| declare iterative=1 | |
| ;; | |
| -x) | |
| export FUCHSIA_DEVSHELL_VERBOSITY=1 | |
| ;; | |
| -xx) | |
| set -x | |
| ;; | |
| --) | |
| shift | |
| break | |
| ;; | |
| help|--help) | |
| if [[ $# -lt 2 || "$2" =~ ^\-\-.* ]]; then | |
| shift | |
| usage "$@" | |
| else | |
| shift | |
| help_command "$@" | |
| fi | |
| exit 0 | |
| ;; | |
| -*) | |
| usage | |
| fx-error "Unknown global argument $1" | |
| exit 1 | |
| ;; | |
| *) | |
| break | |
| ;; | |
| esac | |
| shift | |
| done | |
| if [[ $# -lt 1 ]]; then | |
| usage | |
| fx-error "Missing command name" | |
| exit 1 | |
| fi | |
| command_name="$1" | |
| command_path="$(find_executable ${command_name})" | |
| if [[ $? -ne 0 || ! -x "${command_path}" ]]; then | |
| if [[ -n "${command_path}" ]]; then | |
| _path_message=" in the expected location $(_relative "${command_path}")" | |
| fi | |
| fx-error "Cannot find executable for ${command_name}${_path_message}."\ | |
| "If this is a tool produced by the build, make sure your"\ | |
| "\`fx set\` config produces it in the $(_relative "$(get_host_tools_dir)") directory." | |
| exit 1 | |
| fi | |
| declare -r cmd_and_args="$@" | |
| shift # Removes the command name. | |
| if [[ "${command_name}" != "vendor" || $# -lt 2 ]]; then | |
| metric_name="${command_name}" | |
| else | |
| metric_name="vendor/$1/$2" | |
| fi | |
| track-command-execution "${metric_name}" "$@" & | |
| declare -r start_time="$SECONDS" | |
| "${command_path}" "$@" | |
| declare -r retval=$? | |
| declare -r end_time="$SECONDS" | |
| declare -r ellapsed_time=$(( 1000 * (end_time - start_time) )) # milliseconds | |
| if [ -z "${iterative}" ]; then | |
| track-command-finished "${ellapsed_time}" "${retval}" "${command_name}" "$@" & | |
| exit ${retval} | |
| elif which inotifywait >/dev/null; then | |
| monitor_source_changes() { | |
| # Watch everything except out/ and files/directories beginning with "." | |
| # such as lock files, swap files, .git, etc'. | |
| inotifywait -qrme modify \ | |
| --exclude "(/\.|lock|compile_commands.json)" \ | |
| "${fuchsia_dir}" \ | |
| @"${fuchsia_dir}"/out \ | |
| @"${fuchsia_dir}"/zircon/public | |
| } | |
| elif which apt-get >/dev/null; then | |
| echo "Missing inotifywait" | |
| echo "Try: sudo apt-get install inotify-tools" | |
| exit 1 | |
| elif which fswatch >/dev/null; then | |
| monitor_source_changes() { | |
| fswatch --one-per-batch --event=Updated \ | |
| -e "${fuchsia_dir}"/out/ \ | |
| -e "${fuchsia_dir}"/zircon/public/ \ | |
| -e "/\." \ | |
| -e "lock" \ | |
| -e "/compile_commands.json" \ | |
| . | |
| } | |
| else | |
| echo "Missing fswatch" | |
| echo "Try: brew install fswatch" | |
| exit 1 | |
| fi | |
| monitor_and_run() { | |
| local -r event_pipe="$1" | |
| local -r display_name="$2" | |
| shift 2 | |
| # Explicitly bind $event_pipe to a numbered FD so read behaves consistently | |
| # on Linux and Mac shells ("read <$event_pipe" closes $event_pipe after the | |
| # first read on Mac bash). | |
| exec 3<"${event_pipe}" | |
| while read -u 3; do | |
| if [[ "$(uname -s)" != "Darwin" ]]; then | |
| # Drain all subsequent events in a batch. | |
| # Otherwise when multiple files are changes at once we'd run multiple | |
| # times. | |
| read -u 3 -d "" -t .01 | |
| fi | |
| # Allow at most one fx -i invocation per Fuchsia dir at a time. | |
| # Otherwise multiple concurrent fx -i invocations can trigger each other | |
| # and cause a storm. | |
| echo "---------------------------------- fx -i ${display_name} ---------------------------------------" | |
| "$@" | |
| echo "--- Done!" | |
| done | |
| } | |
| monitor_and_run <(monitor_source_changes) "${cmd_and_args}" "${command_path}" "$@" |