haskell / cabal

Official upstream development repository for Cabal and cabal-install
https://haskell.org/cabal
Other
1.61k stars 691 forks source link

Proposal for a Cabal plugin API (inversion of control) #2395

Open bitemyapp opened 9 years ago

bitemyapp commented 9 years ago

doctest and ghc-mod are particularly emblematic of stuff that is awesome, but frequently breaks for various reasons. Some of that could be ameliorated with better tool integration.

ghc-mod has to be linked against the version of Cabal in the project otherwise it won't work properly.

/cc @ttuegel

ttuegel commented 9 years ago

I didn't think of this when we first discussed making a plugin API, but part of this issue would be solved by making it easier for library consumers to drive the entire configure/build process. That doesn't make sense for a tool like doctest (or maybe it does, and I don't understand that workflow). But I think it would make more sense for ghc-mod to call configure, build, etc. itself. Basically, the API should be so easy to use, no one hesitates to make a cabal-install-like command. That means we need to expose some of cabal-install's functionality, see #1848.

bitemyapp commented 9 years ago

@ttuegel have you looked at doctest's source? It's sorrowfully hacky. I'm not sure how it could be better done without tool/API support though.

DanielG commented 9 years ago

+1 for exposing more of cabal-install as a library. While a plugin system might be the better solution in the long run just being able to access some of the internals of cabal-install would already lift quite some burdens. I don't think it would fix the problems when different Cabal versions are in use by ghc-mod and the cabal-install executable on the users' PATH. See https://github.com/DanielG/cabal-helper for how we deal with that now.

ttuegel commented 8 years ago

Although I still think there is great value in making it easier for external agents to drive the configure-build-test-install process, I realized recently that we already have most of the machinery needed to implement a simpler, yet still powerful, plugin model.

The setup-depends work solves many of the problems with build-type: Custom, but your project is still tightly coupled to the Cabal API. External programs using the Cabal API have the same problem. The difficulties here are not insurmountable and are often outweighed by the tremendous power using the API gives you. But I think there is a big unexploited niche for solutions that need only a fraction of the power.

Consider our build-type: Configure, which allows a configuration script to output build info stanzas for the package description. I suggest we implement an architecture where plugins are executable programs which take a package description as text through stdin and output the modified description as text through stdout. (We could augment this later to allow plugins to modify different phases.)

Presumably, plugins would still link to the Cabal library to parse the package description, but it matters much less that the versions match because package descriptions are generally parseable between many versions. (Although, we would probably need some parser improvements here.)

For example, for automatic discovery of doctest tests, a package author could specify

configure-plugin: doctest-discover (doctest-discover >= 0.1)

and during configuration the package description could be modified by that executable to add the discovered tests.

This is just a rough sketch so far, but what do we think?

ezyang commented 8 years ago

To be clear, there are two things you might be referring to package description: the literal Cabal file (which can be parsed into GenericPackageDescription), or the description with conditionals resolved (PackageDescription). The latter's Binary/Show/Read serialization is NOT stable across versions. The former is insufficient for most plugins, who want the configured package.

ttuegel commented 8 years ago

I mean the literal Cabal file. I realize this is not enough for some of the plugins currently in existence, but I think there is a big, unexploited space of potential plugins that don't exist because the cost of integrating with the Cabal API is too high. I think the doctest-discover example I gave captures that well: at the moment, it operates as a GHC preprocessing stage because it would be unnecessary difficult to integrate with Cabal. I think something like this would also be relevant to your recent effort to clean up the PackageTests. This isn't enough power to implement something like ghc-mod, but I think it's a useful subset of functionality for a tiny fraction of the cost. I think this would also give us a good proof of concept if we wanted to offer a similar plugin interface for other phases, too.

ttuegel commented 8 years ago

Oh, I should also point out: there is nothing stopping us from using the literal Cabal file format for a textual representation of the package description even after all the conditionals have been resolved. And of course the Read/Show/Binary instances are too fragile for this.

ezyang commented 8 years ago

Yes, that's true.

I was under the impression that most plugins are also going to want access to LocalBuildInfo, but I haven't actually checked.

Blaisorblade commented 8 years ago

I was under the impression that most plugins are also going to want access to LocalBuildInfo, but I haven't actually checked.

They probably would, lest they hack it with flattenPackageDescription and risk getting contradictory/incompatible settings. I'd like cabal-install deserialize-lbi (which might exist as cabal-helper) to dump the content back into a standard format—I'm fine with anything that allows me to get a PackageDescription in the end.

Once I wanted to extract language settings from the cabal file and ended up with exactly flattenPackageDescription—I dread the day it'll break. This was needed for an implementation of ctags for Haskell based on GHC's API (hidden inside Agda)—the parser already needs to know the correct language extensions:

https://github.com/agda/agda/commit/6c68473f0238aaf6ac7d8515a4a4e050320b4385#diff-a39ee6b3b1eb9ff16e04e127c3e9dd73R93

ezyang commented 8 years ago

If someone wants to take a look at LocalBuildInfo, and propose a textual format for it (keeping in mind that some stuff really ought to be private so that we can still refactor it) that would be helpful.

Blaisorblade commented 8 years ago

Taking a stab at this:

  1. What I'm sure I needed was the configured PackageDescription, which doesn't need so much design, and that's probably useful anyway. By reusing the literal Cabal file format (as @ttuegel suggested), without conditional directives, we could guarantee that the normal cabal parser followed by flattenPackageDescription should give the same PackageDescription. That could be a start for doctests and my hTags example.
  2. For LocalBuildInfo itself we'd have a less trivial design effort.
    • Is YAML or some similar encoding scheme acceptable? Otherwise one also needs to design a concrete syntax.
    • OTOH, all examples we're looking at care about typechecking/linking the files in an equivalent way, not getting the same result. This excludes (hopefully) backend options but includes
      • actual package IDs (most crucially)—concretely, the entirety of componentsConfigs.
      • any GHC options (any language and warning settings, package/unit IDs of dependencies, ...), if not in PackageDescription (I see none).
      • info on sources, if not in PackageDescription (I see none).
      • maybe, extra info that is required to use Cabal APIs
  3. At a glance: all of LocalBuildInfo is currently exported, and changes will require using CPP or other tricks. Adding the cabal version(s) to the output and using dynamic ifs on that would work—that's less bad than using CPP to conditionally-depend on LocalBuildInfo functions, or the current solutions. And it would still be an improvement over having to link to the exact Cabal version.