Summary
I recently needed to update a library to support building with Cabal 3.14.0.0. This was more difficult than I hoped. I think listing the exact (breaking) API changes in a single changelog document would have made it easier for me to do this. Should all major libraries adopt such a policy?
Ideal workflow
When a package I depend on makes a breaking change my ideal workflow would be:
- read the changelogs to see if there are any changes that could silently break code
- bump the version bounds try to build
- if there are errors, search changelogs for key words from the error
- read about the change
- follow recommended migration strategies if possible or figure out something by myself
This workflow relies critically on being able to find the changelog entry that corresponds to any breaking API change.
Do you think this is a reasonable workflow?
A case study of the status quo
This issue is motivated by my attempt at updating the uuagc package to support newer versions of the Cabal package.
The first step I took was relax the upper bound on Cabal from < 3.11 to < 3.15 and built with --constraint="Cabal == 3.14.*" which resulted in these errors:
src/Distribution/Simple/UUAGC/UUAGC.hs:60:50: error: [GHC-61689]
Module ‘Distribution.Utils.Path’ does not export ‘PackageDir’.
|
60 | import Distribution.Utils.Path (getSymbolicPath, PackageDir, SourceDir, SymbolicPath)
| ^^^^^^^^^^
src/Distribution/Simple/UUAGC/UUAGC.hs:60:62: error: [GHC-61689]
Module ‘Distribution.Utils.Path’ does not export ‘SourceDir’.
|
60 | import Distribution.Utils.Path (getSymbolicPath, PackageDir, SourceDir, SymbolicPath)
| ^^^^^^^^^
So PackageDir and SourceDir got removed in 3.12 or 3.14. I looked in the changelog to see what changed, but immediately ran into the fact that Cabal’s changelog just links to separate markdown documents for each version, that’s a bit inconvenient but I could to open both and search for PackageDir. Sadly that results in no hits.
I had to fall back on opening both versions on Hackage to find out 3.12 did still have PackageDir and it was dropped in 3.14. At this point I also noticed that Pkg and Source were introduced at the same time and they did (almost) fit in the same place where PackageDir and SourceDir used to be. Finally, the buildDir function also changed slightly and had to search around for something reasonable that would fit the types. In the end, I’m not 100% sure I did the right thing.
Further compilation revealed two new errors related to a different change:
src/Distribution/Simple/UUAGC/UUAGC.hs:109:52: error: [8;;https://errors.haskell.org/messages/GHC-83865GHC-838658;;]
• Couldn't match type ‘[Char]’ with ‘Suffix’
Expected: Suffix
Actual: String
• In the expression: "ag"
In the first argument of ‘(:)’, namely ‘("ag", ag)’
In the expression: ("ag", ag) : ("lag", ag) : knownSuffixHandlers
|
109 | hooks = simpleUserHooks { hookedPreProcessors = ("ag", ag):("lag",ag):knownSuffixHandlers
| ^^^^
So some new Suffix type has been introduced somewhere in 3.12 or 3.14. Again the changelogs do not mention Suffix at all.
The only other way was to open both versions on Hackage and search for Suffix in their API directly. That showed me that Distribution.Simple.PreProcess.Suffix was introduced in 3.12.
Now to figure out what actually changed I looked over the changelog for 3.12 again, now with the added knowledge of it being part of the preprocessing mechanisms, but I still cannot find it.
At this point I gave up trying to figure out what had changed and just looked at the Haddock which said Suffix is just a newtype over String and that it had an IsString instance, so I just slapped a OverloadedStrings on the Simple.UUAGC.UUAGC module and that fixed the error. I want to stress that this is me just blindly applying a change until the typechecker accepts it, which is bad practice!
In fact, I just discovered that someone has written a description of this change even mentioning this is a breaking change for some users, but I think the only way to find it is to view the git blame of the line in Distribution.Simple.PreProcess.