Skip to main content

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)
l <$$> r = l <> Doc.line <> r
#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.

Author

Travis Cardwell

Published

Tags