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

Support cmd.exe on windows #326

Closed
casey opened this issue Jun 23, 2018 · 22 comments
Closed

Support cmd.exe on windows #326

casey opened this issue Jun 23, 2018 · 22 comments

Comments

@casey
Copy link
Owner

casey commented Jun 23, 2018

Currently, just uses sh to execute all recipes on windows. This requires users to have installed sh, usually from cygwin or git-bash.

I would like to make this dependency optional. It should be possible for a user to create a justfile that uses cmd.exe to execute recipes.

I'm not sure the best way to indicate that a justfile uses cmd.exe for recipe execution, but my thought it to have just search for a file called justfile.com, and if it finds it use cmd.exe to execute all recipes within. It may be desirable to allow individual recipes to use cmd.exe, or to give some kind of syntax within a justfile, aside from the name, for using cmd.exe so all that is up in the air.

I don't know enough about windows to be able to do this, so I'm looking for help! If you're a windows dev and would like to implement this, do reach out!

@joshuawarner32
Copy link
Contributor

The use case that I'm most interested in here is being able to set up a single justfile that works across windows/mac/linux. My (initial, unsubstantiated) thought is that, for most commands, I'll want them to work on both sh and cmd. Now, happily some simple commands are already fairly portable across both of those, but many commands may need "implementations" for both sh and cmd.

Now, we could have these split out into different justfiles (distinguished by extension, like you suggested) - but it would make a lot more sense to me if they were actually packed into a single file, with a concise syntax to specify different "implementations" of the same command, for different shells.

Here's an example syntax (not a serious suggestion, just for discussion purposes):

# List the changes that have occurred to .rs files since origin/master
[sh] changes:
  git diff --stat origin/master..|grep -E '\.rs\b'
[cmd] changes:
  git diff --stat origin/master..|find '.rs' # I don't know cmd/batch that well, so this could be totally wrong... but for discussion purposes, ...

This syntax can naturally be extended to specify that a single implementation happens to work across both shells:

[sh|cmd] diff:
  git diff --stat origin/master..

This feature does have some overlap with the ability to specify an interpreter through a shebang-style line, but the key difference I'm proposing is that a single "command" (from the user's perspective) can have multiple different implementations for different platforms.

What I'm getting at, is that I'd like to be able to suggest a single workflow/command to run for both windows and mac users, while also making it obvious which commands are not (yet) translated for my platform. So, for example, just --list would show all commands that are specified for either platform, but clearly show that some of them won't work on my platform.

$ just --list
Available recipes:
    build            # Build a final executable (available on all platforms)
    ssh_prod [sh only]   # Ssh to prod.  Only available on `sh`

Of course, there are a lot of other ways to solve the same "one workflow for all platforms" problem. This is just one thought. 😄

@casey
Copy link
Owner Author

casey commented Jun 25, 2018

I actually think the syntax you propose is pretty reasonable, since it kind of looks like rust attributes, and it looks natural in just --list.

I'm wondering how just would choose which version to run though, if both are available. A user might be in cmd.exe, but have sh installed, in which case it's not clear which version should be called.

@joshuawarner32
Copy link
Contributor

