| #!/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 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[:SSH_PORT] Target a specific device. |
| DEVICE may be a Fuchsia device name to be resolved using |
| ffx or an IP address. |
| An IPv4 address must be specified directly, while an IPv6 |
| need to be surrounded by brackets. |
| SSH_PORT, if specified, will be used for all commands |
| that rely on SSH to connect to the device instead of the |
| default SSH port (22). |
| Note: See "fx help set-device" for more help and to set |
| the default device 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 |
| if [[ -n "$(list_optional_features)" ]]; then |
| echo " --enable|disable=FEATURE Enable or disable a feature (non-persistent). Valid features:" |
| help_optional_features |
| fi |
| } |
| |
| 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 helpheader { |
| cat <<END |
| Run Fuchsia development commands. Must be run from a directory |
| that is contained in a Platform Source Tree. |
| |
| For full help, use \`fx help --full\`. |
| |
| For detailed help on any command, use \`fx help <command>\`. |
| |
| |
| 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 |
| } |
| |
| function shorthelp { |
| helpheader |
| cat <<END |
| |
| configure and build |
| set <product>.<board> Set the product and board that will be built |
| list-products List available products |
| list-boards List available boards |
| status Show the current target configuration |
| build Build the currently set configuration |
| clean Clean the build artifacts |
| |
| use the emulator |
| emu Emulate a device and interact with it |
| You can hit return after the emulator starts up |
| to enter the shell. Or use \`fx shell\` commands. |
| emu --headless Run without a graphic terminal |
| shell dm shutdown Shut down Fuchsia on the emulator |
| emu -h Show emu help |
| |
| test |
| test <component> Run a test |
| test --info [<pattern>] List tests in the current build |
| help test Detailed help for the test command |
| |
| preparing a fuchsia target and device |
| list-devices List all reachable Fuchsia targets |
| set-device Set the default target for the build |
| unset-device Unset the default target |
| flash Install Fuchsia from fastboot |
| ota Send incremental over-the-air update to the device |
| serve Start the package server and attach to a |
| running Fuchsia target |
| |
| start a remote shell on a Fuchsia target |
| shell Initiate an interactive SSH session |
| shell <cmd> Run a command |
| |
| other |
| format-code Format code, usually to prepare for review |
| rfc Scaffold RFCs from the template and automatically fills metadata files |
| |
| example basic workflow using an emulator |
| $ fx set core.qemu-x64 --with //examples/hello_world |
| $ fx build |
| $ fx emu --headless -N # headless with networking |
| $ fx serve # [Start in a new window] |
| $ fx test hello-world-cpp-unittests # [Start in a new window] Run test |
| $ fx shell dm shutdown # Shut down the emulator |
| |
| online documentation |
| Fuchsia development: https://fuchsia.dev/fuchsia-src/development |
| Fuchsia workflows: https://fuchsia.dev/fuchsia-src/development/build/fx |
| END |
| } |
| |
| function usage { |
| cat <<END |
| usage: fx [--dir BUILD_DIR] [-d DEVICE_NAME] [-i] [-x] COMMAND [...] |
| END |
| shorthelp |
| } |
| |
| function fullhelp { |
| helpheader |
| help_list_commands "$@" |
| } |
| |
| function find_tree_root { |
| local parent="$1" |
| if [[ ! -d "$parent" ]]; then |
| return 1 |
| fi |
| while [[ ! -d "${parent}/.jiri_root" ]]; do |
| parent="$(dirname "${parent}")" |
| if [[ "$parent" == "/" ]]; then |
| return 1 |
| fi |
| done |
| echo "$parent" |
| } |
| |
| # 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. |
| # |
| # NOTE: The FUCHSIA_DIR environment variable is ignored here because it |
| # could point to a different Fuchsia checkout in some developer setups. |
| # |
| # This logic is replicated in //scripts/fx, //scripts/hermetic_env, |
| # //scripts/zsh_completion/_fx, and //src/developer/ffx/scripts. For |
| # consistency, copy any changes here to those files as well. |
| if ! fuchsia_dir="$(find_tree_root "$(pwd)")"; then |
| echo >&2 "ERROR: Cannot find the Platform Source Tree in a parent of the current directory: $(pwd)" |
| exit 1 |
| fi |
| |
| script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" |
| |
| if [[ "${script_dir}" != "${fuchsia_dir}"* ]]; then |
| echo >&2 "ERROR: You are executing fx from outside of the current source tree" |
| echo >&2 "ERROR: This is not supported as fx does not have a stable internal API" |
| echo >&2 |
| echo >&2 " 'fx' was executed from: ${BASH_SOURCE[0]}" |
| echo >&2 " 'fuchsia directory' resolved to: ${fuchsia_dir}" |
| echo >&2 |
| echo >&2 "To run a command in the current Fuchsia directory, run fx from:" |
| echo >&2 " ${fuchsia_dir}/scripts/fx" |
| echo >&2 "Or, if you use fx-env.sh, source fx-env from the current fuchsia dir:" |
| echo >&2 " source ${fuchsia_dir}/scripts/fx-env.sh" |
| exit 1 |
| 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 $? |
| |
| declare -r cmd_locator_sh="${fuchsia_dir}/tools/devshell/lib/fx-cmd-locator.sh" |
| source "${cmd_locator_sh}" || exit $? |
| |
| declare -r features_sh="${fuchsia_dir}/tools/devshell/lib/fx-optional-features.sh" |
| source "${features_sh}" || exit $? |
| |
| while [[ $# -ne 0 ]]; do |
| case $1 in |
| --dir=*|-d=*|--disable=*|--enable=*) |
| # Turn --switch=value into --switch value. |
| arg="$1" |
| shift |
| set -- "${arg%%=*}" "${arg#*=}" "$@" |
| continue |
| ;; |
| --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 |
| ;; |
| --disable) |
| if [[ $# -lt 2 ]]; then |
| usage |
| fx-error "Missing argument to --disable" |
| exit 1 |
| fi |
| shift # Removes --disable. |
| feature="$1" |
| if ! is_valid_feature "${feature}"; then |
| fx-error "Unknown feature \"${feature}\". Valid values are: $(list_optional_features)" |
| exit 1 |
| fi |
| env_name="$(get_disable_feature_env_name "${feature}")" |
| export ${env_name}=1 |
| ;; |
| --enable) |
| if [[ $# -lt 2 ]]; then |
| usage |
| fx-error "Missing argument to --enable" |
| exit 1 |
| fi |
| shift # Removes --enable. |
| feature="$1" |
| if ! is_valid_feature "${feature}"; then |
| fx-error "Unknown feature \"${feature}\". Valid values are: $(list_optional_features)" |
| exit 1 |
| fi |
| env_name="$(get_disable_feature_env_name "${feature}")" |
| export ${env_name}=0 |
| ;; |
| -d) |
| if [[ $# -lt 2 ]]; then |
| usage |
| fx-error "Missing device name for -d argument" |
| exit 1 |
| fi |
| shift # removes -d |
| if ! is-valid-device "$1"; then |
| fx-error "Invalid device: $1. See valid values in 'fx help set-device'" |
| exit 1 |
| fi |
| export FUCHSIA_DEVICE_NAME="$1" |
| ;; |
| -i) |
| declare iterative=1 |
| ;; |
| -x) |
| export FUCHSIA_DEVSHELL_VERBOSITY=1 |
| ;; |
| -xx) |
| set -x |
| ;; |
| --) |
| shift |
| break |
| ;; |
| help|--help) |
| if [[ "$2" =~ ^\-\-full ]]; then |
| shift |
| fullhelp "$@" |
| elif [[ $# -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[0]}" ]]; 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 |
| |
| # For each optional feature, force the existence of the FUCHSIA_DISABLE_* env |
| # variable. This is needed so that non-bash code can reliably use this |
| # to check for optional features that have default values. |
| for f in $(list_optional_features); do |
| _disabled_env_name="$(get_disable_feature_env_name "${f}")" |
| is_feature_enabled "$f" |
| export ${_disabled_env_name}=$? |
| done |
| |
| export FX_ENTRY_CMD="$command_name" |
| |
| 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 |
| |
| mkdir -p "${FX_CACHE_DIR}" |
| |
| track-command-execution "${metric_name}" "$@" & |
| declare -r start_time="$SECONDS" |
| "${command_path[@]}" "$@" |
| declare -r retval=$? |
| declare -r end_time="$SECONDS" |
| declare -r elapsed_time=$(( 1000 * (end_time - start_time) )) # milliseconds |
| |
| if [ -z "${iterative}" ]; then |
| track-command-finished "${elapsed_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}" "$@" |