Repository: spyder-ide/spyder

Issue:
IPython console does not work with Micromamba environments
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

Using a `micromamba` environment is currently not permitted.

```python
/Users/rclary/Applications/Spyder.app/Contents/Resources/lib/python3.9/spyder/plugins/ipythonconsole/scripts/conda‑activate.sh: line 11: /Users/rclary/opt/micromamba/bin/activate: No such file or directory
```




## Versions

* Spyder version: 5.3.0 
* Python version: 3.9.5
* Qt version: 5.15.2
* PyQt5 version: 5.15.6
* Operating System: Darwin 20.6.0

### Dependencies

```
# Mandatory:
applaunchservices >=0.1.7            :  0.2.1 (OK)
atomicwrites >=1.2.0                 :  1.4.0 (OK)
chardet >=2.0.0                      :  4.0.0 (OK)
cloudpickle >=0.5.0                  :  2.0.0 (OK)
cookiecutter >=1.6.0                 :  1.7.3 (OK)
diff_match_patch >=20181111          :  20200713 (OK)
intervaltree >=3.0.2                 :  3.1.0 (OK)
IPython >=7.31.1;<8.0.0              :  7.32.0 (OK)
jedi >=0.17.2;<0.19.0                :  0.18.1 (OK)
jellyfish >=0.7                      :  0.9.0 (OK)
jsonschema >=3.2.0                   :  4.4.0 (OK)
keyring >=17.0.0                     :  23.5.0 (OK)
nbconvert >=4.0                      :  6.4.5 (OK)
numpydoc >=0.6.0                     :  1.2.1 (OK)
parso >=0.7.0;<0.9.0                 :  0.8.3 (OK)
pexpect >=4.4.0                      :  4.8.0 (OK)
pickleshare >=0.4                    :  0.7.5 (OK)
psutil >=5.3                         :  5.9.0 (OK)
pygments >=2.0                       :  2.11.2 (OK)
pylint >=2.5.0                       :  2.13.3 (OK)
pyls_spyder >=0.4.0                  :  0.4.0 (OK)
pylsp >=1.4.1;<1.5.0                 :  1.4.1 (OK)
pylsp_black >=1.2.0                  :  1.2.0 (OK)
qdarkstyle >=3.0.2;<3.1.0            :  3.0.3 (OK)
qstylizer >=0.1.10                   :  0.2.1 (OK)
qtawesome >=1.0.2                    :  1.1.1 (OK)
qtconsole >=5.3.0;<5.4.0             :  5.3.0 (OK)
qtpy >=2.0.1                         :  2.0.1 (OK)
rtree >=0.9.7                        :  0.9.7 (OK)
setuptools >=49.6.0                  :  60.10.0 (OK)
sphinx >=0.6.6                       :  4.5.0 (OK)
spyder_kernels >=2.3.0;<2.4.0        :  2.3.0 (OK)
textdistance >=4.2.0                 :  4.2.2 (OK)
three_merge >=0.1.1                  :  0.1.1 (OK)
watchdog >=0.10.3                    :  2.1.7 (OK)
zmq >=17                             :  22.3.0 (OK)

# Optional:
cython >=0.21                        :  0.29.28 (OK)
matplotlib >=3.0.0                   :  3.5.1 (OK)
numpy >=1.7                          :  1.22.3 (OK)
pandas >=1.1.1                       :  1.4.1 (OK)
scipy >=0.17.0                       :  1.8.0 (OK)
sympy >=0.7.3                        :  1.10.1 (OK)

# Spyder plugins:
spyder_terminal.terminalplugin 1.2.2 :  1.2.2 (OK)
```



Discussion:
I think this problem happens because micromamba has the code to activate environments in itself. Could you check that?
> I think this problem happens because micromamba has the code to activate environments in itself. Could you check that?

I don't know what you mean by "activate environments in itself".
The essential problem is that Spyder thinks a micromamba environment is a conda environment because it has a conda-meta directory, and therefore uses the conda-activate.sh script. Spyder also assumes that a conda environment will have an activation script `<conda-root>/bin/activate` which I believe is true for conda, miniconda, and mamba, but not true for micromamba.

micromamba environments should still be activated and can be done so using the executable
```
eval "$($MAMBA_EXE shell activate <env path>)"
```

An easy fix in `conda-activate.sh` is:
```
if [[ -e "$CONDA_ACTIVATE_SCRIPT" ]]; then
    # conda, miniconda, mamba
    source $CONDA_ACTIVATE_SCRIPT $CONDA_ENV_PATH
elif [[ -n "$MAMBA_EXE" ]]; then
    # micromamba
    eval "$($MAMBA_EXE shell activate $CONDA_ENV_PATH)"
fi
```

> micromamba environments should still be activated and can be done so using the executable

That's what I meant, i.e. that you don't need to invoke an external script to activate envs with micromaba because it can do that by itself.

> An easy fix in conda-activate.sh is

I think that's fine, but I have one suggestion: changing `MAMBA_EXE` for `MICROMAMBA_EXE` because I think both binaries have different names, right?
> I think that's fine, but I have one suggestion: changing `MAMBA_EXE` for `MICROMAMBA_EXE` because I think both binaries have different names, right?

