{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}

-- | Functions to calculate nix-style hashes for package ids.
--
-- The basic idea is simple, hash the combination of:
--
--   * the package tarball
--   * the ids of all the direct dependencies
--   * other local configuration (flags, profiling, etc)
--
-- See 'PackageHashInputs' for a detailed list of what determines the hash.
module Distribution.Client.PackageHash
  ( -- * Calculating package hashes
    PackageHashInputs (..)
  , PackageHashConfigInputs (..)
  , PackageSourceHash
  , hashedInstalledPackageId
  , hashPackageHashInputs
  , renderPackageHashInputs

    -- ** Platform-specific variations
  , hashedInstalledPackageIdLong
  , hashedInstalledPackageIdShort
  ) where

import Distribution.Client.Compat.Prelude
import Prelude ()

import Distribution.Client.HashValue
import Distribution.Client.Types
  ( InstalledPackageId
  )
import Distribution.Package
  ( PackageId
  , PackageIdentifier (..)
  , PkgconfigName
  , mkComponentId
  )
import Distribution.Simple.Compiler
  ( AbiTag (..)
  , CompilerId
  , DebugInfoLevel (..)
  , OptimisationLevel (..)
  , PackageDB
  , ProfDetailLevel (..)
  , showProfDetailLevel
  )
import Distribution.Simple.InstallDirs
  ( PathTemplate
  , fromPathTemplate
  )
import qualified Distribution.Solver.Types.ComponentDeps as CD
import Distribution.System
  ( OS (OSX, Windows)
  , Platform
  , buildOS
  )
import Distribution.Types.Flag
  ( FlagAssignment
  , showFlagAssignment
  )
import Distribution.Types.PkgconfigVersion (PkgconfigVersion)

import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.Map as Map
import qualified Data.Set as Set

-------------------------------
-- Calculating package hashes
--

-- | Calculate a 'InstalledPackageId' for a package using our nix-style
-- inputs hashing method.
--
-- Note that due to path length limitations on Windows, this function uses
-- a different method on Windows that produces shorted package ids.
-- See 'hashedInstalledPackageIdLong' vs 'hashedInstalledPackageIdShort'.
hashedInstalledPackageId :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageId :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageId
  | OS
buildOS OS -> OS -> Bool
forall a. Eq a => a -> a -> Bool
== OS
Windows = PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdShort
  | OS
buildOS OS -> OS -> Bool
forall a. Eq a => a -> a -> Bool
== OS
OSX = PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdVeryShort
  | Bool
otherwise = PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdLong

-- | Calculate a 'InstalledPackageId' for a package using our nix-style
-- inputs hashing method.
--
-- This produces large ids with big hashes. It is only suitable for systems
-- without significant path length limitations (ie not Windows).
hashedInstalledPackageIdLong :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdLong :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdLong
  pkghashinputs :: PackageHashInputs
pkghashinputs@PackageHashInputs{PackageId
pkgHashPkgId :: PackageId
pkgHashPkgId :: PackageHashInputs -> PackageId
pkgHashPkgId, Maybe Component
pkgHashComponent :: Maybe Component
pkgHashComponent :: PackageHashInputs -> Maybe Component
pkgHashComponent} =
    String -> InstalledPackageId
mkComponentId (String -> InstalledPackageId) -> String -> InstalledPackageId
forall a b. (a -> b) -> a -> b
$
      PackageId -> String
forall a. Pretty a => a -> String
prettyShow PackageId
pkgHashPkgId -- to be a bit user friendly
        String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> (Component -> String) -> Maybe Component -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" Component -> String
displayComponent Maybe Component
pkgHashComponent
        String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-"
        String -> String -> String
forall a. [a] -> [a] -> [a]
++ HashValue -> String
showHashValue (PackageHashInputs -> HashValue
hashPackageHashInputs PackageHashInputs
pkghashinputs)
    where
      displayComponent :: CD.Component -> String
      displayComponent :: Component -> String
displayComponent Component
CD.ComponentLib = String
""
      displayComponent (CD.ComponentSubLib UnqualComponentName
s) = String
"-l-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ UnqualComponentName -> String
forall a. Pretty a => a -> String
prettyShow UnqualComponentName
s
      displayComponent (CD.ComponentFLib UnqualComponentName
s) = String
"-f-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ UnqualComponentName -> String
forall a. Pretty a => a -> String
prettyShow UnqualComponentName
s
      displayComponent (CD.ComponentExe UnqualComponentName
s) = String
"-e-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ UnqualComponentName -> String
forall a. Pretty a => a -> String
prettyShow UnqualComponentName
s
      displayComponent (CD.ComponentTest UnqualComponentName
s) = String
"-t-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ UnqualComponentName -> String
forall a. Pretty a => a -> String
prettyShow UnqualComponentName
s
      displayComponent (CD.ComponentBench UnqualComponentName
s) = String
"-b-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ UnqualComponentName -> String
forall a. Pretty a => a -> String
prettyShow UnqualComponentName
s
      displayComponent Component
