Cabal-add: extend Cabal build-depends from the command line

As discussed at Convenience in the Haskell ecosystem - #169 by Bodigrim, I’ve developed a small command-line tool to add build-depends. I tried a few examples and convinced myself that it’s robust enough, but would appreciate more user acceptance testing before I decide to clutter Hackage with one more package.

$ cabal-add --help
Usage: cabal-add (-f|--cabal-file FILE) [-c|--component ARG] DEP

  Extend build-depends from the command line

Available options:
  -f,--cabal-file FILE     Cabal file to edit in place.
  -c,--component ARG       Package component to update (the main library, if
                           omitted). Wildcards such as 'exe', 'test' or 'bench'
                           are supported.
  DEP                      Package(s) to add to build-depends section. Version
                           bounds can be provided as well, use quotes to escape
                           comparisons from your shell. E. g., 'foo < 0.2'.
21 Likes

As promised, I had a go at breaking this, since I got some time today. I managed to very nearly do it with a common stanza… but it appears that Cabal is fine with having two build-depends clauses in a target, so that doesn’t actually break anything. Very impressive work! (It even detects whether I’ve used leading or trailing commas, which is really nice.)

That being said, I dislike the restriction that it can only add packages at the beginning of the build-depends. Personally, I strongly prefer a style where the first line is reserved for base, and this tool messes it up. It would be nicer to, for instance, insert the new package in alphabetical order, or something similar.

2 Likes

I’m afraid this goes beyond the scope of cabal-add: it cannot reasonably guess all your stylistic preferences. For instance, "the first line is reserved for base" and “dependencies are sorted alphabetically” are conflicting requirements: does, say, aeson go above or below? Is sorting case-sensitive or not? One tool for one job; use cabal format, or cabal-fmt, or another, yet unwritten Cabal formatter.

It’s pretty much the same as with HLS: a code action for a missing module / function inserts an import somewhere, but one has to run a code formatter of their choice to bring everything up to their preferences.

1 Like

Fair enough. I’ve noticed (and been annoyed by) this behaviour from HLS too.

I haven’t specifically tried to break it, but I use it all the time now and I love it! Thanks Bodigrim, this is amazing!

1 Like

Is it worth starting a discussion about upstreaming this into Cabal? That would let us start to do things like incorporating cabal add into error messages (as discussed in the Ergonomics of Cabal thread).

2 Likes

We’re not adding a new ad-hoc semi-parser into cabal proper – that’s the opposite direction of how that codebase needs to evolve. We need the exact-printer to add features to cabal directly. Instead, this should be a prime example of a tool that works with the new external commands system and can be promoted as a flagship example of such.

8 Likes

I think it does suggest one potential feature for external commands: The ability to hook into error messages so that cabal-add could suggest to use itself when the relevant error fires.

4 Likes

FWIW I personally do not intend to push on upstreaming into Cabal, but just to be clear: cabal-add builds atop Distribution.Fields and does not reimplement a parser.

5 Likes

Oh nice – does that mean that it ends up reformatting the file to some degree / dropping comments? Or is there a trick to preserving that?

1 Like

Everything is preserved.

Ideally one should go [Field Position] -> [Field ByteString] (see the snippet at Meta: Exact-printer Mega-issue · Issue #7544 · haskell/cabal · GitHub), then express adding a new dependency as [Field ByteString] -> [Field ByteString] transformation, then fold everything back. This is doable even in the current state of Cabal-syntax, but I took a shortcut sufficient for my purposes: instead of transforming [Field ByteString], I just figure out at which Position to insert build-depends and perform insertion on the original ByteString.

The most cumbersome part is to guess the desired style (newlines, indentation, leading / trailing comma). But this will not get any better with exact printer advances.

I published Haddocks for anyone interested to take a peek at the architecture: Distribution.Client.Add

10 Likes

Now this is very interesting: I hadn’t known that Cabal already provides Position information. This already gets you most of the way to an exact-printing parser — as you note, it’s not exactly the same thing, but it gives a great deal more information than I had thought was available.

1 Like

@Bodigrim getting it done :clap::grin:

1 Like

Is anyone interested to integrate cabal-add into HLS?

5 Likes

I’ve got a moment to support cabal.project instead of single Cabal file. Could someone please give it a go before I make a release?

3 Likes

Finally released on Hackage: cabal-add: Extend Cabal build-depends from the command line

19 Likes

@Bodigrim how hard do you think it’d be to make it a little more general than modifying exactly one type of field? It feels to me that the answer is: not hard. I’m pondering about developing it in a full-fledged exact parser/printer.

Looking forward to using something like that instead of the relatively crude hacks in https://github.com/nomeata/cabal-plan-bounds/blob/master/src/ReplaceDependencies.hs

Thought I’d mention that there’s an in-progress proposal for an exact printer for cabal files here as well, may be useful ideas there, or vice versa. Cabal exact print by jappeace · Pull Request #65 · haskellfoundation/tech-proposals · GitHub

2 Likes

@artem adding stuff (dependencies, modules, etc.) is fairly easy: even if you fail to find an existing field and insert an item there, you can always just add a new field with a single item at the beginning of the section. Removing or modifying stuff is more complicated, especially because of conditions and common stanzas.

2 Likes