Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the (prefix) shell #9

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

certik
Copy link

@certik certik commented Jan 22, 2025

It's not working yet, but almost:

(spawn2) conda-spawn/conda_spawn(shell)$ $CONDA_PREFIX/bin/conda spawn  -n t1 --shell shell
~/repos/conda-spawn/conda_spawn$  source "/var/folders/vg/lymvsp1153sgwwfdh3pm45
hw0000gn/T/conda-spawn-c14_qd7s.sh" && PS1="(t1) ${PS1:-}" && stty echo
.: command not found
~/repos/conda-spawn/conda_spawn$ 

It launches shell, but doesn't load the environment correctly yet. I think shell doesn't support . yet (prefix-dev/shell#203), only source.

The activation script contains:

. "/Users/ondrej/miniforge3/envs/spawn2/etc/conda/deactivate.d/libxml2_deactivate.sh"
unset CONDA_EXE
unset _CE_M
unset _CE_CONDA
unset CONDA_PYTHON_EXE
export PATH='/Users/ondrej/miniforge3/envs/spawn2/envs/t1/bin:/Users/ondrej/miniforge3/condabin:/Users/ondrej/.pixi/bin:/Users/ondrej/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Users/ondrej/.cargo/bin:/Users/ondrej/.cache/lm-studio/bin'
export CONDA_PREFIX='/Users/ondrej/miniforge3/envs/spawn2/envs/t1'
export CONDA_SHLVL='2'
export CONDA_DEFAULT_ENV='t1'
export CONDA_PROMPT_MODIFIER='(t1) '
export CONDA_PREFIX_1='/Users/ondrej/miniforge3/envs/spawn2'

I think this is coming from conda itself, so it fails.

@certik
Copy link
Author

certik commented Jan 22, 2025

Worked around the "." bug. Now we get:

$  source "/var/folders/vg/lymvsp1153sgwwfdh3pm45
hw0000gn/T/conda-spawn-eh3swl3r.sh" && PS1="(t1) ${PS1:-}" && stty echo
Syntax error:   × Failed to parse input
  ╰─▶ Failure to parse at Pos((3, 43))
   ╭────
 1 │ if test -n "${xml_catalog_files_libxml2:-}"; then
   ·                                           ┬
   ·                                           ╰── expected QUOTED_ESCAPE_CHAR, QUOTED_CHAR, VARIABLE_EXPANSION, SUB_COMMAND, or EXIT_STATUS
   ╰────
  help: expected QUOTED_ESCAPE_CHAR, QUOTED_CHAR, VARIABLE_EXPANSION,
        SUB_COMMAND, or EXIT_STATUS

That's another bug in the shell: prefix-dev/shell#204.

@certik
Copy link
Author

certik commented Jan 22, 2025

I modified the file /Users/ondrej/miniforge3/envs/spawn2/etc/conda/deactivate.d/libxml2_deactivate.sh to parse as a temporary workaround for prefix-dev/shell#204 and I think we have it!

(spawn2) conda-spawn/conda_spawn(shell)$ $CONDA_PREFIX/bin/conda spawn  -n t1 --shell shell                                      
~/repos/conda-spawn/conda_spawn$  source "/var/folders/vg/lymvsp1153sgwwfdh3pm45
hw0000gn/T/conda-spawn-ji3_9fl7.sh" && PS1="(t1) ${PS1:-}" && stty echo
~/repos/conda-spawn/conda_spawn$ which python
/Users/ondrej/miniforge3/envs/spawn2/envs/t1/bin/python
~/repos/conda-spawn/conda_spawn$ 
CTRL-D
(spawn2) conda-spawn/conda_spawn(shell)$ 

I don't know how other shells hide the source "/var/folders/vg/lymvsp1153sgwwfdh3pm45 hw0000gn/T/conda-spawn-ji3_9fl7.sh" && PS1="(t1) ${PS1:-}" && stty echo line, but for now I don't mind, as long as it works.

conda_spawn/shell.py Outdated Show resolved Hide resolved
conda_spawn/shell.py Outdated Show resolved Hide resolved
conda_spawn/activate.py Outdated Show resolved Hide resolved
@certik
Copy link
Author

certik commented Jan 22, 2025

Here is how to use this. Check out this PR locally, then:

conda activate base
pip install .

Then launch a new terminal with the shell, put conda into your ~/bin (for example), no other setup. Then:

conda spawn --shell shell -n lf

And it just works.

@certik
Copy link
Author

certik commented Jan 22, 2025

TODO: figure out how to automatically launch shell if running inside shell.

@certik certik marked this pull request as ready for review January 22, 2025 21:18
@certik
Copy link
Author

certik commented Jan 22, 2025

@jaimergp this is ready for review. I tested it with the shell on both macOS and Windows, it works great on both systems. There are some bugs in the shell that we need to fix, but on the conda side it seems to work great.

An example on Windows:

image

The "OK" string comes from my temporary local modifications of some of the Conda scripts, since the shell can't execute all of them yet. You can ignore it.

Example on macOS in ghostty and the shell:

image

@@ -275,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"]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed on Windows, since it incorrectly recognizes the shell as cmd.exe and then proceeds with cmd instead of shell. In this case we simply check the SHELL variable, and use it if it is set, which fixes the problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add support for Prefix's shell in shellingham.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, working on it. Shellingham recognizes shell on macOS but not Windows. I'll fix it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so this is tricky. With sarugaku/shellingham#92 shellingham now correctly recognizes shell on Windows. However, on Windows conda is launched from conda.bat scripts and so technically conda is still launched from cmd.exe even though the actual shell is different.

I don't know how to fix that, other than what we did above. Or, we can try to figure out how to launch conda directly, which should be preferable on Windows, it actually works:

~/repos/math_notes(main)$ ~/AppData/Local/miniforge3/Scripts/conda.exe spawn myst
(myst) ~/repos/math_notes(main)$
CTRL-D
~/repos/math_notes(main)$ conda spawn myst

(myst) C:\Users\ondrejcertik\repos\math_notes>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I found a solution:

~$ cat bin/conda2.bat
@shell -c "C:\Users\ondrejcertik\AppData\Local\miniforge3\Scripts\conda.exe %*"
~$ conda2 spawn myst
(myst) ~$

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, the old solution was:

        if sys.platform == "win32" and name == "cmd" and "SHELL" in os.environ:
            name = os.environ["SHELL"]

But it's not needed anymore.

self._files_to_remove.append(f.name)

def spawn(self, command: Iterable[str] | None = None) -> int:
proc = self.spawn_popen(command)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use the tty in Unix systems?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably can. I wanted to keep the code uniform to work on all platforms. Do you want to have different code for Windows and Unix?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ttys are needed for PS1 support I'm afraid.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using the example from PowerShell. Do ttys work on Windows?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the PS1 support, that is now fixed in 8789c31, everything works:

~$ conda spawn lf
(lf) ~$
CTRL-D
~$

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that PS1 works on Windows also:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some shells, the PS1 variable is not ever exported, only local to the current session. So a process like conda's doesn't "see it" when it wants to expand it with something like PS1 = os.environ.get("PS1, "") + " (base)".

But if Prefix's shell always exports it I guess we are ok.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we are not exporting $PS1. Can you give an example of a conda command (besides conda activate which is now replaced with conda spawn) that requires to modify $PS1?

Copy link
Member

@jaimergp jaimergp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution. I also talked with Wolf about this but right now I don't feel like conda spawn should be accepting new shell features. I'm still figuring out the ideal UX with the existing conda support (and some of them are missing, like csh or fish).

That said, it's not like this won't ever be merged. Just not right now. Things that would need to happen besides conda spawn being a bit more mature / recognized in the ecosystem:

  • Tests
  • Ability to specify a command
  • Prompt modification support
  • Shellingham support (ideally)

Hope you can understand!

@certik
Copy link
Author

certik commented Jan 29, 2025

Thanks. I opened up issues for the things that you mentioned:

Update: the main issues are now fixed, and I'll fix Shellingham soon. @jaimergp let me know what else you need on the shell side.

@jaimergp
Copy link
Member

Awesome! And very quick too.

The first three items I mentioned were directed towards this PR: we need to add tests for the new shell (not sure at what level, this might be trickier than with default system shells), and also implement support for running commands from the CLI and modifying the PS1 prompt here.

@certik
Copy link
Author

certik commented Jan 30, 2025

@jaimergp ok, I think everything works for me now. Commands work:

~/repos/conda-spawn(shell)$ conda spawn myst -- myst --help
Usage: myst [options] [command]

Options:
  -v, --version               Print the current version of MyST
  -d, --debug                 Log out any errors to the console
  -h, --help                  display help for command

Commands:
  init [options]              Initialize a MyST project in the current
                              directory
  build [options] [files...]  Build PDF, LaTeX, Word and website exports from
                              MyST files
  start [options]             Start the current project as a website
  clean [options] [files...]  Remove exports, temp files and installed
                              templates
  templates                   List and download templates

Environment works:

~/repos/conda-spawn(shell)$ conda spawn myst
(myst) ~/repos/conda-spawn(shell)$ which myst
C:\Users\ondrejcertik\AppData\Local\miniforge3\envs\myst\Scripts\myst.exe

The code seems clean, all the issues you raised are addressed.

Except testing of shell in this PR. This can be done by conda installing it at the CI, and then testing it that way. We have to make a new shell release for that, as I fixed a few things there since the last release. But other than that, let me know what else is needed here to get this merged.

delete=False,
mode="w",
) as f:
f.write(self.script())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need a newline here too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed self.script() would end with a new line. I should add it here just in case.

@@ -164,6 +164,41 @@ def executable(self):
return "zsh"


class ShellShell(PosixShell):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should rename this to avoid confusion:

Suggested change
class ShellShell(PosixShell):
class PrefixShellShell(PosixShell):

@@ -1077,6 +1077,12 @@ def _hook_postamble(self) -> str:
return "Remove-Variable CondaModuleArgs"


class ShellActivator(PosixActivator):
pathsep_join = ";".join if on_win else ":".join
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is surprising 🤔 Isn't shell supposed to be cross-platform? Why do they need different separators 🤔 Just curious.

Copy link
Author

@certik certik Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually at the core of the issue of being cross platform. On Windows the $PATH environment variable contains ;, on Linux and macOS it contains : as a separator. Consequently, there are two ways to deal with this: we either emulate a more posix like environment (posix requires :) from inside the script, but we must revert back to $PATH with ; for any programs that we call from the script, otherwise they won't work, since on Windows programs expect ; as a separator (that is for example what Xonsh or git-bash is doing); or you keep things native, you don't emulate anything. And that is what the shell is doing.

This is related to an issue of absolute paths: posix doesn't allow paths like C:\something, each absolute path must start with /. Again, one has two options, emulating paths and translating them for programs (git-bash) or using native paths (shell).

In order to write cross-platform scripts, one must either user relative paths, or use some platform-independent mechanism to append to $PATH.

@jaimergp
Copy link
Member

I don't know how other shells hide the source "/var/folders/vg/lymvsp1153sgwwfdh3pm45 hw0000gn/T/conda-spawn-ji3_9fl7.sh" && PS1="(t1) ${PS1:-}" && stty echo line, but for now I don't mind, as long as it works.

That's because they are using the tty mode, not the popen one. I think it's worth it, but we can also do that in a second PR.

@jaimergp
Copy link
Member

Except testing of shell in this PR. This can be done by conda installing it at the CI, and then testing it that way. We have to make a new shell release for that, as I fixed a few things there since the last release. But other than that, let me know what else is needed here to get this merged.

You can add it to the test requirements in pixi.toml and then copy some of the basic tests to at least have something.

@certik
Copy link
Author

certik commented Jan 31, 2025

That's because they are using the tty mode, not the popen one. I think it's worth it, but we can also do that in a second PR.

Do you have an example how to use the tty mode on Windows? The modules that you use on linux do not seem to be available on Windows.

@jaimergp
Copy link
Member

jaimergp commented Feb 3, 2025

Do you have an example how to use the tty mode on Windows?

So far I only figured it out in Unix, so that'd be enough for now.

@certik
Copy link
Author

certik commented Feb 3, 2025

So far I only figured it out in Unix, so that'd be enough for now.

Ok. Do you suggest to use tty for Linux/macOS and the current approach on Windows? (I need this to run on Windows also.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants