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

Feature request: shell attributes #2177

Closed
W1M0R opened this issue Jun 20, 2024 · 6 comments
Closed

Feature request: shell attributes #2177

W1M0R opened this issue Jun 20, 2024 · 6 comments

Comments

@W1M0R
Copy link

W1M0R commented Jun 20, 2024

This feature request is similar to some of the comments around these issues:

  1. Allow inverted cfg attributes, like [not(macos)] #1895
  2. Cross-platform justfiles #531

Imagine the following justfile:

[bash]
tips:
  @echo "You are using bash. Setup your shell like this:"

[zsh]
tips:
  @echo "You are using zsh. Setup your shell like this:"

[ps1]
tips:
  Write-Host "You are using PowerShell. Setup your shell like this:"

If you invoke just tips from within a bash shell, the output will be You are using bash. Setup your shell like this:.
If you invoke just tips from within a zsh shell, the output will be You are using zsh. Setup your shell like this:.

Put simply, like just can detect the os you are running in and prefer the os-specific recipes using the os attribute family (linux, windows, etc), it would be great if just could detect the shell you are running in and prefer shell-specific recipes using a shell attribute family.

To allow for the graceful expansion of this feature, the shell attribute could accept a parameter, e.g. shell. The value of shell can determine how the recipe is executed. shell=this can mean that the recipe is executed using the same shell that it was invoked from (it could still be a new process of that shell), shell=default can mean the recipe is executed using the default configured shell for executions (e.g. sh or whatever the user set), and shell=zsh can mean the recipe is executed using the provided shell (assuming that shell can be found in the path).

[powershell('shell=this')]
tips:
  Write-Host "This recipe was invoked from Windows PowerShell, and also executed by it."

[zsh('shell=default')]
tips:
  @echo "This recipe was invoked from Zsh, but executed using your configured shell, e.g. sh."

[bash('shell=zsh')]
tips:
  @echo "This recipe was invoked from Bash, but executed using Zsh."

Perhaps an alternative to the shell parameter, is to piggy-back on the existing support for recipe shebangs. If the recipe contains no shebang, then it is executed using the globally configured executor, e.g. sh, but if it has a shebang, then the recipe is executed using that. We might introduce a special shebang #!self that means the same shell as the invoker. In my mind, however, once this is a feature, the expectation of the users will be to have the recipe execute using the same shell runner as specified by the attribute, e.g. #!self will probably always be used, in which case an attribute parameter might be preferred.

[powershell]
tips:
  #!powershell
  Write-Host "This recipe was invoked from Windows PowerShell, and also executed by it."

[zsh]
tips:
  @echo "This recipe was invoked from Zsh, but executed using your configured shell, e.g. sh."

[bash]
tips:
  #!zsh
  @echo "This recipe was invoked from Bash, but executed using Zsh."

[fish]
tips:
  #!self
  @echo "This recipe was invoked from Fish, and also executed by it."

In terms of attribute names, they can be the common names of the shells:

  1. pwsh means pwsh if it exists, otherwise powershell as a fallback
  2. powershell means powershell if it exists, otherwise pwsh as a fallback
  3. bash means bash
@casey
Copy link
Owner

casey commented Jun 20, 2024

This can be done by checking the $SHELL variable, which should be set to the user's login shell:

tips:
  #!/usr/bin/env bash
  case "$SHELL" in
    */zsh)
      echo zsh
      ;;
    */bash)
      echo bash
      ;;
    */sh)
      echo sh
      ;;
  esac

Does that work?

@W1M0R
Copy link
Author

W1M0R commented Jun 21, 2024

Thanks for the code snippet @casey, it looks like that would work.

The shell functionality request makes the recipes more portable and cleaner, simplifying justfiles used in cross platform projects. On Windows, if the shell is set to PowerShell and there is no bash installed, then a PowerShell specific set of checks would have to be done.

@casey
Copy link
Owner

casey commented Jun 21, 2024

I think this is covered by using set windows-shell to use PowerShell on Windows, and then having [windows] recipes for use on Windows.

In practice, for portability, justfiles should probably stick to sh on Unix and PowerShell on Windows, and then use [windows] and [unix] attributes on different recipes, so I think that having shell-specific recipes probably don't add much on top of that.

@casey casey closed this as completed Jun 21, 2024
@W1M0R
Copy link
Author

W1M0R commented Jun 22, 2024

Sounds like a good approach. Your suggestion would keep the justfiles cleaner (and simpler) and the environments more uniform in the end.

@W1M0R
Copy link
Author

W1M0R commented Jul 10, 2024

I like the following trick:

# Initialize OS Setup
init:
	just _init-{{os()}}

_init-linux:
	# Linux init

_init-macos:
	# macOS init

_init-windows:
	# Windows init

If there was a function called invocation_shell() (producing outputs such as bash, elvish, pwsh, powershell, etc, then it could be used in a similar way:

# this option does not work for non-posix shells
# just_shell := env("SHELL")

# this option only works if you have already configured starship for your shell
# just_shell := env("STARSHIP_SHELL")

# this option somehow fails to detect bash (SHELL seems to be unset and _ results in `just`)
# just_shell := file_stem(env("SHELL", env("_", env("STARSHIP_SHELL", "sh"))))

# this option may require specialised shell detection logic
# just_shell := invocation_shell()

# this option seems to be the most flexible
just_shell := invocation_process_name()

# Initialize Shell Setup
init-shell:
	just _init-shell-{{just_shell}}

_init-shell-bash:
	# Bash init

_init-shell-elvish:
	# Elvish init

_init-shell-pwsh:
	# Init powershell

When a user runs just init-shell from a pwsh environment, then effectively _init-shell-pwsh gets called, that can then perform some initialisation of the shell environment.

The SHELL environment variable is not always set. I found a workaround where I can install and hook starship in every shell, and then rely on the STARSHIP_SHELL environment variable to retrieve that information.

Ideally, an invocation_shell function could implement the logic to detect various shells. I'm not sure how it should work in situations where different shells are invoked, and thus potentially polluting each other's environments, e.g. imagine starting with a bash shell, then running a nushell from there, and then an elvish shell from there, etc. Each shell will layer its environment, and thereby making it difficult to know which shell is the "leaf" shell. In this case, it might be better to look at the just process tree, and find the first non-just parent process of just.

Making the first non-just parent process name available as a function, could be a more generic solution that does not specifically implement shell detection logic, in which case the function could be invocation_process_name. This could produce the following results:

invocation invocation_process_name
bash -c just bla bash
nu -c just bla nu
pixi run just bla pixi
direnv exec . just bla direnv
shadowenv exec just bla shadowenv
aqua exec just bla aqua

@casey Would you be open to having a function like invocation_shell or invocation_process_name in just?

@casey
Copy link
Owner

casey commented Jul 10, 2024

You can get the shell from the $SHELL environment variable.

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

No branches or pull requests

2 participants