fix(install): put ludic on PATH in every shell, not just $SHELL's

The installer edited one profile — whichever ~/.zshrc or ~/.bashrc $SHELL
pointed at — and skipped any profile that did not already exist. So a fresh
account got nothing written at all, a bash user's ~/.bashrc is not read by the
login shell macOS Terminal starts, and ~/.zshrc is only read by interactive zsh.
The toolchain installed correctly and `ludic` was still not a command.

The PATH edit now lives in one file, <install>/env (plus env.fish), and each
profile gets a single line that sources it: ~/.profile for sh and for login bash
with no .bash_profile, ~/.zshenv because zsh never reads ~/.profile and reads
this one for every invocation, ~/.bashrc and ~/.bash_profile when they already
exist, and fish's config when fish is installed. Missing .profile/.zshenv are
created; .bash_profile deliberately is not, since creating it would stop bash
from reading ~/.profile at all.

Sourcing a shared file rather than appending an export keeps a re-install from
accumulating a second entry, and leaves one place to delete when uninstalling.
Verified with a staged HOME: zsh -i, zsh -c, bash -l, bash -i and sh -l all
resolve ludic; a second run reports "already on your PATH" and writes nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-05 22:20:39 +03:00
parent 29fcef3b9c
commit 44e752b606
3 changed files with 90 additions and 41 deletions

View file

@ -42,8 +42,10 @@ formatter and the language server — with one command:
curl -fsSL https://workshopsoft.pages.workshopsoft.io/ludic/install.sh | sh curl -fsSL https://workshopsoft.pages.workshopsoft.io/ludic/install.sh | sh
``` ```
It installs into `~/.ludic` and puts `~/.ludic/bin` on your `PATH`; nothing else It installs into `~/.ludic` and puts `~/.ludic/bin` on your `PATH` in every
on the machine is touched, and uninstalling is `rm -rf ~/.ludic`. Where a shell — the PATH line lives in `~/.ludic/env`, sourced from `~/.profile`,
`~/.zshenv` and your bash or fish config. Nothing else on the machine is touched;
uninstalling is `rm -rf ~/.ludic` and deleting those two-line blocks. Where a
prebuilt toolchain exists for your platform it is downloaded and verified against prebuilt toolchain exists for your platform it is downloaded and verified against
a published checksum; where it does not, the installer bootstraps from the a published checksum; where it does not, the installer bootstraps from the
compiler's own IR seed with clang. Either way you need clang (or Xcode's Command compiler's own IR seed with clang. Either way you need clang (or Xcode's Command

View file

@ -4,3 +4,11 @@ type: fix
`bin/ludicc` and `VERSION` beside the *current* directory, so it answered `bin/ludicc` and `VERSION` beside the *current* directory, so it answered
"(version unknown)" from a project — which is the only place a user ever runs it. "(version unknown)" from a project — which is the only place a user ever runs it.
It now resolves the compiler through the install root, like every other command. It now resolves the compiler through the install root, like every other command.
`install.sh` also now puts `ludic` on `PATH` for every shell, not just the one
`$SHELL` names. The PATH edit lives in one file (`~/.ludic/env`) that each
profile sources, and the profiles are chosen to cover what people actually open:
`~/.profile` for sh and login bash, `~/.zshenv` because zsh never reads
`~/.profile`, `~/.bashrc`/`~/.bash_profile` when they already exist, and fish's
config when fish is installed. Re-running the installer does not add a second
copy, and `--no-modify-path` still touches nothing.

View file

