Path: blob/master/snap/local/scriptlets/selective-checkout
8851 views
#!/usr/bin/env bash # This scriptlet enhances the pull step that will only build # development snapshots snaps if the latest tagged release has been # promoted to the stable channel. This ensures that there's always # a revision of the stable release snap available in the edge channel # for the publisher to promote to stable as currently the build # infrastructure only supports build on code push (but not new tagged # releases) at this time. # https://forum.snapcraft.io/t/selective-checkout-check-out-the-tagged-release-revision-if-it-isnt-promoted-to-the-stable-channel/10617 # # Copyright 2025 林博仁(Buo-ren Lin) <[email protected]> # SPDX-License-Identifier: CC-BY-SA-4.0 SELECTIVE_CHECKOUT_DEBUG="${SELECTIVE_CHECKOUT_DEBUG:-false}" set \ -o errexit \ -o errtrace \ -o nounset \ -o pipefail for required_command in \ curl \ cut \ head \ jq \ realpath \ sed \ sort \ tail \ tr; do if ! command -v "${required_command}" >/dev/null; then printf -- \ 'Fatal: This script requires the "%s" command in your command search PATHs.\n' \ "${required_command}" \ >&2 exit 1 fi done init(){ script="$( realpath \ --strip \ "${BASH_SOURCE[0]}" )" script_filename="${script##*/}" script_name="${script_filename%%.*}" export SCRIPT_NAME="${script_name}" # checkout_mode: # - snapshot: Build as-is # - release: Build the latest tagged release # tag_pattern_release: We assume all tags contains dots or underscores release tags # # Indirection used # shellcheck disable=SC2034 local \ checkout_mode \ flag_append_packaging_version=false \ flag_dry_run=false \ flag_debug_tracing=false \ flag_force_snapshot=false \ flag_force_stable=false \ packaging_revision \ postfix_dirty_marker_packaging=-d \ postfix_dirty_marker_upstream=-dirty \ tag_pattern_beta='-beta[[:digit:]]+$' \ tag_pattern_release='.*[._].*' \ tag_pattern_release_candidate='-rc[[:digit:]]+$' \ tag_pattern_stable \ tag_prefix_release=v \ revision_minimal_length_packaging=4 \ revision_minimal_length_upstream=7 \ snap_version \ snap_version_postfix_seperator=+ \ upstream_version if ! determining_runtime_parameters \ flag_append_packaging_version \ tag_pattern_beta \ flag_debug_tracing \ flag_dry_run \ flag_force_snapshot \ flag_force_stable \ postfix_dirty_marker_packaging \ revision_minimal_length_packaging \ tag_pattern_release_candidate \ tag_pattern_release \ tag_prefix_release \ snap_version_postfix_seperator \ tag_pattern_stable \ postfix_dirty_marker_upstream \ revision_minimal_length_upstream \ "${@}"; then printf \ 'Error: Error(s) occurred while determining the runtime parameters.\n' \ 1>&2 exit 1 fi if test "${flag_debug_tracing}" = true; then set -o xtrace fi vcs_check_runtime_dependencies \ "${PWD}" if test "${flag_force_snapshot}" = true; then printf -- \ '%s: Info: Force building development snapshots\n' \ "${script_name}" checkout_mode=snapshot elif test "$(vcs_detect "${PWD}")" = not_found; then printf -- \ '%s: Info: Build from source archive\n' \ "${script_name}" checkout_mode=snapshot elif vcs_is_dirty \ "${PWD}"; then # If tracked files are modified # or staging area not empty printf -- \ '%s: Info: Working tree is dirty, building development snapshot with additional changes\n' \ "${script_name}" checkout_mode=snapshot elif ! \ vcs_has_release_tags \ "${PWD}" \ "${tag_pattern_release}"; then printf -- \ '%s: Warning: No release tags found, assuming building from development snapshots.\n' \ "${script_name}" \ 1>&2 checkout_mode=snapshot else printf -- \ '%s: Info: Determining version to be built...\n' \ "${script_name}" local \ release_type_to_build=development-snapshot \ last_stable_tag \ last_stable_version \ last_stable_version_on_the_snap_store \ last_release_candidate_tag \ last_release_candidate_version \ last_release_candidate_version_on_the_snap_store \ last_beta_tag \ last_beta_version \ last_beta_version_on_the_snap_store local -a all_release_tags=() local -A \ map_of_tag_to_normalized_version \ map_of_release_type_to_snap_channel map_of_release_type_to_snap_channel=( [stable]=stable [release-candidate]=candidate [beta]=beta [development-snapshot]=edge ) if ! { test -v CRAFT_PROJECT_NAME \ || test -v SNAPCRAFT_PROJECT_NAME }; then printf -- \ "%s: Error: This script requires either the CRAFT_PROJECT_NAME or the SNAPCRAFT_PROJECT_NAME environment variable to be set.\\n" \ "${script_name}" \ >&2 exit 1 fi local project_name="${CRAFT_PROJECT_NAME:-"${SNAPCRAFT_PROJECT_NAME}"}" mapfile \ -t all_release_tags \ < <( vcs_query_release_tags \ "${PWD}" \ "${tag_pattern_release}" ) if ! { test -v CRAFT_PROJECT_DIR \ || test -v SNAPCRAFT_PROJECT_DIR }; then printf -- \ "%s: Error: This script requires either the CRAFT_PROJECT_DIR or the SNAPCRAFT_PROJECT_DIR environment variable to be set.\\n" \ "${script_name}" \ >&2 exit 1 fi project_dir="${CRAFT_PROJECT_DIR:-"${SNAPCRAFT_PROJECT_DIR}"}" printf -- \ '%s: INFO: Determining normalized release version strings.\n' \ "${script_name}" determine_normalized_release_version_string \ map_of_tag_to_normalized_version \ "${all_release_tags[@]}" printf -- \ '%s: INFO: Detecting stable releases...\n' \ "${script_name}" determine_stable_release_details \ tag_pattern_stable \ "${tag_pattern_release_candidate}" \ "${tag_pattern_beta}" \ last_stable_tag \ last_stable_version \ last_stable_version_on_the_snap_store \ "${snap_version_postfix_seperator}" \ "${flag_force_stable}" \ "${all_release_tags[@]}" printf -- \ '%s: INFO: Detecting release candidate releases...\n' \ "${script_name}" determine_release_candidate_release_details \ "${tag_pattern_release_candidate}" \ last_release_candidate_tag \ last_release_candidate_version \ last_release_candidate_version_on_the_snap_store \ "${snap_version_postfix_seperator}" \ "${all_release_tags[@]}" printf -- \ '%s: INFO: Detecting beta releases...\n' \ "${script_name}" determine_beta_release_details \ "${tag_pattern_beta}" \ last_beta_tag \ last_beta_version \ last_beta_version_on_the_snap_store \ "${snap_version_postfix_seperator}" \ "${all_release_tags[@]}" local \ selected_release_tag \ selected_release_version \ selected_snap_channel_version release_type_to_build="$( determine_which_version_to_build \ "${last_stable_tag}" \ "${last_stable_version}" \ "${last_stable_version_on_the_snap_store}" \ "${last_release_candidate_tag}" \ "${last_release_candidate_version}" \ "${last_release_candidate_version_on_the_snap_store}" \ "${last_beta_tag}" \ "${last_beta_version}" \ "${last_beta_version_on_the_snap_store}" \ "${flag_force_stable}" \ "${flag_force_snapshot}" )" case "${release_type_to_build}" in stable) selected_release_tag="${last_stable_tag}" selected_release_version="${last_stable_version}" selected_snap_channel_version="${last_stable_version_on_the_snap_store}" ;; release-candidate) selected_release_tag="${last_release_candidate_tag}" selected_release_version="${last_release_candidate_version}" selected_snap_channel_version="${last_release_candidate_version_on_the_snap_store}" ;; beta) selected_release_tag="${last_beta_tag}" selected_release_version="${last_beta_version}" selected_snap_channel_version="${last_beta_version_on_the_snap_store}" ;; development-snapshot) : # Nothing to do here ;; *) printf -- \ '%s: %s: FATAL: Invalid release_type_to_build(%s), report bug.\n' \ "${script_name}" \ "${FUNCNAME[0]}" \ "${release_type_to_build}" \ 1>&2 exit 1 ;; esac unset \ flag_version_mismatch_stable \ flag_version_mismatch_beta \ flag_version_mismatch_release_candidate \ flag_force_stable \ last_stable_tag \ last_stable_version \ last_stable_version_on_the_snap_store \ last_release_candidate_tag \ last_release_candidate_version \ last_release_candidate_version_on_the_snap_store \ last_beta_tag \ last_beta_version \ last_beta_version_on_the_snap_store if test "${release_type_to_build}" != development-snapshot; then printf -- \ "%s: Info: The last tagged %s release(%s) hasn't been promoted to the %s channel(%s) on the Snap Store yet, checking out %s.\\n" \ "${script_name}" \ "${release_type_to_build}" \ "${selected_release_version}" \ "${map_of_release_type_to_snap_channel["${release_type_to_build}"]}" \ "${selected_snap_channel_version}" \ "${selected_release_version}" checkout_mode=release else printf -- '%s: Info: Last tagged releases is all in their respective channels, building development snapshot\n' \ "${script_name}" checkout_mode=snapshot fi unset \ all_release_tags \ map_of_release_type_to_snap_channel \ release_type_to_build \ selected_release_version \ selected_snap_channel_version fi unset \ tag_pattern_release case "${checkout_mode}" in snapshot) : # do nothing ;; release) if test "${flag_dry_run}" == true; then printf -- \ '%s: Info: Would check out "%s" tag.\n' \ "${script_name}" \ "${selected_release_tag}" else vcs_checkout_tag \ "${PWD}" \ "${selected_release_tag}" fi ;; *) printf -- \ '%s: Error: Invalid checkout_mode selected.\n' \ "${script_name}" \ >&2 exit 1 ;; esac unset \ checkout_mode \ selected_release_tag upstream_version="$( vcs_describe_version \ "${PWD}" \ "${revision_minimal_length_upstream}" \ "${postfix_dirty_marker_upstream}" \ | normalize_version )" if test "${flag_append_packaging_version}" = true; then packaging_revision="$( vcs_describe_revision \ "${project_dir}" \ "${revision_minimal_length_packaging}" \ "${postfix_dirty_marker_packaging}" )" snap_version="${upstream_version}+pkg-${packaging_revision}" unset \ project_dir \ packaging_revision \ postfix_dirty_marker_packaging \ revision_minimal_length_packaging else snap_version="${upstream_version}" fi unset \ postfix_dirty_marker_upstream \ revision_minimal_length_upstream \ tag_prefix_release \ upstream_version printf -- '%s: Info: Snap version determined to be "%s".\n' \ "${script_name}" \ "${snap_version}" if test "${flag_dry_run}" = false; then if command -v craftctl >/dev/null; then if ! craftctl set version="${snap_version}"; then printf \ 'Error: Unable to set the snap version string.\n' \ 1>&2 exit 2 fi else if ! snapcraftctl set-version "${snap_version}"; then printf \ 'Error: Unable to set the snap version string.\n' \ 1>&2 exit 2 fi fi fi exit 0 } determining_runtime_parameters(){ local -n flag_append_packaging_version_ref="${1}"; shift local -n tag_pattern_beta_ref="${1}"; shift local -n flag_debug_tracing_ref="${1}"; shift local -n flag_dry_run_ref="${1}"; shift local -n flag_force_snapshot_ref="${1}"; shift local -n flag_force_stable_ref="${1}"; shift local -n postfix_dirty_marker_packaging_ref="${1}"; shift local -n revision_minimal_length_packaging_ref="${1}"; shift local -n tag_pattern_release_candidate_ref="${1}"; shift local -n tag_pattern_release_ref="${1}"; shift local -n tag_prefix_release_ref="${1}"; shift local -n snap_version_postfix_seperator_ref="${1}"; shift local -n tag_pattern_stable_ref="${1}"; shift local -n postfix_dirty_marker_upstream_ref="${1}"; shift local -n revision_minimal_length_upstream_ref="${1}"; shift while true; do if test "${#}" -eq 0; then break else case "${1}" in # Append packaging revision after snap version --append-packaging-revision) # Indirect access # shellcheck disable=SC2034 flag_append_packaging_version_ref=true ;; --beta-tag-pattern*) if test "${1}" != --beta-tag-pattern; then # Indirect access # shellcheck disable=SC2034 tag_pattern_beta_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --beta-tag-pattern requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 tag_pattern_beta_ref="${2}" shift 1 fi ;; # Enable execution tracing --debug) printf -- \ '%s: %s: Warning: The --debug command option is deprecated, set the SELECTIVE_CHECKOUT_DEBUG environment variable to "true" for a better debugging experience.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 export SELECTIVE_CHECKOUT_DEBUG=true ;; --debug-tracing) # Indirect access # shellcheck disable=SC2034 flag_debug_tracing_ref=true ;; # Don't run snapcraftctl for testing purpose --dry-run) # Indirect access # shellcheck disable=SC2034 flag_dry_run_ref=true ;; # Force building development snapshot regardless the status of the snap --force-snapshot) # Indirect access # shellcheck disable=SC2034 flag_force_snapshot_ref=true ;; --force-stable) # Indirect access # shellcheck disable=SC2034 flag_force_stable_ref=true ;; --packaging-dirty-marker-postfix*) if test "${1}" != --packaging-dirty-marker-postfix; then # Indirect access # shellcheck disable=SC2034 postfix_dirty_marker_packaging_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --packaging-dirty-marker-postfix requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 postfix_dirty_marker_packaging_ref="${2}" shift 1 fi ;; --packaging-revision-minimal-length*) if test "${1}" != --packaging-revision-minimal-length; then # Indirect access # shellcheck disable=SC2034 revision_minimal_length_packaging_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --packaging-revision-minimal-length requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 revision_minimal_length_packaging_ref="${2}" shift 1 fi ;; --release-candidate-tag-pattern*) if test "${1}" != --release-candidate-tag-pattern; then # Indirect access # shellcheck disable=SC2034 tag_pattern_release_candidate_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --release-candidate-tag-pattern requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 tag_pattern_release_candidate_ref="${2}" shift 1 fi ;; --release-tag-pattern*) if test "${1}" != --release-tag-pattern; then # Indirect access # shellcheck disable=SC2034 tag_pattern_release_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --release-tag-pattern requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 tag_pattern_release_ref="${2}" shift 1 fi ;; # Set the prefix for all release tags(default: `v`), the prefix will be stripped from snap version string --release-tag-prefix*) if test "${1}" != --release-tag-prefix; then # Indirect access # shellcheck disable=SC2034 tag_prefix_release_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --release-tag-prefix requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 tag_prefix_release_ref="${2}" shift 1 fi ;; # Set the seperator for the postfixed string in the snap version string # the postfixed string will be stripped before comparing with the stripped # uptream release version --snap-postfix-seperator*) if test "${1}" != --snap-postfix-seperator; then # Indirect access # shellcheck disable=SC2034 snap_version_postfix_seperator_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --snap-postfix-seperator requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 snap_version_postfix_seperator_ref="${2}" shift 1 fi ;; --stable-tag-pattern*) if test "${1}" != --stable-tag-pattern; then # Indirect access # shellcheck disable=SC2034 tag_pattern_stable_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --stable-tag-pattern requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 tag_pattern_stable_ref="${2}" shift 1 fi ;; --upstream-dirty-marker-postfix*) if test "${1}" != --upstream-dirty-marker-postfix; then # Indirect access # shellcheck disable=SC2034 postfix_dirty_marker_upstream_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --upstream-dirty-marker-postfix requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 postfix_dirty_marker_upstream_ref="${2}" shift 1 fi ;; --upstream-revision-minimal-length*) if test "${1}" != --upstream-revision-minimal-length; then # Indirect access # shellcheck disable=SC2034 revision_minimal_length_upstream_ref="$( cut \ --delimiter== \ --fields=2 \ <<< "${1}" )" else if test "${#}" -eq 0; then printf -- \ '%s: %s: Error: --upstream-revision-minimal-length requires one argument.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi # Indirect access # shellcheck disable=SC2034 revision_minimal_length_upstream_ref="${2}" shift 1 fi ;; *) printf -- \ '%s: %s: Error: Invalid command-line argument "%s".\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${1}" \ >&2 return 1 ;; esac shift 1 fi done } normalize_version(){ # This is not a simple substitution # shellcheck disable=SC2001 sed "s#^${tag_prefix_release}##" \ | tr _ . } determine_normalized_release_version_string(){ local -n map_of_tag_to_normalized_version_ref="${1}"; shift local -a all_release_tags=("${@}"); set -- local normalized_release_version for tag in "${all_release_tags[@]}"; do if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Found release tag: %s\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${tag}" \ 1>&2 fi normalized_release_version="$( normalize_version <<< "${tag}" )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Normalized release version: %s\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${normalized_release_version}" \ 1>&2 fi # Indirection # shellcheck disable=SC2034 map_of_tag_to_normalized_version_ref["${tag}"]="${normalized_release_version}" done unset \ normalized_release_version \ tag } determine_stable_release_details(){ # Indirection # shellcheck disable=SC2034 local -n tag_pattern_stable_ref="${1}"; shift local tag_pattern_release_candidate="${1}"; shift local tag_pattern_beta="${1}"; shift local -n last_stable_tag_ref="${1}"; shift local -n last_stable_version_ref="${1}"; shift local -n last_stable_version_on_the_snap_store_ref="${1}"; shift local snap_version_postfix_seperator="${1}"; shift local flag_force_stable="${1}"; shift local -a all_release_tags=("${@}"); set -- local stable_tag_pattern_grep_opts if test -v tag_pattern_stable_ref; then stable_tag_pattern_grep_opts= else stable_tag_pattern_grep_opts=--invert-match tag_pattern_stable_ref="${tag_pattern_beta}|${tag_pattern_release_candidate}" fi local -a stable_tags=() mapfile \ -t stable_tags \ < <( echo -n "${all_release_tags[@]}" \ | tr ' ' "\\n" \ | grep \ --extended-regexp \ ${stable_tag_pattern_grep_opts} \ --regexp="(${tag_pattern_stable_ref})" ) if test "${#stable_tags[@]}" -eq 0; then last_stable_tag_ref= last_stable_version_ref= # NOTE: The store CAN have releases, though... last_stable_version_on_the_snap_store_ref= else last_stable_tag_ref="$( echo -n "${stable_tags[@]}" \ | tr ' ' "\\n" \ | sort --version-sort \ | tail --lines=1 )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Last stable release tag determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_stable_tag_ref}" \ 1>&2 fi last_stable_version_ref="${map_of_tag_to_normalized_version["${last_stable_tag}"]}" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Last stable version determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_stable_version_ref}" \ 1>&2 fi last_stable_version_on_the_snap_store_ref="$( snap_query_version \ "${project_name}" \ stable \ "${snap_version_postfix_seperator}" )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Last stable version on the snap store determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_stable_version_on_the_snap_store_ref}" \ 1>&2 fi fi unset \ stable_tag_pattern_grep_opts \ tag_pattern_stable } determine_release_candidate_release_details(){ local tag_pattern_release_candidate="${1}"; shift local -n last_release_candidate_tag_ref="${1}"; shift local -n last_release_candidate_version_ref="${1}"; shift local -n last_release_candidate_version_on_the_snap_store_ref="${1}"; shift local snap_version_postfix_seperator="${1}"; shift local -a all_release_tags=("${@}"); set -- local -a release_candidate_tags mapfile \ -t release_candidate_tags \ < <( echo -n "${all_release_tags[@]}" \ | tr ' ' "\\n" \ | grep \ --extended-regexp \ --regexp="${tag_pattern_release_candidate}" ) if test "${#release_candidate_tags[@]}" -eq 0; then if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: No release candidate release tags found.\n' \ "${SCRIPT_NAME}" \ 1>&2 fi last_release_candidate_tag_ref= last_release_candidate_version_ref= last_release_candidate_version_on_the_snap_store_ref= else last_release_candidate_tag_ref="$( echo -n "${release_candidate_tags[@]}" \ | tr ' ' "\\n" \ | grep \ --extended-regexp \ --regexp="${tag_pattern_release_candidate}" \ | sort --version-sort \ | tail --lines=1 )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: Last release candidate release tag determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${last_release_candidate_tag_ref}" \ 1>&2 fi last_release_candidate_version_ref="${map_of_tag_to_normalized_version["${last_release_candidate_tag_ref}"]}" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: Last release candidate version determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${last_release_candidate_version_ref}" \ 1>&2 fi last_release_candidate_version_on_the_snap_store_ref="$( snap_query_version \ "${project_name}" \ candidate \ "${snap_version_postfix_seperator}" )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: Last release candidate version on the snap store determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${last_release_candidate_version_on_the_snap_store_ref}" \ 1>&2 fi fi unset \ tag_pattern_release_candidate } determine_beta_release_details(){ local tag_pattern_beta="${1}"; shift local -n last_beta_tag_ref="${1}"; shift local -n last_beta_version_ref="${1}"; shift local -n last_beta_version_on_the_snap_store_ref="${1}"; shift local snap_version_postfix_seperator="${1}"; shift local all_release_tags=("${@}"); set -- local -a beta_tags mapfile \ -t beta_tags \ < <( echo -n "${all_release_tags[@]}" \ | tr ' ' "\\n" \ | grep \ --extended-regexp \ --regexp="${tag_pattern_beta}" ) if test "${#beta_tags[@]}" -eq 0; then if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: No beta release tags found.\n' \ "${SCRIPT_NAME}" \ 1>&2 fi last_beta_tag_ref= last_beta_version_ref= last_beta_version_on_the_snap_store= else last_beta_tag_ref="$( echo -n "${beta_tags[@]}" \ | tr ' ' "\\n" \ | grep \ --extended-regexp \ --regexp="${tag_pattern_beta}" \ | sort --version-sort \ | tail --lines=1 )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: Last beta release tag determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${last_beta_tag_ref}" \ 1>&2 fi last_beta_version_ref="${map_of_tag_to_normalized_version["${last_beta_tag_ref}"]}" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: Last beta version determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${last_beta_version_ref}" \ 1>&2 fi last_beta_version_on_the_snap_store="$( snap_query_version \ "${project_name}" \ beta \ "${snap_version_postfix_seperator}" )" if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: DEBUG: Last beta version on the snap store determines to be "%s".\n' \ "${SCRIPT_NAME}" \ "${last_beta_version_on_the_snap_store_ref}" \ 1>&2 fi fi unset \ map_of_tag_to_normalized_version \ snap_version_postfix_seperator \ tag_pattern_beta } determine_which_version_to_build(){ local last_stable_tag="${1}"; shift local last_stable_version="${1}"; shift local last_stable_version_on_the_snap_store="${1}"; shift local last_release_candidate_tag="${1}"; shift local last_release_candidate_version="${1}"; shift local last_release_candidate_version_on_the_snap_store="${1}"; shift local last_beta_tag="${1}"; shift local last_beta_version="${1}"; shift local last_beta_version_on_the_snap_store="${1}"; shift local flag_force_stable="${1}"; shift local flag_force_snapshot="${1}"; shift local release_type_to_build=undetermined # Case: --force-snapshot is specified if test "${flag_force_snapshot}" == true; then printf -- \ '%s: %s: DEBUG: The --force-snapshot command option is specified, development snapshot will be built.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ 1>&2 release_type_to_build=development-snapshot # Case: Build the stable version when: # # --force-stable command-line option is specified # AND There is stable release version to build on elif test "${flag_force_stable}" == true; then if test -z "${last_stable_version}"; then printf -- \ '%s: %s: Error: The --force-stable command-line option is specified, but no stable versions are found.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ 1>&2 return 1 fi printf -- \ '%s: %s: DEBUG: The --force-stable command-line option is specified, stable release will be built.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ 1>&2 release_type_to_build=stable # Case: Build the stable version when: # # The stable version is available # AND ( # There's no snap available in the stable channel of the Snap # Store # OR The stable version is newer than the one on the stable # channel of the snap store # ) elif test -n "${last_stable_version}" \ && { test -z "${last_stable_version_on_the_snap_store}" \ || version_former_is_greater_than_latter \ "${last_stable_version}" \ "${last_stable_version_on_the_snap_store}" }; then if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Stable version(%s) is newer than the one on the Snap Store(%s), stable release will be built.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_stable_version}" \ "${last_stable_version_on_the_snap_store:-none}" \ 1>&2 fi release_type_to_build=stable # Case: Build release candidate version when: # # Release candidate version is available # AND ( # There's no snap on the Snap Store's stable channel # OR The release candidate version is newer than the version # on the Snap Store's stable channel # ) # AND ( # There's no release in the Snap Store's candidate channel # OR The release candidate version is newer than the version # in the Snap Store's candidate channel # ) elif test -n "${last_release_candidate_version}" \ && { test -z "${last_stable_version_on_the_snap_store}" \ || version_former_is_greater_than_latter \ "${last_release_candidate_version}" \ "${last_stable_version_on_the_snap_store}" } && { test -z "${last_release_candidate_version_on_the_snap_store}" \ || version_former_is_greater_than_latter \ "${last_release_candidate_version}" \ "${last_release_candidate_version_on_the_snap_store}" }; then if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ "%s: %s: DEBUG: Release candidate version(%s) is not on the Snap Store's candidate channel and is newer than the version on the stable channel(%s), release candidate version will be built.\\n" \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_release_candidate_version}" \ "${last_stable_version_on_the_snap_store:-none}" \ 1>&2 fi release_type_to_build=release-candidate # Case: Build beta version when # # The beta version is available # AND ( # There's NO snap on the stable channel of the Snap Store # OR The beta version is newer than the stable version on # the Snap Store # ) # AND ( # There's NO snap on the candidate channel of the Snap Store # OR The beta version is newer than the candidate version # on the Snap Store # ) # AND ( # There's NO snap on the beta channel of the Snap Store # OR The beta version is newer than the beta version on # the Snap Store # ) elif test -n "${last_beta_version}" \ && { test -z "${last_stable_version_on_the_snap_store}" \ || version_former_is_greater_than_latter \ "${last_beta_version}" \ "${last_stable_version_on_the_snap_store}" } && { test -z "${last_release_candidate_version_on_the_snap_store}" \ || version_former_is_greater_than_latter \ "${last_beta_version}" \ "${last_release_candidate_version_on_the_snap_store}" } && { test -z "${last_beta_version_on_the_snap_store}" \ || version_former_is_greater_than_latter \ "${last_beta_version}" \ "${last_beta_version_on_the_snap_store}" }; then if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ "%s: %s: DEBUG: Beta version(%s) is newer than the versions in the stabler channels(stable(%s), candidate(%s)), and is newer than the one shipped in the Snap Store's beta channel(%s), beta version will be built.\\n" \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_beta_version}" \ "${last_stable_version_on_the_snap_store:-none}" \ "${last_release_candidate_version_on_the_snap_store:-none}" \ "${last_beta_version_on_the_snap_store:-none}" \ 1>&2 fi release_type_to_build=beta # Case: Build development snapshot when: # # All other versions are built else if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ "%s: %s: DEBUG: All other versions are on the Snap Store(stable(%s), candidate(%s), beta(%s)), development snapshot will be built.\\n" \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${last_stable_version_on_the_snap_store:-none}" \ "${last_release_candidate_version_on_the_snap_store:-none}" \ "${last_beta_version_on_the_snap_store:-none}" \ 1>&2 fi release_type_to_build=development-snapshot fi printf \ '%s' \ "${release_type_to_build}" } # Query snap version for specific channel, we use the Snap Store API # Output: snap version string, or null string if the channel has no # snap # http://api.snapcraft.io/docs/info.html#snap_info snap_query_version(){ if test $# -ne 3; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi # positional parameters local snap_identifier="${1}"; shift local release_channel="${1}"; shift local snap_version_postfix_seperator="${1}"; shift snap_arch="${SNAP_ARCH:-amd64}" local snap_version_in_release_channel local info_store_api_call_response if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ '%s: %s: DEBUG: Checking what snap revisions are available for the %s snap at the %s release channel...\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${snap_identifier}" \ "${release_channel}" \ 1>&2 fi if ! info_store_api_call_response="$( curl \ --fail \ --silent \ --show-error \ --header 'Snap-Device-Series: 16' \ "https://api.snapcraft.io/v2/snaps/info/${snap_identifier}?fields=version&architecture=${snap_arch}" \ 2>&1 )"; then # If the response is 404, the snap hasn't published to the # the specified channel(or, at all) at the Snap Store yet regex_curl_request_returns_404='\(22\).*: 404$' grep_opts=( --extended-regexp --regexp="${regex_curl_request_returns_404}" --quiet ) if grep "${grep_opts[@]}" <<<"${info_store_api_call_response}"; then if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf -- \ "%s: %s: DEBUG: The /snaps/info/ Snap Store API call returns 404, interpreting as the snap hasn't published to the store...\\n" \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ 1>&2 fi return 0 fi printf \ '%s: %s: Error: Unable to call the Snap Store info API to retrieve snap revision info: %s\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${info_store_api_call_response}" \ 1>&2 return 1 fi if ! snap_version_in_release_channel="$( jq \ --raw-output \ ".\"channel-map\"[] | select( .channel.name == \"${release_channel}\" ).version" \ <<< "${info_store_api_call_response}" \ 2>&1 )"; then printf \ '%s: %s: Error: Unable to query the snap version from the store info API response: %s\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${snap_version_in_release_channel}" \ 1>&2 return 2 fi if test -z "${snap_version_in_release_channel}"; then # No snaps available in the channel return 0 fi if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then printf \ '%s: %s: DEBUG: Snap version determined to be: "%s".\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ "${snap_version_in_release_channel}" \ 1>&2 fi printf %s "${snap_version_in_release_channel}" } # Determine which VCS is used # FIXME: Allow specifying any node under the working directory, not just the root node vcs_detect(){ if test $# -ne 1; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local source_tree_root_dir="${1}"; shift 1 if test -e "${source_tree_root_dir}"/.git; then printf git elif test -e "${source_tree_root_dir}"/.hg; then printf mercurial elif test -e "${source_tree_root_dir}"/.svn; then printf subversion else printf not_found fi } # Ensure depending software is available before using them vcs_check_runtime_dependencies(){ if test $# -ne 1; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) if ! command -v git &>/dev/null; then # Markdown code markup is not Bash tilde expression # shellcheck disable=SC2016 printf -- \ '%s: %s: Error: `git` command not found in the command search PATHs.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi ;; mercurial) if ! command -v hg &>/dev/null; then # Markdown code markup is not Bash tilde expression # shellcheck disable=SC2016 printf -- \ '%s: %s: Error: `hg` command not found in the command search PATHs.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi ;; subversion) if ! command -v svn &>/dev/null; then # Markdown code markup is not Bash tilde expression # shellcheck disable=SC2016 printf -- \ '%s: %s: Error: `svn` command not found in the command search PATHs.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 1 fi ;; *) printf -- \ '%s: %s: Warning: Unknown VCS type, assuming none.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 0 ;; esac } vcs_is_dirty(){ if test $# -ne 1; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) # If tracked files are modified # or staging area not empty if ! \ git -C "${source_tree_root_dir}" diff \ --quiet \ || ! \ git -C "${source_tree_root_dir}" diff \ --staged \ --quiet; then return 0 else return 1 fi ;; mercurial) # NOTE: # The existence of untracked files is not consider dirty, # imitating Git's `--dirty` option of the describe # subcommand if test -n "$( hg --cwd "${source_tree_root_dir}" status \ --added \ --deleted \ --modified \ --removed )"; then return 0 else return 1 fi ;; subversion) # NOTE: Is there any straightforward way to check this? if test -n "$( svn status -q )"; then return 0 else return 1 fi ;; *) printf -- \ '%s: %s: Warning: Unknown VCS type, assuming dirty.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 return 0 ;; esac } vcs_has_release_tags() { if test $# -ne 2; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 # Pattern to match a release tag, in extended regular expression(ERE) local -r tag_pattern_release="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) if git -C "${source_tree_root_dir}" tag \ --list \ | grep \ --extended-regexp \ --quiet \ --regexp="${tag_pattern_release}"; then return 0 else return 1 fi ;; mercurial) if hg --cwd "${source_tree_root_dir}" tags \ | grep \ --extended-regexp \ --quiet \ --regexp="${tag_pattern_release}"; then return 0 else return 1 fi ;; subversion) local \ source_tree_root_url \ tags_dir_url source_tree_root_url="$( svn info \ --show-item url \ "${source_tree_root_dir}" )" # Supported source URLs: # * /trunk # * /tags/_tag_name_ case "${source_tree_root_url}" in */trunk) # Strip trailing shortest matched pattern # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html tags_dir_url="${source_tree_root_url%/trunk}/tags" ;; */tags/*) tags_dir_url="${source_tree_root_url%/*}" ;; *) printf -- \ 'Warning: %s: Unsupported SVN source URL, assuming no release tags found.\n' \ "${FUNCNAME[0]}" \ >&2 return 1 ;; esac if svn list \ "${tags_dir_url}" \ | sed 's#/$##' \ | grep \ --extended-regexp \ --quiet \ --regexp="${tag_pattern_release}"; then return 0 else return 1 fi ;; *) printf -- \ '%s: Warning: Unknown VCS type, assuming no release tags found.\n' \ "${FUNCNAME[0]}" \ >&2 return 1 ;; esac } vcs_query_release_tags(){ if test $# -ne 2; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 # Pattern to match a release tag, in extended regular expression(ERE) local -r tag_pattern_release="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) if git -C "${source_tree_root_dir}" tag \ --list \ | grep \ --extended-regexp \ --regexp="${tag_pattern_release}"; then return 0 else return 1 fi ;; mercurial) if hg --cwd "${source_tree_root_dir}" tags \ --quiet \ | grep \ --extended-regexp \ --regexp="${tag_pattern_release}"; then return 0 else return 1 fi ;; subversion) local \ source_tree_root_url \ tags_dir_url source_tree_root_url="$( svn info \ --show-item url \ "${source_tree_root_dir}" )" # Supported source URLs: # * /trunk # * /tags/_tag_name_ case "${source_tree_root_url}" in */trunk) # Strip trailing shortest matched pattern # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html tags_dir_url="${source_tree_root_url%/trunk}/tags" ;; */tags/*) tags_dir_url="${source_tree_root_url%/*}" ;; *) printf -- \ 'Warning: %s: Unsupported SVN source URL, assuming no release tags found.\n' \ "${FUNCNAME[0]}" \ >&2 return 1 ;; esac if svn list \ "${tags_dir_url}" \ | sed 's#/$##' \ | grep \ --extended-regexp \ --regexp="${tag_pattern_release}"; then return 0 else return 1 fi ;; *) printf -- \ '%s: Warning: Unknown VCS type, assuming no release tags found.\n' \ "${FUNCNAME[0]}" \ >&2 return 1 ;; esac } vcs_checkout_tag(){ if test $# -ne 2; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 local -r tag_to_be_checked_out="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) git -C "${source_tree_root_dir}" checkout \ "${tag_to_be_checked_out}" return 0 ;; mercurial) hg --cwd "${source_tree_root_dir}" checkout \ --rev "${tag_to_be_checked_out}" return 0 ;; subversion) local \ source_tree_root_url \ tags_dir_url source_tree_root_url="$( svn info \ --show-item url \ "${source_tree_root_dir}" )" # Supported source URLs: # * /trunk # * /tags/_tag_name_ case "${source_tree_root_url}" in */trunk) # Strip trailing shortest matched pattern # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html tags_dir_url="${source_tree_root_url%/trunk}/tags" ;; */tags/*) tags_dir_url="${source_tree_root_url%/*}" ;; *) printf -- \ 'Warning: %s: Unsupported SVN source URL, assuming check out failed.\n' \ "${FUNCNAME[0]}" \ >&2 return 1 ;; esac if svn checkout \ "${tags_dir_url}"/"${tag_to_be_checked_out}"; then return 0 else return 1 fi ;; *) printf -- \ '%s: Warning: Unknown VCS type, not doing anything.\n' \ "${FUNCNAME[0]}" \ >&2 return 1 ;; esac } # Describe software version, use tags when available vcs_describe_version(){ if test $# -ne 3; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 local -r revision_identifier_length_minimum="${1}"; shift 1 local -r dirty_postfix="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) git describe \ --abbrev="${revision_identifier_length_minimum}" \ --always \ --dirty="${dirty_postfix}" \ --tags return 0 ;; mercurial) hg --cwd "${source_tree_root_dir}" log \ --rev . \ --template "{latesttag}{sub('^-0-.*', '', '-{latesttagdistance}-m{shortest(node, ${revision_identifier_length_minimum})}')}" if vcs_is_dirty "${source_tree_root_dir}"; then printf -- %s "${dirty_postfix}" fi return 0 ;; subversion) local \ source_tree_root_url source_tree_root_url="$( svn info \ --show-item url \ "${source_tree_root_dir}" )" # Supported source URLs: # * /trunk # * /tags/_tag_name_ case "${source_tree_root_url}" in */trunk) printf %s \ "rev$( svn info \ --show-item revision \ "${source_tree_root_dir}" )" ;; */tags/*) local tag tag="${source_tree_root_url##*/}" printf %s "${tag}" ;; *) printf -- \ 'Warning: %s: Unsupported SVN source URL.\n' \ "${FUNCNAME[0]}" \ >&2 printf unknown return 1 ;; esac if vcs_is_dirty "${source_tree_root_dir}"; then printf -- %s "${dirty_postfix}" fi ;; *) printf -- \ '%s: Warning: Unknown VCS type.\n' \ "${FUNCNAME[0]}" \ >&2 printf unknown return 0 ;; esac } # Describe version control revision, only revision identifier/hash with # customizable minimum length # FIXME: Some node among source tree should be enough for source_tree_root_dir vcs_describe_revision(){ if test $# -ne 3; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r source_tree_root_dir="${1}"; shift 1 local -r revision_identifier_length_minimum="${1}"; shift 1 local -r dirty_postfix="${1}"; shift 1 case "$(vcs_detect "${source_tree_root_dir}")" in git) if ! git -C "${source_tree_root_dir}" describe --always >/dev/null; then printf unknown else git -C "${source_tree_root_dir}" describe \ --abbrev="${revision_identifier_length_minimum}" \ --always \ --dirty="${dirty_postfix}" \ --match=nothing fi return 0 ;; mercurial) if ! hg --cwd "${source_tree_root_dir}" status >/dev/null; then printf unknown else # FIXME: Is there a better way of generating this only using Mercurial? local hg_revision hg_revision+="$( hg --cwd "${source_tree_root_dir}" log \ --rev . \ --template "{shortest(node, ${revision_identifier_length_minimum})}" )" if vcs_is_dirty \ "${SCRIPT_NAME}" \ "${source_tree_root_dir}"; then hg_revision+="${dirty_postfix}" fi printf -- %s "${hg_revision}" fi return 0 ;; subversion) svn info \ --show-item revision \ "${source_tree_root_dir}" if vcs_is_dirty \ "${SCRIPT_NAME}" \ "${source_tree_root_dir}"; then printf -- %s "${dirty_postfix}" fi ;; *) printf -- \ '%s: %s: Warning: Unknown VCS type.\n' \ "${SCRIPT_NAME}" \ "${FUNCNAME[0]}" \ >&2 printf unknown return 0 ;; esac } # Check whether a version string is newer than the other version string # identical version is considered "not newer" version_former_is_greater_than_latter(){ if test $# -ne 2; then printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 exit 1 fi local -r former="${1}"; shift local -r latter="${1}"; shift if test "${former}" = "${latter}"; then return 1 fi local newer_version newer_version="$( printf \ -- \ '%s\n%s' \ "${former}" \ "${latter}" \ | sort \ --version-sort \ --reverse \ | head \ --lines=1 )" if test "${newer_version}" = "${former}"; then return 0 else return 1 fi } init "${@}"