CD.ComponentSetup = String
"-setup"

-- | On Windows we have serious problems with path lengths. Windows imposes a
-- maximum path length of 260 chars, and even if we can use the windows long
-- path APIs ourselves, we cannot guarantee that ghc, gcc, ld, ar, etc etc all
-- do so too.
--
-- So our only choice is to limit the lengths of the paths, and the only real
-- way to do that is to limit the size of the 'InstalledPackageId's that we
-- generate. We do this by truncating the package names and versions and also
-- by truncating the hash sizes.
--
-- Truncating the package names and versions is technically ok because they are
-- just included for human convenience, the full source package id is included
-- in the hash.
--
-- Truncating the hash size is disappointing but also technically ok. We
-- rely on the hash primarily for collision avoidance not for any security
-- properties (at least for now).
hashedInstalledPackageIdShort :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdShort :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdShort pkghashinputs :: PackageHashInputs
pkghashinputs@PackageHashInputs{PackageId
pkgHashPkgId :: PackageHashInputs -> PackageId
pkgHashPkgId :: PackageId
pkgHashPkgId} =
  String -> InstalledPackageId
mkComponentId (String -> InstalledPackageId) -> String -> InstalledPackageId
forall a b. (a -> b) -> a -> b
$
    String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate
      String
"-"
      -- max length now 64
      [ Int -> String -> String
truncateStr Int
14 (PackageName -> String
forall a. Pretty a => a -> String
prettyShow PackageName
name)
      , Int -> String -> String
truncateStr Int
8 (Version -> String
forall a. Pretty a => a -> String
prettyShow Version
version)
      , HashValue -> String
showHashValue (Int -> HashValue -> HashValue
truncateHash Int
20 (PackageHashInputs -> HashValue
hashPackageHashInputs PackageHashInputs
pkghashinputs))
      ]
  where
    PackageIdentifier PackageName
name Version
version = PackageId
pkgHashPkgId

    -- Truncate a string, with a visual indication that it is truncated.
    truncateStr :: Int -> String -> String
truncateStr Int
n String
s
      | String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
n = String
s
      | Bool
otherwise = Int -> String -> String
forall a. Int -> [a] -> [a]
take (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"_"

-- | On macOS we shorten the name very aggressively.  The mach-o linker on
-- macOS has a limited load command size, to which the name of the library
-- as well as its relative path (\@rpath) entry count.  To circumvent this,
-- on macOS the libraries are not stored as
--  @store/<libraryname>/libHS<libraryname>.dylib@
-- where libraryname contains the libraries name, version and abi hash, but in
--  @store/lib/libHS<very short libraryname>.dylib@
-- where the very short library name drops all vowels from the package name,
-- and truncates the hash to 4 bytes.
--
-- We therefore we only need one \@rpath entry to @store/lib@ instead of one
-- \@rpath entry for each library. And the reduced library name saves some
-- additional space.
--
-- This however has two major drawbacks:
-- 1) Packages can collide more easily due to the shortened hash.
-- 2) The libraries are *not* prefix relocatable anymore as they all end up
--    in the same @store/lib@ folder.
--
-- The ultimate solution would have to include generating proxy dynamic
-- libraries on macOS, such that the proxy libraries and the linked libraries
-- stay under the load command limit, and the recursive linker is still able
-- to link all of them.
hashedInstalledPackageIdVeryShort :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdVeryShort :: PackageHashInputs -> InstalledPackageId
hashedInstalledPackageIdVeryShort pkghashinputs :: PackageHashInputs
pkghashinputs@PackageHashInputs{PackageId
pkgHashPkgId :: PackageHashInputs -> PackageId
pkgHashPkgId :: PackageId
pkgHashPkgId} =
  String -> InstalledPackageId
mkComponentId (String -> InstalledPackageId) -> String -> InstalledPackageId
forall a b. (a -> b) -> a -> b
$
    String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate
      String
"-"
      [ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> String -> Bool) -> String -> Char -> Bool
