From 2a730c4f6bbbcf8025370b1e201885ffc3d2e8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Tue, 21 Jan 2025 19:21:57 -0700 Subject: [PATCH 01/13] Add support for the (prefix) shell --- conda_spawn/shell.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index ef1f699..ca98543 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -163,6 +163,10 @@ class ZshShell(PosixShell): def executable(self): return "zsh" +class ShellShell(PosixShell): + def executable(self): + return "shell" + class CshShell(Shell): pass @@ -260,6 +264,7 @@ def args(self) -> tuple[str, ...]: "posix": PosixShell, "powershell": PowershellShell, "pwsh": PowershellShell, + "shell": ShellShell, "tcsh": CshShell, "xonsh": XonshShell, "zsh": ZshShell, From aaef7db544e55c112b6b4dde1e9d218338cbbc59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Tue, 21 Jan 2025 19:32:38 -0700 Subject: [PATCH 02/13] Fix args and source --- conda_spawn/shell.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index ca98543..5d908aa 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -140,7 +140,7 @@ def _sigwinch_passthrough(sig, data): # We set the PS1 prompt outside the script because it's otherwise invisible. # stty echo is equivalent to `child.setecho(True)` but the latter didn't work # reliably across all shells and OSs. - child.sendline(f' . "{f.name}" && {self.prompt()} && stty echo') + child.sendline(f' source "{f.name}" && {self.prompt()} && stty echo') os.read(child.child_fd, 4096) # consume buffer before interact if Path(executable).name == "zsh": # zsh also needs this for a truly silent activation @@ -167,6 +167,9 @@ class ShellShell(PosixShell): def executable(self): return "shell" + def args(self): + return () + class CshShell(Shell): pass From 3a6d38492cb266e344f628cc886ed8d18ad1d12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Tue, 21 Jan 2025 21:46:08 -0700 Subject: [PATCH 03/13] Use `source` instead of `.` --- conda_spawn/activate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda_spawn/activate.py b/conda_spawn/activate.py index 9f16ecf..f35daf3 100644 --- a/conda_spawn/activate.py +++ b/conda_spawn/activate.py @@ -840,7 +840,7 @@ class PosixActivator(_Activator): unset_var_tmpl = "unset %s" export_var_tmpl = "export %s='%s'" set_var_tmpl = "%s='%s'" - run_script_tmpl = '. "%s"' + run_script_tmpl = 'source "%s"' hook_source_path = Path( CONDA_PACKAGE_ROOT, From f7680609e8918455e1b7a01b50e971ced0319788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Tue, 21 Jan 2025 22:15:59 -0700 Subject: [PATCH 04/13] Fix formatting --- conda_spawn/shell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index 5d908aa..5d92584 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -163,6 +163,7 @@ class ZshShell(PosixShell): def executable(self): return "zsh" + class ShellShell(PosixShell): def executable(self): return "shell" From 2b7a2642019fe0aa94f6d54b16a616c8449c5496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 22 Jan 2025 13:35:32 -0700 Subject: [PATCH 05/13] Use pipes directly, no pexpect on Windows --- conda_spawn/shell.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index 5d92584..327a521 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -169,7 +169,29 @@ def executable(self): return "shell" def args(self): - return () + return ("--interact", "--norc") + + def spawn_popen( + self, command: Iterable[str] | None = None, **kwargs + ) -> subprocess.Popen: + try: + with NamedTemporaryFile( + prefix="conda-spawn-", + suffix=self.Activator.script_extension, + delete=False, + mode="w", + ) as f: + f.write(self.script()) + return subprocess.Popen( + [self.executable(), *self.args(), f.name], env=self.env(), **kwargs + ) + finally: + self._files_to_remove.append(f.name) + + def spawn(self, command: Iterable[str] | None = None) -> int: + proc = self.spawn_popen(command) + proc.communicate() + return proc.wait() class CshShell(Shell): From ee833394f890e38d8c5fac0ed793149a00c2e95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 22 Jan 2025 13:41:49 -0700 Subject: [PATCH 06/13] Makes it work --- conda_spawn/activate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda_spawn/activate.py b/conda_spawn/activate.py index f35daf3..ddaee59 100644 --- a/conda_spawn/activate.py +++ b/conda_spawn/activate.py @@ -830,9 +830,9 @@ def backslash_to_forwardslash( class PosixActivator(_Activator): - pathsep_join = ":".join + pathsep_join = ";".join if on_win else ":".join sep = "/" - path_conversion = staticmethod(win_path_to_unix if on_win else _path_identity) + path_conversion = staticmethod(win_path_to_unix if False else _path_identity) script_extension = ".sh" tempfile_extension = None # output to stdout command_join = "\n" From ec9f9860d8849ec4a13ebd0a9b45b5d26853a118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 22 Jan 2025 13:45:02 -0700 Subject: [PATCH 07/13] Split into ShellActivator --- conda_spawn/activate.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/conda_spawn/activate.py b/conda_spawn/activate.py index ddaee59..591ad7a 100644 --- a/conda_spawn/activate.py +++ b/conda_spawn/activate.py @@ -830,9 +830,9 @@ def backslash_to_forwardslash( class PosixActivator(_Activator): - pathsep_join = ";".join if on_win else ":".join + pathsep_join = ":".join sep = "/" - path_conversion = staticmethod(win_path_to_unix if False else _path_identity) + path_conversion = staticmethod(win_path_to_unix if on_win else _path_identity) script_extension = ".sh" tempfile_extension = None # output to stdout command_join = "\n" @@ -840,7 +840,7 @@ class PosixActivator(_Activator): unset_var_tmpl = "unset %s" export_var_tmpl = "export %s='%s'" set_var_tmpl = "%s='%s'" - run_script_tmpl = 'source "%s"' + run_script_tmpl = '. "%s"' hook_source_path = Path( CONDA_PACKAGE_ROOT, @@ -1077,6 +1077,11 @@ def _hook_postamble(self) -> str: return "Remove-Variable CondaModuleArgs" +class ShellActivator(PosixActivator): + pathsep_join = ";".join if on_win else ":".join + path_conversion = staticmethod(_path_identity) + run_script_tmpl = 'source "%s"' + activator_map: dict[str, type[_Activator]] = { "posix": PosixActivator, "ash": PosixActivator, @@ -1089,4 +1094,5 @@ def _hook_postamble(self) -> str: "cmd.exe": CmdExeActivator, "fish": FishActivator, "powershell": PowerShellActivator, + "shell": ShellActivator, } From 8b9647687d3c52bd72abc7a18c0cca7a80d3e67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 22 Jan 2025 13:47:16 -0700 Subject: [PATCH 08/13] Use it --- conda_spawn/shell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index 327a521..fcf5260 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -140,7 +140,7 @@ def _sigwinch_passthrough(sig, data): # We set the PS1 prompt outside the script because it's otherwise invisible. # stty echo is equivalent to `child.setecho(True)` but the latter didn't work # reliably across all shells and OSs. - child.sendline(f' source "{f.name}" && {self.prompt()} && stty echo') + child.sendline(f' . "{f.name}" && {self.prompt()} && stty echo') os.read(child.child_fd, 4096) # consume buffer before interact if Path(executable).name == "zsh": # zsh also needs this for a truly silent activation @@ -165,6 +165,8 @@ def executable(self): class ShellShell(PosixShell): + Activator = activate.ShellActivator + def executable(self): return "shell" From f0587628c13f42a20e4deb11f169cce88a004c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 22 Jan 2025 13:50:39 -0700 Subject: [PATCH 09/13] Fix formatting --- conda_spawn/activate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conda_spawn/activate.py b/conda_spawn/activate.py index 591ad7a..d7cc9ff 100644 --- a/conda_spawn/activate.py +++ b/conda_spawn/activate.py @@ -1082,6 +1082,7 @@ class ShellActivator(PosixActivator): path_conversion = staticmethod(_path_identity) run_script_tmpl = 'source "%s"' + activator_map: dict[str, type[_Activator]] = { "posix": PosixActivator, "ash": PosixActivator, From 5352a0781849614163b6bba81f88a4464d996bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 22 Jan 2025 14:09:07 -0700 Subject: [PATCH 10/13] Use the SHELL variable on Windows if it exists --- conda_spawn/shell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index fcf5260..19278eb 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -308,6 +308,8 @@ def default_shell_class(): def detect_shell_class(): try: name, _ = shellingham.detect_shell() + if sys.platform == "win32" and name == "cmd" and "SHELL" in os.environ: + name = os.environ["SHELL"] except shellingham.ShellDetectionFailure: return default_shell_class() else: From 3ea6774edac496589a2e46381d4e344070a451fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 29 Jan 2025 07:33:23 -0700 Subject: [PATCH 11/13] Execute command if provided --- conda_spawn/shell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index 19278eb..f5656cc 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -184,6 +184,8 @@ def spawn_popen( mode="w", ) as f: f.write(self.script()) + if command: + f.write(" ".join(command)) return subprocess.Popen( [self.executable(), *self.args(), f.name], env=self.env(), **kwargs ) From 8789c3188af999cf0c3434441a2803a6621840eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 29 Jan 2025 07:42:43 -0700 Subject: [PATCH 12/13] Change the PS1 prompt --- conda_spawn/shell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index f5656cc..9a49977 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -184,6 +184,7 @@ def spawn_popen( mode="w", ) as f: f.write(self.script()) + f.write(f"{self.prompt()}\n") if command: f.write(" ".join(command)) return subprocess.Popen( From 0cd06d9429c6a75b1b2ccc261856566e0fdeca9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 29 Jan 2025 17:37:51 -0700 Subject: [PATCH 13/13] Remove the workaround on Windows --- conda_spawn/shell.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/conda_spawn/shell.py b/conda_spawn/shell.py index 9a49977..d2b6a5c 100644 --- a/conda_spawn/shell.py +++ b/conda_spawn/shell.py @@ -311,8 +311,6 @@ def default_shell_class(): def detect_shell_class(): try: name, _ = shellingham.detect_shell() - if sys.platform == "win32" and name == "cmd" and "SHELL" in os.environ: - name = os.environ["SHELL"] except shellingham.ShellDetectionFailure: return default_shell_class() else: