| #!/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_build_dir { |
| (fx-build-dir-if-present && echo "${FUCHSIA_BUILD_DIR}") |
| } |
| |
| function commands { |
| local dirs=() |
| dirs+=("${fuchsia_dir}"/tools/devshell{,/contrib}) |
| local -r build_dir="$(get_build_dir)" |
| if [[ -n "${build_dir}" ]]; then |
| dirs+=("${build_dir}/tools") |
| fi |
| |
| ( |
| for dir in "${dirs[@]}"; do |
| for cmd in "${dir}"/*; do |
| if [[ -x "${cmd}" && -f "${cmd}" ]]; then |
| basename "${cmd}" |
| fi |
| done |
| done |
| echo "${buildtools_whitelist}" |
| ) | sort -u |
| } |
| |
| function find_command { |
| local -r cmd=$1 |
| |
| local command_path="${fuchsia_dir}/tools/devshell/${cmd}" |
| if [[ -x "${command_path}" ]]; then |
| echo "${command_path}" |
| return 0 |
| fi |
| |
| local command_path="${fuchsia_dir}/tools/devshell/contrib/${cmd}" |
| if [[ -x "${command_path}" ]]; then |
| echo "${command_path}" |
| return 0 |
| fi |
| |
| local -r build_dir=$(get_build_dir) |
| if [[ -n "${build_dir}" ]]; then |
| local command_path="${build_dir}/tools/${cmd}" |
| if [[ -x "${command_path}" ]]; then |
| echo "${command_path}" |
| return 0 |
| fi |
| fi |
| |
| for tool in ${buildtools_whitelist}; do |
| if [[ "$cmd" != "$tool" ]]; then |
| continue |
| fi |
| local command_path="${fuchsia_dir}/buildtools/${tool}" |
| if [[ -x "${command_path}" ]]; then |
| echo "${command_path}" |
| return 0 |
| fi |
| done |
| |
| return 1 |
| } |
| |
| function help { |
| local cmd="$1" |
| if [[ -z "${cmd}" ]]; then |
| for cmd in $(commands); do |
| local cmd_path="$(find_command "${cmd}")" |
| if [[ $(file -b --mime "${cmd_path}" | cut -d / -f 1) == "text" ]]; then |
| echo "${cmd} | $(sed -n '1,/^###/s/^### //p' < "${cmd_path}")" |
| else |
| echo "${cmd}" |
| fi |
| done | column -t -s '|' -c 2 |
| else |
| local cmd_path="$(find_command "${cmd}")" |
| if [[ -z "$cmd_path" ]]; then |
| echo "Command not found" |
| elif [[ $(file -b --mime "${cmd_path}" | cut -d / -f 1) == "text" ]]; then |
| fx-print-command-help "${cmd_path}" |
| else |
| echo "No help found. Try \`fx ${cmd} -h\`" |
| fi |
| fi |
| } |
| |
| function usage { |
| cat <<END |
| usage: fx [--dir BUILD_DIR] [-d DEVICE_NAME] [-i] [-x] COMMAND [...] |
| |
| Run Fuchsia development commands. Must be run with either a current working |
| directory that is contained in a Platform Source Tree or the FUCHSIA_DIR |
| environment variable set to the root of a Platform Source Tree. |
| |
| commands: |
| $(help) |
| |
| optional arguments: |
| --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. |
| |
| optional shell extensions: |
| fx-go |
| fx-update-path |
| fx-set-prompt |
| |
| To use these shell extensions, first source fx-env.sh into your shell: |
| |
| $ source scripts/fx-env.sh |
| |
| END |
| } |
| |
| buildtools_whitelist="gn ninja" |
| |
| 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 |
| ;; |
| --) |
| shift |
| break |
| ;; |
| help) |
| if [[ $# -gt 1 ]]; then |
| shift |
| help "$@" |
| exit |
| else |
| usage |
| exit 1 |
| fi |
| ;; |
| -*) |
| 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_command ${command_name})" |
| |
| if [[ $? -ne 0 ]]; then |
| usage |
| fx-error "Unknown command ${command_name}" |
| exit 1 |
| fi |
| |
| declare -r cmd_and_args="$@" |
| shift # Removes the command name. |
| |
| track-command-execution "${command_name}" "$@" & |
| "${command_path}" "$@" |
| declare -r retval=$? |
| if [ -z "${iterative}" ]; then |
| 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}" "$@" |