forall a b c. (a -> b -> c) -> b -> a -> c
flip Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem String
"aeiou") (PackageName -> String
forall a. Pretty a => a -> String
prettyShow PackageName
name)
      , Version -> String
forall a. Pretty a => a -> String
prettyShow Version
version
      , HashValue -> String
showHashValue (Int -> HashValue -> HashValue
truncateHash Int
4 (PackageHashInputs -> HashValue
hashPackageHashInputs PackageHashInputs
pkghashinputs))
      ]
  where
    PackageIdentifier PackageName
name Version
version = PackageId
pkgHashPkgId

-- | All the information that contributes to a package's hash, and thus its
-- 'InstalledPackageId'.
data PackageHashInputs = PackageHashInputs
  { PackageHashInputs -> PackageId
pkgHashPkgId :: PackageId
  , PackageHashInputs -> Maybe Component
pkgHashComponent :: Maybe CD.Component
  , PackageHashInputs -> HashValue
pkgHashSourceHash :: PackageSourceHash
  , PackageHashInputs -> Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps :: Set (PkgconfigName, Maybe PkgconfigVersion)
  , PackageHashInputs -> Set InstalledPackageId
pkgHashDirectDeps :: Set InstalledPackageId
  , PackageHashInputs -> PackageHashConfigInputs
pkgHashOtherConfig :: PackageHashConfigInputs
  }

type PackageSourceHash = HashValue

-- | Those parts of the package configuration that contribute to the
-- package hash.
data PackageHashConfigInputs = PackageHashConfigInputs
  { PackageHashConfigInputs -> CompilerId
pkgHashCompilerId :: CompilerId
  , PackageHashConfigInputs -> AbiTag
pkgHashCompilerABI :: AbiTag
  , PackageHashConfigInputs -> Platform
pkgHashPlatform :: Platform
  , PackageHashConfigInputs -> FlagAssignment
pkgHashFlagAssignment :: FlagAssignment -- complete not partial
  , PackageHashConfigInputs -> [String]
pkgHashConfigureScriptArgs :: [String] -- just ./configure for build-type Configure
  , PackageHashConfigInputs -> Bool
pkgHashVanillaLib :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashSharedLib :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashDynExe :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashFullyStaticExe :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashGHCiLib :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashProfLib :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashProfExe :: Bool
  , PackageHashConfigInputs -> ProfDetailLevel
pkgHashProfLibDetail :: ProfDetailLevel
  , PackageHashConfigInputs -> ProfDetailLevel
pkgHashProfExeDetail :: ProfDetailLevel
  , PackageHashConfigInputs -> Bool
pkgHashCoverage :: Bool
  , PackageHashConfigInputs -> OptimisationLevel
pkgHashOptimization :: OptimisationLevel
  , PackageHashConfigInputs -> Bool
pkgHashSplitObjs :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashSplitSections :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashStripLibs :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashStripExes :: Bool
  , PackageHashConfigInputs -> DebugInfoLevel
pkgHashDebugInfo :: DebugInfoLevel
  , PackageHashConfigInputs -> Map String [String]
pkgHashProgramArgs :: Map String [String]
  , PackageHashConfigInputs -> [String]
pkgHashExtraLibDirs :: [FilePath]
  , PackageHashConfigInputs -> [String]
pkgHashExtraLibDirsStatic :: [FilePath]
  , PackageHashConfigInputs -> [String]
pkgHashExtraFrameworkDirs :: [FilePath]
  , PackageHashConfigInputs -> [String]
pkgHashExtraIncludeDirs :: [FilePath]
  , PackageHashConfigInputs -> Maybe PathTemplate
pkgHashProgPrefix :: Maybe PathTemplate
  , PackageHashConfigInputs -> Maybe PathTemplate
pkgHashProgSuffix :: Maybe PathTemplate
  , PackageHashConfigInputs -> [Maybe PackageDB]
pkgHashPackageDbs :: [Maybe PackageDB]
  , -- Haddock options
    PackageHashConfigInputs -> Bool
pkgHashDocumentation :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockHoogle :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockHtml :: Bool
  , PackageHashConfigInputs -> Maybe String
pkgHashHaddockHtmlLocation :: Maybe String
  , PackageHashConfigInputs -> Bool
pkgHashHaddockForeignLibs :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockExecutables :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockTestSuites :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockBenchmarks :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockInternal :: Bool
  , PackageHashConfigInputs -> Maybe String
pkgHashHaddockCss :: Maybe FilePath
  , PackageHashConfigInputs -> Bool
pkgHashHaddockLinkedSource :: Bool
  , PackageHashConfigInputs -> Bool
pkgHashHaddockQuickJump :: Bool
  , PackageHashConfigInputs -> Maybe PathTemplate
pkgHashHaddockContents :: Maybe PathTemplate
  , PackageHashConfigInputs -> Maybe PathTemplate
pkgHashHaddockIndex :: Maybe PathTemplate
  , PackageHashConfigInputs -> Maybe String
pkgHashHaddockBaseUrl :: Maybe String
  , PackageHashConfigInputs -> Maybe String
pkgHashHaddockLib :: Maybe String
  , PackageHashConfigInputs -> Maybe String
pkgHashHaddockOutputDir :: Maybe FilePath
  --     TODO: [required eventually] pkgHashToolsVersions     ?
  --     TODO: [required eventually] pkgHashToolsExtraOptions ?
  }
  deriving (Int -> PackageHashConfigInputs -> String -> String
[PackageHashConfigInputs] -> String -> String
PackageHashConfigInputs -> String
(Int -> PackageHashConfigInputs -> String -> String)
-> (PackageHashConfigInputs -> String)
-> ([PackageHashConfigInputs] -> String -> String)
-> Show PackageHashConfigInputs
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
$cshowsPrec :: Int -> PackageHashConfigInputs -> String -> String
showsPrec :: Int -> PackageHashConfigInputs -> String -> String
$cshow :: PackageHashConfigInputs -> String
show :: PackageHashConfigInputs -> String
$cshowList :: [PackageHashConfigInputs] -> String -> String
showList :: [PackageHashConfigInputs] -> String -> String
Show)

-- | Calculate the overall hash to be used for an 'InstalledPackageId'.
hashPackageHashInputs :: PackageHashInputs -> HashValue
hashPackageHashInputs :: PackageHashInputs -> HashValue
hashPackageHashInputs = ByteString -> HashValue
hashValue (ByteString -> HashValue)
-> (PackageHashInputs -> ByteString)
-> PackageHashInputs
-> HashValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageHashInputs -> ByteString
renderPackageHashInputs

-- | Render a textual representation of the 'PackageHashInputs'.
--
-- The 'hashValue' of this text is the overall package hash.
renderPackageHashInputs :: PackageHashInputs -> LBS.ByteString
renderPackageHashInputs :: PackageHashInputs -> ByteString
renderPackageHashInputs
  PackageHashInputs
    { PackageId
pkgHashPkgId :: PackageHashInputs -> PackageId
pkgHashPkgId :: PackageId
pkgHashPkgId
    , Maybe Component
pkgHashComponent :: PackageHashInputs -> Maybe Component
pkgHashComponent :: Maybe Component
pkgHashComponent
    , HashValue
pkgHashSourceHash :: PackageHashInputs -> HashValue
pkgHashSourceHash :: HashValue
pkgHashSourceHash
    , Set InstalledPackageId
pkgHashDirectDeps :: PackageHashInputs -> Set InstalledPackageId
pkgHashDirectDeps :: Set InstalledPackageId
pkgHashDirectDeps
    , Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps :: PackageHashInputs -> Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps :: Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps
    , pkgHashOtherConfig :: PackageHashInputs -> PackageHashConfigInputs
pkgHashOtherConfig =
      PackageHashConfigInputs{Bool
[String]
[Maybe PackageDB]
Maybe String
Maybe PathTemplate
ProfDetailLevel
DebugInfoLevel
OptimisationLevel
Map String [String]
AbiTag
CompilerId
Platform
FlagAssignment
pkgHashCompilerId :: PackageHashConfigInputs -> CompilerId
pkgHashCompilerABI :: PackageHashConfigInputs -> AbiTag
pkgHashPlatform :: PackageHashConfigInputs -> Platform
pkgHashFlagAssignment :: PackageHashConfigInputs -> FlagAssignment
pkgHashConfigureScriptArgs :: PackageHashConfigInputs -> [String]
pkgHashVanillaLib :: PackageHashConfigInputs -> Bool
pkgHashSharedLib :: PackageHashConfigInputs -> Bool
pkgHashDynExe :: PackageHashConfigInputs -> Bool
pkgHashFullyStaticExe :: PackageHashConfigInputs -> Bool
pkgHashGHCiLib :: PackageHashConfigInputs -> Bool
pkgHashProfLib :: PackageHashConfigInputs -> Bool
pkgHashProfExe :: PackageHashConfigInputs -> Bool
pkgHashProfLibDetail :: PackageHashConfigInputs -> ProfDetailLevel
pkgHashProfExeDetail :: PackageHashConfigInputs -> ProfDetailLevel
pkgHashCoverage :: PackageHashConfigInputs -> Bool
pkgHashOptimization :: PackageHashConfigInputs -> OptimisationLevel
pkgHashSplitObjs :: PackageHashConfigInputs -> Bool
pkgHashSplitSections :: PackageHashConfigInputs -> Bool
pkgHashStripLibs :: PackageHashConfigInputs -> Bool
pkgHashStripExes :: PackageHashConfigInputs -> Bool
pkgHashDebugInfo :: PackageHashConfigInputs -> DebugInfoLevel
pkgHashProgramArgs :: PackageHashConfigInputs -> Map String [String]
pkgHashExtraLibDirs :: PackageHashConfigInputs -> [String]
pkgHashExtraLibDirsStatic :: PackageHashConfigInputs -> [String]
pkgHashExtraFrameworkDirs :: PackageHashConfigInputs -> [String]
pkgHashExtraIncludeDirs :: PackageHashConfigInputs -> [String]
pkgHashProgPrefix :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashProgSuffix :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashPackageDbs :: PackageHashConfigInputs -> [Maybe PackageDB]
pkgHashDocumentation :: PackageHashConfigInputs -> Bool
pkgHashHaddockHoogle :: PackageHashConfigInputs -> Bool
pkgHashHaddockHtml :: PackageHashConfigInputs -> Bool
pkgHashHaddockHtmlLocation :: PackageHashConfigInputs -> Maybe String
pkgHashHaddockForeignLibs :: PackageHashConfigInputs -> Bool
pkgHashHaddockExecutables :: PackageHashConfigInputs -> Bool
pkgHashHaddockTestSuites :: PackageHashConfigInputs -> Bool
pkgHashHaddockBenchmarks :: PackageHashConfigInputs -> Bool
pkgHashHaddockInternal :: PackageHashConfigInputs -> Bool
pkgHashHaddockCss :: PackageHashConfigInputs -> Maybe String
pkgHashHaddockLinkedSource :: PackageHashConfigInputs -> Bool
pkgHashHaddockQuickJump :: PackageHashConfigInputs -> Bool
pkgHashHaddockContents :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashHaddockIndex :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashHaddockBaseUrl :: PackageHashConfigInputs -> Maybe String
pkgHashHaddockLib :: PackageHashConfigInputs -> Maybe String
pkgHashHaddockOutputDir :: PackageHashConfigInputs -> Maybe String
pkgHashCompilerId :: CompilerId
pkgHashCompilerABI :: AbiTag
pkgHashPlatform :: Platform
pkgHashFlagAssignment :: FlagAssignment
pkgHashConfigureScriptArgs :: [String]
pkgHashVanillaLib :: Bool
pkgHashSharedLib :: Bool
pkgHashDynExe :: Bool
pkgHashFullyStaticExe :: Bool
pkgHashGHCiLib :: Bool
pkgHashProfLib :: Bool
pkgHashProfExe :: Bool
pkgHashProfLibDetail :: ProfDetailLevel
pkgHashProfExeDetail :: ProfDetailLevel
pkgHashCoverage :: Bool
pkgHashOptimization :: OptimisationLevel
pkgHashSplitObjs :: Bool
pkgHashSplitSections :: Bool
pkgHashStripLibs :: Bool
pkgHashStripExes :: Bool
pkgHashDebugInfo :: DebugInfoLevel
pkgHashProgramArgs :: Map String [String]
pkgHashExtraLibDirs :: [String]
pkgHashExtraLibDirsStatic :: [String]
pkgHashExtraFrameworkDirs :: [String]
pkgHashExtraIncludeDirs :: [String]
pkgHashProgPrefix :: Maybe PathTemplate
pkgHashProgSuffix :: Maybe PathTemplate
pkgHashPackageDbs :: [Maybe PackageDB]
pkgHashDocumentation :: Bool
pkgHashHaddockHoogle :: Bool
pkgHashHaddockHtml :: Bool
pkgHashHaddockHtmlLocation :: Maybe String
pkgHashHaddockForeignLibs :: Bool
pkgHashHaddockExecutables :: Bool
pkgHashHaddockTestSuites :: Bool
pkgHashHaddockBenchmarks :: Bool
pkgHashHaddockInternal :: Bool
pkgHashHaddockCss :: Maybe String
pkgHashHaddockLinkedSource :: Bool
pkgHashHaddockQuickJump :: Bool
pkgHashHaddockContents :: Maybe PathTemplate
pkgHashHaddockIndex :: Maybe PathTemplate
pkgHashHaddockBaseUrl :: Maybe String
pkgHashHaddockLib :: Maybe String
pkgHashHaddockOutputDir :: Maybe String
..}
    } =
    -- The purpose of this somewhat laboured rendering (e.g. why not just
    -- use show?) is so that existing package hashes do not change
    -- unnecessarily when new configuration inputs are added into the hash.

    -- In particular, the assumption is that when a new configuration input
    -- is included into the hash, that existing packages will typically get
    -- the default value for that feature. So if we avoid adding entries with
    -- the default value then most of the time adding new features will not
    -- change the hashes of existing packages and so fewer packages will need
    -- to be rebuilt.

    -- TODO: [nice to have] ultimately we probably want to put this config info
    -- into the ghc-pkg db. At that point this should probably be changed to
    -- use the config file infrastructure so it can be read back in again.
    String -> ByteString
