optparse-applicative Compatibility
As described in the ansi-wl-pprint is
Deprecated!?!? blog entry, the ansi-wl-pprint
library has been deprecated in favor of the prettyprinter
library since November 2020. This month, the ansi-wl-pprint
maintainers released a new version (1.0.2)
that changes the implementation to the prettyprinter
package, using the prettyprinter-compat-ansi-wl-pprint
compatibility package. This prompted the maintainers of optparse-applicative
to release two new versions. Version 0.17.1.0
adds support for that new version of ansi-wl-pprint
, and
version 0.18.0.0
switches to prettyprinter
. This blog entry is about
updating projects to be compatible with these changes.
The optparse-applicative
API uses
ansi-wl-pprint
or prettyprinter
to format
--help
text. For example, the headerDoc
and footerDoc
functions can be used to display formatted content. Software that does
not make use of such functionality are easy to update, while software
that does must use either ansi-wl-pprint
or prettyprinter
, depending on the version of
optparse-applicative
being used.
Cabal supports this by using configuration like the following.
flag optparse-applicative_ge_0_18
description: Use optparse-applicative 0.18 or newer
default: False
manual: False
executable ...
if flag(optparse-applicative_ge_0_18)
build-depends:
optparse-applicative >=0.18 && <0.19
, prettyprinter >=1.7.1 && <1.8
else
build-depends:
ansi-wl-pprint >=0.6.8 && <1.1
, optparse-applicative >=0.13 && <0.18
Flag optparse-applicative_ge_0_18
is configured with
manual: False
, and Cabal automatically tries
inverting the value to find a package set that satisfies the
constraints.
The application source must of course be modified to use CPP
so that imports and API usage match the libraries that are being used
for compilation. Note that since ansi-wl-pprint
version
1.0.2
uses prettyprinter
, it is possible that
both packages will be available. I think that using
optparse-applicative
conditionals like the following is
clearer than using ansi-wl-pprint
conditionals, but either
would work.
-- https://hackage.haskell.org/package/ansi-wl-pprint
#if !MIN_VERSION_optparse_applicative (0,18,0)
import qualified Text.PrettyPrint.ANSI.Leijen as Doc
import Text.PrettyPrint.ANSI.Leijen (Doc)
#endif
-- https://hackage.haskell.org/package/optparse-applicative
#if MIN_VERSION_optparse_applicative (0,18,0)
import Options.Applicative.Help.Pretty (Doc)
#endif
-- https://hackage.haskell.org/package/prettyprinter
#if MIN_VERSION_optparse_applicative (0,18,0)
import qualified Prettyprinter as Doc
#endif
The prettyprinter
Doc
type has a type parameter that is specified in the
optparse-applicative
Doc
type alias. There a number of differences in the API, so I defined a
number of compatibility functions like the following.
(<$$>) :: Doc -> Doc -> Doc
#if MIN_VERSION_optparse_applicative (0,18,0)
<$$> r = l <> Doc.line <> r
l #else
<$$>) = (Doc.<$$>)
(#endif
Unfortunately, Stack does
not automatically try inverting such flag values. One instead has to
manually configure the flag to match the versions of the
dependencies in the snapshot being used. Flags are configured per
package in the package-flags
configuration in the Stackage build-constraints.yaml
configuration file. Since these flags are set unconditionally, timing is
significant: the flag must be set when the nightly
snapshot
switches to optparse-applicative
version
0.18.0.0
. Note that this is why the above flag must be
false by default.
What about automated tests? My projects have separate
stack.yaml
files for each test, and the flag can be set
appropriately within those configuration files. For example, a bm
test that uses the new version is configured as follows.
flags:
bm:
optparse-applicative_ge_0_18: true
This works well, but there is a complication. Flags configured in a
stack.yaml
file are ignored when a flag for the project is
specified on the command line. I have some projects where examples are
only built when they are requested by setting a flag. For now, I have
simply disabled examples that cause problems. In the future, I may
update project configurations to resolve this issue. For example, a
script could parse the stack.yaml
file and set the flag on
the command line.