summaryrefslogtreecommitdiff
path: root/hadrian/src/Hadrian
diff options
context:
space:
mode:
Diffstat (limited to 'hadrian/src/Hadrian')
-rw-r--r--hadrian/src/Hadrian/BuildPath.hs21
-rw-r--r--hadrian/src/Hadrian/Builder/Git.hs3
-rw-r--r--hadrian/src/Hadrian/Haskell/Cabal.hs15
-rw-r--r--hadrian/src/Hadrian/Haskell/Cabal/Parse.hs11
-rw-r--r--hadrian/src/Hadrian/Haskell/Hash.hs257
-rw-r--r--hadrian/src/Hadrian/Haskell/Hash.hs-boot8
-rw-r--r--hadrian/src/Hadrian/Package.hs2
7 files changed, 299 insertions, 18 deletions
diff --git a/hadrian/src/Hadrian/BuildPath.hs b/hadrian/src/Hadrian/BuildPath.hs
index 8d2806b587..342a2ca7eb 100644
--- a/hadrian/src/Hadrian/BuildPath.hs
+++ b/hadrian/src/Hadrian/BuildPath.hs
@@ -110,17 +110,28 @@ parseWayUnit = Parsec.choice
, Parsec.char 'l' *> pure Logging
] Parsec.<?> "way unit (thr, debug, dyn, p, l)"
--- | Parse a @"pkgname-pkgversion"@ string into the package name and the
+-- | Parse a @"pkgname-pkgversion-pkghash"@ string into the package name and the
-- integers that make up the package version.
-parsePkgId :: Parsec.Parsec String () (String, [Integer])
-parsePkgId = parsePkgId' "" Parsec.<?> "package identifier (<name>-<version>)"
+--
+-- If no hash was assigned, an empty string is returned in its place.
+parsePkgId :: Parsec.Parsec String () (String, [Integer], String)
+parsePkgId = parsePkgId' "" Parsec.<?> "package identifier (<name>-<version>(-<hash>?))"
where
parsePkgId' currName = do
s <- Parsec.many1 Parsec.alphaNum
_ <- Parsec.char '-'
let newName = if null currName then s else currName ++ "-" ++ s
- Parsec.choice [ (newName,) <$> parsePkgVersion
- , parsePkgId' newName ]
+ Parsec.choice
+ [ (,,) newName <$> parsePkgVersion
+ <*> Parsec.option "" (Parsec.try $ do
+ _ <- Parsec.char '-'
+ -- Ensure we're not parsing a libDynName as a hash
+ _ <- Parsec.notFollowedBy (Parsec.string "ghc" *> parsePkgVersion)
+ parsePkgHash)
+ , parsePkgId' newName ]
+
+parsePkgHash :: Parsec.Parsec String () String
+parsePkgHash = Parsec.many1 Parsec.alphaNum
-- | Parse "."-separated integers that describe a package's version.
parsePkgVersion :: Parsec.Parsec String () [Integer]
diff --git a/hadrian/src/Hadrian/Builder/Git.hs b/hadrian/src/Hadrian/Builder/Git.hs
index 6875a48fbd..0f073ac463 100644
--- a/hadrian/src/Hadrian/Builder/Git.hs
+++ b/hadrian/src/Hadrian/Builder/Git.hs
@@ -2,12 +2,13 @@ module Hadrian.Builder.Git (gitArgs) where
import Expression
--- | Default command line arguments for invoking the archiving utility @tar@.
+-- | Default command line arguments for invoking the archiving utility @git@.
gitArgs :: Args
gitArgs = mconcat
[ builder (Git ListFiles) ? mconcat
[ arg "ls-files"
, arg "--recurse-submodules"
, arg "-z"
+ , getInputs
]
]
diff --git a/hadrian/src/Hadrian/Haskell/Cabal.hs b/hadrian/src/Hadrian/Haskell/Cabal.hs
index f5864b6297..1220e6bbe4 100644
--- a/hadrian/src/Hadrian/Haskell/Cabal.hs
+++ b/hadrian/src/Hadrian/Haskell/Cabal.hs
@@ -10,8 +10,8 @@
-- Cabal files.
-----------------------------------------------------------------------------
module Hadrian.Haskell.Cabal (
- pkgVersion, pkgIdentifier, pkgSynopsis, pkgDescription, pkgDependencies,
- pkgGenericDescription, cabalArchString, cabalOsString,
+ pkgVersion, pkgUnitId, pkgSynopsis, pkgDescription, pkgSimpleIdentifier,
+ pkgDependencies, pkgGenericDescription, cabalArchString, cabalOsString
) where
import Development.Shake
@@ -20,15 +20,19 @@ import Distribution.PackageDescription (GenericPackageDescription)
import Hadrian.Haskell.Cabal.Type
import Hadrian.Oracles.Cabal
import Hadrian.Package
+import {-# SOURCE #-} Hadrian.Haskell.Hash (pkgUnitId)
+
-- | Read a Cabal file and return the package version. The Cabal file is tracked.
pkgVersion :: Package -> Action String
pkgVersion = fmap version . readPackageData
--- | Read a Cabal file and return the package identifier, e.g. @base-4.10.0.0@.
+-- | Read a Cabal file and return the package identifier without a hash, e.g. @base-4.10.0.0@.
-- The Cabal file is tracked.
-pkgIdentifier :: Package -> Action String
-pkgIdentifier package = do
+--
+-- For an identifier complete with the hash use 'pkgUnitId'
+pkgSimpleIdentifier :: Package -> Action String
+pkgSimpleIdentifier package = do
cabal <- readPackageData package
return $ if null (version cabal)
then name cabal
@@ -72,3 +76,4 @@ cabalOsString "mingw32" = "windows"
cabalOsString "darwin" = "osx"
cabalOsString "solaris2" = "solaris"
cabalOsString other = other
+
diff --git a/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs b/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
index 74ae8fb16a..760f4295c9 100644
--- a/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
+++ b/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
@@ -63,7 +63,6 @@ import Hadrian.Target
import Base
import Builder
import Context
-import Flavour
import Settings
import Distribution.Simple.LocalBuildInfo
import qualified Distribution.Simple.Register as C
@@ -344,7 +343,7 @@ registerPackage rs context = do
pd <- packageDescription <$> readContextData context
db_path <- packageDbPath (PackageDbLoc (stage context) (iplace context))
dist_dir <- Context.buildPath context
- pid <- pkgIdentifier (package context)
+ pid <- pkgUnitId (stage context) (package context)
-- Note: the @cPath@ is ignored. The path that's used is the 'buildDir' path
-- from the local build info @lbi@.
lbi <- liftIO $ C.getPersistBuildConfig cPath
@@ -356,12 +355,12 @@ registerPackage rs context = do
-- This is copied and simplified from Cabal, because we want to install the package
-- into a different package database to the one it was configured against.
register :: FilePath
- -> FilePath
+ -> String -- ^ Package Identifier
-> FilePath
-> C.PackageDescription
-> LocalBuildInfo
-> IO ()
-register pkg_db conf_file build_dir pd lbi
+register pkg_db pid build_dir pd lbi
= withLibLBI pd lbi $ \lib clbi -> do
absPackageDBs <- C.absolutePackageDBPaths packageDbs
@@ -372,13 +371,13 @@ register pkg_db conf_file build_dir pd lbi
writeRegistrationFile installedPkgInfo
where
- regFile = conf_file
+ regFile = pkg_db </> pid <.> "conf"
reloc = relocatable lbi
-- Using a specific package db here is why we have to copy the function from Cabal.
packageDbs = [C.SpecificPackageDB pkg_db]
writeRegistrationFile installedPkgInfo = do
- writeUTF8File (pkg_db </> regFile <.> "conf") (CP.showInstalledPackageInfo installedPkgInfo)
+ writeUTF8File regFile (CP.showInstalledPackageInfo installedPkgInfo)
-- | Build autogenerated files @autogen/cabal_macros.h@ and @autogen/Paths_*.hs@.
diff --git a/hadrian/src/Hadrian/Haskell/Hash.hs b/hadrian/src/Hadrian/Haskell/Hash.hs
new file mode 100644
index 0000000000..ee644168e2
--- /dev/null
+++ b/hadrian/src/Hadrian/Haskell/Hash.hs
@@ -0,0 +1,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
diff --git a/hadrian/src/Hadrian/Haskell/Hash.hs-boot b/hadrian/src/Hadrian/Haskell/Hash.hs-boot
new file mode 100644
index 0000000000..b6fc67e36f
--- /dev/null
+++ b/hadrian/src/Hadrian/Haskell/Hash.hs-boot
@@ -0,0 +1,8 @@
+module Hadrian.Haskell.Hash where
+
+import Hadrian.Package
+import Stage
+import Development.Shake
+
+pkgUnitId :: Stage -> Package -> Action String
+
diff --git a/hadrian/src/Hadrian/Package.hs b/hadrian/src/Hadrian/Package.hs
index 6bc31d7c58..6291edee74 100644
--- a/hadrian/src/Hadrian/Package.hs
+++ b/hadrian/src/Hadrian/Package.hs
@@ -81,4 +81,4 @@ instance NFData PackageType
instance Binary Package
instance Hashable Package
-instance NFData Package \ No newline at end of file
+instance NFData Package