LBS.pack (String -> ByteString) -> String -> ByteString
forall a b. (a -> b) -> a -> b
$
      [String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$
        [Maybe String] -> [String]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe String] -> [String]) -> [Maybe String] -> [String]
forall a b. (a -> b) -> a -> b
$
          [ String -> (PackageId -> String) -> PackageId -> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry String
"pkgid" PackageId -> String
forall a. Pretty a => a -> String
prettyShow PackageId
pkgHashPkgId
          , String -> (Component -> String) -> Maybe Component -> Maybe String
forall {f :: * -> *} {t}.
Functor f =>
String -> (t -> String) -> f t -> f String
mentry String
"component" Component -> String
forall a. Show a => a -> String
show Maybe Component
pkgHashComponent
          , String -> (HashValue -> String) -> HashValue -> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry String
"src" HashValue -> String
showHashValue HashValue
pkgHashSourceHash
          , String
-> (Set (PkgconfigName, Maybe PkgconfigVersion) -> String)
-> Set (PkgconfigName, Maybe PkgconfigVersion)
-> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry
              String
"pkg-config-deps"
              ( String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
", "
                  ([String] -> String)
-> (Set (PkgconfigName, Maybe PkgconfigVersion) -> [String])
-> Set (PkgconfigName, Maybe PkgconfigVersion)
-> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((PkgconfigName, Maybe PkgconfigVersion) -> String)
-> [(PkgconfigName, Maybe PkgconfigVersion)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map
                    ( \(PkgconfigName
pn, Maybe PkgconfigVersion
mb_v) ->
                        PkgconfigName -> String
forall a. Pretty a => a -> String
prettyShow PkgconfigName
pn
                          String -> String -> String
forall a. [a] -> [a] -> [a]
++ case Maybe PkgconfigVersion
mb_v of
                            Maybe PkgconfigVersion
Nothing -> String
""
                            Just PkgconfigVersion
v -> String
" " String -> String -> String
forall a. [a] -> [a] -> [a]
++ PkgconfigVersion -> String
forall a. Pretty a => a -> String
prettyShow PkgconfigVersion
v
                    )
                  ([(PkgconfigName, Maybe PkgconfigVersion)] -> [String])
-> (Set (PkgconfigName, Maybe PkgconfigVersion)
    -> [(PkgconfigName, Maybe PkgconfigVersion)])
-> Set (PkgconfigName, Maybe PkgconfigVersion)
-> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Set (PkgconfigName, Maybe PkgconfigVersion)
-> [(PkgconfigName, Maybe PkgconfigVersion)]
forall a. Set a -> [a]
Set.toList
              )
              Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps
          , String
-> (Set InstalledPackageId -> String)
-> Set InstalledPackageId
-> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry
              String
"deps"
              ( String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
", "
                  ([String] -> String)
-> (Set InstalledPackageId -> [String])
-> Set InstalledPackageId
-> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (InstalledPackageId -> String) -> [InstalledPackageId] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map InstalledPackageId -> String
forall a. Pretty a => a -> String
prettyShow
                  ([InstalledPackageId] -> [String])
-> (Set InstalledPackageId -> [InstalledPackageId])
-> Set InstalledPackageId
-> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Set InstalledPackageId -> [InstalledPackageId]
forall a. Set a -> [a]
Set.toList
              )
              Set InstalledPackageId
pkgHashDirectDeps
          , -- and then all the config
            String -> (CompilerId -> String) -> CompilerId -> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry String
"compilerid" CompilerId -> String
forall a. Pretty a => a -> String
prettyShow CompilerId
pkgHashCompilerId
          , String -> (AbiTag -> String) -> AbiTag -> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry String
"compilerabi" AbiTag -> String
forall a. Pretty a => a -> String
prettyShow AbiTag
pkgHashCompilerABI
          , String -> (Platform -> String) -> Platform -> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry String
"platform" Platform -> String
forall a. Pretty a => a -> String
prettyShow Platform
pkgHashPlatform
          , String
-> FlagAssignment
-> (FlagAssignment -> String)
-> FlagAssignment
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"flags" FlagAssignment
forall a. Monoid a => a
mempty FlagAssignment -> String
showFlagAssignment FlagAssignment
pkgHashFlagAssignment
          , String
-> [String] -> ([String] -> String) -> [String] -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"configure-script" [] [String] -> String
unwords [String]
pkgHashConfigureScriptArgs
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"vanilla-lib" Bool
True Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashVanillaLib
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"shared-lib" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashSharedLib
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"dynamic-exe" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashDynExe
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"fully-static-exe" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashFullyStaticExe
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"ghci-lib" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashGHCiLib
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"prof-lib" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashProfLib
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"prof-exe" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashProfExe
          , String
-> ProfDetailLevel
-> (ProfDetailLevel -> String)
-> ProfDetailLevel
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"prof-lib-detail" ProfDetailLevel
ProfDetailDefault ProfDetailLevel -> String
showProfDetailLevel ProfDetailLevel
pkgHashProfLibDetail
          , String
-> ProfDetailLevel
-> (ProfDetailLevel -> String)
-> ProfDetailLevel
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"prof-exe-detail" ProfDetailLevel
ProfDetailDefault ProfDetailLevel -> String
showProfDetailLevel ProfDetailLevel
pkgHashProfExeDetail
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"hpc" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashCoverage
          , String
-> OptimisationLevel
-> (OptimisationLevel -> String)
-> OptimisationLevel
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"optimisation" OptimisationLevel
NormalOptimisation (Int -> String
forall a. Show a => a -> String
show (Int -> String)
-> (OptimisationLevel -> Int) -> OptimisationLevel -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OptimisationLevel -> Int
forall a. Enum a => a -> Int
fromEnum) OptimisationLevel
pkgHashOptimization
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"split-objs" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashSplitObjs
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"split-sections" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashSplitSections
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"stripped-lib" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashStripLibs
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"stripped-exe" Bool
True Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashStripExes
          , String
-> DebugInfoLevel
-> (DebugInfoLevel -> String)
-> DebugInfoLevel
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"debug-info" DebugInfoLevel
NormalDebugInfo (Int -> String
forall a. Show a => a -> String
show (Int -> String)
-> (DebugInfoLevel -> Int) -> DebugInfoLevel -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DebugInfoLevel -> Int
forall a. Enum a => a -> Int
fromEnum) DebugInfoLevel
pkgHashDebugInfo
          , String
-> [String] -> ([String] -> String) -> [String] -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"extra-lib-dirs" [] [String] -> String
unwords [String]
pkgHashExtraLibDirs
          , String
-> [String] -> ([String] -> String) -> [String] -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"extra-lib-dirs-static" [] [String] -> String
unwords [String]
pkgHashExtraLibDirsStatic
          , String
-> [String] -> ([String] -> String) -> [String] -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"extra-framework-dirs" [] [String] -> String
unwords [String]
pkgHashExtraFrameworkDirs
          , String
-> [String] -> ([String] -> String) -> [String] -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"extra-include-dirs" [] [String] -> String
unwords [String]
pkgHashExtraIncludeDirs
          , String
-> Maybe PathTemplate
-> (Maybe PathTemplate -> String)
-> Maybe PathTemplate
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"prog-prefix" Maybe PathTemplate
forall a. Maybe a
Nothing (String -> (PathTemplate -> String) -> Maybe PathTemplate -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" PathTemplate -> String
fromPathTemplate) Maybe PathTemplate
pkgHashProgPrefix
          , String
-> Maybe PathTemplate
-> (Maybe PathTemplate -> String)
-> Maybe PathTemplate
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"prog-suffix" Maybe PathTemplate
forall a. Maybe a
Nothing (String -> (PathTemplate -> String) -> Maybe PathTemplate -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" PathTemplate -> String
fromPathTemplate) Maybe PathTemplate
pkgHashProgSuffix
          , String
-> [Maybe PackageDB]
-> ([Maybe PackageDB] -> String)
-> [Maybe PackageDB]
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"package-dbs" [] ([String] -> String
unwords ([String] -> String)
-> ([Maybe PackageDB] -> [String]) -> [Maybe PackageDB] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe PackageDB -> String) -> [Maybe PackageDB] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Maybe PackageDB -> String
forall a. Show a => a -> String
show) [Maybe PackageDB]
pkgHashPackageDbs
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"documentation" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashDocumentation
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-hoogle" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockHoogle
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-html" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockHtml
          , String
-> Maybe String
-> (Maybe String -> String)
-> Maybe String
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-html-location" Maybe String
forall a. Maybe a
Nothing (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"") Maybe String
pkgHashHaddockHtmlLocation
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-foreign-libraries" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockForeignLibs
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-executables" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockExecutables
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-tests" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockTestSuites
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-benchmarks" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockBenchmarks
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-internal" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockInternal
          , String
-> Maybe String
-> (Maybe String -> String)
-> Maybe String
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-css" Maybe String
forall a. Maybe a
Nothing (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"") Maybe String
pkgHashHaddockCss
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-hyperlink-source" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockLinkedSource
          , String -> Bool -> (Bool -> String) -> Bool -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-quickjump" Bool
False Bool -> String
forall a. Pretty a => a -> String
prettyShow Bool
pkgHashHaddockQuickJump
          , String
-> Maybe PathTemplate
-> (Maybe PathTemplate -> String)
-> Maybe PathTemplate
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-contents-location" Maybe PathTemplate
forall a. Maybe a
Nothing (String -> (PathTemplate -> String) -> Maybe PathTemplate -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" PathTemplate -> String
fromPathTemplate) Maybe PathTemplate
pkgHashHaddockContents
          , String
-> Maybe PathTemplate
-> (Maybe PathTemplate -> String)
-> Maybe PathTemplate
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-index-location" Maybe PathTemplate
forall a. Maybe a
Nothing (String -> (PathTemplate -> String) -> Maybe PathTemplate -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" PathTemplate -> String
fromPathTemplate) Maybe PathTemplate
pkgHashHaddockIndex
          , String
-> Maybe String
-> (Maybe String -> String)
-> Maybe String
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-base-url" Maybe String
forall a. Maybe a
Nothing (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"") Maybe String
pkgHashHaddockBaseUrl
          , String
-> Maybe String
-> (Maybe String -> String)
-> Maybe String
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-lib" Maybe String
forall a. Maybe a
Nothing (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"") Maybe String
pkgHashHaddockLib
          , String
-> Maybe String
-> (Maybe String -> String)
-> Maybe String
-> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt String
"haddock-output-dir" Maybe String
forall a. Maybe a
Nothing (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"") Maybe String
pkgHashHaddockOutputDir
          ]
            [Maybe String] -> [Maybe String] -> [Maybe String]
