blob: e3e6d2c65b50ea948302eaa0d06946b2c467be6c [file] [log] [blame]
#!/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.
### set up a build directory
## usage: fx set TARGET [OUTDIR]
## [--board NAME|PATH]
## [--product NAME|PATH]
## [--monolith PATH]
## [--preinstall PATH]
## [--available PATH]
## [--netboot]
## [--args ARG] [--help-args [ARG]] [--variant VARIANT]
## [--goma|--no-goma] [--no-ensure-goma]
## [--goma-dir DIR]
## [--ccache|--no-ccache]
## [--release]
## [--zircon-arg ARG]
##
## where TARGET is x64 or arm64
##
## OUTDIR is the directory where the build output goes.
## If it begins with `//` or `out/` then it's taken as relative to FUCHSIA_DIR.
## Otherwise it should be an absolute path or a path relative to the current
## working directory that winds up in `FUCHSIA_DIR/out`.
## It defaults to `out/TARGET`.
##
## This is a wrapper around running `gn gen --check OUTDIR --args ...`.
## If GN fails with an error, `fx set` does not change anything.
## If GN succeeds, this also points subsequent `fx` commands at OUTDIR,
## just as `fx use` does and ensures Goma is ready (if enabled).
##
## NAME|PATH can be provided as arugments to --board and --product. In the
## case of NAME the name is searched for in //layer/board/NAME.gni, otherwise it
## is treated as a PATH.
##
## optional arguments:
## --board Use the given board target definition. Board
## configurations are used to modulate hardware specific
## behavior, such as configuring the set of drivers
## included, or adding peripheral specific configurations.
## If no board is given, a default board for the architecture
## is selected from the current layer, e.g.
## garnet/boards/x64.gni.
## --product Include the given product in the build. Defaults to
## the default product for the current layer (e.g.,
## "garnet/products/default.gni" for the Garnet layer).
## Product configurations define a set of packages to be
## included in the monolith, preinstall and available
## package sets, as well as product oriented configurations.
## --monolith Additional packages to be built and included in the
## monolithic system image. Monolith is the set of packages
## that make up an OTA image.
## If the --monolith argument is given multiple times,
## all the specified packages are included in this set.
## These packages are added to the available set defined
## by the board and product specifications.
## --preinstall Additional packages to be built and included in the
## system image alongside the monolithic system image.
## Packages in preinstall are not part of OTA updates,
## instead they are updated dynamically.
## If the --preinstall argument is given multiple times,
## all the specified packages are included in this set.
## These packages are added to the available set defined
## by the board and product specifications.
## --available Additional packages to be built and included in the
## set of packages available for pushing dynamically.
## If the --available argument is given multiple times,
## all the specified available are included in this set.
## These packages are added to the available set defined
## by the board and product specifications.
## --netboot Ensure that a network ramboot image is always built.
## --variant Pass a `select_variant=[VARIANT*,...]` GN build argument
## collecting all the --variant arguments in order.
## --fuzz-with Pass a sanitizer name, e.g. "--fuzz-with asan" to
## enable ALL supporting fuzzers. Use --variant for
## individual fuzzers, e.g. "--variant asan-fuzzer/foo".
## --args Additional argument to pass to gn. If the --args
## argument is given multiple times, all the specified
## arguments are passed to gn.
## N.B. Arguments must be expressed using GN's syntax.
## In particular this means that for strings they must
## be quoted with double-quotes, and the quoting must
## survive, for example, the shell. Thus when passing
## an argument that takes a string, pass it with
## something like --args=foo='"bar"'. E.g.,
## bash$ fx set x64 --args=foo='"bar"'
## More complicated arguments, e.g., lists, require
## their own special syntax. See GN documentation
## for the syntax of each kind of argument.
## --help-args Display GN arguments documentation. If --help-args
## is followed by a GN build argument identifier, just
## that argument's documentation is displayed.
## If --help-args is used alone, all GN build arguments
## are displayed (lots of output).
## This option requires an existing build directory.
## --goma|--no-goma Whether to use the goma service during the build. Goma
## attempts to make builds faster using remote build
## servers. Defaults to detecting whether goma is installed
## on your machine.
## --no-ensure-goma Skip ensuring that goma is started when using goma.
## --goma-dir The directory where goma is installed. Defaults to
## ~/goma.
## --ccache|--no-ccache Whether to use ccache during the build. Ccache attempts
## to make builds faster by caching build artifacts.
## Defaults to detecting whether the CCACHE_DIR environment
## variable is set to a directory.
## --ide Pass --ide=VALUE to gn when generating to create project
## files usable with that IDE. Useful values include "vs"
## for Visual Studio or "xcode" for Xcode.
## --release an alias for "--args=is_debug=false"
##
## Deprecated flags:
##
## --boards Use the listed board target definition (seperated by
## comas) in the build, each board results in an import
## statement and MUST define the `fuchsia_packages`
## variable as well as deal with variable re-definition.
## This flag is deprecated, please use --board instead.
## --products Include the listed products (separated by commas) in
## the build. Defaults to the default product for the
## current layer (e.g., "garnet/packages/default.gni" for
## the Garnet layer). If the --products argument is
## given multiple times, the products configurations
## are merged.
## This flag is deprecated, please use exactly one
## --product and specify packages to add in addition to
## the product with --packages.
## --packages Additional packages to be built and made available.
## If the --packages argument is given multiple times,
## all the specified packages are included in the
## build.
## This flag is deprecated, please use one of
## --available, --preinstall, or --monolith.
## --zircon-arg ARG Additional arguments to pass to Zircon make. Can be given
## multiple times.
##
## Example:
##
## $ fx set x64 kitchensink --product ermine --available topaz/packages/kitchen_sink
## -> architecture: x64
## build directory: out/kitchensink
## board: topaz/boards/x64.gni
## product: topaz/products/ermine.gni
## available: topaz/packages/kitchen_sink (all other packages)
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/vars.sh || exit $?
function guess_config_within_layer {
local config_type="$1"
local config_name="$2"
local __resultvar="$3"
# Guess the petal we're using. We'll search at this petal and below for
# config matching the type and short name
local current_petal
current_petal="$(${FUCHSIA_DIR}/build/gn/guess_layer.py)" || return 1
readonly current_petal
# Compute a lookup order starting at the current petal going down.
local petal_order="${current_petal}"
case $current_petal in
vendor/*)
petal_order="${petal_order},topaz,peridot,garnet"
;;
"topaz")
petal_order="${petal_order},peridot,garnet"
;;
"peridot")
petal_order="${petal_order},garnet"
;;
esac
readonly petal_order
# Look through petals in this order to find configs with matching names.
IFS=,
local petal
for petal in $petal_order; do
guessed_config="${petal}/${config_type}/${config_name}.gni"
if [[ -a "${FUCHSIA_DIR}/${guessed_config}" ]]; then
echo "Guessing ${config_type} config ${guessed_config}"
eval "${__resultvar}"="${guessed_config}"
return
fi
done
echo "Could not guess a ${config_type} configuration matching \"${config_name}\""
echo "Please specify the full path from the root of the checkout such as"
echo "garnet/${config_type}/base.gni"
exit 1
}
function main {
fx-standard-switches "$@"
set -- "${FX_ARGV[@]}"
if [[ $# -lt 1 ]]; then
fx-command-help
return 1
fi
local arch=
case $1 in
x64 | x86 | x64-64)
arch=x64
;;
arm64 | aarch64)
arch=arm64
;;
*)
# TODO(alainv): Add support for extracting arch from board configs.
echo Unknown target \"$1\"
fx-command-help
return 1
;;
esac
shift
cd "${FUCHSIA_DIR}"
local gn_cmd='gen'
local -a gn_switches=(--check)
local gn_args="target_cpu=\"${arch}\""
local boards=()
local products=()
local available=()
local preinstall=()
local monolith=()
local packages=()
local zircon_args=()
local extra_packages=()
local build_dir=
local variant=
local use_goma
local goma_dir
local ensure_goma=1
local ccache
while [[ $# -ne 0 ]]; do
case "$1" in
--board)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
boards+=("$2")
shift
;;
--product)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
products+=("$2")
shift
;;
--available)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
IFS=, available+=("$2")
shift
;;
--preinstall)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
IFS=, preinstall+=("$2")
shift
;;
--monolith)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
IFS=, monolith+=("$2")
shift
;;
--boards)
echo >&2 "--boards is deprecated, --board is preferred"
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
# Multiple boards may be comma separated
IFS=, boards+=("$2")
shift
;;
--products)
echo >&2 "--products is deprecated, --product is preferred"
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
# Multiple products may be comma separated
IFS=, products+=("$2")
shift
;;
--packages)
echo >&2 "--packages is deprecated, --monolith, --preinstall or --available are preferred"
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
# Multiple packages may be comma separated
IFS=, packages+=("$2")
shift
;;
--zircon-arg)
echo >&2 "--zircon-arg is deprecated, common use case is no longer required"
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
zircon_args+=("$2")
shift
;;
--netboot)
extra_packages+=('build/packages/netboot')
;;
--goma)
use_goma=1
;;
--no-goma)
use_goma=0
;;
--no-ensure-goma)
ensure_goma=0
;;
--goma-dir)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
goma_dir=$2
if [[ ! -d "${goma_dir}" ]]; then
echo -e "error: GOMA directory does not exist: "${goma_dir}""
return 1
fi
shift
;;
--release)
gn_args+=" is_debug=false"
;;
--variant)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
variant+="\"$2\","
shift
;;
--fuzz-with)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
variant+="{variant=\"$2-fuzzer\" target_type=[\"fuzzed_executable\"]},"
shift
;;
--args)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
gn_args+=" $2"
shift
;;
--help-args)
gn_cmd=args
if [[ $# -ge 2 ]] && [[ "$2" != '--*' ]]; then
gn_switches+=("--list=$2")
shift
else
gn_switches+=(--list)
fi
;;
--ccache)
ccache=1
;;
--no-ccache)
ccache=0
;;
--ide)
if [[ $# -lt 2 ]]; then
fx-command-help
return 1
fi
gn_switches+=("--ide=$2")
shift
;;
--*)
fx-command-help
return 1
;;
*)
# A non-option argument is the build_dir, but there can be only one.
if [[ -n "$build_dir" ]]; then
fx-command-help
return 1
fi
build_dir="$1"
;;
esac
shift
done
if [[ -z "${products}${packages}" ]]; then
# This is the default logic GN would use, but if a user specified --netboot
# we would short-circuit the logic, so repeat it here.
local layers
layers="$(${FUCHSIA_DIR}/build/gn/guess_layer.py)" || return 1
readonly layers
local layer
for layer in $layers; do
products+="${products:+,}$layer/products/default.gni"
done
fi
if [[ -z "${boards}" ]]; then
# Import the board target definition from the current layer if it exists.
local layers
if [[ -z "${layers}" ]]; then
layers="$(${FUCHSIA_DIR}/build/gn/guess_layer.py)" || return 1
fi
readonly layers
local layer
for layer in $layers; do
if [[ -e "$layer/boards/$arch.gni" ]]; then
boards+="${boards:+,}$layer/boards/$arch.gni"
fi
done
fi
# Remove any trailing slash from build directory name.
build_dir="${build_dir%/}"
local config_build_dir
case "$build_dir" in
'')
# Default is "//out/$target_cpu". Store it as relative.
config_build_dir="out/${arch}"
build_dir="${FUCHSIA_DIR}/${config_build_dir}"
;;
//*|out/*)
# GN-style "source-relative" path or relative out/something.
config_build_dir="${build_dir#//}"
build_dir="${FUCHSIA_DIR}/${config_build_dir}"
;;
*)
# Absolute or relative path. Canonicalize it to source-relative.
local abs_build_dir
abs_build_dir="$(cd "${build_dir%/*}" >/dev/null 2>&1; pwd)/${build_dir##*/}" || {
echo >&2 "ERROR: Missing parent directories for ${build_dir}"
return 1
}
if [[ "$abs_build_dir" == "${FUCHSIA_DIR}"/out/* ]]; then
config_build_dir="${abs_build_dir#${FUCHSIA_DIR}/}"
else
echo >&2 "WARNING: ${abs_build_dir} is not a subdirectory of ${FUCHSIA_DIR}/out"
config_build_dir="$abs_build_dir"
fi
;;
esac
# If a goma directory wasn't specified explicitly then default to "~/goma".
if [[ -z "${goma_dir}" ]]; then
goma_dir="$HOME/goma"
fi
# Automatically detect goma and ccache if not specified explicitly.
if [[ -z "${use_goma}" ]] && [[ -z "${ccache}" ]]; then
if [[ -d "${goma_dir}" ]]; then
use_goma=1
elif [[ -n "${CCACHE_DIR}" ]] && [[ -d "${CCACHE_DIR}" ]]; then
ccache=1
fi
fi
for board in ${boards[@]}; do
if [[ ! -a "${FUCHSIA_DIR}/${board}" ]]; then
local guessed_board=""
guess_config_within_layer "boards" "${board}" guessed_board
board="${guessed_board}"
fi
gn_args+=" import(\"//${board}\")"
done
# Add goma or ccache settings as appropriate.
if [[ "${use_goma}" -eq 1 ]]; then
gn_args+=" use_goma=true goma_dir=\"${goma_dir}\""
elif [[ "${ccache}" -eq 1 ]]; then
gn_args+=" use_ccache=true"
fi
for product in ${products[@]}; do
if [[ ! -a "${FUCHSIA_DIR}/${product}" ]]; then
local guessed_product=""
guess_config_within_layer "products" "${product}" guessed_product
product="${guessed_product}"
fi
gn_args+=" import(\"//${product}\")"
done
# Board configs MUST declare fuchsia_packages, and boards must be non-empty.
gn_args+=" fuchsia_packages+=["
for package in ${packages[@]} ${extra_packages[@]}; do
gn_args+="\"${package}\","
done
gn_args+="]"
gn_args+=" if (!defined(available)) { available = [] }"
gn_args+=" available+=["
for package in ${available[@]}; do
gn_args+="\"${package}\","
done
gn_args+="]"
gn_args+=" if (!defined(preinstall)) { preinstall = [] }"
gn_args+=" preinstall+=["
for package in ${preinstall[@]}; do
gn_args+="\"${package}\","
done
gn_args+="]"
gn_args+=" if (!defined(monolith)) { monolith = [] }"
gn_args+=" monolith+=["
for package in ${monolith[@]}; do
gn_args+="\"${package}\","
done
gn_args+="]"
if [[ -n "${variant}" ]]; then
gn_args+=" select_variant=[${variant}]"
fi
mkdir -p "${build_dir}"
echo "${zircon_args[@]}" > "${build_dir}.zircon-args"
# Using a subshell with -x prints out the gn command precisely with shell
# quoting so a cut&paste to the command line works. Always show the real
# meaning of what this script does so everyone learns how GN works.
(
set -x
"${FUCHSIA_DIR}/buildtools/gn" ${gn_cmd} "${build_dir}" \
"${gn_switches[@]}" --args="${gn_args}" "$@"
# If GN failed, don't update .config.
) || return
fx-config-write "${config_build_dir}"
if [[ "${use_goma}" -eq 1 ]] && [[ "${ensure_goma}" -eq 1 ]]; then
if ! [[ $("${goma_dir}/gomacc" port) =~ ^[0-9]+$ ]]; then
"${goma_dir}/goma_ctl.py" ensure_start || return $?
fi
fi
}
main "$@"