Managing project-specific configurations in ZSH
A short post outlining how I implement project-specific shell configurations in Linux.
zsh quality of lifeI recently started working on the RTEMS kernel as my GSoC project which requires me to source some shell parameters to configure the workspace accordingly. A pretty common pattern to do this in software development is to create a new shell script which is sourced manually every time the project is worked upon.
The benefits of this approach, as compared to storing everything in the global
shell config (~/.zshrc
or ~/.bashrc
), is modularity. As any sane Linux user
would have it, I prefer not to pollute my shell environment with parameters I
won’t need when I’m not working on the project. But one major drawback of doing
this is that you have to manually source the config every time you open up a new
shell as follows:
source <project-dir >/source.sh
Now, I have a pretty dynamic workflow, which involves spawning and closing the terminal emulator a whole bunch of times, and having to do this manually each time gets pretty annoying when you’re developing software. So let’s fix that, shall we?
Thinking of the solution #
zsh
is my shell of choice and it supports adding hooks
. Now,
hooks are pretty awesome in the fact that they allow us to run functions based
on certain events. The event of our interest is chpwd
, which allows us to run
a function every time the directory changes.
With the shell-specific details out of the way, we can move ahead to working out
the implementation details of the hook callback itself. What I ended up with was
an “upward search” approach. It involves reserving a name for the shell script
to be sourced, .source.sh
in my case, and to keep searching each directory up
the file path until the file is found, or we land at ~
. This allows the shell
config to be sourced even if we land in a subdirectory of the project, which is
a basic requirement for me. git
also uses the same approach for
determining the project root directory.
# Look for the project config by traversing up the path
function source-local-config() {
local file=".source.sh"
local parent="$PWD"
while [[ "$parent" != "$HOME" ]]; do
if [[ -f "$parent/$file" && -r "$parent/$file" ]]; then
source "$parent/$file"
return
fi
parent="${parent%/*}"
done
}
It does not make sense to have
~/.source.sh
as keeping those configurations in~/.zshrc
would be better.
Finalizing #
With the callback logic implemented, all we need to do is to define it in
~/.zshrc
, as follows:
# Run once on startup and then add hook for directory change
source-local-config
autoload -U add-zsh-hook
add-zsh-hook chpwd source-local-config
Note that I also invoke the function on startup in addition to registering the
hook. This is to handle the edge case where I might open up my terminal emulator
directly in a project. Since the chpwd
event only registers when we invoke
cd
, that is why we have this startup invocation to handle the shell spawning
edge case as well.
That concludes the zsh setup. To add a project-specific config, I simply create
a .source.sh
file in the folder similar to the following:
#!/bin/sh
export RTEMS_DIR="$(realpath "$(dirname "$0")")"
export RTEMS_PREFIX="$RTEMS_DIR/build/v6"
export PATH="$RTEMS_PREFIX/bin:\
$RTEMS_DIR/rsb/source-builder:\
$PATH"
I have to admit this simple setup is a huge quality of life improvement for me, and I hope it helps you as well.
My
.zshrc
actually resides in~/.config/zsh/
as I’ve set my$ZDOTDIR
to$XDG_CONFIG_HOME/zsh
according to the XDG spec. Therefore, the more apt choice would be to use$ZDOTDIR/.zshrc
instead of~/.zshrc
.
Comments
Nothing yet.