Speed up shell startup 9x (1.9s → 0.2s)

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 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-09 09:27:12 +01:00
commit 6a00847d4e
11 changed files with 83 additions and 23 deletions

25
00-completion-cache Executable file
View file

@ -0,0 +1,25 @@
# shellcheck shell=bash
# Cache completion scripts to avoid subprocess overhead on every shell start.
#
# Usage: cached_completion <tool> <generation-command...>
# 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"
}

View file

@ -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
_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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -22,6 +22,7 @@ Use these in all scripts — do not manipulate `PATH` or check permissions manua
- `path_append <dir>` — add directory to end of `$PATH` (checks existence, prevents duplicates)
- `path_prepend <dir>` — add directory to start of `$PATH`
- `require_private <file>` — warn if file is group/world-accessible; use in credential files
- `cached_completion <tool> <gen-cmd...>` — 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 <path> ]]` 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

View file

@ -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).