haskell-infra / hackage-trustees

Issue tracker for Hackage maintainance and trustee operations
https://hackage.haskell.org/packages/trustees/
42 stars 7 forks source link

Packages failing to uphold versioning contract via reexports from trans-deps #227

Closed hvr closed 1 year ago

hvr commented 5 years ago

Consider the example of http-conduit-2.0.* which reexports entities such as parseUrl from http-client >= 0.2 && < 0.5.

The problem here is that the type-signature of parseUrl changed between http-client-0.2 and http-client-0.3 and this was in fact properly signaled by a major version increment in http-client's package version; however, the maintainer of http-conduit carelessly reexported parseUrl via the public API of http-conduit-2.0.* but failed to ensure that the reexported entity had a well-defined type-signature by setting appropriate version bounds on http-client (the current < 0.5 bounds were added by Trustees without taking into account the issue at hand).

In other words, as a consumer of http-conduit who correctly declares the dependency as

build-depends: http-conduit == 2.0.*

and import

import Network.HTTP.Conduit (parseUrl)

you now may either have imported

parseUrl :: Failure HttpException m => String -> m Request

or

parseUrl :: MonadThrow m => String -> m Request

depending on whether http-conduit ends up depending on http-client == 0.2.* or http-client == 0.3.*.

This clearly represents a failure to uphold the API versioning contract which morally denotes that the advertised API version shall be a good identifier for the exported API of a package.

A consumer can in principle use the hacky workaround of controlling http-client's transitive deps for all its reexported entities, i.e. by declaring a fake/artificial build-depends: http-client == 0.2.*.

However, this has multiple problems and is merely treating the symptoms: As a consumer you'd need to become aware of any reexported entities of a package you depend on, and start depending on the transitive deps where these come from (and be constantly vigilant about dependency starting to add reexported entities in later versions); For one this breaks the abstraction that packages represent but more importantly this shifts the burden to every consumer and is therefore clearly non-optimal; also it's not allowed to add such new dependencies via metadata revisions which consequently causes a huge problem for Hackage curation by Trustees as there's no benign way to fix such situations.

Long story short, ensuring the well-definedness of exported and reexported API entities is the responsibility of the API provider in order for the Hackage ecosystem to work efficiently.


A concrete example of a consumer is aws-0.8.4 and aws-0.8.5 which end up in this pernicious situation:

cabal repl -zb aws==0.8.5 -w ghc-7.8.4 --constraint 'http-client == 0.2.*' --constraint 'http-conduit==2.0.0.*'

works, but

cabal repl -zb aws==0.8.5 -w ghc-7.8.4 --constraint 'http-client == 0.3.*' --constraint 'http-conduit==2.0.0.*'

fails with

[ 1 of 64] Compiling Aws.Ec2.InstanceMetadata ( Aws/Ec2/InstanceMetadata.hs, dist/build/Aws/Ec2/InstanceMetadata.o )

Aws/Ec2/InstanceMetadata.hs:20:41:
    No instance for (exceptions-0.10.2:Control.Monad.Catch.MonadThrow
                       (ResourceT IO))
      arising from a use of ‘HTTP.parseUrl’
    In a stmt of a 'do' block:
      req <- HTTP.parseUrl ("http://169.254.169.254/" ++ p ++ '/' : x)
    In the expression:
      do { req <- HTTP.parseUrl
                    ("http://169.254.169.254/" ++ p ++ '/' : x);
           HTTP.responseBody <$> HTTP.httpLbs req mgr }
    In an equation for ‘getInstanceMetadata’:
        getInstanceMetadata mgr p x
          = do { req <- HTTP.parseUrl
                          ("http://169.254.169.254/" ++ p ++ '/' : x);
                 HTTP.responseBody <$> HTTP.httpLbs req mgr }

but there doesn't appear to be a metadata revision possible to perform on aws-0.8.[45] which can prevent aws depending on the combination of http-client-0.3.* and http-conduit-2.0.0.*, as aws doesn't directly build-depends on http-client.

Also this is quite problematic as aws-0.8.4 is the highest version in the 0.8.* major version range which is compatible with GHC 7.4.2 (see image below) and would therefore likely be selected by the cabal solver when a aws < 0.9 upper bound is in place:

aws-shape


Consequently, in order to treat the root cause we'd instead have to ensure that http-conduit-2.0.0.* doesn't export ill-defined type-signatures of its public API entities. And the only way seems to be to revise http-conduit-2.0.0.* to constraint it to http-client-0.2.* (or alternatively to http-client-0.3.* -- which in this case however would break consumers which were coded against the original http-client-0.2 typesignatures).

andreasabel commented 1 year ago

Closing for lack of activity.