Skip to content
This repository was archived by the owner on Mar 13, 2026. It is now read-only.

Commit 7c5dba3

Browse files
authored
Remove opam's pre-command shell hook (#14)
The `opam init` command adds some code to the user's shell config file to set up the user's environment to be able to run programs installed by opam. Firstly the user's PATH variable is prefixed with the bin directory from the default opam switch. Additionally, opam installs a pre-command hook into the shell so that before every command is run, the user's PATH variable is _again_ prefixed with the path to the bin directory from the current opam switch. This poses a problem for the dune binary distro, because if the user's current opam switch contains dune (which is very likely given dune is a dependency of the majority of opam packages), the instance of dune from the opam switch will take precedence over the dune instance from the binary distro. As long as opam's pre-command shell hook is present, opam's bin directory will always be at the front of PATH when commands are run regardless of any additional PATH manipulation done by the install script in the user's shell config, as the former is run before _every_ command, while the latter is only run once when the shell starts. This commit changes the install script to add logic to the user's shell config file to uninstall opam's pre-command shell hook. Modifications to the PATH performed by opam's setup will still take place when the shell starts, so programs in the default opam switch will still be runnable from the user's environment. Users will still also be able to change their opam environment by running `eval $(opam env)` which will prefix PATH with the current opam switch's bin directory. The only difference is that opam's pre-command shell hook will no longer run before each command is evaluated. In order for the pre-command hook to be uninstalled, dune's setup must take place after opam's setup in the user's shell config. For zsh and fish this works because dune's setup is appended to the same file to which opam adds its setup logic, so dune's setup is guaranteed to appear after opam's setup. For bash it's more complicated as `opam init` adds its setup to ~/.profile by default, but it's possible that users will move this logic manually to other config files such as ~/.bashrc. This means that appending dune's logic to the end of (say) ~/.bashrc doesn't guarantee that it will run after opam's setup, as a user may have configured their ~/.profile to source their ~/.bashrc and then run the opam setup. The simplest way to ensure that dune is always initialized after opam is to detect which bash config file contains opam's setup logic, and append dune's setup logic to the same file. The install script is modified to this effect, and an additional prompt is added so users can override the choice of which config file to modify. Signed-off-by: Stephen Sherratt <stephen@sherra.tt>
1 parent ea14f57 commit 7c5dba3

File tree

5 files changed

+353
-23
lines changed

5 files changed