@ -219,35 +219,75 @@ install_from_source() {
} }
# ---- PATH ------------------------------------------------------------------- # ---- PATH -------------------------------------------------------------------
#
# "Installed but `ludic` is not a command" is the most common way an installer
# fails, so this is deliberate about it.
#
# The PATH edit itself lives in one file, <install>/env, and each shell profile
# gets a single line that sources it. That keeps the change easy to find, easy to
# undo (delete the line, delete ~/.ludic), and means a later install does not
# accumulate a second export.
#
# Which profiles: enough to cover every shell the user actually opens, not just
# the one $SHELL happens to name.
#
# ~/.profile sh, dash, and bash login shells that have no .bash_profile.
# Created when missing — it is the portable catch-all.
# ~/.bash_profile bash login shells (what macOS Terminal starts). Only touched
# ~/.bashrc when they already exist: creating .bash_profile would stop
# bash from reading ~/.profile and silently break other tools.
# ~/.zshenv zsh reads this for *every* invocation, interactive or not,
# and never reads ~/.profile. Created when missing.
# fish config only when fish is installed.
# The shell profiles worth touching: the login shell's, plus ~/.profile as the # write <install>/env and <install>/env.fish — the one place the PATH is set
# catch-all. Appending a guarded line is the least surprising thing an installer write_env_files() {
# can do to someone's dotfiles, and it is easy to find and delete. cat > "${INSTALL_DIR}/env" <<EOF
profiles() { # Adds the Ludic toolchain to PATH. Sourced from your shell profile; safe to
case "${SHELL:-}" in # source more than once. Written by install.sh — edit ~/.ludic instead.
*/zsh) printf '%s\n' "${ZDOTDIR:-$HOME}/.zshrc" ;; case ":\${PATH}:" in
*/bash) printf '%s\n' "$HOME/.bashrc" ;; *":${INSTALL_DIR}/bin:"*) ;;
*/fish) printf '%s\n' "$HOME/.config/fish/config.fish" ;; *) PATH="${INSTALL_DIR}/bin:\${PATH}"; export PATH ;;
esac esac
[ -f "$HOME/.profile" ] && printf '%s\n' "$HOME/.profile" EOF
return 0 cat > "${INSTALL_DIR}/env.fish" <<EOF
# Adds the Ludic toolchain to PATH (fish). Written by install.sh.
if not contains "${INSTALL_DIR}/bin" \$PATH
set -gx PATH "${INSTALL_DIR}/bin" \$PATH
end
EOF
}
# append the source line to one profile, unless it is already there
add_line() {
_file="$1"
_line="$2"
if [ -f "$_file" ] && grep -qF "$_line" "$_file" 2>/dev/null; then
ALREADY="${ALREADY} ${_file}"
return 0
fi
mkdir -p "$(dirname "$_file")" 2>/dev/null || true
printf '\n# ludic\n%s\n' "$_line" >> "$_file" || {
warn "could not write ${_file}"
return 0
}
TOUCHED="${TOUCHED} ${_file}"
} }
add_to_path() { add_to_path() {
_bin="${INSTALL_DIR}/bin"
case ":${PATH}:" in *":${_bin}:"*) return 0 ;; esac
[ "$MODIFY_PATH" -eq 1 ] || return 0 [ "$MODIFY_PATH" -eq 1 ] || return 0
_added='' write_env_files
for _p in $(profiles); do _posix=". \"${INSTALL_DIR}/env\""
[ -e "$_p" ] || continue
if grep -q 'ludic/bin\|LUDIC_INSTALL' "$_p" 2>/dev/null; then _added="$_p"; continue; fi add_line "${HOME}/.profile" "$_posix"
case "$_p" in add_line "${ZDOTDIR:-$HOME}/.zshenv" "$_posix"
*/config.fish) printf '\n# Ludic\nset -gx PATH %s $PATH\n' "$_bin" >> "$_p" ;; # bash: only files that already exist, so we never shadow ~/.profile
*) printf '\n# Ludic\nexport PATH="%s:$PATH"\n' "$_bin" >> "$_p" ;; [ -f "${HOME}/.bash_profile" ] && add_line "${HOME}/.bash_profile" "$_posix"
esac [ -f "${HOME}/.bashrc" ] && add_line "${HOME}/.bashrc" "$_posix"
_added="$_p" if have fish || [ -f "${HOME}/.config/fish/config.fish" ]; then
done add_line "${HOME}/.config/fish/config.fish" "source \"${INSTALL_DIR}/env.fish\""
PATH_NOTE="$_added" fi
return 0
} }
# ---- go --------------------------------------------------------------------- # ---- go ---------------------------------------------------------------------
@ -293,7 +333,8 @@ else
fi fi
fi fi
PATH_NOTE='' TOUCHED=''
ALREADY=''
add_to_path add_to_path
# ---- report ----------------------------------------------------------------- # ---- report -----------------------------------------------------------------
@ -314,21 +355,19 @@ if [ "$OS" != darwin ]; then
fi fi
say "" say ""
if [ -n "$PATH_NOTE" ]; then if [ "$MODIFY_PATH" -eq 1 ]; then
say " ${DIM}added ${INSTALL_DIR}/bin to your PATH in ${PATH_NOTE} — open a new shell, or:${R}" [ -n "${TOUCHED# }" ] && say " ${DIM}added ${INSTALL_DIR}/bin to your PATH in:${R}${TOUCHED# }"
say " export PATH=\"${INSTALL_DIR}/bin:\$PATH\"" [ -n "${ALREADY# }" ] && say " ${DIM}already on your PATH in:${R}${ALREADY# }"
say ""
else
case ":${PATH}:" in
*":${INSTALL_DIR}/bin:"*) ;;
*)
say " ${DIM}add this to your shell profile:${R}"
say " export PATH=\"${INSTALL_DIR}/bin:\$PATH\""
say ""
;;
esac
fi fi
case ":${PATH}:" in
*":${INSTALL_DIR}/bin:"*) ;;
*)
say " ${DIM}this shell was started before that, so for right now:${R}"
say " export PATH=\"${INSTALL_DIR}/bin:\$PATH\""
say " ${DIM}(new terminals pick it up on their own)${R}"
;;
esac
say ""
say "next:" say "next:"
say " ${B}ludic new mygame${R} create a project" say " ${B}ludic new mygame${R} create a project"
say " ${B}cd mygame && ludic run${R} build it and play" say " ${B}cd mygame && ludic run${R} build it and play"