#!/usr/bin/env bash # # This script contains utilities for builder_script calls # # * builder_ functions and variables are defined here. # * REPO_ROOT defines the top level of this repository # * THIS_SCRIPT_PATH defines the full path of the running script # * THIS_SCRIPT_NAME defines the basename of the running script # * THIS_SCRIPT_IDENTIFIER defines the repo-relative path of the running script # * _builder_ functions and variables are internal use only for builder.inc.sh, and # subject to change at any time. Do not use them in other scripts. # * Note: the running script is the top-level script that includes either # builder.inc.sh directly, or, just in the Keyman repo, via build-utils.sh. # # _builder_init is called internally at the bottom of this file after we have # all function declarations in place. function _builder_init() { _builder_findRepoRoot _builder_setBuildScriptIdentifiers if [[ -n "$TERM" ]] && [[ "$TERM" != "dumb" ]] && [[ "$TERM" != "unknown" ]]; then builder_use_color true else builder_use_color false fi } function _builder_findRepoRoot() { # See https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself # None of the answers are 100% correct for cross-platform # On macOS, requires coreutils (`brew install coreutils`) local SCRIPT=$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}") REPO_ROOT=$(dirname $(dirname "$SCRIPT")) readonly REPO_ROOT } # Used to build script-related build variables useful for referencing the calling script # and for prefixing `builder_finish_action` outputs in order to more clearly identify the calling # script. # # Assumes that `THIS_SCRIPT` has been set, typically like this: # # ```bash # ## START STANDARD BUILD SCRIPT INCLUDE # # adjust relative paths as necessary # THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" # . "$(dirname "$THIS_SCRIPT")/resources/builder.inc.sh" # ## END STANDARD BUILD SCRIPT INCLUDE # ``` # function _builder_setBuildScriptIdentifiers() { if [ ! -z ${THIS_SCRIPT+x} ]; then THIS_SCRIPT_PATH="$(dirname "$THIS_SCRIPT")" readonly THIS_SCRIPT_PATH THIS_SCRIPT_NAME="$(basename "$THIS_SCRIPT")" readonly THIS_SCRIPT_NAME # Leaves only the part of the path based upon REPO_ROOT. THIS_SCRIPT_IDENTIFIER=${THIS_SCRIPT_PATH#"$REPO_ROOT/"} readonly THIS_SCRIPT_IDENTIFIER else echo "Warning: THIS_SCRIPT not defined; builder.inc.sh has not been sourced with standard script include." fi } ################################################################################ # Standard build script functions for managing command line, actions and targets ################################################################################ # The following allows coloring of warning and error lines, but only works if there's a # terminal attached, so not on the build machine. # Overrides default colorization of logging; can be used in command-line with # --color or --no-color, or overridden as necessary on a per-script basis. # # Parameters # 1: use_color true or false builder_use_color() { if $1; then COLOR_RED=$(tput setaf 1) COLOR_GREEN=$(tput setaf 2) COLOR_YELLOW=$(tput setaf 3) COLOR_BLUE=$(tput setaf 4) COLOR_PURPLE=$(tput setaf 5) COLOR_TEAL=$(tput setaf 6) COLOR_WHITE=$(tput setaf 7) COLOR_GREY=$(tput setaf 8) COLOR_RESET=$(tput sgr0) # e.g. VSCode https://code.visualstudio.com/updates/v1_69#_setmark-sequence-support HEADING_SETMARK='\x1b]1337;SetMark\x07' # Used by `builder_display_usage` when marking special terms (actions, targets, options) # in the plain-text description area. BUILDER_TERM_START="$COLOR_BLUE" BUILDER_TERM_END="$COLOR_RESET" else COLOR_RED= COLOR_GREEN= COLOR_YELLOW= COLOR_BLUE= COLOR_PURPLE= COLOR_TEAL= COLOR_WHITE= COLOR_GREY= COLOR_RESET= HEADING_SETMARK= BUILDER_TERM_START="<" BUILDER_TERM_END=">" fi } function builder_die() { echo echo "${COLOR_RED}$*" echo exit 1 } #################################################################################### # # builder_ functions for standard build script parameter and process management # #################################################################################### # # builder_ names are reserved. # _builder_ names are internal use and subject to change # _builder_debug=false # # builder_extra_params: string containing all parameters after '--' # builder_extra_params=() # returns 0 if first parameter is in the array passed as second parameter # # Usage: # if _builder_item_in_array "item" "${array[@]}"; then ...; fi # Parameters: # 1: item item to search for in array # 2: array bash array, e.g. array=(one two three) _builder_item_in_array() { local e match="$1" shift [[ -z "$match" ]] && return 1 for e; do [[ "$e" == $match ]] && return 0; done return 1 } # # Returns `0` if first parameter is in the array passed as second parameter, # where the array may contain globs. # # ### Parameters # # * 1: `item` item to search for in array # * 2: `array` bash array, e.g. `array=(one two three)` # # ### Example # # ```bash # array=(foo bar it*) # if _builder_item_in_glob_array "item" "${array[@]}"; then ...; fi # ``` # _builder_item_in_glob_array() { local e match="$1" shift [[ -z "$match" ]] && return 1 for e; do [[ "$match" == $e ]] && return 0; done return 1 } _builder_item_is_target() { local item="$1" [[ $item =~ ^: ]] && return 1 return 0 } function _builder_warn_if_incomplete() { if [ -n "${_builder_current_action}" ]; then local scope="[$THIS_SCRIPT_IDENTIFIER] " echo "${COLOR_YELLOW}## ${scope}Warning - $_builder_current_action never reported success or failure${COLOR_RESET}" # exit 1 # If we wanted this scenario to result in a forced build-script fail. fi # Since we've already warned about this once, we'll clear the variable to prevent repetitions. _builder_current_action= } # Used by a `trap` statement later to facilitate auto-reporting failures on error detection # without obscuring failure exit/error codes. _builder_failure_trap() { local trappedExitCode=$? local action target _builder_cleanup_deps # Since 'exit' is also trapped, we can also handle end-of-script incomplete actions. if [[ $trappedExitCode == 0 ]]; then # While there weren't errors, were there any actions that never reported success or failure? _builder_warn_if_incomplete return fi # If we've reached this point, we're here because an error occurred. # Iterate across currently-active actions and report their failures. if [ -n "${_builder_current_action}" ]; then action="${_builder_current_action}" if [[ $action =~ : ]]; then IFS=: read -r action target <<< $action target=:$target else target=:project fi builder_finish_action failure $action$target # Make 100% sure that the exit code chains fully. # Without this, nested scripts have failed to chain errors from npm calls past the script # that directly executed the failed npm command. exit $trappedExitCode fi } # # Removes temporary `_builder_deps_built` file when top-level build script # finishes. # _builder_cleanup_deps() { if ! builder_is_dep_build && [[ ! -z ${_builder_deps_built+x} ]]; then if $_builder_debug; then echo "[DEBUG] Dependencies that were built:" cat "$_builder_deps_built" fi rm -f "$_builder_deps_built" _builder_deps_built= fi } # # Builds the standardized `action:target` string for the specified action-target # pairing and also returns 0 if the user has asked to perform it on the command # line. Otherwise, returns 0 and sets an empty string in place of the matched # pair. # # The string will be set as `_builder_matched_action`, which is for # builder.inc.sh internal use, used by `builder_start_action`. # # ### Usage # # ```bash # if build_has_action action[:target]; then ...; fi # ```` # # Parameters: # 1: action[:target] name of action:target # Example: # # ```bash # if builder_has_action build:app; then ... # ``` # builder_has_action() { local action="$1" target if [[ $action =~ : ]]; then IFS=: read -r action target <<< $action target=:$target else target=':*' fi if _builder_item_in_array "$action$target" "${_builder_chosen_action_targets[@]}"; then # To avoid WET re-processing of the $action$target string set _builder_matched_action="$action$target" if [[ $target == ':*' ]]; then _builder_matched_action_name="$action" else _builder_matched_action_name="$action$target" fi return 0 else _builder_matched_action= return 1 fi } # # Returns `0` if the user has asked to perform action on target on the command # line, and then starts the action. Should be paired with # `builder_finish_action`. # # ### Usage # # ```bash # if builder_start_action action[:target]; then ...; fi # ``` # # ### Parameters # # * 1: `action[:target]` name of action, and optionally also target, if # target excluded starts for all defined targets # # ### Example # # ```bash # if builder_start_action build:app; then ... # ``` # builder_start_action() { local scope="[$THIS_SCRIPT_IDENTIFIER] " if builder_has_action $1; then # In a dependency quick build (the default), determine whether we actually # need to run this step. Uses data passed to builder_describe_outputs to # verify whether a target output is present. if builder_is_dep_build && ! builder_is_full_dep_build && [[ ! -z ${_builder_dep_path[$_builder_matched_action]+x} ]] && [[ -e "$KEYMAN_ROOT/${_builder_dep_path[$_builder_matched_action]}" ]]; then echo "$scope skipping $_builder_matched_action_name, up-to-date" return 1 fi echo "${COLOR_BLUE}## $scope$_builder_matched_action_name starting...${COLOR_RESET}" if [ -n "${_builder_current_action}" ]; then _builder_warn_if_incomplete fi _builder_current_action="$_builder_matched_action" # Build dependencies as required _builder_do_build_deps "$_builder_matched_action" return 0 else return 1 fi } # # Returns 0 if the user has --option on the command line # # Usage: # if build_has_option option; then ...; fi # Parameters: # 1: option name of option, i.e. --option # Example: # if build_has_option --debug; then # builder_has_option() { local option="$1" if _builder_item_in_array "$option" "${_builder_chosen_options[@]}"; then return 0 fi return 1 } _builder_trim() { local var="$*" # remove leading whitespace characters var="${var#"${var%%[![:space:]]*}"}" # remove trailing whitespace characters var="${var%"${var##*[![:space:]]}"}" printf '%s' "$var" } # # Expands an in-repo-relative path to a repo-relative path. A path starting with # `/` is expected to be relative to repo root, not filesystem root. Otherwise, # it's relative to current script path, not current working directory. The # returned path will not have a prefix `/`, and will be relative to # `$KEYMAN_ROOT`. Assumes realpath is installed (brew coreutils on macOS). # _builder_expand_relative_path() { local path="$1" if [[ "$path" =~ ^/ ]]; then echo "${path:1}" else realpath --canonicalize-missing --relative-to="$KEYMAN_ROOT" "$THIS_SCRIPT_PATH/$path" fi } # # Expands an `[action][:target]` string, replacing missing values with `*`, # for example: # # * `build` --> `build:*` # * `build:app` --> `build:app` # * `:app` --> `*:app` # # Supports multiple action:targets in the string # _builder_expand_action_target() { local input="$1" target= action= if [[ "$input" =~ : ]]; then action=$(echo "$input" | cut -d: -f 1 -) target=$(echo "$input" | cut -d: -f 2 -) else action=$input fi if [[ -z "$action" ]]; then action='*' fi if [[ -z "$target" ]]; then target='*' fi echo "$action:$target" } _builder_expand_action_targets() { local input=($1) e output=() for e in "${input[@]}"; do e=`_builder_expand_action_target "$e"` output+=($e) done if [[ ${#output[@]} == 0 ]]; then echo "*:*" else echo "${output[@]}" fi } # # Describes a build script, defines available parameters and their meanings. Use # together with `builder_parse` to process input parameters. # # ### Usage # # ```bash # builder_describe description param_desc... # ``` # # ### Parameters # # * `description` A short description of what the script does. # * `param_desc` Space separated name and description of parameter, e.g. # `"build Builds the target"` # This parameter may be repeated to describe all parameters. # # There are four types of parameters that may be specified: # # * **Option:** `"--option[,-o][=var] [One line description]"` # # All options must have a longhand form with two prefix hyphens, # e.g. `--option`. The `,-o` shorthand form is optional. When testing if # the option is set with `builder_has_option`, always use the longhand # form. # # if `=var` is specified, then the next parameter will be a variable stored in # `$var` for that option. e.g. `--option=opt` means `$opt` will have the value # `"foo"` when the script is called for `--option foo`. # # * **Action**: `"action [One line description]"` # # Actions must be a single word, lower case. To specify an action as the # default, append a `+` to the action name, e.g. `"test+ Test the project"`. # If there is no default specified, then it will be `build`. # # * **Target:** `":target [One line description]"` # # A target always starts with colon, e.g. `:project`. # # * **Dependency:** "@/path/to/dependency [action][:target] ..." # # A dependency always starts with `@`. The path to the dependency will be # relative to the build script folder, or to the root of the repository, if # the path starts with `/`, not to the root of the file system. It is an error # to specify a dependency outside the repo root. # # Relative paths will be expanded to full paths, again, relative to the root # of the repository. # # Dependencies may be limited to specific `action:target`. If not specified, # dependencies will be built for all actions on all targets. Either `action` # or `:target` may be omitted, and multiple actions and targets may be # specified, space separated. # builder_describe() { _builder_description="$1" _builder_actions=() _builder_targets=() _builder_options=() _builder_deps=() # array of all dependencies for this script _builder_default_action=build declare -A -g _builder_params declare -A -g _builder_options_short declare -A -g _builder_options_var declare -A -g _builder_dep_path # array of output files for action:target pairs declare -A -g _builder_dep_related_actions # array of action:targets associated with a given dependency declare -A -g _builder_internal_dep # array of internal action:targets dependency relationships shift # describe each target, action, and option possibility while [[ $# -gt 0 ]]; do local key="$1" local value="$(echo "$key" | cut -d" " -f 1 -)" local description= if [[ $key =~ [[:space:]] ]]; then description="$(_builder_trim "$(echo "$key" | cut -d" " -f 2- -)")" fi if [[ $value =~ ^: ]]; then # Parameter is a target _builder_targets+=($value) elif [[ $value =~ ^@ ]]; then # Parameter is a dependency local dependency="${value:1}" dependency="`_builder_expand_relative_path "$dependency"`" _builder_deps+=($dependency) _builder_dep_related_actions[$dependency]="`_builder_expand_action_targets "$description"`" # We don't want to add deps to params, so shift+continue shift continue elif [[ $value =~ ^-- ]]; then # Parameter is an option # Look for a shorthand version of the option local option_var= if [[ $value =~ = ]]; then option_var="$(echo "$value" | cut -d= -f 2 -)" value="$(echo "$value" | cut -d= -f 1 -)" fi if [[ $value =~ , ]]; then local option_long="$(echo "$value" | cut -d, -f 1 -)" local option_short="$(echo "$value" | cut -d, -f 2 -)" _builder_options+=($option_long) _builder_options_short[$option_short]="$option_long" if [[ ! -z "$option_var" ]]; then _builder_options_var[$option_long]="$option_var" fi value="$option_long, $option_short" else _builder_options+=($value) if [[ ! -z "$option_var" ]]; then _builder_options_var[$value]="$option_var" fi fi if [[ ! -z $option_var ]]; then value="$value $option_var" fi else # Parameter is an action if [[ $value =~ \+$ ]]; then # If the action name has a '+' suffix then it is the default action value=${value//+} _builder_default_action=$value fi _builder_actions+=($value) fi if [[ -z "${description}" ]]; then description=$(_builder_get_default_description "$value") fi _builder_params[${value}]="$description" shift done # We'll always add a :project if no target is specified if (( ! ${#_builder_targets[@]} )); then _builder_targets+=(:project) _builder_params[\:project]=$(_builder_get_default_description ":project") fi } # # Defines an output file or folder expected to be present after successful # completion of an action for a target. Used to skip actions for dependency # builds. If `:target` is not provided, assumes `:project`. # # Relative paths are relative to script folder; absolute paths are relative # to repository root, not filesystem root. # # ### Usage # # ```bash # builder_describe_outputs action:target filename [...] # ``` # # ### Parameters # # * 1: `action[:target]` action and/or target associated with file # * 2: `filename` name of file or folder to check # * 3+: ... repeat previous arguments for additional outputs # # ### Example # # ```bash # builder_describe_outputs \ # configure /node_modules \ # build build/index.js # ``` # function builder_describe_outputs() { while [[ $# -gt 0 ]]; do local key="$1" path="$2" action target path="`_builder_expand_relative_path "$path"`" if [[ $key =~ : ]]; then action="$(echo "$key" | cut -d: -f 1 -)" target=":$(echo "$key" | cut -d: -f 2 -)" else # Add dependency expected output file for all targets, as well as a # wildcard target match action="$key" for target in "${_builder_targets[@]}"; do _builder_dep_path[$action$target]="$path" done target=':*' fi _builder_dep_path[$action$target]="$path" shift 2 done _builder_define_default_internal_dependencies } _builder_get_default_description() { local description= local value="$1" # default descriptions for common build actions, targets, and options case "$value" in clean) description="remove build/ folder and build artifacts" ;; configure) description="install dependencies, e.g. npm" ;; build) description="build target(s)" ;; test) description="run automated tests" ;; :project) description="this project" ;; :app) description="main app" ;; :engine) description="engine module" ;; :module) description="this module" ;; :tools) description="build tools for this project" ;; --debug) description="debug build" ;; esac echo "$description" } _builder_parameter_error() { local program="$1" local type="$2" local param="$3" echo "$COLOR_RED$program: invalid $type: $param$COLOR_RESET" echo builder_display_usage exit 64 } # Pre-initializes the color setting based on the options specified to a # a build.sh script, parsing the command line to do so. This is only # needed if said script wishes to use this script's defined colors while # respecting the options provided by the script's caller. # # Usage: # builder_check_color "$@" # Parameters # 1: $@ all command-line arguments (as with builder_parse) builder_check_color() { # Process command-line arguments while [[ $# -gt 0 ]] ; do local key="$1" case "$key" in --color) builder_use_color true ;; --no-color) builder_use_color false ;; esac shift # past the processed argument done } # # For every build action:target in _builder_chosen_action_targets, add # its full internal dependency tree # _builder_add_chosen_action_target_dependencies() { local action_target e i=0 new_actions=() # Iterate through every action specified on command line; we use this loop # style so that any new actions added here will also be iteratively checked while (( $i < ${#_builder_chosen_action_targets[@]} )); do action_target=${_builder_chosen_action_targets[$i]} # If we have an internal dependency for the chosen action:target pair, add # it to the list, but only if there is a defined output and that output is # missing if [[ ! -z ${_builder_internal_dep[$action_target]+x} ]]; then local dep_output=${_builder_internal_dep[$action_target]} if [[ ! -z ${_builder_dep_path[$dep_output]+x} ]] && [[ ! -e "$KEYMAN_ROOT/${_builder_dep_path[$dep_output]}" ]]; then if ! _builder_item_in_array "$dep_output" "${_builder_chosen_action_targets[@]}"; then _builder_chosen_action_targets+=($dep_output) new_actions+=($dep_output) fi fi fi i=$((i + 1)) done if [[ ${#new_actions[@]} -gt 0 ]]; then echo "Automatically running following required actions with missing outputs:" for e in "${new_actions[@]}"; do echo "* $e" done fi } # # If we have described outputs, then we will setup our # default internal dependency chain: # # configure <- build <- (test,install,publish) # _builder_define_default_internal_dependencies() { for target in "${_builder_targets[@]}"; do _builder_define_default_internal_deps_for_target "$target" done _builder_define_default_internal_deps_for_target ':*' } _builder_define_default_internal_deps_for_target() { local target=$1 _builder_define_default_internal_dep "$target" configure build _builder_define_default_internal_dep "$target" build test _builder_define_default_internal_dep "$target" build install } _builder_define_default_internal_dep() { local target=$1 dep=$2 action=$3 if _builder_item_in_array $dep "${_builder_actions[@]}" && _builder_item_in_array $action "${_builder_actions[@]}"; then _builder_internal_dep[$action$target]=$dep$target fi } # Initializes a build.sh script, parses command line. Will abort the script if # invalid parameters are passed in. Use together with builder_describe which # sets up the possible command line parameters # # Usage: # builder_parse "$@" # Parameters # 1: $@ command-line arguments builder_parse() { _builder_build_deps=--deps builder_verbose= builder_debug= builder_extra_params=() _builder_chosen_action_targets=() _builder_chosen_options=() _builder_current_action= # Process command-line arguments while [[ $# -gt 0 ]] ; do local key="$1" local action= local target= local e has_action has_target has_option longhand_option if [[ $key == "--" ]]; then shift builder_extra_params=("$@") break fi if [[ $key =~ : ]]; then IFS=: read -r action target <<< $key target=:$target else action="$key" target= fi _builder_item_in_array "$action" "${_builder_actions[@]}" && has_action=1 || has_action=0 _builder_item_in_array "$target" "${_builder_targets[@]}" && has_target=1 || has_target=0 # Expand short -o to --option in options lookup if [[ ! -z ${_builder_options_short[$key]+x} ]]; then key=${_builder_options_short[$key]} fi _builder_item_in_array "$key" "${_builder_options[@]}" && has_option=1 || has_option=0 if (( has_action )) && (( has_target )); then # apply the selected action and selected target _builder_chosen_action_targets+=("$key") elif (( has_action )); then # apply the selected action to all targets if [[ ! -z $target ]]; then # A target was specified but is not valid _builder_parameter_error "$0" target "$target" fi for e in "${_builder_targets[@]}"; do _builder_chosen_action_targets+=("$action$e") done elif (( has_target )); then # apply the default action to the selected target if [[ ! -z $action ]]; then # An action was specified but is not valid _builder_parameter_error "$0" action "$action" fi _builder_chosen_action_targets+=("$_builder_default_action$target") elif (( has_option )); then _builder_chosen_options+=("$key") if [[ ! -z ${_builder_options_var[$key]+x} ]]; then shift if [[ $# -eq 0 ]]; then _builder_parameter_error "$0" parameter "$key" fi # Set the variable associated with this option to the next parameter value # A little bit of hoop jumping here to avoid issues with cygwin paths being # corrupted too early in the game local varname=${_builder_options_var[$key]} declare -g $varname="$1" fi else case "$key" in --help|-h) builder_display_usage exit 0 ;; --color) builder_use_color true ;; --no-color) builder_use_color false ;; --verbose|-v) _builder_chosen_options+=(--verbose) builder_verbose=--verbose ;; --debug|-d) _builder_chosen_options+=(--debug) builder_debug=--debug ;; --deps|--no-deps|--force-deps) _builder_build_deps=$key ;; --builder-dep-parent) # internal use parameter for dependency builds - identifier of parent script shift builder_dep_parent="$1" ;; --builder-deps-built) # internal use parameter for dependency builds - path to dependency tracking file shift _builder_deps_built="$1" ;; --builder-report-dependencies) # internal reporting function, ignores all other parameters _builder_report_dependencies ;; *) _builder_parameter_error "$0" parameter "$key" esac fi shift # past the processed argument done if (( ! ${#_builder_chosen_action_targets[@]} )); then for e in "${_builder_targets[@]}"; do _builder_chosen_action_targets+=("$_builder_default_action$e") done fi _builder_add_chosen_action_target_dependencies if $_builder_debug; then echo "[DEBUG] Selected actions and targets:" for e in "${_builder_chosen_action_targets[@]}"; do echo "* $e" done echo echo "[DEBUG] Selected options:" for e in "${_builder_chosen_options[@]}"; do echo "* $e" done fi if builder_is_dep_build; then echo "[$THIS_SCRIPT_IDENTIFIER] dependency build, started by $builder_dep_parent" if [[ -z ${_builder_deps_built+x} ]]; then echo "FATAL ERROR: Expected --builder-deps-built parameter" exit 1 fi else # This is a top-level invocation, not a dependency build, so we want to # track which dependencies have been built, so they don't get built multiple # times. _builder_deps_built=`mktemp` fi # Now that we've successfully parsed options adhering to the _builder spec, we may activate our # action_failure and action_hanging traps. (We don't want them active on scripts not yet using # said script.) # # Note: if an error occurs within a script's function in a `set -e` script, it becomes an exit # instead for the function's caller. So, we need both `err` and `exit` here. # See https://medium.com/@dirk.avery/the-bash-trap-trap-ce6083f36700. trap _builder_failure_trap err exit } _builder_pad() { local count=$1 local text1=$2 local text2=$3 local fmt="%-${count}s%s\n" printf $fmt "$text1" "$text2" } builder_display_usage() { local e program description # Minimum padding is 12 characters, increase this if necessary # if you add other, longer, global options (like --verbose, --debug) local width=12 for e in "${!_builder_params[@]}"; do if (( ${#e} > $width )); then width=${#e} fi done width=$((width + 6)) program="$(basename "$0")" if [[ ! -z ${_builder_description+x} ]]; then echo "Summary:" echo " $_builder_description" echo fi echo "Script Identifier:" echo " $THIS_SCRIPT_IDENTIFIER" echo echo "Usage:" echo " $program [options...] [action][:target]..." echo echo "Actions: " for e in "${_builder_actions[@]}"; do if [[ -v _builder_params[$e] ]]; then description="${_builder_params[$e]}" else description=$(_builder_get_default_description "$e") fi _builder_pad $width " $e" "$description" done echo echo "Targets: " for e in "${_builder_targets[@]}"; do if [[ -v _builder_params[$e] ]]; then description="${_builder_params[$e]}" else description=$(_builder_get_default_description "$e") fi _builder_pad $width " $e" "$description" done echo echo "Options: " for e in "${!_builder_params[@]}"; do if [[ $e =~ ^-- ]]; then _builder_pad $width " $e" "${_builder_params[$e]}" fi done _builder_pad $width " --verbose, -v" "Verbose logging" _builder_pad $width " --debug, -d" "Debug build" _builder_pad $width " --color" "Force colorized output" _builder_pad $width " --no-color" "Never use colorized output" if builder_has_dependencies; then _builder_pad $width " --deps" "Build dependencies if required (default)" _builder_pad $width " --no-deps" "Skip build of dependencies" _builder_pad $width " --force-deps" "Reconfigure and rebuild all dependencies" fi _builder_pad $width " --help, -h" "Show this help" echo echo "Dependencies: " if builder_has_dependencies; then for d in "${_builder_deps[@]}"; do echo " $d" done else echo " This module has no dependencies" fi # Defined in `builder_use_color`; this assumes that said func has been called. local c1=$BUILDER_TERM_START local c0=$BUILDER_TERM_END echo echo "* Specify ${c1}action:target${c0} to run a specific ${c1}action${c0} against a specific ${c1}:target${c0}." echo "* If ${c1}action${c0} is specified without a ${c1}target${c0} suffix, it will be applied to all ${c1}:target${c0}s." echo "* If ${c1}:target${c0} is specified without an ${c1}action${c0} prefix, ${c1}$_builder_default_action:target${c0} will be inferred." echo "* If no ${c1}action${c0}, ${c1}:target${c0}, or ${c1}action:target${c0} entries are specified, ${c1}$_builder_default_action${c0} will run on all ${c1}:target${c0}s." echo } builder_finish_action() { local result="$1" local action="$2" target action_name if [[ $action =~ : ]]; then IFS=: read -r action target <<< $action target=":$target" else target=':*' fi if [[ "$target" == ":*" ]]; then action_name="$action" else action_name="$action$target" fi local scope="[$THIS_SCRIPT_IDENTIFIER] " if [[ "$action$target" == "${_builder_current_action}" ]]; then if [[ $result == success ]]; then echo "${COLOR_GREEN}## $scope$action_name completed successfully${COLOR_RESET}" elif [[ $result == failure ]]; then echo "${COLOR_RED}## $scope$action_name failed${COLOR_RESET}" else echo "${COLOR_RED}## $scope$action_name failed with message: $result${COLOR_RESET}" fi # Remove $action$target from the array; it is no longer a current action _builder_current_action= else echo "${COLOR_YELLOW}## Warning: reporting result of $action_name but the action was never started!${COLOR_RESET}" fi } # # Returns `0` if the dependency should be built for the given action:target # _builder_should_build_dep() { local action_target="$1" local dep="$2" local related_actions=(${_builder_dep_related_actions[$dep]}) # echo "bdra: ${_builder_dep_related_actions[@]}" # echo "target: $action_target" # echo "dep: $2" # echo "ra: ${related_actions[@]}" if ! _builder_item_in_glob_array "$action_target" "${related_actions[@]}"; then return 1 fi return 0 } # # Configure and build all dependencies # Later, may restrict by either action or target # _builder_do_build_deps() { local action_target="$1" if [[ $_builder_build_deps == --no-deps ]]; then # we've been asked to skip dependencies return 0 fi for dep in "${_builder_deps[@]}"; do # Don't attempt to build dependencies that don't match the current # action:target (wildcards supported for matches here) if ! _builder_should_build_dep "$action_target" "$dep"; then echo "[$THIS_SCRIPT_IDENTIFIER] Skipping dependency build $dep for $_builder_matched_action_name" continue fi # Only configure and build the dependency once per invocation if builder_has_module_been_built "$dep"; then continue fi # TODO: add --debug as a standard builder parameter builder_set_module_has_been_built "$dep" "$KEYMAN_ROOT/$dep/build.sh" configure build \ $builder_verbose \ $builder_debug \ $_builder_build_deps \ --builder-deps-built "$_builder_deps_built" \ --builder-dep-parent "$THIS_SCRIPT_IDENTIFIER" done } # # returns `0` if we are in a dependency doing a build. # builder_is_dep_build() { if [[ ! -z ${builder_dep_parent+x} ]]; then return 0 fi return 1 } # # returns `0` if we should attempt to do quick builds in a dependency build, for # example skipping `tsc -b` where a parent may also do it; corresponds to the # `--deps` parameter (which is the default). # builder_is_quick_dep_build() { if builder_is_dep_build && [[ $_builder_build_deps == --deps ]]; then return 0 fi return 1 } # # returns `0` if we should do a full configure and build in a dependency build; # corresponds to the `--force-deps`` parameter. # builder_is_full_dep_build() { if builder_is_dep_build && [[ $_builder_build_deps == --force-deps ]]; then return 0 fi return 1 } # # returns `0` if the current build script has at least one dependency. # builder_has_dependencies() { if [[ ${#_builder_deps[@]} -eq 0 ]]; then return 1 fi return 0 } # # Tests if a dependency module has been built already in the current script # invocation; if not running in a builder context, always returns `1` (i.e. # "false"). # # ### Usage # # ```bash # builder_has_module_been_built dependency-name # ``` # # ### Parameters # # * 1: `dependency-name` the `$SCRIPT_IDENTIFIER` of the dependency # (repo-relative path without leading `/`); or for # external dependencies, a path-like starting with # `/external/`. # # ### Examples # # ```bash # if builder_has_module_been_built common/web/keyman-version; then ... # if builder_has_module_been_built /external/npm-ci; then ... # ``` # builder_has_module_been_built() { local module="$1" if [[ -z ${_builder_deps_built+x} ]]; then # not in a builder context, so we assume a build is needed return 1 fi if [[ -f $_builder_deps_built ]] && grep -qx "$module" $_builder_deps_built; then # dependency history file contains the dependency module return 0 fi return 1 } # # Updates the dependency module build state for the current script invocation; # if not running in a builder context, a no-op. # # ### Usage # # ```bash # builder_set_module_has_been_built dependency-name # ``` # # ### Parameters # # * 1: `dependency-name` the `$SCRIPT_IDENTIFIER` of the dependency # (repo-relative path without leading `/`); or for # external dependencies, a path-like starting with # `/external/`. # # ### Examples # # ```bash # builder_set_module_has_been_built common/web/keyman-version # builder_set_module_has_been_built /external/npm-ci # ``` # builder_set_module_has_been_built() { local module="$1" if [[ ! -z ${_builder_deps_built+x} ]]; then echo "$module" >> $_builder_deps_built fi } # # returns `0` if we should be verbose in output # builder_verbose() { if [[ $builder_verbose == --verbose ]]; then return 0 fi return 1 } # # returns `0` if we are doing a debug build # builder_debug() { if [[ $builder_debug == --debug ]]; then return 0 fi return 1 } # # Reports on all described dependencies, then exits # used by builder-controls.sh # _builder_report_dependencies() { echo "${_builder_deps[@]}" exit 0 } # # Initialize builder once all functions are declared # _builder_init