Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and versions are tracked in the repo-root `VERSION` file.

### Added

- Added the `base-bash` launcher for standalone scripts that want the
base-bash-libs stdlib preloaded from a shebang.
- Added `std_run --timeout`, `--max-attempts`, and `--retry-delay` execution
policy options for timeout-only, retry-only, and timeout-plus-retry command
execution.
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ source "$base_bash_libs_prefix/libexec/lib/bash/std/lib_std.sh"
printf 'base-bash-libs version: %s\n' "$BASE_BASH_LIBS_VERSION"
```

Homebrew installs the standalone `base-bash` launcher on `PATH`. Use it when a
script should run with the stdlib preloaded from its shebang:

```bash
#!/usr/bin/env base-bash

import_base_bash_lib str/lib_str.sh

main() {
local name=" Example "
str_trim name
log_info "Running with base-bash-libs $BASE_BASH_LIBS_VERSION"
std_run echo "$name"
}
```

Load companion libraries with absolute imports from the same package:

```bash
Expand Down Expand Up @@ -100,6 +116,12 @@ import "$base_bash_libs_dir/lib/bash/arg/lib_arg.sh"
import "$base_bash_libs_dir/lib/bash/list/lib_list.sh"
```

You can also run source-checkout scripts through the launcher:

```bash
vendor/base-bash-libs/bin/base-bash ./scripts/tool.sh
```

### Vendored or Submodule Layout

For projects that vendor dependencies or use git submodules, place this
Expand Down
223 changes: 223 additions & 0 deletions bin/base-bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#!/usr/bin/env bash

#
# base-bash
# Standalone launcher for Bash scripts that use base-bash-libs.
#
# Responsibilities:
# - run a Bash script with lib/bash/std/lib_std.sh preloaded
# - preserve stdlib wrapper-flag behavior for the target script
# - provide package-relative imports without establishing Base runtime state
#

base_bash_die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}

base_bash_resolve_path() {
local source_path="${1:-}"
local link_dir
local target

[[ -n "$source_path" ]] || return 1

if [[ "$source_path" != */* ]]; then
if [[ -e "$source_path" ]]; then
source_path="./$source_path"
else
source_path="$(command -v -- "$source_path" 2>/dev/null || true)"
[[ -n "$source_path" ]] || return 1
fi
fi

while [[ -L "$source_path" ]]; do
link_dir="$(cd -- "$(dirname -- "$source_path")" && pwd -P)" || return 1
target="$(readlink "$source_path")" || return 1
if [[ "$target" == /* ]]; then
source_path="$target"
else
source_path="$link_dir/$target"
fi
done

link_dir="$(cd -- "$(dirname -- "$source_path")" && pwd -P)" || return 1
printf '%s/%s\n' "$link_dir" "$(basename -- "$source_path")"
}

base_bash_package_root() {
local source_path
local bin_dir

source_path="$(base_bash_resolve_path "$0")" || return 1
bin_dir="$(cd -- "$(dirname -- "$source_path")" && pwd -P)" || return 1
cd -- "$bin_dir/.." && pwd -P
}

base_bash_ensure_supported_bash() {
local candidate
local candidates=()
local current_version
local display_version

current_version="${BASE_BASH_LIBS_TEST_BASH_VERSION:-${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}}"
((current_version >= 42)) && return 0

if [[ -n "${BASE_BASH_LIBS_TEST_BASH_CANDIDATES:-}" ]]; then
IFS=: read -r -a candidates <<<"$BASE_BASH_LIBS_TEST_BASH_CANDIDATES"
else
candidates=(/opt/homebrew/bin/bash /usr/local/bin/bash)
fi

for candidate in "${candidates[@]}"; do
[[ -x "$candidate" ]] || continue
[[ "$candidate" == "${BASH:-}" ]] && continue
exec "$candidate" "$0" "$@"
done

if [[ -n "${BASE_BASH_LIBS_TEST_BASH_VERSION:-}" ]]; then
display_version="${BASE_BASH_LIBS_TEST_BASH_VERSION:0:1}.${BASE_BASH_LIBS_TEST_BASH_VERSION:1}"
else
display_version="${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"
fi

{
printf 'ERROR: base-bash requires Bash 4.2 or newer; current version is %s.\n' "$display_version"
printf 'A supported Bash was not found at /opt/homebrew/bin/bash or /usr/local/bin/bash.\n'
} >&2
exit 1
}

base_bash_lib_dir_is_usable() {
local candidate="${1:-}"

[[ -n "$candidate" ]] || return 1
[[ -f "$candidate/std/lib_std.sh" ]]
}

base_bash_resolve_lib_dir() {
local package_root="$1"
local explicit_dir="${BASE_BASH_LIBS_DIR:-}"
local candidate

if [[ -n "$explicit_dir" ]]; then
base_bash_lib_dir_is_usable "$explicit_dir" || {
base_bash_die "BASE_BASH_LIBS_DIR '$explicit_dir' does not contain std/lib_std.sh."
}
cd -L -- "$explicit_dir" && pwd -L
return $?
fi

for candidate in "$package_root/lib/bash" "$package_root/libexec/lib/bash"; do
if base_bash_lib_dir_is_usable "$candidate"; then
cd -L -- "$candidate" && pwd -L
return $?
fi
done

base_bash_die "Unable to find base-bash-libs under '$package_root'. Set BASE_BASH_LIBS_DIR to a compatible lib/bash directory."
}

base_bash_source_stdlib() {
local script_path="$1"
local stdlib_path="$BASE_BASH_LIBS_DIR/std/lib_std.sh"
shift

BASE_BASH_BOOTSTRAP_SOURCE="$script_path"
# shellcheck source=/dev/null
source "$stdlib_path" "$@" || exit $?
unset BASE_BASH_BOOTSTRAP_SOURCE
}

base_bash_filter_runtime_args() {
base_bash_runtime_args=()

while (($#)); do
case "$1" in
--debug-wrapper|--verbose-wrapper|--utc-wrapper|--color)
;;
*)
base_bash_runtime_args+=("$1")
;;
esac
shift
done
}

import_base_bash_lib() {
local relative_path="${1:-}"
local lib_path

[[ -n "$relative_path" ]] || fatal_error "import_base_bash_lib: no library path provided."
[[ "$relative_path" != /* ]] || fatal_error "import_base_bash_lib: expected a path relative to '$BASE_BASH_LIBS_DIR', got '$relative_path'."

case "$relative_path" in
..|../*|*/..|*/../*)
fatal_error "import_base_bash_lib: refusing path outside base-bash-libs root: '$relative_path'."
;;
esac

lib_path="$BASE_BASH_LIBS_DIR/$relative_path"
[[ -f "$lib_path" ]] || fatal_error "base-bash-libs library '$relative_path' was not found at '$lib_path'."

# shellcheck source=/dev/null
source "$lib_path" || fatal_error "Failed to import base-bash-libs library '$lib_path'."
}

base_bash_run_script() {
local script_path="$1"
local package_root="$2"
local -a runtime_args=()
shift 2

[[ -f "$script_path" ]] || base_bash_die "Script '$script_path' was not found."

BASE_BASH_LIBS_DIR="$(base_bash_resolve_lib_dir "$package_root")" || exit $?
export BASE_BASH_LIBS_DIR

base_bash_filter_runtime_args "$@"
base_bash_source_stdlib "$script_path" "$@"
runtime_args=("${base_bash_runtime_args[@]}")

unset -f main

# shellcheck source=/dev/null
source "$script_path" "${runtime_args[@]}" || exit $?

if ! declare -F main >/dev/null 2>&1; then
fatal_error "Script '$script_path' did not define main()."
fi

main "${runtime_args[@]}"
}

base_bash_usage() {
cat >&2 <<'EOF'
Usage:
base-bash <script> [args...]

Run a Bash script with base-bash-libs stdlib preloaded.
EOF
}

main() {
local package_root
local script_path

base_bash_ensure_supported_bash "$@"
package_root="$(base_bash_package_root)" || base_bash_die "Unable to resolve base-bash-libs package root."

case "${1:-}" in
""|-h|--help|help)
base_bash_usage
return 2
;;
esac

script_path="$(base_bash_resolve_path "$1")" || base_bash_die "Script '$1' was not found."
shift

base_bash_run_script "$script_path" "$package_root" "$@"
}

main "$@"
24 changes: 20 additions & 4 deletions lib/bash/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,26 @@ Standalone scripts can source the library directly:
source "/absolute/path/to/lib/bash/std/lib_std.sh"
```

Base entrypoints preload this library through Base's own runtime bootstrap.
Standalone scripts should source it explicitly. Callers should run on Bash 4.2
or newer; the library has passive Bash version helpers, but sourcing it does not
prompt, install packages, or re-exec the caller.
Standalone scripts can also use the `base-bash` launcher when it is installed on
`PATH`:

```bash
#!/usr/bin/env base-bash

import_base_bash_lib str/lib_str.sh

main() {
local name=" Example "
str_trim name
std_run echo "$name"
}
```

Base entrypoints preload this library through Base's own runtime bootstrap. The
`base-bash` launcher provides the same stdlib preload pattern without Base
runtime state. Callers should run on Bash 4.2 or newer; the library has passive
Bash version helpers, but sourcing it does not prompt, install packages, or
re-exec the caller.

## Initialization Contract

Expand Down
Loading
Loading