cabal icon indicating copy to clipboard operation
cabal copied to clipboard

external commands need to explicitly handle their own name being passed to the executable

Open MangoIV opened this issue 1 year ago • 10 comments

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

  1. have a command cabal-bla in your environment that doesn't allow to take the argument "bla"
  2. run cabal bla
  3. 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)

MangoIV avatar Aug 25 '24 11:08 MangoIV

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.

ulysses4ever avatar Aug 25 '24 20:08 ulysses4ever

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 avatar Aug 25 '24 20:08 Bodigrim

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.

MangoIV avatar Aug 25 '24 21:08 MangoIV

Maybe I can understand better when I read why cargo did it like this…

MangoIV avatar Aug 25 '24 21:08 MangoIV

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.

mpickering avatar Aug 27 '24 08:08 mpickering

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 avatar Aug 27 '24 08:08 MangoIV

@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.

mpickering avatar Aug 27 '24 09:08 mpickering

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.

fgaz avatar Aug 27 '24 09:08 fgaz

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 avatar Aug 27 '24 09:08 MangoIV

@MangoIV good catch! Thanks, fixed now.

Bodigrim avatar Aug 28 '24 18:08 Bodigrim

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 avatar Sep 30 '25 14:09 mniip

@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?

mpickering avatar Oct 01 '25 11:10 mpickering

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-foo and cabal-bar can 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-foo and cabal-bar simply execve cabal-foobar foo or cabal-foobar bar. This has the issue that apparently on some platforms it's difficult to exactly forward all CLI arguments unmodified.

  • cabal-foo and cabal-bar are symlinks to cabal-foobar which 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-install to 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-foo contains the functionality of bar that 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.

mniip avatar Oct 01 '25 12:10 mniip

I will open a PR for this. :)

MangoIV avatar Oct 01 '25 13:10 MangoIV

For the record, I'm in favor of changing the behaviour. I find the status quo counterintuitive.

Bodigrim avatar Oct 01 '25 20:10 Bodigrim

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.

hasufell avatar Oct 02 '25 09:10 hasufell

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.

mpickering avatar Oct 02 '25 09:10 mpickering

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.

hasufell avatar Oct 03 '25 03:10 hasufell