Is there a nice way to set directory/project local environment variables?

I have been working on several projects, and they require different environment variables (e.g., PATH for different versions of clang executables, PYTHONPATH for several external modules). Whenever I work on one project, I have to modify these environment variables myself (e.g., change .zshrc/.bashrc and source it); and I sometimes forget and make mistakes.

Is there a way/project that helps do this automatically, similar to what virtualenv does in Python?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

There’re mature tools designed to set environment variables for a specific directory.

Compared with other tools designed for this, direnv is the best of them. One of the main benefit is that it supports unloading the environment variables when you exit from that directory.

direnv is an environment switcher for the shell. It knows how to hook into bash, zsh, tcsh, fish shell and elvish to load or unload environment variables depending on the current directory. This allows project-specific environment variables without cluttering the ~/.profile file.

What makes direnv distinct between other similar tools:

  • direnv is written in Go, faster compared with its counterpart written in Python
  • direnv supports unloading environment variables when you quit from the specific dir
  • direnv covers many shells

Similar projects

  • Environment Modules – one of the oldest (in a good way) environment-loading systems
  • autoenv – lightweight; doesn’t support unloads; slow written in Python
  • zsh-autoenv – a feature-rich mixture of autoenv and smartcd: enter/leave events, nesting, stashing (Zsh-only).
  • ~~asdf~~, asdf is a plugin manager to switch different versions of the same executable. NOT a env switcher at all.

Method 2

Yuch, too much maintenance and too much individual stuff.
I’d recommend putting most of the effort into having appropriately scoped names that are appropriate for the platform so you can just have all of them all of the time. PYTHONPATH is a good example… you’re unlikely to want to repurpose it for a Ruby project… You can group and mark the group with comments in the .bashrc to ease maintenance.
It is not always possible to do that, i.e.. when there is conflict (plus it requires editing and not using discrete files) and sometimes you will need a framework specific setup file. One approach to that is to have aliases setup to run them, e.g.

e.g.

alias pp='. ~/pp.setup' # For using Python
alias rb='. /rb.setup'  # For using Ruby

You could also create a function, something like ‘switcher’ that uses/sets a variable and just switches using a parameter passed in, or just toggles to what isn’t current.

Method 3

There are management tools such as modules, that allow dynamic modification of a user’s environment… though this one is no longer developed; last version is from 2012

Example:

$ module load gcc/3.1.1
$ which gcc
/usr/local/gcc/3.1.1/linux/bin/gcc

Method 4

You can use direnv to load environment variables on directory change. From its homepage:

direnv is an environment switcher for the shell. It knows how to hook into bash, zsh, tcsh, fish shell and elvish to load or unload environment variables depending on the current directory. This allows project-specific environment variables without cluttering the ~/.profile file.

I am using it with the xonsh shell (via a small additional plugin) and it works excellent.

Method 5

For zsh, this is very easy to do by invoking add-zsh-hook on the chpwd event.

env_on_chdir () {
    case $PWD in
        /home/user/path/to/dir )
            export GO111MODULE=on;
            ;;
        /home/user/other/dir )
            export NO_COLOR=true;
            ;;
        * )
            # change background, when entering any other directory
            export GO111MODULE=off;
            unset NO_COLOR;
            ;;
    esac
}

# add env_on_chdir to chpwd_functions
add-zsh-hook chpwd env_on_chdir

For more, see https://www.refining-linux.org/archives/42-ZSH-Gem-8-Hook-function-chpwd.html.

Method 6

You can source any file you like, it doesn’t have to be .zshrc. You could make an env-setup script for each different project, and keep it in the project directory.

If you want to be able to start new shells and have them load the env settings, you have a couple options:

  • Always start your shells in the project directory, and have your .zshrc [[ -e ./PROJECT-ENV.sh ]] && source ./PROJECT-ENV.sh (which you keep there). DANGEROUS: starting a new shell in a directory with hostile contents runs whatever code is in the file you source.
  • .zshrc: source ~/current-project.sh. update current project with ln -sf "$PWD/THIS-PROJECT-ENV.sh ~/current-project.sh

If only a few commands for each project need the env vars set, you could make wrapper scripts for them that set the variables, and then leave your shell rc file untouched.

Another idea: you could hack something into PROMPT_COMMAND, which runs before every prompt is displayed. Or hook cd/pushd/popd by writing shell functions with those names which check/set env vars and then run builtin cd.

Method 7

Inspired by @Peter Cordes and @Michael Durrant’s answers, I came up with a limited and lightweight solution for my special case.

Firstly let me re-illustrate my real issues.

I need to use different llvm related executables (clang, llvm-config, etc) that are under project local directories, and I need to use 1 default version for general purpose (compiling, etc). I also need to need to use different python modules (llvm python bindings) inside different local directories.

Since all my llvm related projects are built with similar directory structures, I specify the llvm version related environment variable (MY_LLVM_VERSION) and add the directories to PATH and PYTHONPATH. I used a function named _my_llvm_version that customize the variable interactively. As functions only have effects inside it, I use aliases to do the real source work. Then let MY_LLVM_VERSION defaults to my commonly used one (git). These are all inside ~/.zlocal that will be sourced for an interactive zsh. (Full file is here). By sourcing ~/.zlocal, I’m able to change PATH and PYTHONPATH according to MY_LLVM_VERSION.

function _my_llvm_version() {
    available=("3.3" "3.4" "3.5" "git")
    USAGE="my_llvm_version 3.3|3.4|3.5|git"
    if [ $# -ne 1 ] || ! (( ${available[(I)${1}]} )); then
      echo "usage: $USAGE"
    fi
    MY_LLVM_VERSION="$1"
}
alias my_ll33='_my_llvm_version 3.3;source ~/.zlocal'
alias my_ll34='_my_llvm_version 3.4;source ~/.zlocal'
alias my_ll35='_my_llvm_version 3.5;source ~/.zlocal'
alias my_llgit='_my_llvm_version git;source ~/.zlocal'
: ${MY_LLVM_VERSION:=git}

I think it should be more maintainable and do not invoke a child shell. It should be useful to add llvm version to PS1 area, but currently I do not need that.

Of course, this solution is not general and not a real solution to my original question.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x