forall a. [a] -> [a] -> [a]
++ (String -> [String] -> [Maybe String] -> [Maybe String])
-> [Maybe String] -> Map String [String] -> [Maybe String]
forall k a b. (k -> a -> b -> b) -> b -> Map k a -> b
Map.foldrWithKey (\String
prog [String]
args [Maybe String]
acc -> String
-> [String] -> ([String] -> String) -> [String] -> Maybe String
forall {t}.
Eq t =>
String -> t -> (t -> String) -> t -> Maybe String
opt (String
prog String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-options") [] [String] -> String
unwords [String]
args Maybe String -> [Maybe String] -> [Maybe String]
forall a. a -> [a] -> [a]
: [Maybe String]
acc) [] Map String [String]
pkgHashProgramArgs
    where
      entry :: String -> (t -> String) -> t -> Maybe String
entry String
key t -> String
format t
value = String -> Maybe String
forall a. a -> Maybe a
Just (String
key String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
": " String -> String -> String
forall a. [a] -> [a] -> [a]
++ t -> String
format t
value)
      mentry :: String -> (t -> String) -> f t -> f String
mentry String
key t -> String
format f t
value = (t -> String) -> f t -> f String
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\t
v -> String
key String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
": " String -> String -> String
forall a. [a] -> [a] -> [a]
++ t -> String
format t
v) f t
value
      opt :: String -> t -> (t -> String) -> t -> Maybe String
opt String
key t
def t -> String
format t
value
        | t
value t -> t -> Bool
forall a. Eq a => a -> a -> Bool
== t
def = Maybe String
forall a. Maybe a
Nothing
        | Bool
otherwise = String -> (t -> String) -> t -> Maybe String
forall {t}. String -> (t -> String) -> t -> Maybe String
entry String
key t -> String
format t
value