Search code examples
shellanacondacondafish

How to wrap the conda command in a fish function to initialize conda only on demand?


From time to time I use the miniconda package manager. Normally, conda adds the following line to ~/.config/fish/config.fish upon installation:

eval /home/quappas/.apps/miniconda3/bin/conda "shell.fish" "hook" $argv | source

This line must be executed before conda can be used. However, it is quite slow to execute and having it in config.fish causes a significant startup delay every time I open a terminal. This is annoying because most of the time I don't even want to use conda when I open a terminal. So I decided to remove the line from config.fish and define a function conda.fish to wrap the conda command instead:

function conda --wraps 'conda'
    if not set -q CONDA_INITIALIZED
        echo 'Initializing conda...'
        eval /home/quappas/.apps/miniconda3/bin/conda "shell.fish" "hook" | source
        set -g CONDA_INITIALIZED 1
    end
    command conda $argv
end

With this function, for some reason I can do

conda
conda activate myenv

in a new terminal and it works just fine. However I do

conda activate myenv

directly in a new terminal, I get conda's "Your shell has not been properly configured to use 'conda activate'." error. Confusingly if I do

conda activate myenv
conda activate myenv

in a new terminal, the first command gives me the above error but the second one activates my environment successfully without complaint.

I'm not sure if this is a problem with my function, fish or conda. What can I do to be able to use the conda command normally, but only run the slow conda initialization script when I actually call conda?


Solution

  • The issue is that the conda integration happens by defining a function called conda. So calling command conda is wrong, they want it to be called via a wrapper function.

    See what happens after you run conda once and then use type conda to see the definition:

    conda is a function with definition
    # Defined via `source`
    function conda
        set -l CONDA_EXE /opt/miniconda3/bin/conda
        if [ (count $argv) -lt 1 ]
            $CONDA_EXE
        else
            set -l cmd $argv[1]
            set -e argv[1]
            switch $cmd
                case activate deactivate
                    eval ($CONDA_EXE shell.fish $cmd $argv)
                case install update upgrade remove uninstall
                    $CONDA_EXE $cmd $argv
                    and eval ($CONDA_EXE shell.fish reactivate)
                case '*'
                    $CONDA_EXE $cmd $argv
            end
        end
    end
    

    You can either do that in your own function, or call conda $argv, which would ordinarily be an infinite loop.

    Since that seems a bit awkward, how about this instead:

    function conda --wraps 'conda'
        echo 'Initializing conda...'
        # We erase ourselves because conda defines a function of the same name.
        # This allows checking that that happened and can prevent infinite loops
        functions --erase conda
        /home/quappas/.apps/miniconda3/bin/conda "shell.fish" "hook" | source
    
        if not functions -q conda
            # If the function wasn't defined, we should not do the call below.
            echo 'Something went wrong initializing conda!' >&2
            return 1
        end
        
        # Now we can call `conda`, which is a function, but not this one (because we erased it),
        # so this is not an infinite loop.
        conda $argv
    end
    

    The variable is now unnecessary since this function is never called twice in the same shell, and I removed the dangerous and unnecessary eval.