blob: 7b52fb10a4a234298bb4e3c266a2de230cae98c6 [file] [log] [blame] [edit]
#!/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
ffx emu start Emulate a device and interact with it
ffx emu start --headless Run without a graphic terminal
shell dm shutdown Shut down Fuchsia on the emulator
ffx emu --help 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 ffx emu start --headless --net tap # headless with tun/tap-based networking
$ fx serve # [Start in a new window]
$ fx test hello-world-cpp-unittests # [Start in a new window] Run test
$ fx ffx emu stop # 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"
export PATH="${PREBUILT_ALL_PATHS}:${PATH}"
"${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|lcov.info)" \
"${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} ---------------------------------------"
local start=$(date +%s)
"$@"
local end=$(date +%s)
echo "--- Done in $((end-start)) seconds"
done
}
monitor_and_run <(monitor_source_changes) "${cmd_and_args}" "${command_path}" "$@"