We have to solve the same problem of "which version to run", regardless of whether the commands are in one file or across several... no? In your original proposal, a user could (I think?) have both a justfile and a justfile.cmd in the same directory, and there has to be some way of choosing between them. (Although, now that I think about it, we could technically refuse to operate under those circumstances, but that doesn't seem useful).

As to actually making the choice, that's a good question, it's not immediately obvious what it should do.

As a dummy proposal, we could start out by always preferring sh if it's installed on windows, otherwise falling back to cmd.exe otherwise. The user would always be able to override that with --shell, and maybe there would be a way to set override the default, system-wide?

@justdan96
Copy link

justdan96 commented Jun 25, 2018

I would personally suggest a different approach:

  1. Make Powershell the default shell on Windows, unless sh.exe is on the PATH.
  2. Allow evaluation in calling other recipes, i.e.:

test: test-{{os()}}

This allows Windows users to use a decent shell, allow existing just recipes to work, adds functionality that is otherwise useful and allows users to write recipes that work cross-platform.

Just my opinion, would love to see something created around this!

@casey
Copy link
Owner Author

casey commented Jun 26, 2018

@joshuawarner32 Yeah, totally, the problem of which version to run exists for both versions, just curious to hear your thoughts.

I think preferring sh by default is a good idea, since sh recipes have a chance of being cross-platform whereas cmd.exe or powershell recipes will be windows only. Our options are also constrained by the need to remain backwards compatible. By requiring users to opt in with --shell=PowerShell.exe or [powershell] we'll avoid changing existing existing behavior.

I'm wary of changing behavior based on autodetecting what shells are available, since it might vary unexpectedly based on installing or uninstalling different packages, or depend on whether just is launched via cmd.exe or git-bash's bash.

@justdan96 Thanks for mentioning Powershell! I had totally forgot that it was an option. Is powershell harder to learn than cmd.exe? If it's just a strictly better shell than cmd.exe, than it makes sense always use it, instead of supporting both.

@runeimp
Copy link

runeimp commented Jun 26, 2018

CMD is lighter weight than PowerShell and supported farther back in Windows versions. PowerShell requires .NET be installed (for the most part) which isn't the default if you go back a few versions of Windows. Many older Windows users may be very familiar with CMD and BATCHfile creation. Most newer Windows shell scripters are going to use PowerShell though. It's at least as powerful as BASH with newer versions being backed by .NET. Though also far more verbose.

So supporting both options has many advantages. If we're tagging the shell option then the mechanism to support one is the same or at least very similar for both, no?

@justdan96
Copy link

Powershell was included with the OS as far back as XP SP3, so you don't have to worry about support any more. Moreover it is a proper shell which even supports some Unix-like aliases such as ls and pwd.

Appreciate the point about auto detection, and backwards compatibility is always a concern. Maybe special comments could switch behaviour? A switch for selecting shells might work.

Are there any thoughts about introducing interpolation in recipe names, as I suggested? I thought that could allow switching by OS without introducing new syntax.

@justdan96
Copy link

As an aside I have partially got this working for us by creating a small passthru program called sh.exe that translates -cu to -command and then calls Powershell - works for us!

@casey
Copy link
Owner Author

casey commented Jun 26, 2018

@runimp: Thanks for the info! Is PowerShell slower to start up than cmd.exe?

Supporting both shells is definitely a good idea, however there almost always winds up being incompatibilities between shells, so I'd rather start with one and add support for others as necessary, instead of trying to support everything right out of the gate.

@justdan96 I think that adding some kind of syntax to set the default shell for the entire justfile is reasonable. The actual syntax is up for bikeshedding.

@casey
Copy link
Owner Author

casey commented Jun 26, 2018

Also, @justdan96, that's a pretty sweet workaround.

@nathanielknight
Copy link

What about adopting something like rust's cfg attributes wholesale? The syntax is familiar to Rust users and the attribute would be ignored as comments by older versions. You'd end up with something like

#[cfg(shell="sh")
list: 
    ls -R .

#[cfg(shell="pwsh")]
list:
   Get-ChildItem -Recurse .

The semantics would be to ignore rules that aren't configured for the current shell (perhaps with a different error message for commands that are defined, but not defined for your current shell).

I'm also 👍 for PowerShell. I even use it on Ubuntu sometimes (heresy, I know).

@Drainful
Copy link

Drainful commented Jul 5, 2018

Take another +1 for poweshell. @justdan96 can you give me more details about your passthru program?

EDIT: I got the passthru program working. My issue was that I didn't realize it had to be an executable; I tried to use poweshell and batch scripts. I ended up writing it in Rust.

@casey
Copy link
Owner Author

casey commented Jul 5, 2018

@neganp Although old versions would ignore the comments, the non sh recipes would likely be broken, an duplicate recipe names would be an error, unfortunately.

@nathanielknight
Copy link

Ah, of course! Good point.

@casey
Copy link
Owner Author

casey commented Jul 5, 2018

I think that it would be a good idea to land a conservative version of this feature and try to punt on as many tricky details as possible.

My thoughts on what that might look like:

  • Recipes may have an annotation that determines the shell used to run them:
# annotations may be on the same line:
[sh] foo:
  ls /

# or on a previous line:
[powershell]
bar:
  dir C:\\
  • No annotation defaults to sh, to preserve backwards compatibility

  • Possible values are [sh] or [powershell] ([ps] might also be nice as a shortcut)

  • To avoid having to come up with a tiebreaker mechanism, two recipes with the same name but different shells are disallowed.

There are lots of things we might want to add later, but those can wait, since they can be added in a backwards compatible fashion. In particular:

  • cmd.exe support
  • Recipes with the same name and different shells
  • Recipes that are compatible with multiple shells
  • Some way of selecting dependencies dynamically (for example the recipe name interpolation suggested by @justdan96)
  • A way of setting the default shell to use on a per-user or per-project basis

Adding this will look roughly like:

  • Expand GRAMMAR.md with the [SHELL] syntax
  • Add the new TokenKinds to token.rs ([ and ])
  • Teach the tokenizer to tokenize them properly, add tokenizer tests
  • Add a Shell enum, with Powershell and Sh variants
  • Give Recipe a Shell field. (We may want to actually make the Shell enum have a 3rd possibility, which is Shebang, indicating that the recipe is a shebang recipe)
  • Teach the parser to parse the annotations and add the appropriate Shell variant to recipes, add parser tests, (will also need a new kind of error that indicates an invalid annotation, with tests added for that as well)
  • Add the code to actually run recipes using the selected shell
  • Add runtime tests. It would be super, super nice to get these working cross platform, so a mac dev with powershell installed (https://github.com/PowerShell/PowerShell) could run the tests

I don't have immediate plans to tackle this, so if someone is up for it let me know! I'm super happy to provide guidance and advice.

@nathanielknight
Copy link

I'd like to work on this some, but the next substantial chunk of time I can carve out is in mid August. I'll try to chip away at it until then unless someone else wants to take the lead.

I can also split some of the points above into a Project if that would be helpful.

@casey
Copy link
Owner Author

casey commented Jul 5, 2018

@neganp Sweet!

Cool, putting some of the above points into a Project sounds useful.

We could also start a feature branch, into which you could land partial/intermediate PRs.

And definitely ping me with questions, both about the just code, and any general rust stuff. I am super happy to help!

@justdan96
Copy link

That all sounds great. Unfortunately I don't know Rust but if you need any help with testing or documentation please let me know!

@casey
Copy link
Owner Author

casey commented Aug 2, 2018

I was thinking, if just on windows included a copy of sh, and was able to invoke it to run recipes, thus making just fully self-contained, even on windows, would that be a good resolution to this issue?

This would remove the cygwin/sh dependency on windows, and justfiles would naturally be cross-platform. This might be somewhat annoying, and we might have to bundle a cygwin-built sh.exe, along with a cygwin1.dll, and extract it somewhere when we run.

However, it still wouldn't allow recipes written with cmd.exe/powershell syntax.

@justdan96
Copy link

I tested and using Busybox for Windows (https://github.com/rmyorston/busybox-w32) seems to work okay if you set the line endings to Unix.

@casey
Copy link
Owner Author

casey commented May 7, 2019

@justdan96 Thank you for the pointer! I tried out the windows build of busybox, and it seems like it might be a viable option for making Just and justfiles portable between windows and linux.

Here is how it could work:

Windows builds of just would include, using include_bytes!(), an appropriate version of the busybox.exe executable. When just is launched, it writes the binary to a temporary location and uses trmpdir/busybox.exe sh -l to invoke all recipes. The busybox executable is about 500k, but making the windows version of just 1.5mb instead of 1.0mb doesn't seem like a huge deal, and well worth the improved portability.

Hopefully, most justfiles would just work on Windows and Linux. Busybox doesn't support all unix commands and options, but it supports quite a few, and definitely all the commonly used ones.

What do you think? Any drawbacks that I'm not considering?

Note that this would be orthogonal to the shell setting detailed in #361. If I ever get around to implementing that, users could still use it to opt into a different shell on all platforms.

(@joshuawarner32, I think we talked a little about this at the recent Rust meetup, so thanks for pushing me to think about this!)

@casey casey removed this from the soon milestone May 27, 2019
@casey casey added this to the 1.0 milestone May 27, 2019
@casey casey modified the milestones: 1.0, soon Nov 11, 2019
@casey casey modified the milestones: soon, eventually Nov 11, 2019
@casey
Copy link
Owner Author

casey commented Nov 13, 2019

I just released v0.5.0, which includes the ability to set the shell used for recipes and backticks on a justfile-wide basis. This gives just rudimentary support for cmd.exe and powershell, by adding set shell := ["cmd.exe", "/c"] or set shell := ["powershell", "-c"]. (see #530 and the readme for more details)

This doesn't address the desire to write cross-platform justfiles, so I opened #531 to track that separately. I also included a summary of the discussion and design from this thread.

I hope the new set shell := ... feature is useful! Let me know if you have problems with it, or have suggestions for improving it.

@casey casey closed this as completed Nov 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants