Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/next/scripts/extensions.sh
Views: 3960
# global variables managing the state of the extension manager. treat as private.1declare -A extension_function_info # maps a function name to a string with KEY=VALUEs information about the defining extension2declare -i initialize_extension_manager_counter=0 # how many times has the extension manager initialized?3declare -A defined_hook_point_functions # keeps a map of hook point functions that were defined and their extension info4declare -A hook_point_function_trace_sources # keeps a map of hook point functions that were actually called and their source5declare -A hook_point_function_trace_lines # keeps a map of hook point functions that were actually called and their source6declare fragment_manager_cleanup_file # this is a file used to cleanup the manager's produced functions, for build_all_ng7# configuration.8export DEBUG_EXTENSION_CALLS=no # set to yes to log every hook function called to the main build log9export LOG_ENABLE_EXTENSION=yes # colorful logs with stacktrace when enable_extension is called.1011# This is a helper function for calling hooks.12# It follows the pattern long used in the codebase for hook-like behaviour:13# [[ $(type -t name_of_hook_function) == function ]] && name_of_hook_function14# but with the following added behaviors:15# 1) it allows for many arguments, and will treat each as a hook point.16# this allows for easily kept backwards compatibility when renaming hooks, for example.17# 2) it will read the stdin and assume it's (Markdown) documentation for the hook point.18# combined with heredoc in the call site, it allows for "inline" documentation about the hook19# notice: this is not involved in how the hook functions came to be. read below for that.20call_extension_method() {21# First, consume the stdin and write metadata about the call.22write_hook_point_metadata "$@" || true2324# @TODO: hack to handle stdin again, possibly with '< /dev/tty'2526# Then a sanity check, hook points should only be invoked after the manager has initialized.27if [[ ${initialize_extension_manager_counter} -lt 1 ]]; then28display_alert "Extension problem" "Call to call_extension_method() (in ${BASH_SOURCE[1]- $(get_extension_hook_stracktrace "${BASH_SOURCE[*]}" "${BASH_LINENO[*]}")}) before extension manager is initialized." "err"29fi3031# With DEBUG_EXTENSION_CALLS, log the hook call. Users might be wondering what/when is a good hook point to use, and this is visual aid.32[[ "${DEBUG_EXTENSION_CALLS}" == "yes" ]] &&33display_alert "--> Extension Method '${1}' being called from" "$(get_extension_hook_stracktrace "${BASH_SOURCE[*]}" "${BASH_LINENO[*]}")" ""3435# Then call the hooks, if they are defined.36for hook_name in "$@"; do37echo "-- Extension Method being called: ${hook_name}" >>"${EXTENSION_MANAGER_LOG_FILE}"38# shellcheck disable=SC208639[[ $(type -t ${hook_name}) == function ]] && { ${hook_name}; }40done41}4243# what this does is a lot of bash mumbo-jumbo to find all board-,family-,config- or user-defined hook points.44# the meat of this is 'compgen -A function', which is bash builtin that lists all defined functions.45# it will then compose a full hook point (function) that calls all the implementing hooks.46# this centralized function will then be called by the regular Armbian build system, which is oblivious to how47# it came to be. (although it is encouraged to call hook points via call_extension_method() above)48# to avoid hard coding the list of hook-points (eg: user_config, image_tweaks_pre_customize, etc) we use49# a marker in the function names, namely "__" (two underscores) to determine the hook point.50initialize_extension_manager() {51# before starting, auto-add extensions specified (eg, on the command-line) via the ENABLE_EXTENSIONS env var. Do it only once.52[[ ${initialize_extension_manager_counter} -lt 1 ]] && [[ "${ENABLE_EXTENSIONS}" != "" ]] && {53local auto_extension54for auto_extension in $(echo "${ENABLE_EXTENSIONS}" | tr "," " "); do55ENABLE_EXTENSION_TRACE_HINT="ENABLE_EXTENSIONS -> " enable_extension "${auto_extension}"56done57}5859# This marks the manager as initialized, no more extensions are allowed to load after this.60export initialize_extension_manager_counter=$((initialize_extension_manager_counter + 1))6162# Have a unique temporary dir, even if being built concurrently by build_all_ng.63export EXTENSION_MANAGER_TMP_DIR="${SRC}/.tmp/.extensions/${LOG_SUBPATH}"64mkdir -p "${EXTENSION_MANAGER_TMP_DIR}"6566# Log destination.67export EXTENSION_MANAGER_LOG_FILE="${EXTENSION_MANAGER_TMP_DIR}/extensions.log"68echo -n "" >"${EXTENSION_MANAGER_TMP_DIR}/hook_point_calls.txt"6970# globally initialize the extensions log.71echo "-- lib/extensions.sh included. logs will be below, followed by the debug generated by the initialize_extension_manager() function." >"${EXTENSION_MANAGER_LOG_FILE}"7273# log whats happening.74echo "-- initialize_extension_manager() called." >>"${EXTENSION_MANAGER_LOG_FILE}"7576# this is the all-important separator.77local hook_extension_delimiter="__"7879# list all defined functions. filter only the ones that have the delimiter. get only the part before the delimiter.80# sort them, and make them unique. the sorting is required for uniq to work, and does not affect the ordering of execution.81# get them on a single line, space separated.82local all_hook_points83all_hook_points="$(compgen -A function | grep "${hook_extension_delimiter}" | awk -F "${hook_extension_delimiter}" '{print $1}' | sort | uniq | xargs echo -n)"8485declare -i hook_points_counter=0 hook_functions_counter=0 hook_point_functions_counter=08687# initialize the cleanups file.88fragment_manager_cleanup_file="${SRC}"/.tmp/extension_function_cleanup.sh89echo "# cleanups: " >"${fragment_manager_cleanup_file}"9091local FUNCTION_SORT_OPTIONS="--general-numeric-sort --ignore-case" # --random-sort could be used to introduce chaos92local hook_point=""93# now loop over the hook_points.94for hook_point in ${all_hook_points}; do95echo "-- hook_point ${hook_point}" >>"${EXTENSION_MANAGER_LOG_FILE}"9697# check if the hook point is already defined as a function.98# that can happen for example with user_config(), that can be implemented itself directly by a userpatches config.99# for now, just warn, but we could devise a way to actually integrate it in the call list.100# or: advise the user to rename their user_config() function to something like user_config__make_it_awesome()101local existing_hook_point_function102existing_hook_point_function="$(compgen -A function | grep "^${hook_point}\$")"103if [[ "${existing_hook_point_function}" == "${hook_point}" ]]; then104echo "--- hook_point_functions (final sorted realnames): ${hook_point_functions}" >>"${EXTENSION_MANAGER_LOG_FILE}"105display_alert "Extension conflict" "function ${hook_point} already defined! ignoring functions: $(compgen -A function | grep "^${hook_point}${hook_extension_delimiter}")" "wrn"106continue107fi108109# for each hook_point, obtain the list of implementing functions.110# the sort order here is (very) relevant, since it determines final execution order.111# so the name of the functions actually determine the ordering.112local hook_point_functions hook_point_functions_pre_sort hook_point_functions_sorted_by_sort_id113114# Sorting. Multiple extensions (or even the same extension twice) can implement the same hook point115# as long as they have different function names (the part after the double underscore __).116# the order those will be called depends on the name; eg:117# 'hook_point__033_be_awesome()' would be caller sooner than 'hook_point__799_be_even_more_awesome()'118# independent from where they were defined or in which order the extensions containing them were added.119# since requiring specific ordering could hamper portability, we reward extension authors who120# don't mind ordering for writing just: 'hook_point__be_just_awesome()' which is automatically rewritten121# as 'hook_point__500_be_just_awesome()'.122# extension authors who care about ordering can use the 3-digit number, and use the context variables123# HOOK_ORDER and HOOK_POINT_TOTAL_FUNCS to confirm in which order they're being run.124125# gather the real names of the functions (after the delimiter).126hook_point_functions_pre_sort="$(compgen -A function | grep "^${hook_point}${hook_extension_delimiter}" | awk -F "${hook_extension_delimiter}" '{print $2}' | xargs echo -n)"127echo "--- hook_point_functions_pre_sort: ${hook_point_functions_pre_sort}" >>"${EXTENSION_MANAGER_LOG_FILE}"128129# add "500_" to the names of function that do NOT start with a number.130# keep a reference from the new names to the old names (we'll sort on the new, but invoke the old)131declare -A hook_point_functions_sortname_to_realname132declare -A hook_point_functions_realname_to_sortname133for hook_point_function_realname in ${hook_point_functions_pre_sort}; do134local sort_id="${hook_point_function_realname}"135[[ ! $sort_id =~ ^[0-9] ]] && sort_id="500_${sort_id}"136hook_point_functions_sortname_to_realname[${sort_id}]="${hook_point_function_realname}"137hook_point_functions_realname_to_sortname[${hook_point_function_realname}]="${sort_id}"138done139140# actually sort the sort_id's...141# shellcheck disable=SC2086142hook_point_functions_sorted_by_sort_id="$(echo "${hook_point_functions_realname_to_sortname[*]}" | tr " " "\n" | LC_ALL=C sort ${FUNCTION_SORT_OPTIONS} | xargs echo -n)"143echo "--- hook_point_functions_sorted_by_sort_id: ${hook_point_functions_sorted_by_sort_id}" >>"${EXTENSION_MANAGER_LOG_FILE}"144145# then map back to the real names, keeping the order..146hook_point_functions=""147for hook_point_function_sortname in ${hook_point_functions_sorted_by_sort_id}; do148hook_point_functions="${hook_point_functions} ${hook_point_functions_sortname_to_realname[${hook_point_function_sortname}]}"149done150# shellcheck disable=SC2086151hook_point_functions="$(echo -n ${hook_point_functions})"152echo "--- hook_point_functions (final sorted realnames): ${hook_point_functions}" >>"${EXTENSION_MANAGER_LOG_FILE}"153154hook_point_functions_counter=0155hook_points_counter=$((hook_points_counter + 1))156157# determine the variables we'll pass to the hook function during execution.158# this helps the extension author create extensions that are portable between userpatches and official Armbian.159# shellcheck disable=SC2089160local common_function_vars="HOOK_POINT=\"${hook_point}\""161162# loop over the functions for this hook_point (keep a total for the hook point and a grand running total)163for hook_point_function in ${hook_point_functions}; do164hook_point_functions_counter=$((hook_point_functions_counter + 1))165hook_functions_counter=$((hook_functions_counter + 1))166done167common_function_vars="${common_function_vars} HOOK_POINT_TOTAL_FUNCS=\"${hook_point_functions_counter}\""168169echo "-- hook_point: ${hook_point} will run ${hook_point_functions_counter} functions: ${hook_point_functions}" >>"${EXTENSION_MANAGER_LOG_FILE}"170local temp_source_file_for_hook_point="${EXTENSION_MANAGER_TMP_DIR}/extension_function_definition.sh"171172hook_point_functions_loop_counter=0173174# prepare the cleanup for the function, so we can remove our mess at the end of the build.175cat <<-FUNCTION_CLEANUP_FOR_HOOK_POINT >>"${fragment_manager_cleanup_file}"176unset ${hook_point}177FUNCTION_CLEANUP_FOR_HOOK_POINT178179# now compose a function definition. notice the heredoc. it will be written to tmp file, logged, then sourced.180# theres a lot of opportunities here, but for now I keep it simple:181# - execute functions in the order defined by ${hook_point_functions} above182# - define call-specific environment variables, to help extension authors to write portable extensions (eg: EXTENSION_DIR)183cat <<-FUNCTION_DEFINITION_HEADER >"${temp_source_file_for_hook_point}"184${hook_point}() {185echo "*** Extension-managed hook starting '${hook_point}': will run ${hook_point_functions_counter} functions: '${hook_point_functions}'" >>"\${EXTENSION_MANAGER_LOG_FILE}"186FUNCTION_DEFINITION_HEADER187188for hook_point_function in ${hook_point_functions}; do189hook_point_functions_loop_counter=$((hook_point_functions_loop_counter + 1))190191# store the full name in a hash, so we can track which were actually called later.192defined_hook_point_functions["${hook_point}${hook_extension_delimiter}${hook_point_function}"]="DEFINED=yes ${extension_function_info["${hook_point}${hook_extension_delimiter}${hook_point_function}"]}"193194# prepare the call context195local hook_point_function_variables="${common_function_vars}" # start with common vars... (eg: HOOK_POINT_TOTAL_FUNCS)196# add the contextual extension info for the function (eg, EXTENSION_DIR)197hook_point_function_variables="${hook_point_function_variables} ${extension_function_info["${hook_point}${hook_extension_delimiter}${hook_point_function}"]}"198# add the current execution counter, so the extension author can know in which order it is being actually called199hook_point_function_variables="${hook_point_function_variables} HOOK_ORDER=\"${hook_point_functions_loop_counter}\""200201# add it to our (not the call site!) environment. if we export those in the call site, the stack is corrupted.202eval "${hook_point_function_variables}"203204# output the call, passing arguments, and also logging the output to the extensions log.205# attention: don't pipe here (eg, capture output), otherwise hook function cant modify the environment (which is mostly the point)206# @TODO: better error handling. we have a good opportunity to 'set -e' here, and 'set +e' after, so that extension authors are encouraged to write error-free handling code207cat <<-FUNCTION_DEFINITION_CALLSITE >>"${temp_source_file_for_hook_point}"208hook_point_function_trace_sources["${hook_point}${hook_extension_delimiter}${hook_point_function}"]="\${BASH_SOURCE[*]}"209hook_point_function_trace_lines["${hook_point}${hook_extension_delimiter}${hook_point_function}"]="\${BASH_LINENO[*]}"210[[ "\${DEBUG_EXTENSION_CALLS}" == "yes" ]] && display_alert "---> Extension Method ${hook_point}" "${hook_point_functions_loop_counter}/${hook_point_functions_counter} (ext:${EXTENSION:-built-in}) ${hook_point_function}" ""211echo "*** *** Extension-managed hook starting ${hook_point_functions_loop_counter}/${hook_point_functions_counter} '${hook_point}${hook_extension_delimiter}${hook_point_function}':" >>"\${EXTENSION_MANAGER_LOG_FILE}"212${hook_point_function_variables} ${hook_point}${hook_extension_delimiter}${hook_point_function} "\$@"213echo "*** *** Extension-managed hook finished ${hook_point_functions_loop_counter}/${hook_point_functions_counter} '${hook_point}${hook_extension_delimiter}${hook_point_function}':" >>"\${EXTENSION_MANAGER_LOG_FILE}"214FUNCTION_DEFINITION_CALLSITE215216# output the cleanup for the implementation as well.217cat <<-FUNCTION_CLEANUP_FOR_HOOK_POINT_IMPLEMENTATION >>"${fragment_manager_cleanup_file}"218unset ${hook_point}${hook_extension_delimiter}${hook_point_function}219FUNCTION_CLEANUP_FOR_HOOK_POINT_IMPLEMENTATION220221# unset extension vars for the next loop.222unset EXTENSION EXTENSION_DIR EXTENSION_FILE EXTENSION_ADDED_BY223done224225cat <<-FUNCTION_DEFINITION_FOOTER >>"${temp_source_file_for_hook_point}"226echo "*** Extension-managed hook ending '${hook_point}': completed." >>"\${EXTENSION_MANAGER_LOG_FILE}"227} # end ${hook_point}() function228FUNCTION_DEFINITION_FOOTER229230# unsets, lest the next loop inherits them231unset hook_point_functions hook_point_functions_sortname_to_realname hook_point_functions_realname_to_sortname232233# log what was produced in our own debug logfile234cat "${temp_source_file_for_hook_point}" >>"${EXTENSION_MANAGER_LOG_FILE}"235cat "${fragment_manager_cleanup_file}" >>"${EXTENSION_MANAGER_LOG_FILE}"236237# source the generated function.238# shellcheck disable=SC1090239source "${temp_source_file_for_hook_point}"240241rm -f "${temp_source_file_for_hook_point}"242done243244# Dont show any output until we have more than 1 hook function (we implement one already, below)245[[ ${hook_functions_counter} -gt 0 ]] &&246display_alert "Extension manager" "processed ${hook_points_counter} Extension Methods calls and ${hook_functions_counter} Extension Method implementations" "info" | tee -a "${EXTENSION_MANAGER_LOG_FILE}"247}248249cleanup_extension_manager() {250if [[ -f "${fragment_manager_cleanup_file}" ]]; then251display_alert "Cleaning up" "extension manager" "info"252# this will unset all the functions.253# shellcheck disable=SC1090 # dynamic source, thanks, shellcheck254source "${fragment_manager_cleanup_file}"255fi256# reset/unset the variables used257initialize_extension_manager_counter=0258unset extension_function_info defined_hook_point_functions hook_point_function_trace_sources hook_point_function_trace_lines fragment_manager_cleanup_file259}260261# why not eat our own dog food?262# process everything that happened during extension related activities263# and write it to the log. also, move the log from the .tmp dir to its264# final location. this will make run_after_build() "hot" (eg, emit warnings)265run_after_build__999_finish_extension_manager() {266# export these maps, so the hook can access them and produce useful stuff.267export defined_hook_point_functions hook_point_function_trace_sources268269# eat our own dog food, pt2.270call_extension_method "extension_metadata_ready" <<'EXTENSION_METADATA_READY'271*meta-Meta time!*272Implement this hook to work with/on the meta-data made available by the extension manager.273Interesting stuff to process:274- `"${EXTENSION_MANAGER_TMP_DIR}/hook_point_calls.txt"` contains a list of all hook points called, in order.275- For each hook_point in the list, more files will have metadata about that hook point.276- `${EXTENSION_MANAGER_TMP_DIR}/hook_point.orig.md` contains the hook documentation at the call site (inline docs), hopefully in Markdown format.277- `${EXTENSION_MANAGER_TMP_DIR}/hook_point.compat` contains the compatibility names for the hooks.278- `${EXTENSION_MANAGER_TMP_DIR}/hook_point.exports` contains _exported_ environment variables.279- `${EXTENSION_MANAGER_TMP_DIR}/hook_point.vars` contains _all_ environment variables.280- `${defined_hook_point_functions}` is a map of _all_ the defined hook point functions and their extension information.281- `${hook_point_function_trace_sources}` is a map of all the hook point functions _that were really called during the build_ and their BASH_SOURCE information.282- `${hook_point_function_trace_lines}` is the same, but BASH_LINENO info.283After this hook is done, the `${EXTENSION_MANAGER_TMP_DIR}` will be removed.284EXTENSION_METADATA_READY285286# Move temporary log file over to final destination, and start writing to it instead (although 999 is pretty late in the game)287mv "${EXTENSION_MANAGER_LOG_FILE}" "${DEST}/${LOG_SUBPATH:-debug}/extensions.log"288export EXTENSION_MANAGER_LOG_FILE="${DEST}/${LOG_SUBPATH:-debug}/extensions.log"289290# Cleanup. Leave no trace...291[[ -d "${EXTENSION_MANAGER_TMP_DIR}" ]] && rm -rf "${EXTENSION_MANAGER_TMP_DIR}"292}293294# This is called by call_extension_method(). To say the truth, this should be in an extension. But then it gets too meta for anyone's head.295write_hook_point_metadata() {296local main_hook_point_name="$1"297298[[ ! -d "${EXTENSION_MANAGER_TMP_DIR}" ]] && mkdir -p "${EXTENSION_MANAGER_TMP_DIR}"299cat - >"${EXTENSION_MANAGER_TMP_DIR}/${main_hook_point_name}.orig.md" # Write the hook point documentation received via stdin to a tmp file for later processing.300shift301echo -n "$@" >"${EXTENSION_MANAGER_TMP_DIR}/${main_hook_point_name}.compat" # log the 2nd+ arguments too (those are the alternative/compatibility names), separate file.302compgen -A export >"${EXTENSION_MANAGER_TMP_DIR}/${main_hook_point_name}.exports" # capture the exported env vars.303compgen -A variable >"${EXTENSION_MANAGER_TMP_DIR}/${main_hook_point_name}.vars" # capture all env vars.304305# add to the list of hook points called, in order.306echo "${main_hook_point_name}" >>"${EXTENSION_MANAGER_TMP_DIR}/hook_point_calls.txt"307}308309# Helper function, to get clean "stack traces" that do not include the hook/extension infrastructure code.310get_extension_hook_stracktrace() {311local sources_str="$1" # Give this ${BASH_SOURCE[*]} - expanded312local lines_str="$2" # And this # Give this ${BASH_LINENO[*]} - expanded313local sources lines index final_stack=""314IFS=' ' read -r -a sources <<<"${sources_str}"315IFS=' ' read -r -a lines <<<"${lines_str}"316for index in "${!sources[@]}"; do317local source="${sources[index]}" line="${lines[((index - 1))]}"318# skip extension infrastructure sources, these only pollute the trace and add no insight to users319[[ ${source} == */.tmp/extension_function_definition.sh ]] && continue320[[ ${source} == *lib/extensions.sh ]] && continue321[[ ${source} == */compile.sh ]] && continue322# relativize the source, otherwise too long to display323source="${source#"${SRC}/"}"324# remove 'lib/'. hope this is not too confusing.325source="${source#"lib/"}"326# add to the list327arrow="$([[ "$final_stack" != "" ]] && echo "-> ")"328final_stack="${source}:${line} ${arrow} ${final_stack} "329done330# output the result, no newline331# shellcheck disable=SC2086 # I wanna suppress double spacing, thanks332echo -n $final_stack333}334335show_caller_full() {336local frame=0337while caller $frame; do338((frame++))339done340}341# can be called by board, family, config or user to make sure an extension is included.342# single argument is the extension name.343# will look for it in /userpatches/extensions first.344# if not found there will look in /extensions345# if not found will exit 17346declare -i enable_extension_recurse_counter=0347declare -a enable_extension_recurse_stack348enable_extension() {349local extension_name="$1"350local extension_dir extension_file extension_file_in_dir extension_floating_file351local stacktrace352353# capture the stack leading to this, possibly with a hint in front.354stacktrace="${ENABLE_EXTENSION_TRACE_HINT}$(get_extension_hook_stracktrace "${BASH_SOURCE[*]}" "${BASH_LINENO[*]}")"355356# if LOG_ENABLE_EXTENSION, output useful stack, so user can figure out which extensions are being added where357[[ "${LOG_ENABLE_EXTENSION}" == "yes" ]] &&358display_alert "Extension being added" "${extension_name} :: added by ${stacktrace}" ""359360# first a check, has the extension manager already initialized? then it is too late to enable_extension(). bail.361if [[ ${initialize_extension_manager_counter} -gt 0 ]]; then362display_alert "Extension problem" "already initialized -- too late to add '${extension_name}' (trace: ${stacktrace})" "err"363exit 2364fi365366# check the counter. if recurring, add to the stack and return success367if [[ $enable_extension_recurse_counter -gt 1 ]]; then368enable_extension_recurse_stack+=("${extension_name}")369return 0370fi371372# increment the counter373enable_extension_recurse_counter=$((enable_extension_recurse_counter + 1))374375# there are many opportunities here. too many, actually. let userpatches override just some functions, etc.376for extension_base_path in "${SRC}/userpatches/extensions" "${EXTER}/extensions"; do377extension_dir="${extension_base_path}/${extension_name}"378extension_file_in_dir="${extension_dir}/${extension_name}.sh"379extension_floating_file="${extension_base_path}/${extension_name}.sh"380381if [[ -d "${extension_dir}" ]] && [[ -f "${extension_file_in_dir}" ]]; then382extension_file="${extension_file_in_dir}"383break384elif [[ -f "${extension_floating_file}" ]]; then385extension_dir="${extension_base_path}" # this is misleading. only directory-based extensions should have this.386extension_file="${extension_floating_file}"387break388fi389done390391# After that, we should either have extension_file and extension_dir, or throw.392if [[ ! -f "${extension_file}" ]]; then393echo "ERR: Extension problem -- cant find extension '${extension_name}' anywhere - called by ${BASH_SOURCE[1]}"394exit 17 # exit, forcibly. no way we can recover from this, and next extensions will get bogus errors as well.395fi396397local before_function_list after_function_list new_function_list398399# store a list of existing functions at this point, before sourcing the extension.400before_function_list="$(compgen -A function)"401402# error handling during a 'source' call is quite insane in bash after 4.3.403# to be able to catch errors in sourced scripts the only way is to trap404declare -i extension_source_generated_error=0405trap 'extension_source_generated_error=1;' ERR406407# source the file. extensions are not supposed to do anything except export variables and define functions, so nothing should happen here.408# there is no way to enforce it though, short of static analysis.409# we could punish the extension authors who violate it by removing some essential variables temporarily from the environment during this source, and restore them later.410# shellcheck disable=SC1090411source "${extension_file}"412413# remove the trap we set.414trap - ERR415416# decrement the recurse counter, so calls to this method are allowed again.417enable_extension_recurse_counter=$((enable_extension_recurse_counter - 1))418419# test if it fell into the trap, and abort immediately with an error.420if [[ $extension_source_generated_error != 0 ]]; then421display_alert "Extension failed to load" "${extension_file}" "err"422exit 4423fi424425# get a new list of functions after sourcing the extension426after_function_list="$(compgen -A function)"427428# compare before and after, thus getting the functions defined by the extension.429# comm is oldskool. we like it. go "man comm" to understand -13 below430new_function_list="$(comm -13 <(echo "$before_function_list" | sort) <(echo "$after_function_list" | sort))"431432# iterate over defined functions, store them in global associative array extension_function_info433for newly_defined_function in ${new_function_list}; do434extension_function_info["${newly_defined_function}"]="EXTENSION=\"${extension_name}\" EXTENSION_DIR=\"${extension_dir}\" EXTENSION_FILE=\"${extension_file}\" EXTENSION_ADDED_BY=\"${stacktrace}\""435done436437# snapshot, then clear, the stack438local -a stack_snapshot=("${enable_extension_recurse_stack[@]}")439enable_extension_recurse_stack=()440441# process the stacked snapshot, finally enabling the extensions442for stacked_extension in "${stack_snapshot[@]}"; do443ENABLE_EXTENSION_TRACE_HINT="RECURSE ${stacktrace} ->" enable_extension "${stacked_extension}"444done445446}447448449