Conda activate.d and deactivate.d do not behave what I expect

Viewed 490

I'm trying to modify my $PATH variable in conda by using activate.d and deactivate.d like:

In activate.d:

export OLD_PATH = $PATH
export PATH= "/path/to/something:$PATH"

In deactivate.d:

export PATH = $OLD_PATH
unset OLD_PATH

In base environment, echo $PATH outputs:

(base) $ echo $PATH
/home/myname/anaconda3/bin:/home/myname/anaconda3/condabin:/home/myname/bin:/home/myname/.local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/cuda-8.0/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/sbin

Then I activate my environment by:

(base) $ conda activate myproject

In the myproject environment, echo $PATH outputs:

(myproject) $ echo $PATH
/path/to/something:/home/myname/anaconda3/envs/myproject/bin:/home/myname/anaconda3/condabin:/home/myname/bin:/home/myname/.local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/cuda-8.0/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/sbin

Which is exactly what I expect. However, when I try to back to the base environment by using

(myproject) $ conda deactivate

What I expect is echo $PATH will output the same thing as it used to do in the base environment. However, what I get is:

(base) $ echo $PATH
/home/myname/anaconda3/envs/myproject/bin:/home/myname/anaconda3/condabin:/home/myname/bin:/home/myname/.local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/cuda-8.0/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/sbin

Notice that the first path is /home/myname/anaconda3/envs/myproject/bin instead of /home/myname/anaconda3/bin.

What's going on there?

1 Answers

Asymmetry Breaks Invertibility

The issue is that Conda is also manipulating the PATH variable and the order in which the scripts are run is not consistent with a proper inverse operation. Namely, the order of operations for both activate and deactivate is:

  1. Update environment variables (including PATH)
  2. Run scripts in etc/conda/(de)activate.d folders.

For this to be consistent with an inverse operation, one would need symmetry of operations. That is, activate should do (1) then (2), and deactivate should do (2) then (1).

Instead, what happens is that the OLD_PATH you save actually corresponds to after PATH was manipulated to activate the env.

Workaround

Instead of trying to capture an OLD_PATH, you could track only what is added and remove that in the deactivate script. For instance, something like

activate.d script

export MY_ADDED_PATH="/path/to/something"
export PATH="$MY_ADDED_PATH:$PATH"

deactivate.d script

# Credit for escaping: https://unix.stackexchange.com/a/129063/148899
escaped_lhs=$(printf '%s\n' "$MY_ADDED_PATH:" | sed 's:[][\\/.^$*]:\\&:g')
export PATH=$(echo "$PATH" | sed "s/$escaped_lhs//") 
unset MY_ADDED_PATH

This seems to work for me on osx-64.


Debugging Tip

Conda can be a mess to deal with because it is a mix of shell functions and Python. However, one of the neat things is that the Conda entry point (e.g., bin/conda) has some internal functions that return strings of shell commands.

Specifically, one can inspect what would be run in the shell when running the command conda activate foo, using

$CONDA_EXE shell.posix activate foo

Similarly,

$CONDA_EXE shell.posix deactivate

will list out the operations for deactivating. This isn't all Conda does in activating and deactivating, but it covers most of it.

Related