diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 40f082e..9a0fcfd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,7 +77,7 @@ jobs: fail-fast: false matrix: os: [macos-latest,ubuntu-latest,windows-latest] - pyver: ["3.10","3.12"] + pyver: ["3.9","3.12"] solver: ["conda", "mamba", "micromamba"] steps: - name: Retrieve the source code @@ -116,12 +116,10 @@ jobs: conda create -n testbase -c ./conda-bld nb_conda_kernels python=${{ matrix.pyver }} notebook=$NBVER pytest pytest-cov mock requests conda activate testbase solver=${{ matrix.solver }} - export CONDA_EXE=$(echo $CONDA_EXE | sed -E "s@^(.*/)conda(.*)@\\1${solver}\\2@") + [ $solver = conda ] || export CONDA_EXE=$(echo $CONDA_EXE | sed -E "s@^(.*/)conda(.*)@\\1${solver}\\2@") + [ $solver = mamba ] && export NBCK_NO_ACTIVATE_SCRIPT=yes [ $solver = micromamba ] && export MAMBA_EXE=$CONDA_EXE [ $solver = micromamba ] && export MAMBA_ROOT_PREFIX=$CONDA_ROOT - $CONDA_EXE info - - $CONDA_EXE env list python -m nb_conda_kernels list python -m pytest -v --cov=nb_conda_kernels tests 2>&1 | tee build.log # Because Windows refuses to preserve the error code diff --git a/nb_conda_kernels/manager.py b/nb_conda_kernels/manager.py index fa53c76..3bbfc99 100644 --- a/nb_conda_kernels/manager.py +++ b/nb_conda_kernels/manager.py @@ -308,7 +308,9 @@ def _all_specs(self): all_specs = {} # We need to be able to find conda-run in the base conda environment # even if this package is not running there - conda_prefix = self._conda_info['conda_prefix'] + conda_info = self._conda_info + conda_exe = conda_info['conda_exe'] + conda_prefix = conda_info['conda_prefix'] all_envs = self._all_envs() for env_name, env_path in all_envs.items(): kspec_base = join(env_path, 'share', 'jupyter', 'kernels') @@ -358,7 +360,7 @@ def _all_specs(self): display_name += ' *' spec['display_name'] = display_name if env_path != sys.prefix: - spec['argv'] = RUNNER_COMMAND + [conda_prefix, env_path] + spec['argv'] + spec['argv'] = RUNNER_COMMAND + [conda_exe, env_path] + spec['argv'] metadata = spec.get('metadata', {}) metadata.update({ 'conda_env_name': env_name, diff --git a/nb_conda_kernels/runner.py b/nb_conda_kernels/runner.py index 8699755..96b2f2b 100644 --- a/nb_conda_kernels/runner.py +++ b/nb_conda_kernels/runner.py @@ -1,6 +1,7 @@ from __future__ import print_function import os +from os.path import join, exists import sys import subprocess import locale @@ -14,25 +15,39 @@ def exec_in_env(conda_prefix, env_path, *command): # Run the standard conda activation script, and print the # resulting environment variables to stdout for reading. is_current_env = env_path == sys.prefix + mamba_path = os.environ.get('MAMBA_EXE') if sys.platform.startswith('win'): if is_current_env: subprocess.Popen(list(command)).wait() + return else: - activate = os.path.join(conda_prefix, 'Scripts', 'activate.bat') - ecomm = [os.environ['COMSPEC'], '/S', '/U', '/C', '@echo', 'off', '&&', - 'chcp', '65001', '&&', 'call', activate, env_path, '&&', - '@echo', 'CONDA_PREFIX=%CONDA_PREFIX%', '&&',] + list(command) - subprocess.Popen(ecomm).wait() + activate_path = join(conda_prefix, 'Scripts', 'activate.bat') + if exists(activate_path): + ecomm = [os.environ['COMSPEC'], '/S', '/U', '/C', '@echo', 'off', '&&', + 'chcp', '65001', '&&', 'call', activate_path, env_path, '&&', + '@echo', 'CONDA_PREFIX=%CONDA_PREFIX%', '&&'] + list(command) + subprocess.Popen(ecomm).wait() + return else: - quoted_command = [quote(c) for c in command] if is_current_env: - os.execvp(quoted_command[0], quoted_command) - else: - activate = os.path.join(conda_prefix, 'bin', 'activate') - ecomm = ". '{}' '{}' && echo CONDA_PREFIX=$CONDA_PREFIX && exec {}".format(activate, env_path, ' '.join(quoted_command)) + os.execvp(command[0], list(command)) + activate = None + env_path = quote(env_path) + bin_path = join(conda_prefix, "bin") + if mamba_path and exists(mamba_path): + activate = 'eval "$({} shell activate {} --shell posix)"'.format(quote(mamba_path), env_path) + elif exists(join(bin_path, "activate")) and not os.environ.get('NBCK_NO_ACTIVATE_SCRIPT'): + activate = '. {}/activate {}'.format(quote(bin_path), env_path) + elif exists(join(bin_path, "conda")): + activate = 'eval "$({}/conda shell.posix activate {})"'.format(quote(bin_path), env_path) + if activate: + ecomm = (activate + '\nexec ') + ' '.join(quote(c) for c in command) + print(ecomm, file=sys.stderr) ecomm = ['sh' if 'bsd' in sys.platform else 'bash', '-c', ecomm] os.execvp(ecomm[0], ecomm) + raise RuntimeError('Could not determine an activation method') + if __name__ == '__main__': exec_in_env(*(sys.argv[1:])) diff --git a/tests/test_config.py b/tests/test_config.py index 9e6fd6b..7371e26 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -109,7 +109,8 @@ def test_kernel_name_format(monkeypatch, tmp_path, name_format, expected): "metadata": { "debugger": True } } mock_info = { - 'conda_prefix': '/' + 'conda_prefix': '/', + 'conda_exe': '/conda.exe' } env_name = "dummy_env" def envs(*args): @@ -215,7 +216,8 @@ def test_remove_kernelspec(tmp_path, kernel_name, expected): def test_kernel_metadata(monkeypatch, tmp_path, kernelspec): mock_info = { - 'conda_prefix': '/' + 'conda_prefix': '/', + 'conda_exe': '/conda.exe' } def envs(*args): @@ -259,6 +261,7 @@ def envs(*args): def test_kernel_metadata_debugger_override(monkeypatch, tmp_path, kernelspec): mock_info = { + 'conda_exe': '/conda.exe', 'conda_prefix': '/' }