From 6a00847d4e50a28fe7591b848a30c48681106031 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Mon, 9 Mar 2026 09:27:12 +0100 Subject: [PATCH] =?UTF-8?q?Speed=20up=20shell=20startup=209x=20(1.9s=20?= =?UTF-8?q?=E2=86=92=200.2s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lazy-load Intel oneAPI setvars.sh (~1.5s) via wrapper functions that source the environment on first use of icc/icx/ifort/etc. Cache all shell completion outputs to ~/.cache/bash.d/ so they are sourced from disk instead of regenerated via subprocess on every shell start. Cache invalidates automatically when the tool binary is updated. Co-Authored-By: Claude Opus 4.6 --- 00-completion-cache | 25 +++++++++++++++++++++ 20-oneapi | 50 +++++++++++++++++++++++++++++++++-------- 50-asdf-completion | 3 +-- 50-bun-completion | 3 +-- 50-fj-completion | 3 +-- 50-gh-completion | 3 +-- 50-podman-completion | 3 +-- 50-tailscale-completion | 3 +-- 50-uv-completion | 2 +- CLAUDE.md | 3 +++ README.md | 8 ++++++- 11 files changed, 83 insertions(+), 23 deletions(-) create mode 100755 00-completion-cache diff --git a/00-completion-cache b/00-completion-cache new file mode 100755 index 0000000..aa38fbe --- /dev/null +++ b/00-completion-cache @@ -0,0 +1,25 @@ +# shellcheck shell=bash +# Cache completion scripts to avoid subprocess overhead on every shell start. +# +# Usage: cached_completion +# Example: cached_completion gh gh completion -s bash +# +# The cache file is regenerated when the tool binary is newer than the cache. +# Cache location: ${XDG_CACHE_HOME:-$HOME/.cache}/bash.d/ + +_COMP_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/bash.d" + +cached_completion() { + local tool="$1"; shift + local cache_file="${_COMP_CACHE}/${tool}.bash" + local tool_path + tool_path="$(command -v "$tool" 2>/dev/null)" || return + + if [[ ! -f "$cache_file" || "$tool_path" -nt "$cache_file" ]]; then + mkdir -p "$_COMP_CACHE" + "$@" > "$cache_file" 2>/dev/null || return + fi + + # shellcheck disable=SC1090 + . "$cache_file" +} diff --git a/20-oneapi b/20-oneapi index 0b007eb..88a0acf 100755 --- a/20-oneapi +++ b/20-oneapi @@ -1,13 +1,45 @@ # shellcheck shell=bash -# Intel oneAPI toolkit — source the environment setup if installed -# shellcheck disable=SC1090 +# Intel oneAPI toolkit — lazy-loaded to avoid ~1.5s startup penalty. +# The full environment is sourced on first use of any Intel compiler/tool. +# To load manually: _load_oneapi + ONEAPI_SETVARS="/opt/intel/oneapi/setvars.sh" + if [[ -f "$ONEAPI_SETVARS" ]]; then - . "$ONEAPI_SETVARS" --force > /dev/null - if [[ -n "$ONEAPI_ROOT" ]]; then - case ":${LD_LIBRARY_PATH}:" in - *":${ONEAPI_ROOT}/lib:"*) ;; - *) export LD_LIBRARY_PATH="${ONEAPI_ROOT}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" ;; - esac - fi + _load_oneapi() { + # Guard against double-loading + if [[ "${_ONEAPI_LOADED:-0}" -eq 1 ]]; then + return + fi + _ONEAPI_LOADED=1 + + # Remove the lazy-load wrappers before sourcing + local _cmd + for _cmd in icc icpc icx icpx ifort ifx dpcpp; do + unset -f "$_cmd" 2>/dev/null + done + + # shellcheck disable=SC1090 + . "$ONEAPI_SETVARS" --force > /dev/null + + if [[ -n "$ONEAPI_ROOT" ]]; then + case ":${LD_LIBRARY_PATH}:" in + *":${ONEAPI_ROOT}/lib:"*) ;; + *) export LD_LIBRARY_PATH="${ONEAPI_ROOT}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" ;; + esac + fi + } + + # Create wrapper functions for common Intel tools that trigger lazy-load + _oneapi_lazy_wrap() { + local cmd="$1" + # shellcheck disable=SC2139 + eval "${cmd}() { _load_oneapi; command ${cmd} \"\$@\"; }" + } + + for _oneapi_cmd in icc icpc icx icpx ifort ifx dpcpp; do + _oneapi_lazy_wrap "$_oneapi_cmd" + done + unset -f _oneapi_lazy_wrap + unset _oneapi_cmd fi diff --git a/50-asdf-completion b/50-asdf-completion index 0e19688..094a0ec 100755 --- a/50-asdf-completion +++ b/50-asdf-completion @@ -1,4 +1,3 @@ # shellcheck shell=bash -# shellcheck disable=SC1090 # Tab completion for asdf, a tool that manages multiple language runtime versions -command -v asdf &>/dev/null && . <(asdf completion bash) +cached_completion asdf asdf completion bash diff --git a/50-bun-completion b/50-bun-completion index f834df8..fb2238f 100755 --- a/50-bun-completion +++ b/50-bun-completion @@ -1,4 +1,3 @@ # shellcheck shell=bash -# shellcheck disable=SC1090 # Enable bash completion for bun (JavaScript runtime & package manager) -command -v bun &>/dev/null && . <(bun completions) +cached_completion bun bun completions diff --git a/50-fj-completion b/50-fj-completion index c2dd370..5aa9106 100755 --- a/50-fj-completion +++ b/50-fj-completion @@ -1,4 +1,3 @@ # shellcheck shell=bash -# shellcheck disable=SC1090 # Tab completion for fj, the Forgejo CLI (like GitHub CLI but for Forgejo) -command -v fj &>/dev/null && . <(fj completion bash) +cached_completion fj fj completion bash diff --git a/50-gh-completion b/50-gh-completion index a703414..945b6bd 100755 --- a/50-gh-completion +++ b/50-gh-completion @@ -1,4 +1,3 @@ # shellcheck shell=bash -# shellcheck disable=SC1090 # Tab completion for gh, the GitHub CLI -command -v gh &>/dev/null && . <(gh completion -s bash) +cached_completion gh gh completion -s bash diff --git a/50-podman-completion b/50-podman-completion index 8022925..d83e40b 100755 --- a/50-podman-completion +++ b/50-podman-completion @@ -1,4 +1,3 @@ # shellcheck shell=bash -# shellcheck disable=SC1090 # Tab completion for Podman, a rootless container engine -command -v podman &>/dev/null && . <(podman completion bash) +cached_completion podman podman completion bash diff --git a/50-tailscale-completion b/50-tailscale-completion index df8acec..1651ea9 100755 --- a/50-tailscale-completion +++ b/50-tailscale-completion @@ -1,4 +1,3 @@ # shellcheck shell=bash -# shellcheck disable=SC1090 # Tab completion for Tailscale, a mesh VPN for connecting devices -command -v tailscale &>/dev/null && . <(tailscale completion bash) +cached_completion tailscale tailscale completion bash diff --git a/50-uv-completion b/50-uv-completion index b4ac12b..dfb31af 100755 --- a/50-uv-completion +++ b/50-uv-completion @@ -1,3 +1,3 @@ # shellcheck shell=bash # Enable bash completion for uv (Python package manager) -command -v uv &>/dev/null && eval "$(uv generate-shell-completion bash)" +cached_completion uv uv generate-shell-completion bash diff --git a/CLAUDE.md b/CLAUDE.md index bde6283..662624a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,7 @@ Use these in all scripts — do not manipulate `PATH` or check permissions manua - `path_append ` — add directory to end of `$PATH` (checks existence, prevents duplicates) - `path_prepend ` — add directory to start of `$PATH` - `require_private ` — warn if file is group/world-accessible; use in credential files +- `cached_completion ` — cache completion output to `~/.cache/bash.d/`; regenerates when the tool binary is updated. Use this for all new completion scripts instead of `eval` or `. <()` ## Writing Scripts @@ -30,6 +31,8 @@ Use these in all scripts — do not manipulate `PATH` or check permissions manua - Guard directory-dependent exports with `[[ -d ]]` before exporting - Do not suppress stderr from external scripts — only redirect stdout when needed - Avoid `eval` when `. <(cmd)` (process substitution) works +- For shell completions, use `cached_completion` instead of `eval` or `. <()` to avoid subprocess overhead on every shell start +- For slow environment scripts, use lazy-loading (see `20-oneapi` for the wrapper-function pattern) - Prevent `LD_LIBRARY_PATH` duplicates with a `case` guard (see `20-oneapi` for example) ## Permissions diff --git a/README.md b/README.md index 4401002..b3a4920 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Lower numbers load first, so foundational pieces like helper functions (`00-`) a - **`00-path-helper`** — Defines `path_append` and `path_prepend` functions that safely add directories to `$PATH` (checking the directory exists and avoiding duplicates). - **`00-credential-guard`** — Defines `require_private`, which warns at shell startup if a credential file has overly permissive permissions (anything beyond owner-only access). +- **`00-completion-cache`** — Defines `cached_completion`, which caches shell completion scripts to `~/.cache/bash.d/` so they are only regenerated when the tool binary is updated. Used by all `50-*-completion` scripts. ### PATH (`10-`) @@ -46,7 +47,7 @@ Lower numbers load first, so foundational pieces like helper functions (`00-`) a - **`20-android`** — Sets `$ANDROID_HOME`, `$JAVA_HOME`, and adds Android SDK tools to PATH (only when installed). - **`20-ninja`** — Configures Ninja as the default CMake generator and enables ccache for C/C++ builds (only when installed). -- **`20-oneapi`** — Sources the Intel oneAPI environment and adds its libraries to `LD_LIBRARY_PATH`. +- **`20-oneapi`** — Lazy-loads the Intel oneAPI environment. Because `setvars.sh` takes ~1.5 seconds, the full environment is deferred until an Intel tool (`icc`, `icx`, `ifort`, etc.) is first invoked. Call `_load_oneapi` to trigger it manually. ### Prompt (`30-`) @@ -54,9 +55,14 @@ Lower numbers load first, so foundational pieces like helper functions (`00-`) a ### Completions (`50-`) +All completions (except `50-claude-completion`) use `cached_completion` to avoid subprocess overhead on every shell start. Cache files live in `~/.cache/bash.d/` and are automatically regenerated when the tool binary is updated. + - **`50-asdf-completion`** — Tab completion for the asdf version manager. +- **`50-bun-completion`** — Tab completion for Bun (JavaScript runtime & package manager). - **`50-claude-completion`** — Tab completion for [Claude Code](https://claude.com/claude-code) CLI. - **`50-fj-completion`** — Tab completion for the Forgejo CLI (`fj`). +- **`50-gh-completion`** — Tab completion for the GitHub CLI (`gh`). +- **`50-podman-completion`** — Tab completion for Podman (rootless container engine). - **`50-tailscale-completion`** — Tab completion for Tailscale. - **`50-uv-completion`** — Tab completion for [uv](https://github.com/astral-sh/uv) (Python package manager).