+353
-23
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
- Handle the case when the `$SHELL` variable is unset (#13)
88
- Make sure directory containing shell config exists before updating shell
99
config (#10)
10+
- Remove opam's shell hooks if present, which would have caused any dune
11+
installed with opam to be run instead of the dune installed by this script
12+
when running `dune` in the terminal. (#14)
1013

1114
### Added
1215

install.sh

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,24 +327,63 @@ main () {
327327
echo
328328
echo
329329

330+
if [ "$should_update_shell_config" = "n" ]; then
331+
# The remainder of the install script deals with updating the shell
332+
# config file. If the user indicated by command-line option that they
333+
# don't wish to do this, exit now.
334+
exit_message
335+
echo
336+
exit 0
337+
fi
338+
330339
shell_name=${shell_name:-$(infer_shell_name)}
331340
env_dir="$install_root/share/dune/env"
341+
remove_opam_precmd_hook_posix="PROMPT_COMMAND=\"\$(echo \"\$PROMPT_COMMAND\" | tr ';' '\\n' | grep -v _opam_env_hook | paste -sd ';' -)\""
332342
case "$shell_name" in
333343
sh|ash|dash)
344+
shell_config_inferred="${shell_config:-$HOME/.profile}"
334345
env_file="$env_dir/env.bash" # TODO: change this to env.sh once env.sh is added to the dune binary distro
335-
shell_config="${shell_config:-$HOME/.profile}"
346+
remove_opam_precmd_hook=$remove_opam_precmd_hook_posix
336347
;;
337348
bash)
349+
bash_config_candidates="$HOME/.profile $HOME/.bash_profile $HOME/.bashrc"
350+
if [ "${XDG_CONFIG_HOME:-}" ]; then
351+
bash_config_candidates="$bash_config_candidates $XDG_CONFIG_HOME/profile $XDG_CONFIG_HOME/.profile $XDG_CONFIG_HOME/bash_profile $XDG_CONFIG_HOME/.bash_profile $XDG_CONFIG_HOME/bashrc $XDG_CONFIG_HOME/.bashrc"
352+
fi
353+
# When opam is initialized for a user using bash as their shell it adds
354+
# its configuration to ~/.profile by default. It's possible that users
355+
# manually specified a different bash config file such as ~/.bashrc or
356+
# ~/.bash_profile. Also some users may have moved the opam
357+
# configuration from one bash config file to another. It's necessary
358+
# that Dune's configuration be evaluated after opam's configuration. If
359+
# users have multiple different bash configurations present (it's quite
360+
# common to have both ~/.profile and ~/.bashrc with one sourcing the
361+
# other, for example), one way to make sure Dune is initialized after
362+
# opam is to append the Dune configuration to the end of which ever
363+
# bash config file also contains opam's configuration. This function
364+
# chooses the bash config file to add Dune's configuration to by
365+
# searching for a file containing Opam's configuration already, and
366+
# will select ~/.profile by default to match the behaviour of opam.
367+
for config in $bash_config_candidates; do
368+
if test -f "$config" && match=$(grep -Hn '\.opam/opam-init/init\.sh' "$config") ; then
369+
shell_config_with_opam_init="$config"
370+
bash_opam_init_match=$match
371+
break
372+
fi
373+
done
374+
shell_config_inferred="${shell_config_with_opam_init:-$HOME/.profile}"
338375
env_file="$env_dir/env.bash"
339-
shell_config="${shell_config:-$HOME/.bashrc}"
376+
remove_opam_precmd_hook=$remove_opam_precmd_hook_posix
340377
;;
341378
zsh)
342379
env_file="$env_dir/env.zsh"
343-
shell_config="${shell_config:-$HOME/.zshrc}"
380+
shell_config_inferred="$HOME/.zshrc"
381+
remove_opam_precmd_hook="autoload -Uz add-zsh-hook; add-zsh-hook -d precmd _opam_env_hook"
344382
;;
345383
fish)
346384
env_file="$env_dir/env.fish"
347-
shell_config="${shell_config:-$HOME/.config/fish/config.fish}"
385+
shell_config_inferred="$HOME/.config/fish/config.fish"
386+
remove_opam_precmd_hook="functions --erase __opam_env_export_eval"
348387
;;
349388
*)
350389
info "The install script does not recognize your shell ($shell_name)."
@@ -357,11 +396,61 @@ main () {
357396
;;
358397
esac
359398

399+
if [ -z "${shell_config+x}" ]; then
400+
info "The installer can modify your shell config file to set up your environment for running dune from your terminal."
401+
echo
402+
if [ -z "${bash_opam_init_match+x}" ]; then
403+
info "Based on your shell ($shell_name) the installer has inferred that your shell config file is: $shell_config_inferred"
404+
else
405+
info "Your shell is bash and the installer found an existing shell configuration for opam in $shell_config_inferred at:"
406+
echo
407+
echo
408+
info "$bash_opam_init_match"
409+
echo
410+
echo
411+
info "It's recommended to add Dune's configuration to the same file as the existing opam configuration."
412+
fi
413+
echo
414+
while [ -z "${shell_config+x}" ]; do
415+
echo
416+
info "Enter the absolute path of your shell config file or leave blank for default (no modification will be performed yet):"
417+
echo
418+
info_bold "[$shell_config_inferred] >"
419+
read -r choice < /dev/tty
420+
case "$choice" in
421+
"")
422+
shell_config=$shell_config_inferred
423+
;;
424+
'~'/*)
425+
shell_config=$(echo "$choice" | sed "s#~#$HOME#")
426+
echo
427+
warn "Expanding $choice to $shell_config"
428+
;;
429+
/*)
430+
shell_config=$choice
431+
;;
432+
*)
433+
echo
434+
warn "Not an absolute path: $choice"
435+
;;
436+
esac
437+
done
438+
echo
439+
fi
440+
360441
dune_env_call="__dune_env $(unsubst_home "$install_root")"
361442
shell_config_code() {
362-
echo "# From Dune installer:"
443+
echo
444+
echo "# BEGIN configuration from Dune installer"
445+
echo "# This configuration must be placed after any opam configuration in your shell config file."
446+
echo "# This performs several tasks to configure your shell for Dune:"
447+
echo "# - makes sure the dune executable is available in your \$PATH"
448+
echo "# - registers shell completions for dune if completions are available for your shell"
449+
echo "# - removes opam's pre-command hook because it would override Dune's shell configuration"
363450
echo "source $(unsubst_home "$env_file")"
364451
echo "$dune_env_call"
452+
echo "$remove_opam_precmd_hook # remove opam's pre-command hook"
453+
echo "# END configuration from Dune installer"
365454
}
366455

367456
if [ -f "$shell_config" ] && match=$(grep -Hn "$(echo "$dune_env_call" | sed 's#\$#\\$#')" "$shell_config"); then
@@ -372,7 +461,6 @@ main () {
372461
echo
373462
info "Just in case it isn't, here are the lines that need run when your shell starts to initialize Dune:"
374463
echo
375-
echo
376464
shell_config_code
377465
echo
378466
exit_message
@@ -382,7 +470,6 @@ main () {
382470

383471
info "To run dune from your terminal, you'll need to add the following lines to your shell config file ($shell_config):"
384472
echo
385-
echo
386473
shell_config_code
387474
echo
388475

interactive_generic.tcl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ set timeout 10
88
set install_script [lindex $argv 0]
99
set version [lindex $argv 1]
1010
set install_root [lindex $argv 2]
11+
set shell_config [lindex $argv 3]
1112

1213
# Create a temporary directory to store the shell config
1314
set tmp [exec mktemp -d]
1415

15-
spawn "$install_script" "$version" --shell bash --shell-config "$tmp/bashrc"
16+
spawn "$install_script" "$version"
1617

1718
expect "> "
1819
send "$install_root\r"
1920
expect "Dune successfully installed"
2021

21-
expect "Would you like these lines to be appended to $tmp/bashrc?"
22+
expect "Enter the absolute path of your shell config file or leave blank for default"
23+
expect "> "
24+
send "$shell_config\r"
25+
26+
expect "Would you like these lines to be appended to"
2227
send "y\r"
2328

2429
expect "This installer will now exit."
25-

0 commit comments

Comments
 (0)