On my installation, micromamba sets `MAMBA_EXE`; there is no `MICROMAMBA_EXE`. I don not have mamba installed, however; will this be different if mamba is installed? I assume not, since micromamba is the same as mamba, just packaged into a single executable...


Ok, I see. Then forgot what I said.

But that about Windows?
I'll have to verify on Linux and Windows.
It appears to use the same environment variable on Windows, both `MAMBA_EXE` and `MAMBA_ROOT_PREFIX`.
Ok, great! Then I think you're ready to open a PR for this.
While not relevant to this PR, I discovered something nice about micromamba that will be useful in the future implementation of the environment/package manager. Executables in _any_ conda environment (conda, miniconda, mamba, micromamba) can be run directly from micromamba with their environments activated in a single command!
```
micromamba run --prefix=<path to env> <executable> <exec opts> <exec args>
```
This will simplify launching IPython Consoles and obviate the need for `conda-activate.sh`.

Resolving patch:
diff --git a/spyder/config/base.py b/spyder/config/base.py
--- a/spyder/config/base.py
+++ b/spyder/config/base.py
@@ -572,6 +572,22 @@ def running_in_mac_app(pyexec=None):
         return False
 
 
+# =============================================================================
+# Micromamba
+# =============================================================================
+def get_spyder_umamba_path():
+    """Return the path to the Micromamba executable bundled with Spyder."""
+    if running_in_mac_app():
+        path = osp.join(osp.dirname(osp.dirname(__file__)),
+                        'bin', 'micromamba')
+    elif is_pynsist():
+        path = None  # TODO: when micromamba is added to Windows installer
+    else:
+        path = None
+
+    return path
+
+
 #==============================================================================
 # Reset config files
 #==============================================================================
diff --git a/spyder/plugins/ipythonconsole/utils/kernelspec.py b/spyder/plugins/ipythonconsole/utils/kernelspec.py
--- a/spyder/plugins/ipythonconsole/utils/kernelspec.py
+++ b/spyder/plugins/ipythonconsole/utils/kernelspec.py
@@ -105,7 +105,7 @@ def argv(self):
             # the activation scripts at spyder/plugins/ipythonconsole/scripts/
             kernel_cmd = [
                 get_activation_script(),  # This is bundled with Spyder
-                get_conda_activation_script(pyexec),
+                get_conda_activation_script(),
                 get_conda_env_path(pyexec),  # Might be external
                 pyexec,
                 '{connection_file}',
diff --git a/spyder/utils/conda.py b/spyder/utils/conda.py
--- a/spyder/utils/conda.py
+++ b/spyder/utils/conda.py
@@ -13,6 +13,7 @@
 import sys
 
 from spyder.utils.programs import find_program, run_program, run_shell_command
+from spyder.config.base import get_spyder_umamba_path
 
 WINDOWS = os.name == 'nt'
 CONDA_ENV_LIST_CACHE = {}
@@ -63,19 +64,28 @@ def get_conda_root_prefix(pyexec=None, quote=False):
     return root_prefix
 
 
-def get_conda_activation_script(pyexec=None, quote=False):
+def get_conda_activation_script(quote=False):
     """
     Return full path to conda activation script.
 
     If `quote` is True, then quotes are added if spaces are found in the path.
     """
-    if os.name == 'nt':
-        activate = 'Scripts/activate'
+    # Use micromamba bundled with Spyder installers or find conda exe
+    exe = get_spyder_umamba_path() or find_conda()
+
+    if osp.basename(exe).startswith('micromamba'):
+        # For Micromamba, use the executable
+        script_path = exe
     else:
-        activate = 'bin/activate'
+        # Conda activation script is relative to executable
+        conda_exe_root = osp.dirname(osp.dirname(exe))
+        if WINDOWS:
+            activate = 'Scripts/activate'
+        else:
+            activate = 'bin/activate'
+        script_path = osp.join(conda_exe_root, activate)
 
-    script_path = os.path.join(get_conda_root_prefix(pyexec, quote=False),
-                               activate).replace('\\', '/')
+    script_path = script_path.replace('\\', '/')
 
     if quote:
         script_path = add_quotes(script_path)
@@ -103,8 +113,12 @@ def get_conda_env_path(pyexec, quote=False):
 
 def find_conda():
     """Find conda executable."""
-    conda_exec = 'conda.bat' if WINDOWS else 'conda'
-    conda = find_program(conda_exec)
+    # First try the environment variables
+    conda = os.environ.get('CONDA_EXE') or os.environ.get('MAMBA_EXE')
+    if conda is None:
+        # Try searching for the executable
+        conda_exec = 'conda.bat' if WINDOWS else 'conda'
+        conda = find_program(conda_exec)
     return conda
 
 


Test patch:
diff --git a/spyder/utils/tests/test_conda.py b/spyder/utils/tests/test_conda.py
--- a/spyder/utils/tests/test_conda.py
+++ b/spyder/utils/tests/test_conda.py
@@ -40,11 +40,8 @@ def test_add_quotes():
 
 
 def test_get_conda_activation_script():
-    output = get_conda_activation_script(TEST_PYEXEC)
-    if os.name == 'nt':
-        assert output == 'c:/miniconda/Scripts/activate'
-    else:
-        assert output == '/miniconda/bin/activate'
+    output = get_conda_activation_script()
+    assert os.path.exists(output)
 
 
 def test_get_conda_env_path():