cabal
cabal copied to clipboard
external commands need to explicitly handle their own name being passed to the executable
Describe the bug
#9412 passes name to the external executable. The use case is a symlink to an executable that handles multiple commands.
I think this use-case doesn't justify the complication this causes for the average case and it isn't strictly necessary to support the use-case.
To Reproduce
- have a command
cabal-blain your environment that doesn't allow to take the argument"bla" - run
cabal bla - should fail with "doesn't expect "bla""
Expected behavior
cabal bla should be equivalent to cabal-bla
To support the intended use case
If you want to have a singular exe handle multiple plugin arguments, just write a wrapper that invokes the particular exe with the argument and symlink the bash wrapper. This behavior should not go at the cost of the average case behavior.
Additional context
The fix should be easy, just don't pass name to the executable here:
https://github.com/haskell/cabal/blob/9b3ce92dc6ce0188066ee5f76f249025cdc9df91/cabal-install/src/Distribution/Client/Main.hs#L383
(@mpickering)
The context is in https://github.com/haskell/cabal/issues/9405 Apparently, this was modeled after cargo. I don't have a strong preference either way.
I was somewhat surprised by this behavior too, but it's not difficult to workaround, see https://github.com/Bodigrim/cabal-add/blob/267eb6802477076b64568bff774aa7833bcbfa7b/app/Main.hs#L186-L192.
I don’t think the average case should work around the special case if it isn’t absolutely necessary which I don’t see, tbh.
Maybe I can understand better when I read why cargo did it like this…
It seemed sensible to me to implement the external command system exactly like the cargo system. There are less surprises if you are already familiar with that and it is likely they have worked out any usability issues.
As much as I like the rust tooling, I honestly don't understand this and I don't necessarily think it's a good idea to copy something without a clear reason why.
Do you agree with the general notion that the average case should not be complicated at the benefit of some exotic case that can somewhat trivially be worked around?
@MangoIV I am not particularly invested in the design, it seems a very minor task to drop the first argument (and something which is easily discoverable by reading the short documentation section).
It does seem more complicated to me to provide a wrapper which works across platforms and calls an executable forwarding on the right commands.
Here is a similar issue on cargo which was just closed (https://github.com/rust-lang/cargo/issues/9373).
If someone wishes to change the design then I'm not going to object but it does seem like creating unecessary work to me. In my opinion, there is a large benefit to uniformity across ecosystems when it is possible to do so. Making Haskell work as much like the next tool when it comes to packaging and distribution is more important that forging an independent path.
If you want to have a singular exe handle multiple plugin arguments, just write a wrapper
You could even just use argv[0], like coreutils does.
I, too, would prefer the name not to be passed as argv[1]. It interferes with direct use of external commands. As a counterexample to cargo, git behaves like @MangoIV's proposal.
However, the feature is already out. Changing it now would unnecessarily break existing tools.
I was somewhat surprised by this behavior too, but it's not difficult to workaround, see https://github.com/Bodigrim/cabal-add/blob/267eb6802477076b64568bff774aa7833bcbfa7b/app/Main.hs#L186-L192.
@Bodigrim what would the workaround be if the package you want to add is called add and cabal-add is called as a standalone executable? That would certainly not work right now and you would have to read an env-var?
@MangoIV good catch! Thanks, fixed now.
Just running into this issue myself, and I think this design decision is so bad it's essentially a show stopper for most commands.
If you want to support older versions of cabal that predate this feature, your script needs to support being run both directly, and through a cabal external command. The difference between such invocations is exactly this command name being passed as argv[1].
Most commandline interfaces I can think of cannot afford the ambiguity of "maybe strip the first word if it's there" because they accept filenames, or package names, or other similar types of arguments where a sequence of letters is a perfectly valid input. @MangoIV pointed out this issue with cabal add. A similar issue exists with cabal fmt, and with a tool I'm developing, cabal matrix.
You could try to disambiguate based on the presence of $CABAL like @Bodigrim does, but it's a rather common variable name that could be set in other circumstances. For example it's also used by workflows generated by haskell-ci.
However, the feature is already out. Changing it now would unnecessarily break existing tools.
It's quite unfortunate that this slipped by without due criticism, however honestly I would be surprised if there aren't actually more tools that are broken by this (like cabal fmt), or that are burdened by a wonky workaround due to this (like cabal add), than there are tools developed specifically for this feature and which expect argv[1] to always be what it is now.
@mniip It sounds like your use-case is to have a single tool that can be run either directly from the command line or as a cabal external command. In that scenario, the choice to follow the cargo approach (passing the command name as the first argument) does cause friction.
On the other hand, for tools that are single binaries exposing multiple subcommands, having the subcommand name as the first argument makes sense otherwise there’s no way to know which entry point was intended.
There’s a trade-off between these cases. I’d encourage looking at this from both angles, as I think your comment may overstate one side of the issue.
It does seem that some authors would prefer not to receive the command name as the first argument. If that’s the prevailing sentiment, then perhaps @mniip could propose a pull request and outline a migration strategy?
On the other hand, for tools that are single binaries exposing multiple subcommands
Do you have an example of such a tool? Genuinely curious.
otherwise there’s no way to know which entry point was intended
cabal foo will call cabal-foo and cabal bar will call cabal-bar. You have three options here:
-
cabal-fooandcabal-barcan be distinct executables. This follows naturally from the haskell practice of structuring your package as a big library and a thin executable -- in this case many thin executables.Rust people have issues with this approach though: rust executables are statically linked by default because rust has a poor relationship with dynamic linking, and thus the library code is duplicated between the executables.
Haskell supports dynamic linking well enough, and although cabal will statically link executables by default, an executable can request to be dynamically linked in its cabal file (HLS does this).
-
cabal-fooandcabal-barsimply execvecabal-foobar fooorcabal-foobar bar. This has the issue that apparently on some platforms it's difficult to exactly forward all CLI arguments unmodified. -
cabal-fooandcabal-barare symlinks tocabal-foobarwhich reads its argv[0] to identify how to behave. As pointed out above this is the traditional way of doing this in unix: busybox does this, bash does this, etc.The problem with this approach is I don't think you can instruct
cabal-installto create such symlinks.
I’d encourage looking at this from both angles
So how does cabal-foobar benefit from the current behavior of being given the command name? The files cabal-foo and cabal-bar still need to exist in the PATH.
- If they are distinct executables the linking situation is virtually the same except worse, because
cabal-foocontains the functionality ofbarthat cannot be LTO'd out. - Execve'ing and forwarding all arguments didn't get any easier
- If they are symlinked, instead of reading argv[0] you are now reading argv[1]. Is that somehow siginficantly easier? Of course, cabal still cannot create these symlinks for you.
I will open a PR for this. :)
For the record, I'm in favor of changing the behaviour. I find the status quo counterintuitive.
Making Haskell work as much like the next tool when it comes to packaging and distribution is more important that forging an independent path.
I don't want to digress, but I find the path of adopting other peoples approaches without understanding the underlying reasons far more dangerous.
That's how you end up with unnecessary complexity.
In this case, the assessment was that it would be a genuine convenience for authors of external commands if they had the option to inspect which command invoked their executable. The idea was inspired by Cargo, but uniformity across tools was also a strong argument. It wasn’t simply implemented "because Cargo does it".
Of course, with decisions like this, sometimes the assessment later proves mistaken, and in those cases we can revise the design and move forward, as we’ve done here.
I think we both agree that uniformity in packaging expectations matters, while still keeping in mind whether an approach actually fits our ecosystem. There’s a spectrum of choices here, and when our assessments land differently, we can adjust and improve things together for everyone’s benefit.
I think we both agree that uniformity in packaging expectations matters
I don't really know what that means, so I'm not sure I agree.
There's bad examples of packaging that are widespread and good examples.
Uniformity is a good goal when we talk about usability. But that assumes the usability is excellent to begin with. Most of the time achieving uniformity is outside of our control (across devices/brands/ecosystems etc.).
There's a widespread trend that "whatever they do over at rust is probably a good idea" and I think that's a problematic approach.