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

-- | 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)
--
module Distribution.Client.PackageHash (
    -- * Calculating package hashes
    PackageHashInputs(..),
    PackageHashConfigInputs(..),
    PackageSourceHash,
    hashedInstalledPackageId,
    hashPackageHashInputs,
    renderPackageHashInputs,
    -- ** Platform-specific variations
    hashedInstalledPackageIdLong,
    hashedInstalledPackageIdShort,
  ) where

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

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

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 :: PackageHashInputs -> PackageId
pkgHashPkgId :: PackageId
pkgHashPkgId,Maybe Component
pkgHashComponent :: PackageHashInputs -> Maybe Component
pkgHashComponent :: 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 :: PackageId
pkgHashPkgId :: PackageHashInputs -> 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 (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
nInt -> 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 :: PackageId
pkgHashPkgId :: PackageHashInputs -> 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 (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 -> 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]
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

--     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
showList :: [PackageHashConfigInputs] -> String -> String
$cshowList :: [PackageHashConfigInputs] -> String -> String
show :: PackageHashConfigInputs -> String
$cshow :: PackageHashConfigInputs -> String
showsPrec :: Int -> PackageHashConfigInputs -> String -> String
$cshowsPrec :: Int -> 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 :: PackageId
pkgHashPkgId :: PackageHashInputs -> PackageId
pkgHashPkgId,
                          Maybe Component
pkgHashComponent :: Maybe Component
pkgHashComponent :: PackageHashInputs -> Maybe Component
pkgHashComponent,
                          HashValue
pkgHashSourceHash :: HashValue
pkgHashSourceHash :: PackageHashInputs -> HashValue
pkgHashSourceHash,
                          Set InstalledPackageId
pkgHashDirectDeps :: Set InstalledPackageId
pkgHashDirectDeps :: PackageHashInputs -> Set InstalledPackageId
pkgHashDirectDeps,
                          Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps :: Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps :: PackageHashInputs -> Set (PkgconfigName, Maybe PkgconfigVersion)
pkgHashPkgConfigDeps,
                          pkgHashOtherConfig :: PackageHashInputs -> PackageHashConfigInputs
pkgHashOtherConfig =
                            PackageHashConfigInputs{Bool
[String]
[Maybe PackageDB]
Maybe String
Maybe PathTemplate
Platform
CompilerId
Map String [String]
OptimisationLevel
DebugInfoLevel
ProfDetailLevel
FlagAssignment
pkgHashHaddockContents :: Maybe PathTemplate
pkgHashHaddockQuickJump :: Bool
pkgHashHaddockLinkedSource :: Bool
pkgHashHaddockCss :: Maybe String
pkgHashHaddockInternal :: Bool
pkgHashHaddockBenchmarks :: Bool
pkgHashHaddockTestSuites :: Bool
pkgHashHaddockExecutables :: Bool
pkgHashHaddockForeignLibs :: Bool
pkgHashHaddockHtmlLocation :: Maybe String
pkgHashHaddockHtml :: Bool
pkgHashHaddockHoogle :: Bool
pkgHashDocumentation :: Bool
pkgHashPackageDbs :: [Maybe PackageDB]
pkgHashProgSuffix :: Maybe PathTemplate
pkgHashProgPrefix :: Maybe PathTemplate
pkgHashExtraIncludeDirs :: [String]
pkgHashExtraFrameworkDirs :: [String]
pkgHashExtraLibDirs :: [String]
pkgHashProgramArgs :: Map String [String]
pkgHashDebugInfo :: DebugInfoLevel
pkgHashStripExes :: Bool
pkgHashStripLibs :: Bool
pkgHashSplitSections :: Bool
pkgHashSplitObjs :: Bool
pkgHashOptimization :: OptimisationLevel
pkgHashCoverage :: Bool
pkgHashProfExeDetail :: ProfDetailLevel
pkgHashProfLibDetail :: ProfDetailLevel
pkgHashProfExe :: Bool
pkgHashProfLib :: Bool
pkgHashGHCiLib :: Bool
pkgHashFullyStaticExe :: Bool
pkgHashDynExe :: Bool
pkgHashSharedLib :: Bool
pkgHashVanillaLib :: Bool
pkgHashConfigureScriptArgs :: [String]
pkgHashFlagAssignment :: FlagAssignment
pkgHashPlatform :: Platform
pkgHashCompilerId :: CompilerId
pkgHashHaddockContents :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashHaddockQuickJump :: PackageHashConfigInputs -> Bool
pkgHashHaddockLinkedSource :: PackageHashConfigInputs -> Bool
pkgHashHaddockCss :: PackageHashConfigInputs -> Maybe String
pkgHashHaddockInternal :: PackageHashConfigInputs -> Bool
pkgHashHaddockBenchmarks :: PackageHashConfigInputs -> Bool
pkgHashHaddockTestSuites :: PackageHashConfigInputs -> Bool
pkgHashHaddockExecutables :: PackageHashConfigInputs -> Bool
pkgHashHaddockForeignLibs :: PackageHashConfigInputs -> Bool
pkgHashHaddockHtmlLocation :: PackageHashConfigInputs -> Maybe String
pkgHashHaddockHtml :: PackageHashConfigInputs -> Bool
pkgHashHaddockHoogle :: PackageHashConfigInputs -> Bool
pkgHashDocumentation :: PackageHashConfigInputs -> Bool
pkgHashPackageDbs :: PackageHashConfigInputs -> [Maybe PackageDB]
pkgHashProgSuffix :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashProgPrefix :: PackageHashConfigInputs -> Maybe PathTemplate
pkgHashExtraIncludeDirs :: PackageHashConfigInputs -> [String]
pkgHashExtraFrameworkDirs :: PackageHashConfigInputs -> [String]
pkgHashExtraLibDirs :: PackageHashConfigInputs -> [String]
pkgHashProgramArgs :: PackageHashConfigInputs -> Map String [String]
pkgHashDebugInfo :: PackageHashConfigInputs -> DebugInfoLevel
pkgHashStripExes :: PackageHashConfigInputs -> Bool
pkgHashStripLibs :: PackageHashConfigInputs -> Bool
pkgHashSplitSections :: PackageHashConfigInputs -> Bool
pkgHashSplitObjs :: PackageHashConfigInputs -> Bool
pkgHashOptimization :: PackageHashConfigInputs -> OptimisationLevel
pkgHashCoverage :: PackageHashConfigInputs -> Bool
pkgHashProfExeDetail :: PackageHashConfigInputs -> ProfDetailLevel
pkgHashProfLibDetail :: PackageHashConfigInputs -> ProfDetailLevel
pkgHashProfExe :: PackageHashConfigInputs -> Bool
pkgHashProfLib :: PackageHashConfigInputs -> Bool
pkgHashGHCiLib :: PackageHashConfigInputs -> Bool
pkgHashFullyStaticExe :: PackageHashConfigInputs -> Bool
pkgHashDynExe :: PackageHashConfigInputs -> Bool
pkgHashSharedLib :: PackageHashConfigInputs -> Bool
pkgHashVanillaLib :: PackageHashConfigInputs -> Bool
pkgHashConfigureScriptArgs :: PackageHashConfigInputs -> [String]
pkgHashFlagAssignment :: PackageHashConfigInputs -> FlagAssignment
pkgHashPlatform :: PackageHashConfigInputs -> Platform
pkgHashCompilerId :: PackageHashConfigInputs -> CompilerId
..}
                        } =
    -- 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 -> (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-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

      ] [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 (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