Git Product home page Git Product logo

pvp's Introduction

Haskell Package Version Policy (PVP) Specification

This repository is the home of the specification of Haskell's PVP which together with the Common Architecture for Building Applications and Tools (CABAL) specification provides the foundational framework powering Hackage, Haskell's central package repository.

Proposing Changes

The PVP evolves over time to adapt to changing requirements of the community. Everyone is invited to propose and discuss changes to the policy.

Formally, the PVP is maintained by the Core Libraries Committee together with the Hackage Trustees.

Please review the guidelines for contributing.

Building the http://pvp.haskell.org site HTML

The PVP site's static HTML files are generated by Hakyll.

You can either run

cabal run pvp-site -- rebuild
cabal run pvp-site -- server

to regenerate the HTML files, and then start a local HTTP server to preview the changes.

Alternatively, you can use Hakyll's preview mode which automatically rebuilds the HTML files when it detects changes with

cabal run pvp-site -- watch

pvp's People

Contributors

andreasabel avatar bgamari avatar binderdavid avatar bodigrim avatar chuahou avatar gbaz avatar hasufell avatar hvr avatar jaspervdj avatar mattaudesse avatar michaelpj avatar ocharles avatar osa1 avatar phadej avatar psilospore avatar sol avatar tomjaguarpaw avatar wizek avatar xkollar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pvp's Issues

Remove the "2.0.1.0 > 2.0.1" example, or clarify that this is bad

It is true that 2.0.1.0 > 2.0.1 in the lexicographic ordering, because mathematically, a missing number at the end counts as minus infinity. However, in versioning practice, a missing number at the end defaults to 0.

If after 2.0.1 you release 2.0.1.0, it is either by accident, ignorance or spite, in order to confuse others; imo.

There is already a more fundamental discussion at #4, I raised this issue to at least remove or better comment the 2.0.1.0 > 2.0.1 example which could give the idea that such versioning is good practice.

Document differences with SemVer

Many people already know about SemVer, so it may make sense to add a section explaining how PVP relates to SemVer, and what the commonalities and differences with SemVer are.

condition 2 (Non-breaking changes) examples are too limited

  1. Other changes. Otherwise, e.g. if change consist only of corrected documentation, non-visible change to allow different dependency range etc. A.B.C MAY remain the same (other version components may change).

Surely if I do a small tweak that improves output formatting say, that would also come under this, right?

Only mentioning documentation and dependency range changes here seems too small a class of examples to me, no?

Require smaller components to reset?

For example, going from version 1.2.3 to 2.2.3 is fine. I think if you bump a component, all of the smaller components should reset to 0. That means 1.x.y becomes 2.0.0.

Tools section points to outdated tools, so remove or update it

Remove the "Tools" section, it is pointing to unmaintained packages only.

