Skip to content

RFC: Scheme for revising bounds to eliminate corners of the version cartesian products #213

@hvr

Description

@hvr

Problem Statement

The basic abstracted problem can be demonstrated with two dependencies, foo and bar and each with two major API versions. For the general case, these two packages shall be considered independent of each other, i.e. neither depends upon the respective other.

Their exported API is

-- foo-1.1
module Foo where
f :: Char -> Int
-- foo-1.2
module Foo where
f :: Char -> Word
-- bar-0.1
module Bar where
g :: Int -> Bool
-- bar-0.2
module Bar where
g :: Word -> Bool

And now consider a consumer of bar and foo, i.e.

build-depends: foo >= 1.1 && < 1.3, bar >= 0.1 && < 1.3

and combines g and f like so

h :: Char -> Bool
h = Bar.g . Foo.f

Let's assume this definition is semantically sound when it typechecks.

However, only two out of 4 combinations of foo and bar are valid:

foo-1.1 foo-1.2
bar-0.1 OK FAIL
bar-0.2 FAIL OK

This is just one example; another situation that's commonly encountered is something like

foo-1.1 foo-1.2
doo-0.1 OK FAIL
doo-0.2 OK OK

or any rotation of these two. (NB: if only one out of four quadrants is "OK", it's trivial to resolve via revisions)

Solutions to the Problem

The real problem now is how to fix an package description which contains the inaccurate

build-depends: foo >= 1.1 && < 1.3, bar >= 0.1 && < 0.3

dependency specification without forbidding any of all the previously reachable sound quadrants.

A solution.

Indirect solution by introducing extra dummy packages

A possible solution would be to use the network-uri-flag hack, which involves creating a dummy package with an empty library like shown below to encode the valid combinations of foo and bar:

name: foo-bar-compatibility
version: 0

flag _choice
  manual: False

library
  if flag(_choice)
    build-depends: foo == 1.2.*, bar == 0.2.*
  else
    build-depends: foo == 1.1.*, bar == 0.1.*

and then this package foo-bar-compatibility needs to be added to the whitelisted packages allowed to be added retroactively via revisions into build-depends in the hackage-server revision validation logic. Consequently, the package consuming foo and bar would need to be revised into

build-depends: foo >= 1.1 && < 1.3, bar >= 0.1 && < 0.3, foo-bar-compatibility == 0

However, while this approach is possible, it comes at with a couple of obvious downsides:

  • needs to create and maintain new extra packages for encoding inter-package relations
  • management overhead of needing to maintain/grow an adhoc whitelist of these packages (currently this requires a redeploy of hackage-server)
  • this is required for every combination of packages that exhibit the problem at hand
  • the extra packages present a possible additional point of failure if mistakes are made

Direct solution

A more direct solution is to extend the hackage revision rules to allow the introduction of new automatic cabal flags via metadata revisions, with the following machine-checked limitations:

  1. The ability to introduce cabal flags shall be limited to hackage trustees
  2. Such cabal flags introduced via revisions must start with a reserved x-rev- prefix (or another prefix that doesn't yet occur in 01-index.tar at time of deployment)
  3. cabal flags prefixed by x-rev- must be manual: False (Hackage will enforce this invariant during revisions)
  4. Hackage will refuse uploads of 0th revisions with package descriptions containing x-rev- prefixed flag names; this is to ensure that non-trustees cannot create immutable x-rev- flags by accident.

Since these are automatic cabal flags which the cabal spec provides for the very purpose to express these kind of either/or dependency specifications involving multiple packages.

Direct solution for future cabal-versions

If, in a future cabal version, the cabal format provides a flag-less way to express, such as e.g.

    build-depends: foo >= 1.1 && < 1.3
    if lib-version(foo >= 1.2)
        build-depends: bar == 0.1.*
    else
        build-depends: bar == 0.2.*

the revision logic would be adapted to allow introducing if lib-version(...)-conditionals for cabal-version:s which support this predicate; while also disallowing the flag-introduction from the previous section for cabal-version:s which provide this more idiomatic construct.

Metadata

Metadata

Assignees

No one assigned

    Labels

    rfc/discussion 🗣️Proposals/discussion on how to address/resolve some problem on Hackage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions