1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
module Hadrian.Haskell.Hash (pkgUnitId, pkgHashOracle) where
import Development.Shake
import Hadrian.Haskell.Cabal.Type
import Hadrian.Haskell.Cabal
import Hadrian.Oracles.Cabal
import Hadrian.Package
import qualified Crypto.Hash.SHA256 as SHA256
import qualified Data.ByteString.Base16 as Base16
import qualified Data.ByteString.Char8 as BS
import Data.ByteString as BS (readFile)
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Maybe
import Data.List
import Context.Type
import Oracles.Setting
import Hadrian.Target
import Hadrian.Expression
import Builder
import Flavour.Type
import Settings
import Way
import Packages
import Development.Shake.Classes
import Control.Monad
import Utilities
import Base
import Context
import System.Directory.Extra (listFilesRecursive)
import CommandLine
import Control.Arrow (first)
-- | Read a Cabal file and return the package identifier, e.g. @base-4.10.0.0-abcd@.
-- This needs to be an oracle so it's cached
pkgUnitId :: Stage -> Package -> Action String
pkgUnitId stg pkg = do
pid <- pkgSimpleIdentifier pkg
use_hash <- cmdUnitIdHash
if pkgName pkg == "rts"
-- The unit-id will change depending on the way, we need to treat the rts separately
then pure pid
else do
-- Other boot packages still hardcode their unit-id to just <name>, but we
-- can have hadrian generate a different unit-id for them just as cabal does
-- because the boot packages unit-ids are overriden by setting -this-unit-id
-- in the cabal file
hash <- if use_hash
then do
phash <- pkgHash stg pkg
return $ truncateHash 4 phash
else
return "inplace"
pure $ pid <> "-" <> hash
where
truncateHash :: Int -> String -> String
truncateHash = take
data PackageHashInputs = PackageHashInputs {
pkgHashPkgId :: String, -- ^ name-version
pkgHashComponent :: PackageType,
pkgHashSourceHash :: BS.ByteString,
-- pkgHashPkgConfigDeps :: Set (PkgconfigName, Maybe PkgconfigVersion),
pkgHashDirectDeps :: Set.Set String,
pkgHashOtherConfig :: PackageHashConfigInputs
}
-- | Those parts of the package configuration that contribute to the
-- package hash computed by hadrian (which is simpler than cabal's).
--
-- setting in Oracle.setting, which come from system.config
data PackageHashConfigInputs = PackageHashConfigInputs {
pkgHashCompilerId :: String,
pkgHashPlatform :: String,
pkgHashFlagAssignment :: [String], -- complete not partial
pkgHashVanillaLib :: Bool,
pkgHashSharedLib :: Bool,
pkgHashDynExe :: Bool,
pkgHashGHCiLib :: Bool,
pkgHashProfLib :: Bool,
pkgHashProfExe :: Bool,
pkgHashSplitObjs :: Bool,
pkgHashSplitSections :: Bool,
pkgHashStripLibs :: Bool,
pkgHashStripExes :: Bool,
pkgHashProgramArgs :: Map String [String]
-- pkgHashProgPrefix :: Maybe PathTemplate,
-- pkgHashProgSuffix :: Maybe PathTemplate,
-- pkgHashPackageDbs :: [Maybe PackageDB]
-- Captured by extraArgs
-- pkgHashDebugInfo :: DebugInfoLevel,
-- pkgHashCoverage :: Bool,
-- pkgHashFullyStaticExe :: Bool,
-- pkgHashProfLibDetail :: ProfDetailLevel,
-- pkgHashOptimization :: Int,
-- pkgHashProfExeDetail :: ProfDetailLevel,
-- pkgHashExtraLibDirs :: [FilePath],
-- pkgHashExtraLibDirsStatic :: [FilePath],
-- pkgHashExtraFrameworkDirs :: [FilePath],
-- pkgHashExtraIncludeDirs :: [FilePath]
}
deriving Show
newtype PkgHashKey = PkgHashKey (Stage, Package)
deriving (Binary, Eq, Hashable, NFData, Show, Typeable)
type instance RuleResult PkgHashKey = String
pkgHash :: Stage -> Package -> Action String
pkgHash stg p = askOracle . PkgHashKey $ (stg, p)
-- Needs to be an oracle to be cached. Called lots of times.
pkgHashOracle :: Rules ()
pkgHashOracle = void $ addOracleCache $ \(PkgHashKey (stag, pkg)) -> do
let vanilla_ctx = vanillaContext stag pkg
pkg_data <- readPackageData pkg
name <- pkgSimpleIdentifier pkg
stagePkgs <- stagePackages stag
depsHashes <- sequence [pkgHash stag pkg' | pkg' <- packageDependencies pkg_data, pkg' `elem` stagePkgs]
flav <- flavour
let flavourArgs = extraArgs flav
targetOs <- setting TargetOs
projectVersion <- setting ProjectVersion
let pkgHashCompilerId = "ghc-" ++ projectVersion
pkgHashPlatform = targetOs
libWays <- interpretInContext vanilla_ctx (libraryWays flav)
dyn_ghc <- dynamicGhcPrograms flav
flags <- interpret (target vanilla_ctx (Cabal Flags stag) [] []) getArgs
let pkgHashFlagAssignment = flags
pkgHashVanillaLib = vanilla `Set.member` libWays
pkgHashSharedLib = dynamic `Set.member` libWays
pkgHashDynExe = dyn_ghc
pkgHashGHCiLib = False
pkgHashProfLib = profiling `Set.member` libWays
pkgHashProfExe = pkg == ghc && ghcProfiled flav stag
pkgHashSplitObjs = False -- Deprecated
pkgHashSplitSections = ghcSplitSections flav
pkgHashStripExes = False
pkgHashStripLibs = False
pkgHashProgramArgs <- Map.unions <$> (forM (Set.toList libWays) $ \lib_way -> do
let ctx = vanilla_ctx { way = lib_way }
ghcArgs <- interpret (target ctx (Ghc CompileHs stag) [] []) flavourArgs
ghcCArgs <- interpret (target ctx (Ghc CompileCWithGhc stag) [] []) flavourArgs
linkArgs <- interpret (target ctx (Ghc LinkHs stag) [] []) flavourArgs
ccArgs <- interpret (target ctx (Cc CompileC stag) [] []) flavourArgs
hsc2hsArgs <- interpret (target ctx (Hsc2Hs stag) [] []) flavourArgs
-- TODO: Other arguments for other things (a user could pass extra options to any
-- builder we know about and we need to enumerate them here)
return $ Map.fromList (map (first (++ waySuffix lib_way))
[("ghc", ghcArgs)
,("ghc-c", ghcCArgs)
,("ghc-link", linkArgs)
,("hsc2hs", hsc2hsArgs)
,("cc", ccArgs) ]))
let other_config = PackageHashConfigInputs{..}
files <- allFilesInDirectory (pkgPath pkg)
need files
files_hash <- liftIO (SHA256.finalize <$> hashFiles (SHA256.init) files)
return $ BS.unpack $ Base16.encode $ SHA256.hash $
renderPackageHashInputs $ PackageHashInputs
{
pkgHashPkgId = name
, pkgHashComponent = pkgType pkg
, pkgHashSourceHash = files_hash
, pkgHashDirectDeps = Set.fromList depsHashes
, pkgHashOtherConfig = other_config
}
allFilesInDirectory :: FilePath -> Action [FilePath]
allFilesInDirectory dir = liftIO $ listFilesRecursive dir
-- Either use git ls-tree if we are in a git repo, otherwise just get all the
-- files in the given directory.
{- Deb9 toolchain is too old to support git ls-tree properly
git_tree <- isInGitTree
if git_tree
then do
let gitFiles = filter fileFilter . split (=='\NUL')
fileFilter file = not (null file) && ((dir ++ "/*") ?== file)
gitFiles <$> askWithResources [] (target (vanillaContext stage0Boot compiler) -- This value doesn't matter.
(Git ListFiles) [dir] [])
else
liftIO $ listFilesRecursive dir
isInGitTree :: Action Bool
isInGitTree = do
git_commit <- setting ProjectGitCommitId
-- git_commit is not set if we are in a source dist
return $ not ("" == git_commit)
-}
hashFiles :: SHA256.Ctx -> [FilePath] -> IO SHA256.Ctx
hashFiles = foldM hashFile
hashFile :: SHA256.Ctx -> FilePath -> IO SHA256.Ctx
hashFile !ctx fp = do
contents <- BS.readFile fp
return $! SHA256.update ctx contents
renderPackageHashInputs :: PackageHashInputs -> BS.ByteString
renderPackageHashInputs PackageHashInputs{
pkgHashPkgId,
pkgHashComponent,
pkgHashSourceHash,
pkgHashDirectDeps,
pkgHashOtherConfig =
PackageHashConfigInputs{..}
} =
-- 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.
BS.pack $ unlines $ catMaybes $
[ entry "pkgid" show pkgHashPkgId
, entry "component" show pkgHashComponent
, entry "src" show pkgHashSourceHash
, entry "deps" (intercalate ", " . map show
. Set.toList) pkgHashDirectDeps
-- and then all the config
, entry "compilerid" show pkgHashCompilerId
, entry "platform" show pkgHashPlatform
, opt "flags" mempty show pkgHashFlagAssignment
, opt "vanilla-lib" True show pkgHashVanillaLib
, opt "shared-lib" False show pkgHashSharedLib
, opt "dynamic-exe" False show pkgHashDynExe
, opt "ghci-lib" False show pkgHashGHCiLib
, opt "prof-lib" False show pkgHashProfLib
, opt "prof-exe" False show pkgHashProfExe
, opt "split-objs" False show pkgHashSplitObjs
, opt "split-sections" False show pkgHashSplitSections
, opt "stripped-lib" False show pkgHashStripLibs
, opt "stripped-exe" True show pkgHashStripExes
] ++ Map.foldrWithKey (\prog args acc -> opt (prog ++ "-options") [] unwords args : acc) [] pkgHashProgramArgs
where
entry key format value = Just (key ++ ": " ++ format value)
opt key def format value
| value == def = Nothing
| otherwise = entry key format value
|