### Tools
- script to check for API changes in gtk2hs:
<http://code.haskell.org/gtk2hs/tools/apidiff/>
- [precis](http://hackage.haskell.org/package/precis) - a simple tool
for a first approximation of package API differences, see the
[announcement](http://www.haskell.org/pipermail/haskell-cafe/2010-April/077023.html)
- [check-pvp](http://hackage.haskell.org/package/check-pvp) is a program that checks for consistency between package
dependencies and import style.

Does not address Safe Haskell related breakage

In haskell/parsec#88, the fact that a module from the parsec package is no longer inferred safe (according to Safe Haskell) means that a downstream package, MissingH, was broken by a minor version bump. From what I can tell, this form of breaking change is not currently covered by the PVP. Is this something worth adding?

new version number suggestions seem wrong?

In the decision flowchart, the major version bump leaf says something like

Your new version number should be (A+1).B.C or A.(B+1).C

Shouldn't it rather say

Your new version number should be (A+1).0.0 or A.(B+1).0

?

Wording about A.B.C number

A package version number SHOULD have the form A.B.C, and MAY
optionally have any number of additional components, for example 2.1.0.4
(in this case, A=2, B=1, C=0). This policy defines the meaning of
the first three components A-C, the other components can be used in
any way the package maintainer sees fit.

This should mention that version could be e.g. just 1, but than it means that following components are 0, for example example-package-1. Probably also that one MUST NOT release example-package-1 and example-package-1.0 (Does hackage-server enforce this?)

Give guidance about patch releases?

Currently the PVP does not talk about patch releases at all. It only specifies A.B.C version numbers, which include two major versions for breaking changes and one minor version for non-breaking additions. For bug fixes that don't introduce anything new, what is the recommended course of action? I typically see packages either increment C or add a new version component D and increment that for bug fixes.

Clarify bumping policy for bug fixes

This question comes from:

In this situation, position information generated by Alex < 3.2.7 had a bug that was fixed in 3.2.7.
However, cabal had workarounds for the bug which broke after the bug was fixed.

The current PVP does not talk about behavioral changes of a definition, only about changing its type.

I would have thought a bugfix is a patch release (fourth digit bump).
From the current example (workaround breaking) one could also argue for a major version bump for bug fixes that change the output of a function (rather than just making it more defined).

What is your take on this?

The PVP should be extended to mention bug fixes in the appropriate place.

Orphan instance packages

Current PVP text says

breaking change If any entity was removed, or the types of any entities or the definitions of datatypes or classes were changed, or orphan instances were added

Should this restriction be relaxed for -instances / -orphans companion packages, like base-orphans , quickcheck-instances or binary-orphans ?

Clarify rule two

Rule 2 currently says:

Non-breaking change. Otherwise, if only new bindings, types, classes, non-orphan instances or modules (but see below) were added to the interface, then A.B MAY remain the same but the new C MUST be greater than the old C. Note that modifying imports or depending on a newer version of another package may cause extra non-orphan instances to be exported and thus force a minor version change.

There's an ambiguity here. It could be read as requiring C to be increased only when A.B remains the same (which I believe to be intended), or it could be read as requiring C to be increased regardless of whether A.B remains the same. Could this wording be made more clear?

Instructions how to build unclear

Tried the standard cabal configure/install (using ghc 8.0.1 / cabal 2.0.0.2) but got stuck:

$ cabal install
Resolving dependencies...
Notice: installing into a sandbox located at
/Users/tomc/dev/pvp/.cabal-sandbox
Configuring pandoc-1.17.2...
Failed to install pandoc-1.17.2
Build log ( /Users/tomc/dev/pvp/.cabal-sandbox/logs/ghc-8.0.2/pandoc-1.17.2-5JRTd4aGtiYLtWZCoRZa7k.log ):
cabal: Entering directory '/var/folders/db/xq84lg117mj6rvv3gbj9xfkc0000gn/T/cabal-tmp-77851/pandoc-1.17.2'
cabal: Leaving directory '/var/folders/db/xq84lg117mj6rvv3gbj9xfkc0000gn/T/cabal-tmp-77851/pandoc-1.17.2'
cabal: Error: some packages failed to install:
hakyll-4.9.8.0-dHh4AOmdkYG1VA9sp85MG depends on hakyll-4.9.8.0 which failed to
install.
pandoc-1.17.2-5JRTd4aGtiYLtWZCoRZa7k failed during the configure step. The
exception was:
dieVerbatim: user error (cabal:
'/Users/tomc/.stack/programs/x86_64-osx/ghc-8.0.2/bin/ghc' exited with
an error:

/var/folders/db/xq84lg117mj6rvv3gbj9xfkc0000gn/T/cabal-tmp-77851/pandoc-1.17.2/dist/dist-sandbox-86ecebfb/setup/setup.hs:46:3:
error:
• Couldn't match expected type ‘ComponentLocalBuildInfo
-> PreProcessor’
with actual type ‘PreProcessor’
• In the expression:
PreProcessor
{platformIndependent = True,
runPreProcessor = mkSimplePreProcessor
$ \ infile outfile verbosity
-> do { let ...;
.... }}
In the expression:
\ _ lbi
-> PreProcessor
{platformIndependent = True,
runPreProcessor = mkSimplePreProcessor
$ \ infile outfile verbosity -> ...}
In the expression:
("hsb",
\ _ lbi
-> PreProcessor
{platformIndependent = True,
runPreProcessor = mkSimplePreProcessor
$ \ infile outfile verbosity -> ...})

/var/folders/db/xq84lg117mj6rvv3gbj9xfkc0000gn/T/cabal-tmp-77851/pandoc-1.17.2/dist/dist-sandbox-86ecebfb/setup/setup.hs:49:39:
error:
Data constructor not in scope: FlagName :: [Char] -> FlagName
)
pandoc-citeproc-0.10.5.1-JDDvPJ2JUou2wRNLItEWJe depends on
pandoc-citeproc-0.10.5.1 which failed to install.
pvp-site-1.0-KwEwlEDTbLx358NLFFOHpp depends on pvp-site-1.0 which failed to
install.

Mention changing definitons of functions or values as Breaking change

I propose changing

Breaking change. If any entity was removed, or the types of any entities or the definitions of datatypes or classes were changed, or orphan instances were added or any instances were removed, then ...

into (addition emphasised)

Breaking change. If any entity was removed, or the types of any entities or the definitions of datatypes, classes, functions or values were changed, or orphan instances were added or any instances were removed, then ...

Remove point 7 about deprecation

Related: ghc-proposals/ghc-proposals#625

  1. Deprecation. Deprecated entities (via a DEPRECATED pragma) SHOULD be counted as removed for the purposes of upgrading the API, because packages that use -Werror will be broken by the deprecation. In other words the new A.B SHOULD be greater than the previous A.B.

This is a very unreasonable suggestion and in fact would imply two major version bumps for a deprecation.

Is the PVP transitive?

Question

Can a package that adheres to the PVP depend on a package that violates the PVP?

Assumption

My assumption so far has been that the answer to this question is "no".

Rational

Looking at the following sentence:

Note that modifying imports or depending on a newer version of another package may cause extra orphan instances to be exported and thus force a major version change.

This suggests that a package a is "responsible" for orphan instances from transitive dependencies. Now if there is a package c in the transitive dependency graph of a and c is not constrained with a valid upper bound then package a does not honor the PVP as it is not guarded against possible additions of orphan instances to c in the future.

For completeness, the scenario here is:

  • package a depends on package b, specifying a valid upper bound
  • package b depends on package c, violating the PVP by not specifying a valid upper bound
  • package c adheres to the PVP

Does this make sense or do I miss something?

The PVP should include advice about SafeHaskell

The PVP does at the moment not give any advice about SafeHaskell. E.g. it does not say that if you make a previously Safe module unsafe, then you need a major version bump.
Why major bump? Because then import safe statements suddenly error out, and downstream packages become unbuildable.

In the wild:

In directory-1.3.8.0, the module System.Directory is no longer Safe, as it used to be in 1.3.7.*. Thus the new release should rather have been directory-1.4.0.0. Being released as 1.3.8.0, packages using directory < 1.4 and import safe System.Directory are now broken and need a revision.

Prevent non-contiguous version ranges?

The spec does not require version components to go up by 1. It only requires that they increase. So it’s perfectly fine to go from version 1.2.3 to 4.0.0. This is not necessarily a problem, but it is potentially confusing.

Do you need help with pvp.haskell.org?

Is the deployment process to pvp.haskell.org still functional or do you need help with that?

There is a PR that got merged in Jan 2018 that is not on pvp.haskell.org as of today (Nov 2022). So it seems pvp.haskell.org has not been updated for almost 5 years.

Revise deprecation clause

Current state

The 7th clause of the PVP currently states:

Deprecation. Deprecated entities (via a DEPRECATED pragma) SHOULD be
counted as removed for the purposes of upgrading the API, because packages that
use -Werror will be broken by the deprecation. In other words the new A.B
SHOULD be greater than the previous A.B.

Quoting RFC2119:

SHOULD This word, or the adjective "RECOMMENDED", mean that there
may exist valid reasons in particular circumstances to ignore a
particular item, but the full implications must be understood and
carefully weighed before choosing a different course.

Proposed change

Requiring a major version increment for deprecation seems overkill, as nowadays having -Werror enabled by default is rejected on Hackage uploads. Thereby the original rationale makes little sense, as a new deprecation annotation/warning does not cause compilation break, nor does it represent a semantic change. Hence a major version signal is not sensible.

"Recommend" only a minor version bump (so that the deprecation warning can be acted upon guarded by MIN_VERSION_foo(a,b,c) macros, which can only access the major+minor version components.

Suggested rewording:

Deprecation. Deprecated entities (via a DEPRECATED pragma) SHOULD be counted as a non-breaking change. In other words, the new A.B MAY remain the same but the new C SHOULD be greater than the old C.

History

This clause was added back in 2011-06-28 and the change comment says merely

discussion on #haskell suggested deprecation = removal

Going through the IRC logs, the conversation that lead to that change took place earlier on the same day (UTC+2 timestamps):

02:34 < benmachine> so guys, if I'm deprecating things in a module, what effect should that have on the version number of the next release?
02:35 < edwardk> removed functionality should cause the major version # to bump
02:35 < benmachine> sure, but it's not removed
02:36 < edwardk> the major version # are the x & y out of x.y.z.w
02:36 < benmachine> it's still there, but it just spits a warning if you use it
02:36 < dankna> I think deprecation ought to count as removal for that purpose, but I have no idea what the standard says
02:36 < benmachine> I mean deprecating as in the pragam
02:36 < identity_> what about minor API change?
02:36 < identity_> e.g. not backwards compatible change
02:36 < benmachine> dankna: the PVP page on the wiki doesn't know about the deprecate pragma
02:36 < dankna> I see
02:36 < tommd> benmachine: I don't think the PVP says anything, but I'd bump the C (in A.B.C.D versions)
02:36 < edwardk> i'd probably still bump the major version. the reason being that -Wall will crap out
02:36 < benmachine> edwardk: you mean -Werror?
02:37 < edwardk> and virtually everyone who builds on cabal builds with -Werror or -Wall
02:37 < edwardk> yeah
02:37 < aavogt> people widely use -Werror?
02:37 < benmachine> ho hum
02:37 < benmachine> I don't
02:37  * identity_ doesn't
02:37  * identity_ didn't know
02:37 < tommd> That's news to me
02:37 < edwardk> i use -Wall everywhere at least
02:37 < gfarfl> iwtu: actually, since Map.insert takes a key and a value, you need to change that to (uncurry insert (key,value))
02:37 < benmachine> enough people do that I'm inclined to agree with edwardk
02:38 < benmachine> which means I just screwed up :P oh well
02:38 < benmachine> (I released 0.4.0.1 with two things deprecated)
02:38 < benmachine> over 0.4
02:38 < benmachine> so auxiliary question: can I get a version "unreleased"?
02:39 < benmachine> almost certainly overkill in this case
02:39 < benmachine> but would be nice to know

(non-enforced) section on prerelease versions?

Since PVP doesn't have explicit support for prereleases, there are a few techniques that have been practiced in the community, e.g. GHC:

This allows PVP compliant update once the real 9.4.1 is released.

I don't think the spec should enforce any of that, but what if we provide this as a non-enforced practice recommendation.

VSCode has something similar, where major.EVEN_NUMBER.patch marks a release and major.ODD_NUMBER.patch marks a pre-release: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions

So in our terms, X.Y.EVEN_NUMBER.<date-number> is the pre-release for X.Y.ODD_NUMBER or somesuch?

Trying to reach community consensus by focusing on the things that are uncontroversial

There have been a lot of controversies over the PVP in the past and at least from my perspective the PVP does not represent community consensus.

What prevented me in the past from giving any further input on the PVP is that some of the things it specifies are controversial in nature and people can have different valid opinions about specific aspects. Which gave me a feeling of "unresolvable, nothing to achieve here".

But how about we focus, instead of trying to resolve this controversies, on the things were we do have actual community consensus and remove the controversial parts?

Yes, it would be less detailed, yes it would leave things unspecified; BUT, wouldn't that still be better than some document that effectively splits the community?

With any given controversy it is often possible to reach consensus by taking a step back and focusing on the things were it's easy to agree on.

Worst case scenario: Nothing changes.
Best case scenario: We make the PVP something that helps us foster a sense of community.

## Overview

Overview

Ownership

Owner:

Note: Project owner responsibilities include coordinating all the different people that need to contribute to the project, defining specs and goals, communicating project progress, being the point person for decisions, QA, documentation, and a plan for ongoing maintenance. If the project owner can't continue, they need to hand the role over to another person explicitly.

Prioritization

Impact

Alignment

Effort

Measuring success

Next steps

Templates

Feel free to use the following text as templates for further work on this issue:

Further Prioritization
- How does this serve our mission
- Will this help to make us financially sustainable within a year?
- Can this make us move faster?
- Does this increase quality? 
- Does this add technical debt?
- Is it feasible to build and maintain with our current team and runway?
- What insight or research is this project based on?
- Is this problem already solved elsewhere?
- How will we measure our success? Do we have a baseline today that we can compare this against?
- How well do we understand the complexity of the problem, feature to be added or user need to be addressed? 
- What is the estimated timeframe for delivering this project?
Roadmap
## Roadmap & Timeframe   
- Scoping
- Prototyping/validating
- Development
- Release prep (documentation, marketing etc.)
- Release
- Retrospective
Success Metrics
## Measuring success 
Measure: What is the measure?
Current baseline: What is the baseline that we see today?
Target: how do we want to see that move?
Link: link to query on Metabase
Risks
## Risks
If this project is going to go wrong, what's the most likely cause and what can you do to derisk upfront?
- Risk:
- Mitigation:
Documentation & Release
## Documentation & Release Brief
**Estimated delivery date: **
- Which aspect(s) of the platform does this change impact?
- Which group(s) of users will benefit from this change?
- Is this project in response to a user-requested feature? If so who requested it (link to feature request if available)?
- Is this project related to any similar work we have done, or are planning on doing in the next three months?
Next Steps
## Next up
- Create a project board, add this issue to the description
- Open up any issues to de-compartmentalize the project, add them to the project board
- Open a documentation issue, add it to the documentation project board, reference this issue. 
above 
Closing this Issue
- Close all relevant project issues
- Open up issues for improvements 
- Post comm's according to comm's plan
- Post project screen-cast on #documentation slack channel 
- Post project closure in #newsletter slack channel 
- Celebrate!! 

Originally posted by @DUK3135 in https://github.com/opencollective/opencollective/issues/4862

What is the goal of the PVP?

It's not clear to me what the design of the PVP is trying to do. The Rationale section is suggestive, but not quite clear to me. In particular, I can see two possible goals:

  1. Keep packages compiling. The goal of the PVP is to identify which changes can cause downstream packages to no longer compile, and to signal those through appropriate version changes.
  2. Keep packages working. The goal of the PVP is to identify which changes can cause downstream packages to no longer work in some broader sense, and to signal those through appropriate version changes.

The reason why it would be helpful to make this explicit is that it makes it easier to work out what is following the "spirit of the PVP" in cases where the text might not be fully clear or cover a corner case.

The reason this came up was the discussion about warnings for missing methods. Schematically, this is a situation where:

  1. A user makes a change C to their library
  2. C can make downstream code behave in a way we might consider "broken", but will not stop it from compiling. GHC will, however, emit a warning.
  3. The proposal is to change the warning into an error.

Now, if our goal is goal 1, then this proposal changes what is a breaking change. Previously change C would not prevent downstream code from compiling, but now it will do. On the other hand, if our goal is goal 2, then arguably this was always a breaking change, since it can "break" downstream code.

But it's very hard to adjudicate such cases unless we know what the goal of the PVP is.

[RFC] Make PVP ecosystem/language agnostic

Following discussion at #51 and https://discourse.haskell.org/t/the-operator-in-cabal-files/6938/59?u=hasufell I'm starting to question why the specification mentions:

  1. Haskell (language)
  2. Cabal and hackage (ecosystem)

I feel it's odd that a pacakge version policy talks about ecosystem effects, build tools and package serving backends.

That makes it a poor spec, in my opinion. The core of it should deal only with versioning and be very abstract about what constitutes public/visible API.

There have been multiple discussions that have constantly creeped into PVP:

  1. how to deal with upper bounds
  2. what exactly is API (this line gets blurry with some advanced GHC extensions etc)

I think part 1 is up to hackage and the ecosystem to decide. They can use whatever superset of the spec they want, describe upper bound policies, tooling and so on.

Part 2 is up to GHC HQ/SC to decide. The PVP maintainers shouldn't make those decisions.


As such, I propose to make PVP fully language and ecosystem agnostic.

Version the PVP spec itself?

The PVP spec is not versioned. That makes to hard to talk about precisely at any given point in time. I usually link to a specific commit on GitHub to make sure that the content doesn't change, but that's a workaround at best. It would be nice to be able to point to pvp.haskell.org/1.2.3 for a consistent view of the spec.

By comparison, SemVer is versioned and went through a breaking change a while ago: http://semver.org/spec/v2.0.0.html

I bring this up because as the PVP spec changes (like with #18), it's important to be able to talk about things before and after the change in a precise way.

Convention for (optional) annotation of non-inferable major version increments

Augmented Compatibility Annotation Scheme (version 2; WIP)

The Problem

A significant obstacle to being able to automate relaxation of PVP-style upper bounds is that major version increments conflate/overlay two different signals into a single one.

A related problem is that while version numbers are almost always properly assigned according to the PVP rules, mistakes can still happen.

Signal conflation in major version increment signalling

There are two categories of breaking changes that get signaled by the same mechanism:

  1. Breaking changes that can be detected/inferred statically by the exposed API type signature
  2. Breaking changes that can not be detected/inferred statically by the exposed API type signature

Major version increments that fall into the first category have the important property:

  • Given a package which is known to be correct,
    when updating its dependee package to the subsequent new major version (of cat. 1), and it still compiles (in pedantic mode), its preexisting correctness is preserved.

IOW, in this case, we can infer statically and automatically that an upper bound relaxation to the next major version is indeed safe.

These harmless first category major version increments are likely the most frequent ones occurring on Hackage.

For the less frequent second category, however, we don't have any such properties. Even unittests won't give enough confidence (except for trivial enough cases where it's possible to encode all semantic properties a package relies on; but it's unrealistic to assume that authors will invest the required effort and discipline).

The ability to signal this kind of second category is however extremely important for our ecosystem, as otherwise we'd be unable to fix or improve our APIs at the semantic level, and would have to introduce new names each time we want to change or introduce new semantics for existing entities. This tradeoff is obviously undesirable.

In other words, the second category of major version increments is the one which cannot be handled automatically and therefore needs to be detected to avoid allowing incorrect package-build-plans which would be hard to detect and even harder to fix, especially if a massive automation had whitelisted them already.

Misattribution of version increment signalling

As aforementioned, the other problem relevant to automating management of version constraints of dependency are the (infrequently) occurring mistakes in version assignment. We should improve the tooling to help with reducing the risk of mistakes during version assignment; but there will always remain an element of risk.

Scenarios that can occur (listed in order of increasing danger):

  • false positive: Signalling an incompatible change when there isn't one
    • e.g. text-1.0 signalled breaking compat even though there wasn't.
    • Other examples include packages which follow SemVer rules for >=1.0.0 (SemVer minor-ver-increments are seen as major-ver-increments in PVP)
  • false negative: Failing to signal a backward incompatible change
    • cat. 1: e.g. Cabal-1.24.1 accidentally introduced a backward incompatible typesignature change (i.e. cat. 1) (reverted in Cabal-1.24.2; unfortunately, Stackage LTS had already picked up the broken 1.24.1 release)
    • cat. 2: e.g. directory-1.2.3 inadvertently introduced a semantic change (i.e. cat. 2) which went undetected for long time as it shipped with GHC 8.0.1 (and got reverted in directory-1.3; to be shipped with GHC 8.0.2)
    • Other examples include packages which follow SemVer (non-)rules for 0.y.z versions ("Anything may change at any time. The public API should not be considered stable.")

A different scenario (and actually not a case of misattribution) is when a package does a backward incompatible change, and signals that properly, but then reverts back to the original semantics (while again signalling this properly). This happened for aeson-0.9.* -> aeson-0.10.* -> aeson-0.11.*. So aeson-0.9 and aeson-0.11 are semantically compatible to each other.

The Solution

In order to address the problems stated, we need to

  1. detect whether a major version increment is purely of the first category,
  2. annotate the compatibility relation to non-directly-preceding major versions, and
  3. be able to override PVP compatibility semantics retroactively to correct mistakes in the meta-data.

To this end, we can simply introduce a new field for .cabal files which can be interpreted by the build-bot infrastructure:

x-compatibility: <version-lhs> <compat-relation> <version-rhs>

By simply express this meta-data inside .cabal files, we can leverage the existing Hackage .cabal revision mechanism to add missing annotations retroactively, or to fix incorrectly annotated major versions.
Moreover, we make the standard case simple (i.e. no annotations are needed when the PVP has been applied correctly), and we make it possible to handle exceptional cases (annotations can be added in case of mistakes or deliberate deviations from the PVP).

The valid <compat-relation>s are

  • compatible-with
  • incompatible-with
  • semantically-incompatible-with

version-rhs needs to refer to a single version (e.g. 1.2.3) or a version-constraint expression (e.g. >= 1.2 && < 1.3 or == 1.2.*); it must always refer to versions prior to the current package version.

version-lhs needs to be the same version as specified in the version: field. This is a safe-guard against leaving a stale compatibility annotation in your .cabal file, as each time you update the package version:, you'll get an error if you forgot to remove (or update) any preexisting x-compatibility: annotation.

By default, the following implicit x-compatibility annotations are inferred from the version: field (NB: obviously, for single-part version numbers, i.e. only A, a slightly different implicit default is constructed):

version: A.B.C.Ds
x-compatibility: A.B.C.Ds semantically-incompatible-with <A.B
x-compatibility: A.B.C.Ds incompatible-with >=A.B && <A.B.C

TODO define shadowing/combining/overriding rules in case of conflicting annotations

TODO reconsider whether we can reduce the power of <version-rhs> by allowing only single versions

TODO There doesn't seem to be any use-case for compatible-with; after all, the only reason we need to differentiate between major/minor version incs is for the purpose of efficiently inferring PVP-style upper bounds; but from the POV of empiric testing we only need to differentiate between semantic/non-semantic changes

Examples

directory

Annotations are only needed in directory-1.2.3,

name: directory
version: 1.2.3
-- retroactively revised via .cabal edit
x-compatibility: 1.2.3 semantically-incompatible-with <1.2.3

as well as directory-1.3.0 (releases inbetween don't need any annotation, as the property is transitive)

name: directory
version: 1.3.0
-- annotation included in 1.3.0 release (assuming `x-compatibility` was already known then)
x-compatibility: 1.3.0 incompatible-with >=1.2 && <1.2.3

aeson

Under the assumption that aeson-0.9 and aeson-0.11 have only a cat.1 incompatibility:

name: aeson
version: 0.11.0.0
x-compatibility: 0.11.0.0 incompatible-with ==0.9.*

time

We just need to reduce the severity of the major version increment that occured with 1.0.0 which was done mostly for marketing text as mature/stable, than for actual breaking changes.

name: text
version: 1.0.0
x-compatibility: 1.0.0 incompatible-with ==0.12.*

Cabal

The breaking change in 1.24.1 was a cat.1 one; we only need two annotations, one in

name: Cabal
version: 1.24.1.0
-- retroactively revised via .cabal edit
x-compatibility: 1.24.1.0 incompatible-with ==1.24.0.*

and another one in

name: Cabal
version: 1.24.2.0
x-compatibility: 1.24.2.0 incompatible-with ==1.24.1.*

Augmented Compatibility Annotation Scheme (version 1; superseeded)

The Problem

A significant obstacle to being able to automate relaxation of PVP-style upper bounds is that major version increments conflate/overlay two different signals into a single one.

There are two categories of breaking changes that get signaled by the same mechanism:

  1. Breaking changes that can be detected/inferred statically by the exposed API type signature
  2. Breaking changes that can not be detected/inferred statically by the exposed API type signature

Major version increments that fall into the first category have the important property:

  • Given a package which is known to be correct,
    when updating its dependee package to the subsequent new major version (of cat. 1), and it still compiles (in pedantic mode), its preexisting correctness is preserved.

IOW, in this case, we can infer statically and automatically that an upper bound relaxation to the next major version is indeed safe.

These harmless first category major version increments are likely the most frequent ones occuring on Hackage.

For the less frequent second category, however, we don't have any such properties. Even unittests won't give enough confidence (except for trivial enough cases where it's possible to encode all semantic properties a package relies on; but it's unrealistic to assume that authors will invest the required effort and discipline).

The ability to signal this kind of second category is however extremely important for our ecosystem, as otherwise we'd be unable to fix or improve our APIs at the semantic level, and would have to introduce new names each time we want to change or introduce new semantics for existing entities. This tradeoff is obviously undesirable.

In other words, the second category of major version increments is the one which cannot be handled automatically and therefore needs to be detected to avoid allowing incorrect package-build-plans which would be hard to detect and even harder to fix, especially if a massive automation had whitelisted them already.

The Solution

Consequently, we need a way to detect whether the major version increment is purely of the first category.
All we need is literally a single bit of additional information, besides the major version increment.

To this end, we introduce a new convention, by defining a new optional annotation for .cabal files:

x-major-version-kind: <version> <kind>

where currently only the safe token is defined for <kind>.

Without any such annotation recorded for the current major version series we have no knowledge and therefore have to assume pessimistically that the current major version was a result of a category 2 major version increment. This default is also safe-guard, as otherwise we'd risk to err towards incorrectness which is hard to detect, which is what we want to avoid in the first place.

An example would be:

name: foobar
version: 1.2.0
x-major-version-kind: 1.2.0 safe

The version number must match the one declared in the version: field.
This is a safe-guard against leaving a stale x-major-version-kind: annotation in your .cabal file, as each time you update the package version:, you'll get an error if you forgot to remove (or update) any preexisting x-major-version-kind: annotation.

Moreover, since we simply express this meta-data inside .cabal files, we can leverage the existing Hackage .cabal revision mechanism to add missing annotations retroactively, or to fix incorrectly annotated major versions.

Decision tree diagram recommends misleading version numbers

When a major version bump is necessary, the decision tree diagram recommends
A . B . C -> A . (B+1) . C or (A+1) . B . C

To me it would be more sensible to recommend something like

A . B. C -> A . (B+1) . C' or (A+1) . B' . C'

since typically (although not necessarily) the lower version components will change to become zero, rather than staying the same as the former implies.

Is it PVP conform if a package leaks orphan instances or not depending on the versions of its transitive dependencies?

Somewhat related to #23, let's look at the following scenario:

  • package foo-0.1.0 adheres to the PVP
  • an new version of foo adds orphan instances and adheres to the PVP by bumping it's version to 0.2.0
  • package bar depends on foo

Technically, bar could allow both foo-0.1.0 and foo-0.2.0, but that would mean that bar would sometimes leak orphan instances and sometimes not depending on what version of foo the dependency solver picks. For this reason my naive assumption was that this is not PVP conform.

However, I realized that the PVP does not really state that this is not allowed.

If again, however, we look at the FAQ, then we read:

It’s the responsibility of the provider to guarantee that the exposed API is a function of the declared version, and in particular does not depend on the versions of its transitive dependencies, as this defy the purpose of the PVP.

Does this statement apply to orphan instances?

Or in other words, for the scenario given above: Is the constraint foo >= 0.1.0 && < 0.3.0 PVP conform?

Versioning policy for Internal modules

It's been a common practice in the Haskell community to put internal functions to Internal modules, which are not meant for a public API but for a semi-private API that can be useful to access library internals to extend the API. I think it's natural to relax the versioning policy for changes in those Internal modules. Should we mention it somehow in the PVP?

FAQ should mention x-uncurated

According to http://hackage.haskell.org/upload

"Packages which are uncurated have no expectations on them regarding versioning policy."

Yet the PVP FAQ says

"Yes, packages uploaded to Hackage (which is a pre-requisite for being included in Stackage) are expected to honor the PVP."

Since only curated packages must honor the PVP, the PVP FAQ is inaccurate. I suggest editing the "My package is in Stackage" answer - perhaps something like "The PVP requires that you specify all version bounds. If you do not wish to adhere to this aspect of the PVP, your package is not PVP compliant and you must indicate this by setting the x-curation field in your .cabal file to uncurated."

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.