Proposal for `provides`-mechanism
- Dominant language
- Haskell
- Stars
- 1.7k
- Forks
- 750
- Avg merge
- 4d 3h
- Merged PRs (30d)
- 28
Description
## Problem Statement
Currently there is no satisfying way to express packages which
conflict with each other in the same install-plan.
Some limited form of conflicts can be expressed when two packages
depend _directly_ on each other via `build-depends` relations.
However, this is not enough to handle cases where it is necessary to
make sure that two or more packages are mutually exclusive to each
other.
This the need for such mutual exclusivity occurs when entities which
may need to be unique throughout the install-plan, such as
(desugared-into) typeclasses or relocated orphan instances are
involved.
## Motivating Use-cases
### Relocating orphan instances into compat-packages
One recent use-case which can't be solved properly currently is the
following:
- `semigroupoids-4.3` conditionally defines an orphan `instance Foldable (Either a)` (for `base<4.8`)
- Starting with `base-4.8`, `base` started providing `instance Foldable (Either a)`
- Then `base-orphans-0.1` started equally providing an such an orphan instance like `semigroupoids-4.3`
- `semigroupoids-5.0` now depends on `base-orphans`
However, there's currently no way to express that the combination
- `base >= 4.8`
- `semigroupoids < 5`, and
- `base-orphans`
is forbidden to occur in an install-plan.
### Alternative `base`/`Preludes` replacements with incompatible typeclass hierarchies
This use-case refers to a future not-yet implemented
`ExportedRebindableSyntax` extension, which would allow the currently
in scope `Prelude` module to export desugaring rules for things like
`do`-syntax, and therefore allow to provide seamless `Haskell2010`
support (with a different `Monad` class different from `base`'s
`Monad` class) in recent GHCs.
Some packages could then opt in to explicitly support the
`haskell2010` package by depending on `haskell2010` or `base`
conditionally in their `.cabal` file:
```
if flag(haskell2010)
build-depends: haskell2010 == 1.2.*
else
build-depends: base =>4.3 && <4.10
```
However, since each package would have their own `haskell2010` flag,
which are _not_ synchronised by cabal, there is no way to currently
express that `haskell2010` and `base` are mutually exclusive.
## Proposed enhancement
Provide a new `provides: ` declaration (allowed
everywhere a `build-depends:` declaration is currently allowed).
The `` would denote a token in a global namespace which
at most one package selected in a given install-plan may provide.
### Application for orphans scenario
This way, `semigroupoids-4.3`'s cabal file could be edited and have a
```
provides: orphans-base-foldable-either
```
Likewise, `base-orphans` (and possibly other packages such as
`base-compat` which also contain such orphans) would declare
`provides: orphans-base-foldables-either` as well in their respective
`.cabal` files.
### Application for `base`/`Prelude` replacements
`base.cabal` simply declares something like
```
provides: core-primitives
```
and every other package which provides a `base` library which replaces
the core primitives (i.e. core typeclasses, and other entities
wired-in into the language via desugaring rules) and therefore don't
interoperate with `base` would state the same `provides` declaration.
## Alternative `constraint`-mechanism
Once could allow to declare `constraint:` properties in `.cabal` files.
This way, the respective `base-orphans` package versions could start
declaring all package version ranges containing orphans that were
adopted into `base-orphans`, e.g. `base-orphans.cabal` could specify
```
constraint: semigroupoids >= 5
```
or dually, `semigroupoids.cabal` (for semigroupoids-4.3 and other
versions affected) could declare
```
constraint: orphans-base<0
```
if there is no `orphans-base` version compatible with
`semigroupoids-4.3`, and therefore `orphans-base` is incompatible.
However, `constraint` is _very_ powerful. than the proposed
`provided`-mechanism. Being more general, this may be more complex to
implement in the solver. Moreover, when abused it can result in
confusing install-plan results.
Finally, when modelling the situation where more than 2 packages need
to be mutually exclusive, expressing this in the
`constraint`-formulation becomes more complicated and doesn't, as each
involved package needs to know about all other conflicting
packages. Specifically the `base`-replacements use-case with multiple
packages becomes impractical to express.
The proposed `provided`-mechanism is weaker, makes it easy to express
mutual exclusivity among _N_ packages, and is IMO easier to implement
as well as easier to reason about.
## Alternative `conflicts:`-mechanism
During the discussion a variant of `constraint:` was suggested, specifically to express conflicts directly via `conflicts:` which denotes the inverse of `constraints:`. I.e. rather than saying
```
constraint: semigroupoids >= 5
constraint: orphans-base<0
```
one specifies
```
conflicts: semigroupoids < 5
conflicts: orphans-base
```
which is easier to reason about for the use-case of specifying conflicting package versions.
## Alternative `orphan-instances:`-mechanism
This would be a variant of the `provides` mechanism specialised to orphan instances, by providing a machine-verifiable (i.e. tooling can help making sure the enumeration is accurate) enumeration of orphan instances provided by a package.
See https://github.com/haskell/cabal/issues/3061#issuecomment-172824767
/cc @kosmikus @ezyang @dcoutts @nomeata @bergmark @23Skidoo @ttuegel @RyanGlScott
Contributor guide
Research direction
The issue names no implementation files or tests; start by tracing how build-depends declarations are parsed and how the solver constructs an install plan. Define the representation and solver behavior for a global provides token, then add coverage for the orphan-instance and mutually exclusive base-replacement cases described in the proposal.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- build-system
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100