summaryrefslogtreecommitdiff
path: root/hadrian/src/Rules
diff options
context:
space:
mode:
authorBen Gamari <ben@smart-cactus.org>2018-10-23 14:20:13 -0400
committerBen Gamari <ben@smart-cactus.org>2018-10-23 14:20:13 -0400
commit94756201349685a34c4495addd3484fdfcc8b498 (patch)
treefd4a9cee20d3c2b79f56ded7e02fb0c01b26b6c9 /hadrian/src/Rules
parent575b35f4cdc18045bccd42d341d6f25d95c0696c (diff)
parent45f3bff7016a2a0cd9a5455a882ced984655e90b (diff)
downloadhaskell-94756201349685a34c4495addd3484fdfcc8b498.tar.gz
Add 'hadrian/' from commit '45f3bff7016a2a0cd9a5455a882ced984655e90b'
git-subtree-dir: hadrian git-subtree-mainline: 575b35f4cdc18045bccd42d341d6f25d95c0696c git-subtree-split: 45f3bff7016a2a0cd9a5455a882ced984655e90b
Diffstat (limited to 'hadrian/src/Rules')
-rw-r--r--hadrian/src/Rules/BinaryDist.hs294
-rw-r--r--hadrian/src/Rules/Clean.hs34
-rw-r--r--hadrian/src/Rules/Compile.hs83
-rw-r--r--hadrian/src/Rules/Configure.hs57
-rw-r--r--hadrian/src/Rules/Dependencies.hs35
-rw-r--r--hadrian/src/Rules/Documentation.hs210
-rw-r--r--hadrian/src/Rules/Generate.hs501
-rw-r--r--hadrian/src/Rules/Gmp.hs123
-rw-r--r--hadrian/src/Rules/Libffi.hs109
-rw-r--r--hadrian/src/Rules/Library.hs305
-rw-r--r--hadrian/src/Rules/Nofib.hs57
-rw-r--r--hadrian/src/Rules/Program.hs77
-rw-r--r--hadrian/src/Rules/Register.hs103
-rw-r--r--hadrian/src/Rules/Selftest.hs113
-rw-r--r--hadrian/src/Rules/SourceDist.hs114
-rw-r--r--hadrian/src/Rules/Test.hs124
16 files changed, 2339 insertions, 0 deletions
diff --git a/hadrian/src/Rules/BinaryDist.hs b/hadrian/src/Rules/BinaryDist.hs
new file mode 100644
index 0000000000..f0aeb4b827
--- /dev/null
+++ b/hadrian/src/Rules/BinaryDist.hs
@@ -0,0 +1,294 @@
+module Rules.BinaryDist where
+
+import Hadrian.Haskell.Cabal
+
+import Context
+import Expression
+import Oracles.Setting
+import Packages
+import Settings
+import Target
+import Utilities
+
+bindistRules :: Rules ()
+bindistRules = do
+ root <- buildRootRules
+ phony "binary-dist" $ do
+ -- We 'need' all binaries and libraries
+ targets <- mapM pkgTarget =<< stagePackages Stage1
+ need targets
+ version <- setting ProjectVersion
+ targetPlatform <- setting TargetPlatformFull
+ hostOs <- setting BuildOs
+ hostArch <- setting BuildArch
+ rtsDir <- pkgIdentifier rts
+
+ let ghcBuildDir = root -/- stageString Stage1
+ bindistFilesDir = root -/- "bindist" -/- ghcVersionPretty
+ ghcVersionPretty = "ghc-" ++ version ++ "-" ++ targetPlatform
+ distDir = hostArch ++ "-" ++ hostOs ++ "-ghc-" ++ version
+ rtsIncludeDir = ghcBuildDir -/- "lib" -/- distDir -/- rtsDir
+ -/- "include"
+
+ -- We create the bindist directory at <root>/bindist/ghc-X.Y.Z-platform/
+ -- and populate it with Stage2 build results
+ createDirectory bindistFilesDir
+ copyDirectory (ghcBuildDir -/- "bin") bindistFilesDir
+ copyDirectory (ghcBuildDir -/- "lib") bindistFilesDir
+ copyDirectory (rtsIncludeDir) bindistFilesDir
+ {- TODO: Should we ship docs?
+ need ["docs"]
+ copyDirectory (root -/- "docs") bindistFilesDir -}
+
+ -- We then 'need' all the files necessary to configure and install
+ -- (as in, './configure [...] && make install') this build on some
+ -- other machine.
+ need $ map (bindistFilesDir -/-)
+ (["configure", "Makefile"] ++ bindistInstallFiles)
+ need $ map ((bindistFilesDir -/- "wrappers") -/-) ["check-api-annotations"
+ , "check-ppr", "ghc", "ghc-iserv", "ghc-pkg", "ghc-split"
+ , "ghci-script", "ghci", "haddock", "hpc", "hp2ps", "hsc2hs"
+ , "runghc"]
+
+ -- Finally, we create the archive <root>/bindist/ghc-X.Y.Z-platform.tar.xz
+ command [Cwd $ root -/- "bindist"] "tar"
+ [ "-c", "--xz", "-f"
+ , ghcVersionPretty <.> "tar.xz"
+ , ghcVersionPretty ]
+
+ -- Prepare binary distribution configure script
+ -- (generated under <ghc root>/distrib/configure by 'autoreconf')
+ root -/- "bindist" -/- "ghc-*" -/- "configure" %> \configurePath -> do
+ ghcRoot <- topDirectory
+ copyFile (ghcRoot -/- "aclocal.m4") (ghcRoot -/- "distrib" -/- "aclocal.m4")
+ buildWithCmdOptions [] $
+ target (vanillaContext Stage1 ghc) (Autoreconf $ ghcRoot -/- "distrib") [] []
+ -- We clean after ourselves, moving the configure script we generated in
+ -- our bindist dir
+ removeFile (ghcRoot -/- "distrib" -/- "aclocal.m4")
+ moveFile (ghcRoot -/- "distrib" -/- "configure") configurePath
+
+ -- Generate the Makefile that enables the "make install" part
+ root -/- "bindist" -/- "ghc-*" -/- "Makefile" %> \makefilePath ->
+ writeFile' makefilePath bindistMakefile
+
+ root -/- "bindist" -/- "ghc-*" -/- "wrappers/*" %> \wrapperPath ->
+ writeFile' wrapperPath $ wrapper (takeFileName wrapperPath)
+
+ -- Copy various configure-related files needed for a working
+ -- './configure [...] && make install' workflow
+ -- (see the list of files needed in the 'binary-dist' rule above, before
+ -- creating the archive).
+ forM_ bindistInstallFiles $ \file ->
+ root -/- "bindist" -/- "ghc-*" -/- file %> \dest -> do
+ ghcRoot <- topDirectory
+ copyFile (ghcRoot -/- fixup file) dest
+
+ where
+ fixup f | f `elem` ["INSTALL", "README"] = "distrib" -/- f
+ | otherwise = f
+
+-- TODO: This list is surely incomplete -- fix this.
+-- | A list of files that allow us to support a simple
+-- @./configure [--prefix=PATH] && make install@ workflow.
+bindistInstallFiles :: [FilePath]
+bindistInstallFiles =
+ [ "config.sub", "config.guess", "install-sh", "mk" -/- "config.mk.in"
+ , "mk" -/- "install.mk.in", "mk" -/- "project.mk", "settings.in", "README"
+ , "INSTALL" ]
+
+-- | This auxiliary function gives us a top-level 'Filepath' that we can 'need'
+-- for all libraries and programs that are needed for a complete build.
+-- For libraries, it returns the path to the @.conf@ file in the package
+-- database. For programs, it returns the path to the compiled executable.
+pkgTarget :: Package -> Action FilePath
+pkgTarget pkg
+ | isLibrary pkg = pkgConfFile (vanillaContext Stage1 pkg)
+ | otherwise = programPath =<< programContext Stage1 pkg
+
+-- TODO: Augment this Makefile to match the various parameters that the current
+-- bindist scripts support.
+-- | A trivial Makefile that only takes @$prefix@ into account, and not e.g
+-- @$datadir@ (for docs) and other variables, yet.
+bindistMakefile :: String
+bindistMakefile = unlines
+ [ "MAKEFLAGS += --no-builtin-rules"
+ , ".SUFFIXES:"
+ , ""
+ , "include mk/install.mk"
+ , "include mk/config.mk"
+ , ""
+ , ".PHONY: default"
+ , "default:"
+ , "\t@echo 'Run \"make install\" to install'"
+ , "\t@false"
+ , ""
+ , "#------------------------------------------------------------------------------"
+ , "# INSTALL RULES"
+ , ""
+ , "# Hacky function to check equality of two strings"
+ , "# TODO : find if a better function exists"
+ , "eq=$(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))"
+ , ""
+ , "define installscript"
+ , "# $1 = package name"
+ , "# $2 = wrapper path"
+ , "# $3 = bindir"
+ , "# $4 = ghcbindir"
+ , "# $5 = Executable binary path"
+ , "# $6 = Library Directory"
+ , "# $7 = Docs Directory"
+ , "# $8 = Includes Directory"
+ , "# We are installing wrappers to programs by searching corresponding wrappers."
+ , "# If wrapper is not found, we are attaching the common wrapper to it "
+ , "# This implementation is a bit hacky and depends on consistency of program"
+ , "# names. For hadrian build this will work as programs have a consistent "
+ , "# naming procefure. This file is tested on Linux(Ubuntu)"
+ , "# TODO : Check implementation in other distributions"
+ , "\trm -f $2"
+ , "\t$(CREATE_SCRIPT) $2"
+ , "\t@echo \"#!$(SHELL)\" >> $2"
+ , "\t@echo \"exedir=\\\"$4\\\"\" >> $2"
+ , "\t@echo \"exeprog=\\\"$1\\\"\" >> $2"
+ , "\t@echo \"executablename=\\\"$5\\\"\" >> $2"
+ , "\t@echo \"bindir=\\\"$3\\\"\" >> $2"
+ , "\t@echo \"libdir=\\\"$6\\\"\" >> $2"
+ , "\t@echo \"docdir=\\\"$7\\\"\" >> $2"
+ , "\t@echo \"includedir=\\\"$8\\\"\" >> $2"
+ , "\t@echo \"\" >> $2 "
+ , "\tcat wrappers/$1 >> $2"
+ , "\t$(EXECUTABLE_FILE) $2 ;"
+ , "endef"
+ , ""
+ , "# QUESTION : should we use shell commands?"
+ , ""
+ , "# Due to the fact that package database is configured relatively"
+ , "# We do not change the relative paths of executables and libraries"
+ , "# But instead use wrapper scripts whenever necessary"
+ , "LIBPARENT = $(shell dirname $(libdir))"
+ , "GHCBINDIR = \"$(LIBPARENT)/bin\""
+ , ""
+ , ".PHONY: install"
+ , "install: install_bin install_lib install_includes"
+ , ""
+ , "# Check if we need to install docs"
+ , "ifeq \"DOCS\" \"YES\""
+ , "install: install_docs"
+ , "endif"
+ , ""
+ , "# If the relative path of binaries and libraries are altered, we will need to"
+ , "# install additional wrapper scripts at bindir."
+ , "ifneq \"$(LIBPARENT)/bin\" \"$(bindir)\""
+ , "install: install_wrappers"
+ , "endif"
+ , ""
+ , "# We need to install binaries relative to libraries."
+ , "BINARIES = $(wildcard ./bin/*)"
+ , "install_bin:"
+ , "\t@echo \"Copying Binaries to $(GHCBINDIR)\""
+ , "\t$(INSTALL_DIR) \"$(GHCBINDIR)\""
+ , "\tfor i in $(BINARIES); do \\"
+ , "\t\tcp -R $$i \"$(GHCBINDIR)\"; \\"
+ , "\tdone"
+ , "\t@echo \"Copying and installing ghci\""
+ , "\trm -f $(GHCBINDIR)/dir"
+ , "\t$(CREATE_SCRIPT) $(GHCBINDIR)/ghci"
+ , "\t@echo \"#!$(SHELL)\" >> $(GHCBINDIR)/ghci"
+ , "\tcat wrappers/ghci-script >> $(GHCBINDIR)/ghci"
+ , "\t$(EXECUTABLE_FILE) $(GHCBINDIR)/ghci"
+ , ""
+ , "LIBRARIES = $(wildcard ./lib/*)"
+ , "install_lib:"
+ , "\t@echo \"Copying libraries to $(libdir)\""
+ , "\t$(INSTALL_DIR) \"$(libdir)\""
+ , "\tfor i in $(LIBRARIES); do \\"
+ , "\t\tcp -R $$i \"$(libdir)/\"; \\"
+ , "\tdone"
+ , ""
+ , "INCLUDES = $(wildcard ./include/*)"
+ , "install_includes:"
+ , "\t@echo \"Copying libraries to $(includedir)\""
+ , "\t$(INSTALL_DIR) \"$(includedir)\""
+ , "\tfor i in $(INCLUDES); do \\"
+ , "\t\tcp -R $$i \"$(includedir)/\"; \\"
+ , "\tdone"
+ , ""
+ , "DOCS = $(wildcard ./docs/*)"
+ , "install_docs:"
+ , "\t@echo \"Copying libraries to $(docdir)\""
+ , "\t$(INSTALL_DIR) \"$(docdir)\""
+ , "\tfor i in $(DOCS); do \\"
+ , "\t\tcp -R $$i \"$(docdir)/\"; \\"
+ , "\tdone"
+ , ""
+ , "BINARY_NAMES=$(shell ls ./bin/)"
+ , "install_wrappers:"
+ , "\t@echo \"Installing Wrapper scripts\""
+ , "\t$(INSTALL_DIR) \"$(bindir)\""
+ , "\t$(foreach p, $(BINARY_NAMES),\\"
+ , "\t\t$(call installscript,$p,$(bindir)/$p,$(bindir),$(GHCBINDIR),$(GHCBINDIR)/$p,$(libdir),$(docdir),$(includedir)))"
+ , ""
+ , "# END INSTALL"
+ , "# -----------------------------------------------------------------------------" ]
+
+wrapper :: FilePath -> String
+wrapper "ghc" = ghcWrapper
+wrapper "ghc-pkg" = ghcPkgWrapper
+wrapper "ghci" = ghciWrapper
+wrapper "ghci-script" = ghciScriptWrapper
+wrapper "haddock" = haddockWrapper
+wrapper "hsc2hs" = hsc2hsWrapper
+wrapper "runghc" = runGhcWrapper
+wrapper _ = commonWrapper
+
+-- | Wrapper scripts for different programs. Common is default wrapper.
+
+ghcWrapper :: String
+ghcWrapper = "exec \"$executablename\" -B\"$libdir\" ${1+\"$@\"}\n"
+
+ghcPkgWrapper :: String
+ghcPkgWrapper = unlines
+ [ "PKGCONF=\"$libdir/package.conf.d\""
+ , "exec \"$executablename\" --global-package-db \"$PKGCONF\" ${1+\"$@\"}" ]
+
+ghciWrapper :: String
+ghciWrapper = "exec \"$executablename\" --interactive \"$@\"\n"
+
+haddockWrapper :: String
+haddockWrapper = "exec \"$executablename\" -B\"$libdir\" -l\"$libdir\" ${1+\"$@\"}\n"
+
+commonWrapper :: String
+commonWrapper = "exec \"$executablename\" ${1+\"$@\"}\n"
+
+hsc2hsWrapper :: String
+hsc2hsWrapper = unlines
+ [ "HSC2HS_EXTRA=\"--cflag=-fno-stack-protector --lflag=-fuse-ld=gold\""
+ , "tflag=\"--template=$libdir/template-hsc.h\""
+ , "Iflag=\"-I$includedir/\""
+ , "for arg do"
+ , " case \"$arg\" in"
+ , "# On OS X, we need to specify -m32 or -m64 in order to get gcc to"
+ , "# build binaries for the right target. We do that by putting it in"
+ , "# HSC2HS_EXTRA. When cabal runs hsc2hs, it passes a flag saying which"
+ , "# gcc to use, so if we set HSC2HS_EXTRA= then we don't get binaries"
+ , "# for the right platform. So for now we just don't set HSC2HS_EXTRA="
+ , "# but we probably want to revisit how this works in the future."
+ , "# -c*) HSC2HS_EXTRA=;;"
+ , "# --cc=*) HSC2HS_EXTRA=;;"
+ , " -t*) tflag=;;"
+ , " --template=*) tflag=;;"
+ , " --) break;;"
+ , " esac"
+ , "done"
+ , "exec \"$executablename\" ${tflag:+\"$tflag\"} $HSC2HS_EXTRA ${1+\"$@\"} \"$Iflag\"" ]
+
+runGhcWrapper :: String
+runGhcWrapper = "exec \"$executablename\" -f \"$exedir/ghc\" ${1+\"$@\"}\n"
+
+-- | We need to ship ghci executable, which basically just calls ghc with
+-- | --interactive flag.
+ghciScriptWrapper :: String
+ghciScriptWrapper = unlines
+ [ "DIR=`dirname \"$0\"`"
+ , "executable=\"$DIR/ghc\""
+ , "exec $executable --interactive \"$@\"" ]
diff --git a/hadrian/src/Rules/Clean.hs b/hadrian/src/Rules/Clean.hs
new file mode 100644
index 0000000000..abf6933b56
--- /dev/null
+++ b/hadrian/src/Rules/Clean.hs
@@ -0,0 +1,34 @@
+module Rules.Clean (clean, cleanSourceTree, cleanRules) where
+
+import qualified System.Directory as IO
+import Base
+
+clean :: Action ()
+clean = do
+ putBuild "| Removing Hadrian files..."
+ cleanSourceTree
+ path <- buildRoot
+ putBuild $ "| Remove directory " ++ path ++ " (after build completes)"
+ runAfter $ IO.removeDirectoryRecursive path -- since we can't delete the Shake database while Shake is running
+ putSuccess "| Done. "
+
+cleanSourceTree :: Action ()
+cleanSourceTree = do
+ path <- buildRoot
+ forM_ [Stage0 ..] $ removeDirectory . (path -/-) . stageString
+ removeDirectory "sdistprep"
+ cleanFsUtils
+
+-- Clean all temporary fs files copied by configure into the source folder
+cleanFsUtils :: Action ()
+cleanFsUtils = do
+ let dirs = [ "utils/lndir/"
+ , "utils/unlit/"
+ , "rts/"
+ , "libraries/base/include/"
+ , "libraries/base/cbits/"
+ ]
+ liftIO $ forM_ dirs (flip removeFiles ["fs.*"])
+
+cleanRules :: Rules ()
+cleanRules = "clean" ~> clean
diff --git a/hadrian/src/Rules/Compile.hs b/hadrian/src/Rules/Compile.hs
new file mode 100644
index 0000000000..4e85db2df6
--- /dev/null
+++ b/hadrian/src/Rules/Compile.hs
@@ -0,0 +1,83 @@
+module Rules.Compile (compilePackage) where
+
+import Hadrian.Oracles.TextFile
+
+import Base
+import Context
+import Expression
+import Rules.Generate
+import Target
+import Utilities
+
+compilePackage :: [(Resource, Int)] -> Context -> Rules ()
+compilePackage rs context@Context {..} = do
+ root <- buildRootRules
+ let dir = root -/- buildDir context
+ nonHs extension = dir -/- extension <//> "*" <.> osuf way
+ compile compiler obj2src obj = do
+ src <- obj2src context obj
+ need [src]
+ needDependencies context src $ obj <.> "d"
+ buildWithResources rs $ target context (compiler stage) [src] [obj]
+ compileHs = \[obj, _hi] -> do
+ path <- contextPath context
+ (src, deps) <- lookupDependencies (path -/- ".dependencies") obj
+ need $ src : deps
+ needLibrary =<< contextDependencies context
+ buildWithResources rs $ target context (Ghc CompileHs stage) [src] [obj]
+
+ priority 2.0 $ do
+ nonHs "c" %> compile (Ghc CompileCWithGhc) (obj2src "c" $ const False )
+ nonHs "cmm" %> compile (Ghc CompileHs) (obj2src "cmm" isGeneratedCmmFile)
+ nonHs "s" %> compile (Ghc CompileHs) (obj2src "S" $ const False )
+
+ -- TODO: Add dependencies for #include of .h and .hs-incl files (gcc -MM?).
+ [ dir <//> "*" <.> suf way | suf <- [ osuf, hisuf] ] &%> compileHs
+ [ dir <//> "*" <.> suf way | suf <- [obootsuf, hibootsuf] ] &%> compileHs
+
+-- | Discover dependencies of a given source file by iteratively calling @gcc@
+-- in the @-MM -MG@ mode and building generated dependencies if they are missing
+-- until reaching a fixed point.
+needDependencies :: Context -> FilePath -> FilePath -> Action ()
+needDependencies context@Context {..} src depFile = discover
+ where
+ discover = do
+ build $ target context (Cc FindCDependencies stage) [src] [depFile]
+ deps <- parseFile depFile
+ -- Generated dependencies, if not yet built, will not be found and hence
+ -- will be referred to simply by their file names.
+ let notFound = filter (\file -> file == takeFileName file) deps
+ -- We find the full paths to generated dependencies, so we can request
+ -- to build them by calling 'need'.
+ todo <- catMaybes <$> mapM (fullPathIfGenerated context) notFound
+
+ if null todo
+ then need deps -- The list of dependencies is final, need all
+ else do
+ need todo -- Build newly discovered generated dependencies
+ discover -- Continue the discovery process
+
+ parseFile :: FilePath -> Action [String]
+ parseFile file = do
+ input <- liftIO $ readFile file
+ case parseMakefile input of
+ [(_file, deps)] -> return deps
+ _ -> return []
+
+-- | Find a given 'FilePath' in the list of generated files in the given
+-- 'Context' and return its full path.
+fullPathIfGenerated :: Context -> FilePath -> Action (Maybe FilePath)
+fullPathIfGenerated context file = interpretInContext context $ do
+ generated <- generatedDependencies
+ return $ find ((== file) . takeFileName) generated
+
+obj2src :: String -> (FilePath -> Bool) -> Context -> FilePath -> Action FilePath
+obj2src extension isGenerated context@Context {..} obj
+ | isGenerated src = return src
+ | otherwise = (pkgPath package ++) <$> suffix
+ where
+ src = obj -<.> extension
+ suffix = do
+ path <- buildPath context
+ return $ fromMaybe ("Cannot determine source for " ++ obj)
+ $ stripPrefix (path -/- extension) src
diff --git a/hadrian/src/Rules/Configure.hs b/hadrian/src/Rules/Configure.hs
new file mode 100644
index 0000000000..909b3c3357
--- /dev/null
+++ b/hadrian/src/Rules/Configure.hs
@@ -0,0 +1,57 @@
+module Rules.Configure (configureRules) where
+
+import Base
+import Builder
+import CommandLine
+import Context
+import Packages
+import Target
+import Utilities
+
+import qualified System.Info.Extra as System
+
+-- TODO: Make this list complete.
+-- | Files generated by running the @configure@ script.
+configureResults :: [FilePath]
+configureResults =
+ [ configFile, "settings", configH, "compiler/ghc.cabal", "rts/rts.cabal"]
+
+configureRules :: Rules ()
+configureRules = do
+ configureResults &%> \outs -> do
+ skip <- not <$> cmdConfigure
+ if skip
+ then unlessM (doesFileExist configFile) $
+ error $ "Configuration file " ++ configFile ++ " is missing.\n"
+ ++ "Run the configure script manually or let Hadrian run it "
+ ++ "automatically by passing the flag --configure."
+ else do
+ -- We cannot use windowsHost here due to a cyclic dependency.
+ when System.isWindows $ do
+ putBuild "| Checking for Windows tarballs..."
+ quietly $ cmd ["bash", "mk/get-win32-tarballs.sh", "download", System.arch]
+ let srcs = map (<.> "in") outs
+ context = vanillaContext Stage0 compiler
+ need srcs
+ build $ target context (Configure ".") srcs outs
+ -- TODO: This is fragile: we should remove this from behind the
+ -- @--configure@ flag and add a proper dependency tracking.
+ -- We need to copy the directory with unpacked Windows tarball to
+ -- the build directory, so that the built GHC has access to it.
+ -- See https://github.com/snowleopard/hadrian/issues/564.
+ when System.isWindows $ do
+ root <- buildRoot
+ copyDirectory "inplace/mingw" (root -/- "mingw")
+
+ ["configure", configH <.> "in"] &%> \_ -> do
+ skip <- not <$> cmdConfigure
+ if skip
+ then unlessM (doesFileExist "configure") $
+ error $ "The configure script is missing.\nRun the boot script "
+ ++ "manually let Hadrian run it automatically by passing the "
+ ++ "flag --configure."
+ else do
+ need ["configure.ac"]
+ putBuild "| Running boot..."
+ verbosity <- getVerbosity
+ quietly $ cmd [EchoStdout (verbosity >= Loud)] "python3 boot --hadrian"
diff --git a/hadrian/src/Rules/Dependencies.hs b/hadrian/src/Rules/Dependencies.hs
new file mode 100644
index 0000000000..9589d12aa0
--- /dev/null
+++ b/hadrian/src/Rules/Dependencies.hs
@@ -0,0 +1,35 @@
+module Rules.Dependencies (buildPackageDependencies) where
+
+import Data.Bifunctor
+import Data.Function
+
+import Base
+import Context
+import Expression
+import Oracles.ModuleFiles
+import Rules.Generate
+import Target
+import Utilities
+
+buildPackageDependencies :: [(Resource, Int)] -> Context -> Rules ()
+buildPackageDependencies rs context@Context {..} = do
+ root <- buildRootRules
+ root -/- contextDir context -/- ".dependencies.mk" %> \mk -> do
+ srcs <- hsSources context
+ need srcs
+ orderOnly =<< interpretInContext context generatedDependencies
+ if null srcs
+ then writeFileChanged mk ""
+ else buildWithResources rs $
+ target context (Ghc FindHsDependencies stage) srcs [mk]
+ removeFile $ mk <.> "bak"
+
+ root -/- contextDir context -/- ".dependencies" %> \deps -> do
+ mkDeps <- readFile' (deps <.> "mk")
+ writeFileChanged deps . unlines
+ . map (\(src, deps) -> unwords $ src : deps)
+ . map (bimap unifyPath (map unifyPath))
+ . map (bimap head concat . unzip)
+ . groupBy ((==) `on` fst)
+ . sortBy (compare `on` fst)
+ $ parseMakefile mkDeps
diff --git a/hadrian/src/Rules/Documentation.hs b/hadrian/src/Rules/Documentation.hs
new file mode 100644
index 0000000000..92b5ff5476
--- /dev/null
+++ b/hadrian/src/Rules/Documentation.hs
@@ -0,0 +1,210 @@
+module Rules.Documentation (
+ -- * Rules
+ buildPackageDocumentation, documentationRules,
+
+ -- * Utilities
+ haddockDependencies
+ ) where
+
+import Hadrian.Haskell.Cabal
+import Hadrian.Haskell.Cabal.Type
+
+import Base
+import Context
+import Expression (getContextData, interpretInContext)
+import Flavour
+import Oracles.ModuleFiles
+import Packages
+import Settings
+import Target
+import Utilities
+
+docRoot :: FilePath
+docRoot = "docs"
+
+htmlRoot :: FilePath
+htmlRoot = docRoot -/- "html"
+
+pdfRoot :: FilePath
+pdfRoot = docRoot -/- "pdfs"
+
+archiveRoot :: FilePath
+archiveRoot = docRoot -/- "archives"
+
+haddockHtmlLib :: FilePath
+haddockHtmlLib = htmlRoot -/- "haddock-bundle.min.js"
+
+manPageBuildPath :: FilePath
+manPageBuildPath = "docs/users_guide/build-man/ghc.1"
+
+-- TODO: Get rid of this hack.
+docContext :: Context
+docContext = vanillaContext Stage2 (library "Documentation" "docs")
+
+docPaths :: [FilePath]
+docPaths = ["libraries", "users_guide", "Haddock"]
+
+pathPdf :: FilePath -> FilePath
+pathPdf path = pdfRoot -/- path <.> ".pdf"
+
+pathIndex :: FilePath -> FilePath
+pathIndex path = htmlRoot -/- path -/- "index.html"
+
+pathArchive :: FilePath -> FilePath
+pathArchive path = archiveRoot -/- path <.> "html.tar.xz"
+
+-- TODO: Get rid of this hack.
+pathPath :: FilePath -> FilePath
+pathPath "users_guide" = "docs/users_guide"
+pathPath "Haddock" = "utils/haddock/doc"
+pathPath _ = ""
+
+-- | Build all documentation
+documentationRules :: Rules ()
+documentationRules = do
+ buildDocumentationArchives
+ buildHtmlDocumentation
+ buildManPage
+ buildPdfDocumentation
+
+ "docs" ~> do
+ root <- buildRoot
+ let html = htmlRoot -/- "index.html"
+ archives = map pathArchive docPaths
+ pdfs = map pathPdf $ docPaths \\ ["libraries"]
+ need $ map (root -/-) $ [html] ++ archives ++ pdfs
+ need [ root -/- htmlRoot -/- "libraries" -/- "gen_contents_index"
+ , root -/- htmlRoot -/- "libraries" -/- "prologue.txt"
+ , root -/- manPageBuildPath ]
+
+------------------------------------- HTML -------------------------------------
+
+-- | Build rules for HTML documentation.
+buildHtmlDocumentation :: Rules ()
+buildHtmlDocumentation = do
+ mapM_ buildSphinxHtml $ docPaths \\ ["libraries"]
+ buildLibraryDocumentation
+ root <- buildRootRules
+ root -/- htmlRoot -/- "libraries/gen_contents_index" %>
+ copyFile "libraries/gen_contents_index"
+
+ root -/- htmlRoot -/- "libraries/prologue.txt" %>
+ copyFile "libraries/prologue.txt"
+
+ root -/- htmlRoot -/- "index.html" %> \file -> do
+ need [root -/- haddockHtmlLib]
+ need $ map ((root -/-) . pathIndex) docPaths
+ copyFileUntracked "docs/index.html" file
+
+-- | Compile a Sphinx ReStructured Text package to HTML.
+buildSphinxHtml :: FilePath -> Rules ()
+buildSphinxHtml path = do
+ root <- buildRootRules
+ root -/- htmlRoot -/- path -/- "index.html" %> \file -> do
+ need [root -/- haddockHtmlLib]
+ let dest = takeDirectory file
+ build $ target docContext (Sphinx Html) [pathPath path] [dest]
+
+------------------------------------ Haddock -----------------------------------
+
+-- | Build the haddocks for GHC's libraries.
+buildLibraryDocumentation :: Rules ()
+buildLibraryDocumentation = do
+ root <- buildRootRules
+
+ -- Js and Css files for haddock output
+ root -/- haddockHtmlLib %> \_ ->
+ copyDirectory "utils/haddock/haddock-api/resources/html" (root -/- docRoot)
+
+ root -/- htmlRoot -/- "libraries/index.html" %> \file -> do
+ need [root -/- haddockHtmlLib]
+ haddocks <- allHaddocks
+ let libDocs = filter
+ (\x -> takeFileName x `notElem` ["ghc.haddock", "rts.haddock"])
+ haddocks
+ need (root -/- haddockHtmlLib : libDocs)
+ build $ target docContext (Haddock BuildIndex) libDocs [file]
+
+allHaddocks :: Action [FilePath]
+allHaddocks = do
+ pkgs <- stagePackages Stage1
+ sequence [ pkgHaddockFile $ vanillaContext Stage1 pkg
+ | pkg <- pkgs, isLibrary pkg ]
+
+-- Note: this build rule creates plenty of files, not just the .haddock one.
+-- All of them go into the 'docRoot' subdirectory. Pedantically tracking all
+-- built files in the Shake database seems fragile and unnecessary.
+buildPackageDocumentation :: Context -> Rules ()
+buildPackageDocumentation context@Context {..} = when (stage == Stage1 && package /= rts) $ do
+ root <- buildRootRules
+
+ -- Per-package haddocks
+ root -/- htmlRoot -/- "libraries" -/- pkgName package -/- "haddock-prologue.txt" %> \file -> do
+ need [root -/- haddockHtmlLib]
+ -- This is how @ghc-cabal@ used to produces "haddock-prologue.txt" files.
+ syn <- pkgSynopsis package
+ desc <- pkgDescription package
+ let prologue = if null desc then syn else desc
+ liftIO $ writeFile file prologue
+
+ root -/- htmlRoot -/- "libraries" -/- pkgName package -/- pkgName package <.> "haddock" %> \file -> do
+ need [root -/- htmlRoot -/- "libraries" -/- pkgName package -/- "haddock-prologue.txt"]
+ haddocks <- haddockDependencies context
+ srcs <- hsSources context
+ need $ srcs ++ haddocks ++ [root -/- haddockHtmlLib]
+
+ -- Build Haddock documentation
+ -- TODO: Pass the correct way from Rules via Context.
+ dynamicPrograms <- dynamicGhcPrograms =<< flavour
+ let haddockWay = if dynamicPrograms then dynamic else vanilla
+ build $ target (context {way = haddockWay}) (Haddock BuildPackage) srcs [file]
+
+-------------------------------------- PDF -------------------------------------
+
+-- | Build all PDF documentation
+buildPdfDocumentation :: Rules ()
+buildPdfDocumentation = mapM_ buildSphinxPdf docPaths
+
+-- | Compile a Sphinx ReStructured Text package to LaTeX
+buildSphinxPdf :: FilePath -> Rules ()
+buildSphinxPdf path = do
+ root <- buildRootRules
+ root -/- pdfRoot -/- path <.> "pdf" %> \file -> do
+ need [root -/- haddockHtmlLib]
+ withTempDir $ \dir -> do
+ build $ target docContext (Sphinx Latex) [pathPath path] [dir]
+ build $ target docContext Xelatex [path <.> "tex"] [dir]
+ copyFileUntracked (dir -/- path <.> "pdf") file
+
+------------------------------------ Archive -----------------------------------
+
+-- | Build documentation archives.
+buildDocumentationArchives :: Rules ()
+buildDocumentationArchives = mapM_ buildArchive docPaths
+
+buildArchive :: FilePath -> Rules ()
+buildArchive path = do
+ root <- buildRootRules
+ root -/- pathArchive path %> \file -> do
+ need [root -/- haddockHtmlLib]
+ root <- buildRoot
+ let src = root -/- pathIndex path
+ need [src]
+ build $ target docContext (Tar Create) [takeDirectory src] [file]
+
+-- | Build the man page.
+buildManPage :: Rules ()
+buildManPage = do
+ root <- buildRootRules
+ root -/- manPageBuildPath %> \file -> do
+ need [root -/- haddockHtmlLib, "docs/users_guide/ghc.rst"]
+ withTempDir $ \dir -> do
+ build $ target docContext (Sphinx Man) ["docs/users_guide"] [dir]
+ copyFileUntracked (dir -/- "ghc.1") file
+
+-- | Find the Haddock files for the dependencies of the current library.
+haddockDependencies :: Context -> Action [FilePath]
+haddockDependencies context = do
+ depNames <- interpretInContext context (getContextData depNames)
+ sequence [ pkgHaddockFile $ vanillaContext Stage1 depPkg
+ | Just depPkg <- map findPackageByName depNames, depPkg /= rts ]
diff --git a/hadrian/src/Rules/Generate.hs b/hadrian/src/Rules/Generate.hs
new file mode 100644
index 0000000000..c3650c36b1
--- /dev/null
+++ b/hadrian/src/Rules/Generate.hs
@@ -0,0 +1,501 @@
+module Rules.Generate (
+ isGeneratedCmmFile, generatePackageCode, generateRules, copyRules,
+ includesDependencies, generatedDependencies
+ ) where
+
+import Base
+import Expression
+import Flavour
+import Oracles.Flag
+import Oracles.ModuleFiles
+import Oracles.Setting
+import Packages
+import Rules.Gmp
+import Rules.Libffi
+import Settings
+import Target
+import Utilities
+
+-- | Track this file to rebuild generated files whenever it changes.
+trackGenerateHs :: Expr ()
+trackGenerateHs = expr $ need [sourcePath -/- "Rules/Generate.hs"]
+
+primopsSource :: FilePath
+primopsSource = "compiler/prelude/primops.txt.pp"
+
+primopsTxt :: Stage -> FilePath
+primopsTxt stage = buildDir (vanillaContext stage compiler) -/- "primops.txt"
+
+platformH :: Stage -> FilePath
+platformH stage = buildDir (vanillaContext stage compiler) -/- "ghc_boot_platform.h"
+
+isGeneratedCmmFile :: FilePath -> Bool
+isGeneratedCmmFile file = takeBaseName file == "AutoApply"
+
+includesDependencies :: [FilePath]
+includesDependencies = fmap (generatedDir -/-)
+ [ "ghcautoconf.h"
+ , "ghcplatform.h"
+ , "ghcversion.h" ]
+
+ghcPrimDependencies :: Expr [FilePath]
+ghcPrimDependencies = do
+ stage <- getStage
+ path <- expr $ buildPath (vanillaContext stage ghcPrim)
+ return [path -/- "GHC/Prim.hs", path -/- "GHC/PrimopWrappers.hs"]
+
+derivedConstantsDependencies :: [FilePath]
+derivedConstantsDependencies = fmap (generatedDir -/-)
+ [ "DerivedConstants.h"
+ , "GHCConstantsHaskellExports.hs"
+ , "GHCConstantsHaskellType.hs"
+ , "GHCConstantsHaskellWrappers.hs" ]
+
+compilerDependencies :: Expr [FilePath]
+compilerDependencies = do
+ root <- getBuildRoot
+ stage <- getStage
+ isGmp <- (== integerGmp) <$> getIntegerPackage
+ ghcPath <- expr $ buildPath (vanillaContext stage compiler)
+ gmpPath <- expr gmpBuildPath
+ rtsPath <- expr rtsBuildPath
+ mconcat [ return [root -/- platformH stage]
+ , return ((root -/-) <$> includesDependencies)
+ , return ((root -/-) <$> derivedConstantsDependencies)
+ , notStage0 ? isGmp ? return [gmpPath -/- gmpLibraryH]
+ , notStage0 ? return ((rtsPath -/-) <$> libffiDependencies)
+ , return $ fmap (ghcPath -/-)
+ [ "primop-can-fail.hs-incl"
+ , "primop-code-size.hs-incl"
+ , "primop-commutable.hs-incl"
+ , "primop-data-decl.hs-incl"
+ , "primop-fixity.hs-incl"
+ , "primop-has-side-effects.hs-incl"
+ , "primop-list.hs-incl"
+ , "primop-out-of-line.hs-incl"
+ , "primop-primop-info.hs-incl"
+ , "primop-strictness.hs-incl"
+ , "primop-tag.hs-incl"
+ , "primop-vector-tycons.hs-incl"
+ , "primop-vector-tys-exports.hs-incl"
+ , "primop-vector-tys.hs-incl"
+ , "primop-vector-uniques.hs-incl" ] ]
+
+generatedDependencies :: Expr [FilePath]
+generatedDependencies = do
+ root <- getBuildRoot
+ rtsPath <- expr rtsBuildPath
+ mconcat [ package compiler ? compilerDependencies
+ , package ghcPrim ? ghcPrimDependencies
+ , package rts ? return (fmap (rtsPath -/-) libffiDependencies
+ ++ fmap (root -/-) includesDependencies
+ ++ fmap (root -/-) derivedConstantsDependencies)
+ , stage0 ? return (fmap (root -/-) includesDependencies) ]
+
+generate :: FilePath -> Context -> Expr String -> Action ()
+generate file context expr = do
+ contents <- interpretInContext context expr
+ writeFileChanged file contents
+ putSuccess $ "| Successfully generated " ++ file ++ "."
+
+generatePackageCode :: Context -> Rules ()
+generatePackageCode context@(Context stage pkg _) = do
+ root <- buildRootRules
+ let dir = buildDir context
+ generated f = (root -/- dir ++ "//*.hs") ?== f && not ("//autogen/*" ?== f)
+ go gen file = generate file context gen
+ generated ?> \file -> do
+ let unpack = fromMaybe . error $ "No generator for " ++ file ++ "."
+ (src, builder) <- unpack <$> findGenerator context file
+ need [src]
+ build $ target context builder [src] [file]
+ let boot = src -<.> "hs-boot"
+ whenM (doesFileExist boot) . copyFile boot $ file -<.> "hs-boot"
+
+ priority 2.0 $ do
+ when (pkg == compiler) $ do root <//> dir -/- "Config.hs" %> go generateConfigHs
+ root <//> dir -/- "*.hs-incl" %> genPrimopCode context
+ when (pkg == ghcPrim) $ do (root <//> dir -/- "GHC/Prim.hs") %> genPrimopCode context
+ (root <//> dir -/- "GHC/PrimopWrappers.hs") %> genPrimopCode context
+ when (pkg == ghcPkg) $ do root <//> dir -/- "Version.hs" %> go generateVersionHs
+
+ -- TODO: needing platformH is ugly and fragile
+ when (pkg == compiler) $ do
+ root -/- primopsTxt stage %> \file -> do
+ root <- buildRoot
+ need $ [ root -/- platformH stage
+ , primopsSource]
+ ++ fmap (root -/-) includesDependencies
+ build $ target context HsCpp [primopsSource] [file]
+
+ -- only generate this once! Until we have the include logic fixed.
+ -- See the note on `platformH`
+ when (stage == Stage0) $ do
+ root <//> "compiler/ghc_boot_platform.h" %> go generateGhcBootPlatformH
+ root <//> platformH stage %> go generateGhcBootPlatformH
+
+ when (pkg == rts) $ do
+ root <//> dir -/- "cmm/AutoApply.cmm" %> \file ->
+ build $ target context GenApply [] [file]
+ -- XXX: this should be fixed properly, e.g. generated here on demand.
+ (root <//> dir -/- "DerivedConstants.h") <~ (buildRoot <&> (-/- generatedDir))
+ (root <//> dir -/- "ghcautoconf.h") <~ (buildRoot <&> (-/- generatedDir))
+ (root <//> dir -/- "ghcplatform.h") <~ (buildRoot <&> (-/- generatedDir))
+ (root <//> dir -/- "ghcversion.h") <~ (buildRoot <&> (-/- generatedDir))
+ when (pkg == integerGmp) $ do
+ (root <//> dir -/- "ghc-gmp.h") <~ (buildRoot <&> (-/- "include"))
+ where
+ pattern <~ mdir = pattern %> \file -> do
+ dir <- mdir
+ copyFile (dir -/- takeFileName file) file
+
+genPrimopCode :: Context -> FilePath -> Action ()
+genPrimopCode context@(Context stage _pkg _) file = do
+ root <- buildRoot
+ need [root -/- primopsTxt stage]
+ build $ target context GenPrimopCode [root -/- primopsTxt stage] [file]
+
+copyRules :: Rules ()
+copyRules = do
+ root <- buildRootRules
+ forM_ [Stage0 ..] $ \stage -> do
+ let prefix = root -/- stageString stage -/- "lib"
+ prefix -/- "ghc-usage.txt" <~ return "driver"
+ prefix -/- "ghci-usage.txt" <~ return "driver"
+ prefix -/- "llvm-targets" <~ return "."
+ prefix -/- "llvm-passes" <~ return "."
+ prefix -/- "platformConstants" <~ (buildRoot <&> (-/- generatedDir))
+ prefix -/- "settings" <~ return "."
+ prefix -/- "template-hsc.h" <~ return (pkgPath hsc2hs)
+ where
+ infixl 1 <~
+ pattern <~ mdir = pattern %> \file -> do
+ dir <- mdir
+ copyFile (dir -/- takeFileName file) file
+
+generateRules :: Rules ()
+generateRules = do
+ root <- buildRootRules
+ priority 2.0 $ (root -/- generatedDir -/- "ghcautoconf.h") <~ generateGhcAutoconfH
+ priority 2.0 $ (root -/- generatedDir -/- "ghcplatform.h") <~ generateGhcPlatformH
+ priority 2.0 $ (root -/- generatedDir -/- "ghcversion.h") <~ generateGhcVersionH
+
+ forM_ [Stage0 ..] $ \stage ->
+ root -/- ghcSplitPath stage %> \path -> do
+ generate path emptyTarget generateGhcSplit
+ makeExecutable path
+
+ -- TODO: simplify, get rid of fake rts context
+ root -/- generatedDir ++ "//*" %> \file -> do
+ withTempDir $ \dir -> build $
+ target rtsContext DeriveConstants [] [file, dir]
+ where
+ file <~ gen = file %> \out -> generate out emptyTarget gen
+
+-- TODO: Use the Types, Luke! (drop partial function)
+-- We sometimes need to evaluate expressions that do not require knowing all
+-- information about the context. In this case, we don't want to know anything.
+emptyTarget :: Context
+emptyTarget = vanillaContext (error "Rules.Generate.emptyTarget: unknown stage")
+ (error "Rules.Generate.emptyTarget: unknown package")
+
+-- Generators
+
+-- | Given a 'String' replace charaters '.' and '-' by underscores ('_') so that
+-- the resulting 'String' is a valid C preprocessor identifier.
+cppify :: String -> String
+cppify = replaceEq '-' '_' . replaceEq '.' '_'
+
+ghcSplitSource :: FilePath
+ghcSplitSource = "driver/split/ghc-split.pl"
+
+-- ref: rules/build-perl.mk
+-- | Generate the @ghc-split@ Perl script.
+generateGhcSplit :: Expr String
+generateGhcSplit = do
+ trackGenerateHs
+ targetPlatform <- getSetting TargetPlatform
+ ghcEnableTNC <- expr $ yesNo <$> ghcEnableTablesNextToCode
+ perlPath <- getBuilderPath Perl
+ contents <- expr $ readFileLines ghcSplitSource
+ return . unlines $
+ [ "#!" ++ perlPath
+ , "my $TARGETPLATFORM = " ++ show targetPlatform ++ ";"
+ -- I don't see where the ghc-split tool uses TNC, but
+ -- it's in the build-perl macro.
+ , "my $TABLES_NEXT_TO_CODE = " ++ show ghcEnableTNC ++ ";"
+ ] ++ contents
+
+-- | Generate @ghcplatform.h@ header.
+generateGhcPlatformH :: Expr String
+generateGhcPlatformH = do
+ trackGenerateHs
+ hostPlatform <- getSetting HostPlatform
+ hostArch <- getSetting HostArch
+ hostOs <- getSetting HostOs
+ hostVendor <- getSetting HostVendor
+ targetPlatform <- getSetting TargetPlatform
+ targetArch <- getSetting TargetArch
+ targetOs <- getSetting TargetOs
+ targetVendor <- getSetting TargetVendor
+ ghcUnreg <- getFlag GhcUnregisterised
+ return . unlines $
+ [ "#ifndef __GHCPLATFORM_H__"
+ , "#define __GHCPLATFORM_H__"
+ , ""
+ , "#define BuildPlatform_TYPE " ++ cppify hostPlatform
+ , "#define HostPlatform_TYPE " ++ cppify targetPlatform
+ , ""
+ , "#define " ++ cppify hostPlatform ++ "_BUILD 1"
+ , "#define " ++ cppify targetPlatform ++ "_HOST 1"
+ , ""
+ , "#define " ++ hostArch ++ "_BUILD_ARCH 1"
+ , "#define " ++ targetArch ++ "_HOST_ARCH 1"
+ , "#define BUILD_ARCH " ++ show hostArch
+ , "#define HOST_ARCH " ++ show targetArch
+ , ""
+ , "#define " ++ hostOs ++ "_BUILD_OS 1"
+ , "#define " ++ targetOs ++ "_HOST_OS 1"
+ , "#define BUILD_OS " ++ show hostOs
+ , "#define HOST_OS " ++ show targetOs
+ , ""
+ , "#define " ++ hostVendor ++ "_BUILD_VENDOR 1"
+ , "#define " ++ targetVendor ++ "_HOST_VENDOR 1"
+ , "#define BUILD_VENDOR " ++ show hostVendor
+ , "#define HOST_VENDOR " ++ show targetVendor
+ , ""
+ , "/* These TARGET macros are for backwards compatibility... DO NOT USE! */"
+ , "#define TargetPlatform_TYPE " ++ cppify targetPlatform
+ , "#define " ++ cppify targetPlatform ++ "_TARGET 1"
+ , "#define " ++ targetArch ++ "_TARGET_ARCH 1"
+ , "#define TARGET_ARCH " ++ show targetArch
+ , "#define " ++ targetOs ++ "_TARGET_OS 1"
+ , "#define TARGET_OS " ++ show targetOs
+ , "#define " ++ targetVendor ++ "_TARGET_VENDOR 1" ]
+ ++
+ [ "#define UnregisterisedCompiler 1" | ghcUnreg ]
+ ++
+ [ "\n#endif /* __GHCPLATFORM_H__ */" ]
+
+-- | Generate @Config.hs@ files.
+generateConfigHs :: Expr String
+generateConfigHs = do
+ trackGenerateHs
+ cProjectName <- getSetting ProjectName
+ cProjectGitCommitId <- getSetting ProjectGitCommitId
+ cProjectVersion <- getSetting ProjectVersion
+ cProjectVersionInt <- getSetting ProjectVersionInt
+ cProjectPatchLevel <- getSetting ProjectPatchLevel
+ cProjectPatchLevel1 <- getSetting ProjectPatchLevel1
+ cProjectPatchLevel2 <- getSetting ProjectPatchLevel2
+ cBooterVersion <- getSetting GhcVersion
+ intLib <- getIntegerPackage
+ debugged <- ghcDebugged <$> expr flavour
+ let cIntegerLibraryType
+ | intLib == integerGmp = "IntegerGMP"
+ | intLib == integerSimple = "IntegerSimple"
+ | otherwise = error $ "Unknown integer library: " ++ pkgName intLib
+ cSupportsSplitObjs <- expr $ yesNo <$> supportsSplitObjects
+ cGhcWithInterpreter <- expr $ yesNo <$> ghcWithInterpreter
+ cGhcWithNativeCodeGen <- expr $ yesNo <$> ghcWithNativeCodeGen
+ cGhcWithSMP <- expr $ yesNo <$> ghcWithSMP
+ cGhcEnableTablesNextToCode <- expr $ yesNo <$> ghcEnableTablesNextToCode
+ cLeadingUnderscore <- expr $ yesNo <$> flag LeadingUnderscore
+ cGHC_UNLIT_PGM <- fmap takeFileName $ getBuilderPath Unlit
+ cLibFFI <- expr useLibFFIForAdjustors
+ rtsWays <- getRtsWays
+ cGhcRtsWithLibdw <- getFlag WithLibdw
+ let cGhcRTSWays = unwords $ map show rtsWays
+ return $ unlines
+ [ "{-# LANGUAGE CPP #-}"
+ , "module Config where"
+ , ""
+ , "import GhcPrelude"
+ , ""
+ , "#include \"ghc_boot_platform.h\""
+ , ""
+ , "data IntegerLibrary = IntegerGMP"
+ , " | IntegerSimple"
+ , " deriving Eq"
+ , ""
+ , "cBuildPlatformString :: String"
+ , "cBuildPlatformString = BuildPlatform_NAME"
+ , "cHostPlatformString :: String"
+ , "cHostPlatformString = HostPlatform_NAME"
+ , "cTargetPlatformString :: String"
+ , "cTargetPlatformString = TargetPlatform_NAME"
+ , ""
+ , "cProjectName :: String"
+ , "cProjectName = " ++ show cProjectName
+ , "cProjectGitCommitId :: String"
+ , "cProjectGitCommitId = " ++ show cProjectGitCommitId
+ , "cProjectVersion :: String"
+ , "cProjectVersion = " ++ show cProjectVersion
+ , "cProjectVersionInt :: String"
+ , "cProjectVersionInt = " ++ show cProjectVersionInt
+ , "cProjectPatchLevel :: String"
+ , "cProjectPatchLevel = " ++ show cProjectPatchLevel
+ , "cProjectPatchLevel1 :: String"
+ , "cProjectPatchLevel1 = " ++ show cProjectPatchLevel1
+ , "cProjectPatchLevel2 :: String"
+ , "cProjectPatchLevel2 = " ++ show cProjectPatchLevel2
+ , "cBooterVersion :: String"
+ , "cBooterVersion = " ++ show cBooterVersion
+ , "cStage :: String"
+ , "cStage = show (STAGE :: Int)"
+ , "cIntegerLibrary :: String"
+ , "cIntegerLibrary = " ++ show (pkgName intLib)
+ , "cIntegerLibraryType :: IntegerLibrary"
+ , "cIntegerLibraryType = " ++ cIntegerLibraryType
+ , "cSupportsSplitObjs :: String"
+ , "cSupportsSplitObjs = " ++ show cSupportsSplitObjs
+ , "cGhcWithInterpreter :: String"
+ , "cGhcWithInterpreter = " ++ show cGhcWithInterpreter
+ , "cGhcWithNativeCodeGen :: String"
+ , "cGhcWithNativeCodeGen = " ++ show cGhcWithNativeCodeGen
+ , "cGhcWithSMP :: String"
+ , "cGhcWithSMP = " ++ show cGhcWithSMP
+ , "cGhcRTSWays :: String"
+ , "cGhcRTSWays = " ++ show cGhcRTSWays
+ , "cGhcEnableTablesNextToCode :: String"
+ , "cGhcEnableTablesNextToCode = " ++ show cGhcEnableTablesNextToCode
+ , "cLeadingUnderscore :: String"
+ , "cLeadingUnderscore = " ++ show cLeadingUnderscore
+ , "cGHC_UNLIT_PGM :: String"
+ , "cGHC_UNLIT_PGM = " ++ show cGHC_UNLIT_PGM
+ , "cGHC_SPLIT_PGM :: String"
+ , "cGHC_SPLIT_PGM = " ++ show "ghc-split"
+ , "cLibFFI :: Bool"
+ , "cLibFFI = " ++ show cLibFFI
+ , "cGhcThreaded :: Bool"
+ , "cGhcThreaded = " ++ show (any (wayUnit Threaded) rtsWays)
+ , "cGhcDebugged :: Bool"
+ , "cGhcDebugged = " ++ show debugged
+ , "cGhcRtsWithLibdw :: Bool"
+ , "cGhcRtsWithLibdw = " ++ show cGhcRtsWithLibdw ]
+
+-- | Generate @ghcautoconf.h@ header.
+generateGhcAutoconfH :: Expr String
+generateGhcAutoconfH = do
+ trackGenerateHs
+ configHContents <- expr $ map undefinePackage <$> readFileLines configH
+ tablesNextToCode <- expr ghcEnableTablesNextToCode
+ ghcUnreg <- getFlag GhcUnregisterised
+ ccLlvmBackend <- getSetting CcLlvmBackend
+ ccClangBackend <- getSetting CcClangBackend
+ return . unlines $
+ [ "#ifndef __GHCAUTOCONF_H__"
+ , "#define __GHCAUTOCONF_H__" ]
+ ++ configHContents ++
+ [ "\n#define TABLES_NEXT_TO_CODE 1" | tablesNextToCode && not ghcUnreg ]
+ ++
+ [ "\n#define llvm_CC_FLAVOR 1" | ccLlvmBackend == "1" ]
+ ++
+ [ "\n#define clang_CC_FLAVOR 1" | ccClangBackend == "1" ]
+ ++
+ [ "#endif /* __GHCAUTOCONF_H__ */" ]
+ where
+ undefinePackage s
+ | "#define PACKAGE_" `isPrefixOf` s
+ = "/* #undef " ++ takeWhile (/=' ') (drop 8 s) ++ " */"
+ | otherwise = s
+
+-- | Generate @ghc_boot_platform.h@ headers.
+generateGhcBootPlatformH :: Expr String
+generateGhcBootPlatformH = do
+ trackGenerateHs
+ stage <- getStage
+ let chooseSetting x y = getSetting $ if stage == Stage0 then x else y
+ buildPlatform <- chooseSetting BuildPlatform HostPlatform
+ buildArch <- chooseSetting BuildArch HostArch
+ buildOs <- chooseSetting BuildOs HostOs
+ buildVendor <- chooseSetting BuildVendor HostVendor
+ hostPlatform <- chooseSetting HostPlatform TargetPlatform
+ hostArch <- chooseSetting HostArch TargetArch
+ hostOs <- chooseSetting HostOs TargetOs
+ hostVendor <- chooseSetting HostVendor TargetVendor
+ targetPlatform <- getSetting TargetPlatform
+ targetArch <- getSetting TargetArch
+ llvmTarget <- getSetting LlvmTarget
+ targetOs <- getSetting TargetOs
+ targetVendor <- getSetting TargetVendor
+ return $ unlines
+ [ "#ifndef __PLATFORM_H__"
+ , "#define __PLATFORM_H__"
+ , ""
+ , "#define BuildPlatform_NAME " ++ show buildPlatform
+ , "#define HostPlatform_NAME " ++ show hostPlatform
+ , "#define TargetPlatform_NAME " ++ show targetPlatform
+ , ""
+ , "#define " ++ cppify buildPlatform ++ "_BUILD 1"
+ , "#define " ++ cppify hostPlatform ++ "_HOST 1"
+ , "#define " ++ cppify targetPlatform ++ "_TARGET 1"
+ , ""
+ , "#define " ++ buildArch ++ "_BUILD_ARCH 1"
+ , "#define " ++ hostArch ++ "_HOST_ARCH 1"
+ , "#define " ++ targetArch ++ "_TARGET_ARCH 1"
+ , "#define BUILD_ARCH " ++ show buildArch
+ , "#define HOST_ARCH " ++ show hostArch
+ , "#define TARGET_ARCH " ++ show targetArch
+ , "#define LLVM_TARGET " ++ show llvmTarget
+ , ""
+ , "#define " ++ buildOs ++ "_BUILD_OS 1"
+ , "#define " ++ hostOs ++ "_HOST_OS 1"
+ , "#define " ++ targetOs ++ "_TARGET_OS 1"
+ , "#define BUILD_OS " ++ show buildOs
+ , "#define HOST_OS " ++ show hostOs
+ , "#define TARGET_OS " ++ show targetOs
+ , ""
+ , "#define " ++ buildVendor ++ "_BUILD_VENDOR 1"
+ , "#define " ++ hostVendor ++ "_HOST_VENDOR 1"
+ , "#define " ++ targetVendor ++ "_TARGET_VENDOR 1"
+ , "#define BUILD_VENDOR " ++ show buildVendor
+ , "#define HOST_VENDOR " ++ show hostVendor
+ , "#define TARGET_VENDOR " ++ show targetVendor
+ , ""
+ , "#endif /* __PLATFORM_H__ */" ]
+
+-- | Generate @ghcversion.h@ header.
+generateGhcVersionH :: Expr String
+generateGhcVersionH = do
+ trackGenerateHs
+ version <- getSetting ProjectVersionInt
+ patchLevel1 <- getSetting ProjectPatchLevel1
+ patchLevel2 <- getSetting ProjectPatchLevel2
+ return . unlines $
+ [ "#ifndef __GHCVERSION_H__"
+ , "#define __GHCVERSION_H__"
+ , ""
+ , "#ifndef __GLASGOW_HASKELL__"
+ , "# define __GLASGOW_HASKELL__ " ++ version
+ , "#endif"
+ , ""]
+ ++
+ [ "#define __GLASGOW_HASKELL_PATCHLEVEL1__ " ++ patchLevel1 | patchLevel1 /= "" ]
+ ++
+ [ "#define __GLASGOW_HASKELL_PATCHLEVEL2__ " ++ patchLevel2 | patchLevel2 /= "" ]
+ ++
+ [ ""
+ , "#define MIN_VERSION_GLASGOW_HASKELL(ma,mi,pl1,pl2) (\\"
+ , " ((ma)*100+(mi)) < __GLASGOW_HASKELL__ || \\"
+ , " ((ma)*100+(mi)) == __GLASGOW_HASKELL__ \\"
+ , " && (pl1) < __GLASGOW_HASKELL_PATCHLEVEL1__ || \\"
+ , " ((ma)*100+(mi)) == __GLASGOW_HASKELL__ \\"
+ , " && (pl1) == __GLASGOW_HASKELL_PATCHLEVEL1__ \\"
+ , " && (pl2) <= __GLASGOW_HASKELL_PATCHLEVEL2__ )"
+ , ""
+ , "#endif /* __GHCVERSION_H__ */" ]
+
+-- | Generate @Version.hs@ files.
+generateVersionHs :: Expr String
+generateVersionHs = do
+ trackGenerateHs
+ projectVersion <- getSetting ProjectVersion
+ targetOs <- getSetting TargetOs
+ targetArch <- getSetting TargetArch
+ return $ unlines
+ [ "module Version where"
+ , "version, targetOS, targetARCH :: String"
+ , "version = " ++ show projectVersion
+ , "targetOS = " ++ show targetOs
+ , "targetARCH = " ++ show targetArch ]
diff --git a/hadrian/src/Rules/Gmp.hs b/hadrian/src/Rules/Gmp.hs
new file mode 100644
index 0000000000..32265fe401
--- /dev/null
+++ b/hadrian/src/Rules/Gmp.hs
@@ -0,0 +1,123 @@
+module Rules.Gmp (
+ gmpRules, gmpBuildPath, gmpObjectsDir, gmpLibraryH
+ ) where
+
+import Base
+import Context
+import Oracles.Setting
+import Packages
+import Target
+import Utilities
+
+gmpBase :: FilePath
+gmpBase = pkgPath integerGmp -/- "gmp"
+
+gmpLibraryInTreeH :: FilePath
+gmpLibraryInTreeH = "include/gmp.h"
+
+gmpLibrary :: FilePath
+gmpLibrary = ".libs/libgmp.a"
+
+-- | GMP is considered a Stage1 package. This determines GMP build directory.
+gmpContext :: Context
+gmpContext = vanillaContext Stage1 integerGmp
+
+-- TODO: Location of 'gmpBuildPath' is important: it should be outside any
+-- package build directory, as otherwise GMP's object files will match build
+-- patterns of 'compilePackage' rules. We could make 'compilePackage' rules
+-- more precise to avoid such spurious matching.
+-- | Build directory for in-tree GMP library.
+gmpBuildPath :: Action FilePath
+gmpBuildPath = buildRoot <&> (-/- stageString (stage gmpContext) -/- "gmp")
+
+-- | GMP library header, relative to 'gmpBuildPath'.
+gmpLibraryH :: FilePath
+gmpLibraryH = "include/ghc-gmp.h"
+
+-- | Directory for GMP library object files, relative to 'gmpBuildPath'.
+gmpObjectsDir :: FilePath
+gmpObjectsDir = "objs"
+
+configureEnvironment :: Action [CmdOption]
+configureEnvironment = sequence [ builderEnvironment "CC" $ Cc CompileC Stage1
+ , builderEnvironment "AR" (Ar Unpack Stage1)
+ , builderEnvironment "NM" Nm ]
+
+gmpRules :: Rules ()
+gmpRules = do
+ -- Copy appropriate GMP header and object files
+ root <- buildRootRules
+ root <//> gmpLibraryH %> \header -> do
+ windows <- windowsHost
+ configMk <- readFile' =<< (buildPath gmpContext <&> (-/- "config.mk"))
+ if not windows && -- TODO: We don't use system GMP on Windows. Fix?
+ any (`isInfixOf` configMk) [ "HaveFrameworkGMP = YES", "HaveLibGmp = YES" ]
+ then do
+ putBuild "| GMP library/framework detected and will be used"
+ copyFile (gmpBase -/- "ghc-gmp.h") header
+ else do
+ putBuild "| No GMP library/framework detected; in tree GMP will be built"
+ gmpPath <- gmpBuildPath
+ need [gmpPath -/- gmpLibrary]
+ createDirectory (gmpPath -/- gmpObjectsDir)
+ top <- topDirectory
+ build $ target gmpContext (Ar Unpack Stage1)
+ [top -/- gmpPath -/- gmpLibrary] [gmpPath -/- gmpObjectsDir]
+ copyFile (gmpPath -/- "gmp.h") header
+ copyFile (gmpPath -/- "gmp.h") (gmpPath -/- gmpLibraryInTreeH)
+
+ -- Build in-tree GMP library, prioritised so that it matches "before"
+ -- the generic .a library rule in Rules.Library, whenever applicable.
+ priority 2.0 $ root <//> gmpLibrary %> \lib -> do
+ gmpPath <- gmpBuildPath
+ build $ target gmpContext (Make gmpPath) [gmpPath -/- "Makefile"] [lib]
+ putSuccess "| Successfully built custom library 'gmp'"
+
+ -- In-tree GMP header is built by the gmpLibraryH rule
+ root <//> gmpLibraryInTreeH %> \_ -> do
+ gmpPath <- gmpBuildPath
+ need [gmpPath -/- gmpLibraryH]
+
+ -- This causes integerGmp package to be configured, hence creating the files
+ root <//> "gmp/config.mk" %> \_ -> do
+ -- Calling 'need' on @setup-config@ triggers 'configurePackage'.
+ -- TODO: Shall we run 'configurePackage' directly? Why this indirection?
+ setupConfig <- pkgSetupConfigFile gmpContext
+ need [setupConfig]
+
+ -- TODO: Get rid of hard-coded @gmp@.
+ -- Run GMP's configure script
+ root <//> "gmp/Makefile" %> \mk -> do
+ env <- configureEnvironment
+ gmpPath <- gmpBuildPath
+ need [mk <.> "in"]
+ buildWithCmdOptions env $
+ target gmpContext (Configure gmpPath) [mk <.> "in"] [mk]
+
+ -- Extract in-tree GMP sources and apply patches
+ root <//> "gmp/Makefile.in" %> \_ -> do
+ gmpPath <- gmpBuildPath
+ removeDirectory gmpPath
+ -- Note: We use a tarball like gmp-4.2.4-nodoc.tar.bz2, which is
+ -- gmp-4.2.4.tar.bz2 repacked without the doc/ directory contents.
+ -- That's because the doc/ directory contents are under the GFDL,
+ -- which causes problems for Debian.
+ tarball <- unifyPath . fromSingleton "Exactly one GMP tarball is expected"
+ <$> getDirectoryFiles "" [gmpBase -/- "gmp-tarballs/gmp*.tar.bz2"]
+
+ withTempDir $ \dir -> do
+ let tmp = unifyPath dir
+ need [tarball]
+ build $ target gmpContext (Tar Extract) [tarball] [tmp]
+
+ let patch = gmpBase -/- "gmpsrc.patch"
+ patchName = takeFileName patch
+ copyFile patch $ tmp -/- patchName
+ applyPatch tmp patchName
+
+ let name = dropExtension . dropExtension $ takeFileName tarball
+ unpack = fromMaybe . error $ "gmpRules: expected suffix "
+ ++ "-nodoc (found: " ++ name ++ ")."
+ libName = unpack $ stripSuffix "-nodoc" name
+
+ moveDirectory (tmp -/- libName) gmpPath
diff --git a/hadrian/src/Rules/Libffi.hs b/hadrian/src/Rules/Libffi.hs
new file mode 100644
index 0000000000..58ac1efbdc
--- /dev/null
+++ b/hadrian/src/Rules/Libffi.hs
@@ -0,0 +1,109 @@
+module Rules.Libffi (libffiRules, libffiDependencies) where
+
+import Hadrian.Utilities
+
+import Packages
+import Settings.Builders.Common
+import Target
+import Utilities
+
+libffiDependencies :: [FilePath]
+libffiDependencies = ["ffi.h", "ffitarget.h"]
+
+libffiLibrary :: FilePath
+libffiLibrary = "inst/lib/libffi.a"
+
+rtsLibffiLibrary :: Way -> Action FilePath
+rtsLibffiLibrary way = do
+ name <- libffiLibraryName
+ suf <- libsuf way
+ rtsPath <- rtsBuildPath
+ return $ rtsPath -/- "lib" ++ name ++ suf
+
+fixLibffiMakefile :: FilePath -> String -> String
+fixLibffiMakefile top =
+ replace "-MD" "-MMD"
+ . replace "@toolexeclibdir@" "$(libdir)"
+ . replace "@INSTALL@" ("$(subst ../install-sh," ++ top ++ "/install-sh,@INSTALL@)")
+
+-- TODO: check code duplication w.r.t. ConfCcArgs
+configureEnvironment :: Action [CmdOption]
+configureEnvironment = do
+ cFlags <- interpretInContext libffiContext $ mconcat
+ [ cArgs
+ , getStagedSettingList ConfCcArgs ]
+ ldFlags <- interpretInContext libffiContext ldArgs
+ sequence [ builderEnvironment "CC" $ Cc CompileC Stage1
+ , builderEnvironment "CXX" $ Cc CompileC Stage1
+ , builderEnvironment "LD" (Ld Stage1)
+ , builderEnvironment "AR" (Ar Unpack Stage1)
+ , builderEnvironment "NM" Nm
+ , builderEnvironment "RANLIB" Ranlib
+ , return . AddEnv "CFLAGS" $ unwords cFlags ++ " -w"
+ , return . AddEnv "LDFLAGS" $ unwords ldFlags ++ " -w" ]
+
+libffiRules :: Rules ()
+libffiRules = do
+ root <- buildRootRules
+ fmap ((root <//> "rts/build") -/-) libffiDependencies &%> \_ -> do
+ libffiPath <- libffiBuildPath
+ need [libffiPath -/- libffiLibrary]
+
+ -- we set a higher priority because this overlaps
+ -- with the static lib rule from Rules.Library.libraryRules.
+ priority 2.0 $ root <//> libffiLibrary %> \_ -> do
+ useSystemFfi <- flag UseSystemFfi
+ rtsPath <- rtsBuildPath
+ if useSystemFfi
+ then do
+ ffiIncludeDir <- setting FfiIncludeDir
+ putBuild "| System supplied FFI library will be used"
+ forM_ ["ffi.h", "ffitarget.h"] $ \file ->
+ copyFile (ffiIncludeDir -/- file) (rtsPath -/- file)
+ putSuccess "| Successfully copied system FFI library header files"
+ else do
+ libffiPath <- libffiBuildPath
+ build $ target libffiContext (Make libffiPath) [] []
+
+ hs <- getDirectoryFiles "" [libffiPath -/- "inst/include/*"]
+ forM_ hs $ \header ->
+ copyFile header (rtsPath -/- takeFileName header)
+
+ ways <- interpretInContext libffiContext (getLibraryWays <> getRtsWays)
+ forM_ (nubOrd ways) $ \way -> do
+ rtsLib <- rtsLibffiLibrary way
+ copyFileUntracked (libffiPath -/- libffiLibrary) rtsLib
+
+ putSuccess "| Successfully built custom library 'libffi'"
+
+ root <//> "libffi/build/Makefile.in" %> \mkIn -> do
+ libffiPath <- libffiBuildPath
+ removeDirectory libffiPath
+ tarball <- unifyPath . fromSingleton "Exactly one LibFFI tarball is expected"
+ <$> getDirectoryFiles "" ["libffi-tarballs/libffi*.tar.gz"]
+
+ need [tarball]
+ -- Go from 'libffi-3.99999+git20171002+77e130c.tar.gz' to 'libffi-3.99999'
+ let libname = takeWhile (/= '+') $ takeFileName tarball
+
+ root <- buildRoot
+ removeDirectory (root -/- libname)
+ -- TODO: Simplify.
+ actionFinally (do
+ build $ target libffiContext (Tar Extract) [tarball] [root]
+ moveDirectory (root -/- libname) libffiPath) $
+ removeFiles root [libname <//> "*"]
+
+ top <- topDirectory
+ fixFile mkIn (fixLibffiMakefile top)
+
+ -- TODO: Get rid of hard-coded @libffi@.
+ root <//> "libffi/build/Makefile" %> \mk -> do
+ need [mk <.> "in"]
+ libffiPath <- libffiBuildPath
+ forM_ ["config.guess", "config.sub"] $ \file ->
+ copyFile file (libffiPath -/- file)
+
+ env <- configureEnvironment
+ buildWithCmdOptions env $
+ target libffiContext (Configure libffiPath) [mk <.> "in"] [mk]
diff --git a/hadrian/src/Rules/Library.hs b/hadrian/src/Rules/Library.hs
new file mode 100644
index 0000000000..b53bcc8a8c
--- /dev/null
+++ b/hadrian/src/Rules/Library.hs
@@ -0,0 +1,305 @@
+module Rules.Library (libraryRules) where
+
+import Data.Functor
+import Hadrian.Haskell.Cabal
+import Hadrian.Haskell.Cabal.Type
+import qualified System.Directory as IO
+import qualified Text.Parsec as Parsec
+
+import Base
+import Context
+import Expression hiding (way, package)
+import Flavour
+import Oracles.ModuleFiles
+import Packages
+import Rules.Gmp
+import Settings
+import Target
+import Utilities
+
+-- * Library 'Rules'
+
+libraryRules :: Rules ()
+libraryRules = do
+ root <- buildRootRules
+ root -/- "//libHS*-*.dylib" %> buildDynamicLibUnix root "dylib"
+ root -/- "//libHS*-*.so" %> buildDynamicLibUnix root "so"
+ root -/- "//*.a" %> buildStaticLib root
+ priority 2 $ root -/- "//HS*-*.o" %> buildGhciLibO root
+
+-- * 'Action's for building libraries
+
+-- | Build a static library ('LibA') under the given build root, whose path is
+-- the second argument.
+buildStaticLib :: FilePath -> FilePath -> Action ()
+buildStaticLib root archivePath = do
+ l@(BuildPath _ stage _ (LibA pkgname _ way))
+ <- parsePath (parseBuildLibA root)
+ "<.a library (build) path parser>"
+ archivePath
+ let context = libAContext l
+ objs <- libraryObjects context
+ removeFile archivePath
+ build $ target context (Ar Pack stage) objs [archivePath]
+ synopsis <- pkgSynopsis (package context)
+ putSuccess $ renderLibrary
+ (quote pkgname ++ " (" ++ show stage ++ ", way " ++ show way ++ ").")
+ archivePath synopsis
+
+-- | Build a dynamic library ('LibDyn') under the given build root, with the
+-- given suffix (@.so@ or @.dylib@, @.dll@ in the future), where the complete
+-- path of the archive to build is given as the third argument.
+buildDynamicLibUnix :: FilePath -> String -> FilePath -> Action ()
+buildDynamicLibUnix root suffix dynlibpath = do
+ dynlib <- parsePath (parseBuildLibDyn root suffix) "<dyn lib parser>" dynlibpath
+ let context = libDynContext dynlib
+ deps <- contextDependencies context
+ need =<< mapM pkgLibraryFile deps
+ objs <- libraryObjects context
+ build $ target context (Ghc LinkHs $ Context.stage context) objs [dynlibpath]
+
+-- | Build a "GHCi library" ('LibGhci') under the given build root, with the
+-- complete path of the file to build is given as the second argument.
+buildGhciLibO :: FilePath -> FilePath -> Action ()
+buildGhciLibO root ghcilibPath = do
+ l@(BuildPath _ stage _ (LibGhci _ _ _))
+ <- parsePath (parseBuildLibGhci root)
+ "<.o ghci lib (build) path parser>"
+ ghcilibPath
+ let context = libGhciContext l
+ objs <- allObjects context
+ need objs
+ build $ target context (Ld stage) objs [ghcilibPath]
+
+-- * Helpers
+
+-- | Return all Haskell and non-Haskell object files for the given 'Context'.
+allObjects :: Context -> Action [FilePath]
+allObjects context = (++) <$> nonHsObjects context <*> hsObjects context
+
+-- | Return all the non-Haskell object files for the given library context
+-- (object files built from C, C-- and sometimes other things).
+nonHsObjects :: Context -> Action [FilePath]
+nonHsObjects context = do
+ cObjs <- cObjects context
+ cmmSrcs <- interpretInContext context (getContextData cmmSrcs)
+ cmmObjs <- mapM (objectPath context) cmmSrcs
+ eObjs <- extraObjects context
+ return $ cObjs ++ cmmObjs ++ eObjs
+
+-- | Return all the C object files needed to build the given library context.
+cObjects :: Context -> Action [FilePath]
+cObjects context = do
+ srcs <- interpretInContext context (getContextData cSrcs)
+ objs <- mapM (objectPath context) srcs
+ return $ if Threaded `wayUnit` way context
+ then objs
+ else filter ((`notElem` ["Evac_thr", "Scav_thr"]) . takeBaseName) objs
+
+-- | Return extra object files needed to build the given library context. The
+-- resulting list is currently non-empty only when the package from the
+-- 'Context' is @integer-gmp@.
+extraObjects :: Context -> Action [FilePath]
+extraObjects context
+ | package context == integerGmp = do
+ gmpPath <- gmpBuildPath
+ need [gmpPath -/- gmpLibraryH]
+ map unifyPath <$> getDirectoryFiles "" [gmpPath -/- gmpObjectsDir -/- "*.o"]
+ | otherwise = return []
+
+-- | Return all the object files to be put into the library we're building for
+-- the given 'Context'.
+libraryObjects :: Context -> Action [FilePath]
+libraryObjects context@Context{..} = do
+ hsObjs <- hsObjects context
+ noHsObjs <- nonHsObjects context
+
+ -- This will create split objects if required (we don't track them
+ -- explicitly as this would needlessly bloat the Shake database).
+ need $ noHsObjs ++ hsObjs
+
+ split <- interpretInContext context =<< splitObjects <$> flavour
+ let getSplitObjs = concatForM hsObjs $ \obj -> do
+ let dir = dropExtension obj ++ "_" ++ osuf way ++ "_split"
+ contents <- liftIO $ IO.getDirectoryContents dir
+ return . map (dir -/-) $ filter (not . all (== '.')) contents
+
+ (noHsObjs ++) <$> if split then getSplitObjs else return hsObjs
+
+-- * Library paths types and parsers
+
+-- | > libHS<pkg name>-<pkg version>[_<way suffix>].a
+data LibA = LibA String [Integer] Way deriving (Eq, Show)
+
+-- | > <so or dylib>
+data DynLibExt = So | Dylib deriving (Eq, Show)
+
+-- | > libHS<pkg name>-<pkg version>-ghc<ghc version>[_<way suffix>].<so or dylib>
+data LibDyn = LibDyn String [Integer] Way DynLibExt deriving (Eq, Show)
+
+-- | > HS<pkg name>-<pkg version>[_<way suffix>].o
+data LibGhci = LibGhci String [Integer] Way deriving (Eq, Show)
+
+-- | A path of the form
+--
+-- > <build root>/stage<N>/<path/to/pkg/from/ghc/root>/build/<something>
+--
+-- where @something@ describes a library to be build for the given package.
+--
+-- @a@, which represents that @something@, is instantiated as 'LibA', 'LibDyn'
+-- and 'LibGhci' successively in this module, depending on the type of library
+-- we're giving the build rules for.
+data BuildPath a = BuildPath FilePath -- ^ > <build root>/
+ Stage -- ^ > stage<N>/
+ FilePath -- ^ > <path/to/pkg/from/ghc/root>/build/
+ a -- ^ > whatever comes after 'build/'
+ deriving (Eq, Show)
+
+-- | Get the 'Context' corresponding to the build path for a given static library.
+libAContext :: BuildPath LibA -> Context
+libAContext (BuildPath _ stage pkgpath (LibA pkgname _ way)) =
+ Context stage pkg way
+ where
+ pkg = library pkgname pkgpath
+
+-- | Get the 'Context' corresponding to the build path for a given GHCi library.
+libGhciContext :: BuildPath LibGhci -> Context
+libGhciContext (BuildPath _ stage pkgpath (LibGhci pkgname _ way)) =
+ Context stage pkg way
+ where
+ pkg = library pkgname pkgpath
+
+-- | Get the 'Context' corresponding to the build path for a given dynamic library.
+libDynContext :: BuildPath LibDyn -> Context
+libDynContext (BuildPath _ stage pkgpath (LibDyn pkgname _ way _)) =
+ Context stage pkg way
+ where
+ pkg = library pkgname pkgpath
+
+-- | Parse a build path for a library to be built under the given build root,
+-- where the filename will be parsed with the given parser argument.
+parseBuildPath
+ :: FilePath -- ^ build root
+ -> Parsec.Parsec String () a -- ^ what to parse after @build/@
+ -> Parsec.Parsec String () (BuildPath a)
+parseBuildPath root afterBuild = do
+ _ <- Parsec.string root *> Parsec.optional (Parsec.char '/')
+ stage <- parseStage
+ _ <- Parsec.char '/'
+ pkgpath <- Parsec.manyTill Parsec.anyChar (Parsec.try $ Parsec.string "/build/")
+ a <- afterBuild
+ return (BuildPath root stage pkgpath a)
+
+-- | Parse a path to a static library to be built, making sure the path starts
+-- with the given build root.
+parseBuildLibA :: FilePath -> Parsec.Parsec String () (BuildPath LibA)
+parseBuildLibA root = parseBuildPath root parseLibAFilename
+ Parsec.<?> "build path for a static library"
+
+-- | Parse a path to a ghci library to be built, making sure the path starts
+-- with the given build root.
+parseBuildLibGhci :: FilePath -> Parsec.Parsec String () (BuildPath LibGhci)
+parseBuildLibGhci root = parseBuildPath root parseLibGhciFilename
+ Parsec.<?> "build path for a ghci library"
+
+-- | Parse a path to a dynamic library to be built, making sure the path starts
+-- with the given build root.
+parseBuildLibDyn :: FilePath -> String -> Parsec.Parsec String () (BuildPath LibDyn)
+parseBuildLibDyn root ext = parseBuildPath root (parseLibDynFilename ext)
+ Parsec.<?> ("build path for a dynamic library with extension " ++ ext)
+
+-- | Parse the filename of a static library to be built into a 'LibA' value.
+parseLibAFilename :: Parsec.Parsec String () LibA
+parseLibAFilename = do
+ _ <- Parsec.string "libHS"
+ (pkgname, pkgver) <- parsePkgId
+ way <- parseWaySuffix vanilla
+ _ <- Parsec.string ".a"
+ return (LibA pkgname pkgver way)
+
+-- | Parse the filename of a ghci library to be built into a 'LibGhci' value.
+parseLibGhciFilename :: Parsec.Parsec String () LibGhci
+parseLibGhciFilename = do
+ _ <- Parsec.string "HS"
+ (pkgname, pkgver) <- parsePkgId
+ way <- parseWaySuffix vanilla
+ _ <- Parsec.string ".o"
+ return (LibGhci pkgname pkgver way)
+
+-- | Parse the filename of a dynamic library to be built into a 'LibDyn' value.
+parseLibDynFilename :: String -> Parsec.Parsec String () LibDyn
+parseLibDynFilename ext = do
+ _ <- Parsec.string "libHS"
+ (pkgname, pkgver) <- parsePkgId
+ _ <- optional $ Parsec.string "-ghc" *> parsePkgVersion
+ way <- addWayUnit Dynamic <$> parseWaySuffix dynamic
+ _ <- Parsec.string ("." ++ ext)
+ return (LibDyn pkgname pkgver way $ if ext == "so" then So else Dylib)
+
+-- To be kept in sync with Stage.hs's stageString function
+-- | Parse @"stageX"@ into a 'Stage'.
+parseStage :: Parsec.Parsec String () Stage
+parseStage = (Parsec.string "stage" *> Parsec.choice
+ [ Parsec.string (show n) $> toEnum n
+ | n <- map fromEnum [minBound .. maxBound :: Stage]
+ ]) Parsec.<?> "stage string"
+
+-- To be kept in sync with the show instances in 'Way.Type', until we perhaps
+-- use some bidirectional parsing/pretty printing approach or library.
+-- | Parse a way suffix, returning the argument when no suffix is found (the
+-- argument will be vanilla in most cases, but dynamic when we parse the way
+-- suffix out of a shared library file name).
+parseWaySuffix :: Way -> Parsec.Parsec String () Way
+parseWaySuffix w = Parsec.choice
+ [ Parsec.string "_" *> (wayFromUnits <$> Parsec.sepBy1 parseWayUnit (Parsec.string "_"))
+ , pure w
+ ] Parsec.<?> "way suffix (e.g _thr_p, or none for vanilla)"
+ where
+ parseWayUnit = Parsec.choice
+ [ Parsec.string "thr" *> pure Threaded
+ , Parsec.char 'd' *>
+ (Parsec.choice [ Parsec.string "ebug" *> pure Debug
+ , Parsec.string "yn" *> pure Dynamic ])
+ , Parsec.char 'p' *> pure Profiling
+ , Parsec.char 'l' *> pure Logging
+ ] Parsec.<?> "way unit (thr, debug, dyn, p, l)"
+
+-- | Parse a @"pkgname-pkgversion"@ 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>)"
+ 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 ]
+
+-- | Parse "."-separated integers that describe a package's version.
+parsePkgVersion :: Parsec.Parsec String () [Integer]
+parsePkgVersion = fmap reverse (parsePkgVersion' []) Parsec.<?> "package version"
+ where
+ parsePkgVersion' xs = do
+ n <- parseNatural
+ Parsec.choice
+ [ Parsec.try (Parsec.lookAhead (Parsec.char '.' *> (Parsec.letter <|> Parsec.char '_')))
+ $> (n:xs)
+ , Parsec.char '.' *> parsePkgVersion' (n:xs)
+ , pure $ (n:xs) ]
+
+-- | Parse a natural number.
+parseNatural :: Parsec.Parsec String () Integer
+parseNatural = (read <$> Parsec.many1 Parsec.digit) Parsec.<?> "natural number"
+
+-- | Runs the given parser against the given path, erroring out when the parser
+-- fails (because it shouldn't if the code from this module is correct).
+parsePath
+ :: Parsec.Parsec String () a -- ^ parser to run
+ -> String -- ^ string describing the input source
+ -> FilePath -- ^ path to parse
+ -> Action a
+parsePath p inp path = case Parsec.parse p inp path of
+ Left err -> fail $ "Rules.Library.parsePath: path="
+ ++ path ++ ", error:\n" ++ show err
+ Right a -> pure a
diff --git a/hadrian/src/Rules/Nofib.hs b/hadrian/src/Rules/Nofib.hs
new file mode 100644
index 0000000000..0950605199
--- /dev/null
+++ b/hadrian/src/Rules/Nofib.hs
@@ -0,0 +1,57 @@
+module Rules.Nofib where
+
+import Base
+import Expression
+import Oracles.Setting
+import Packages
+
+import System.Environment
+import System.Exit
+
+nofibLogFile :: FilePath
+nofibLogFile = "nofib-log"
+
+-- | Rules for running the @nofib@ benchmark suite.
+nofibRules :: Rules ()
+nofibRules = do
+ root <- buildRootRules
+
+ -- a phony "nofib" rule that just triggers
+ -- the rule below.
+ "nofib" ~> need [root -/- nofibLogFile]
+
+ -- a rule to produce <build root>/nofig-log
+ -- by running the nofib suite and capturing
+ -- the relevant output.
+ root -/- nofibLogFile %> \fp -> do
+ needNofibDeps
+
+ makePath <- builderPath (Make "nofib")
+ top <- topDirectory
+ ghcPath <- builderPath (Ghc CompileHs Stage2)
+ perlPath <- builderPath Perl
+
+ -- some makefiles in nofib rely on a $MAKE
+ -- env var being defined
+ liftIO (setEnv "MAKE" makePath)
+
+ -- this runs make commands in the nofib
+ -- subdirectory, passing the path to
+ -- the GHC to benchmark and perl to
+ -- nofib's makefiles.
+ let nofibArgs = ["WithNofibHc=" ++ (top -/- ghcPath), "PERL=" ++ perlPath]
+ unit $ cmd (Cwd "nofib") [makePath] ["clean"]
+ unit $ cmd (Cwd "nofib") [makePath] (nofibArgs ++ ["boot"])
+ (Exit e, Stdouterr log) <- cmd (Cwd "nofib") [makePath] nofibArgs
+ writeFile' fp log
+ if e == ExitSuccess
+ then putLoud $ "nofib log available at " ++ fp
+ else error $ "nofib failed, full log available at " ++ fp
+
+-- | Build the dependencies required by @nofib@.
+needNofibDeps :: Action ()
+needNofibDeps = do
+ unlitPath <- programPath (vanillaContext Stage1 unlit)
+ mtlPath <- pkgConfFile (vanillaContext Stage1 mtl )
+ need [ unlitPath, mtlPath ]
+ needBuilder (Ghc CompileHs Stage2)
diff --git a/hadrian/src/Rules/Program.hs b/hadrian/src/Rules/Program.hs
new file mode 100644
index 0000000000..f5be21a2e3
--- /dev/null
+++ b/hadrian/src/Rules/Program.hs
@@ -0,0 +1,77 @@
+module Rules.Program (buildProgram) where
+
+import Hadrian.Haskell.Cabal
+import Hadrian.Haskell.Cabal.Type
+
+import Base
+import Context
+import Expression hiding (stage, way)
+import Oracles.Flag
+import Oracles.ModuleFiles
+import Packages
+import Settings
+import Settings.Default
+import Target
+import Utilities
+
+-- | TODO: Drop code duplication
+buildProgram :: [(Resource, Int)] -> Rules ()
+buildProgram rs = do
+ root <- buildRootRules
+ forM_ [Stage0 ..] $ \stage ->
+ [ root -/- stageString stage -/- "bin" -/- "*"
+ , root -/- stageString stage -/- "lib/bin" -/- "*" ] |%> \bin -> do
+ -- This is quite inefficient, but we can't access 'programName' from
+ -- 'Rules', because it is an 'Action' depending on an oracle.
+ sPackages <- filter isProgram <$> stagePackages stage
+ tPackages <- testsuitePackages
+ -- TODO: Shall we use Stage2 for testsuite packages instead?
+ let allPackages = sPackages
+ ++ if stage == Stage1 then tPackages else []
+ nameToCtxList <- forM allPackages $ \pkg -> do
+ let ctx = vanillaContext stage pkg
+ name <- programName ctx
+ return (name <.> exe, ctx)
+
+ case lookup (takeFileName bin) nameToCtxList of
+ Nothing -> error $ "Unknown program " ++ show bin
+ Just (Context {..}) -> do
+ -- Custom dependencies: this should be modeled better in the
+ -- Cabal file somehow.
+ -- TODO: Is this still needed? See 'runtimeDependencies'.
+ when (package == hsc2hs) $ do
+ -- 'Hsc2hs' needs the @template-hsc.h@ file.
+ template <- templateHscPath stage
+ need [template]
+ when (package == ghc) $ do
+ -- GHC depends on @settings@, @platformConstants@,
+ -- @llvm-targets@, @ghc-usage.txt@, @ghci-usage.txt@,
+ -- @llvm-passes@.
+ need =<< ghcDeps stage
+
+ cross <- flag CrossCompiling
+ -- For cross compiler, copy @stage0/bin/<pgm>@ to @stage1/bin/@.
+ case (cross, stage) of
+ (True, s) | s > Stage0 -> do
+ srcDir <- buildRoot <&> (-/- (stageString Stage0 -/- "bin"))
+ copyFile (srcDir -/- takeFileName bin) bin
+ (False, s) | s > Stage0 && (package `elem` [touchy, unlit]) -> do
+ srcDir <- stageLibPath Stage0 <&> (-/- "bin")
+ copyFile (srcDir -/- takeFileName bin) bin
+ _ -> buildBinary rs bin =<< programContext stage package
+
+buildBinary :: [(Resource, Int)] -> FilePath -> Context -> Action ()
+buildBinary rs bin context@Context {..} = do
+ needLibrary =<< contextDependencies context
+ when (stage > Stage0) $ do
+ ways <- interpretInContext context (getLibraryWays <> getRtsWays)
+ needLibrary [ rtsContext { way = w } | w <- ways ]
+ cSrcs <- interpretInContext context (getContextData cSrcs)
+ cObjs <- mapM (objectPath context) cSrcs
+ hsObjs <- hsObjects context
+ let binDeps = cObjs ++ hsObjs
+ need binDeps
+ buildWithResources rs $ target context (Ghc LinkHs stage) binDeps [bin]
+ synopsis <- pkgSynopsis package
+ putSuccess $ renderProgram
+ (quote (pkgName package) ++ " (" ++ show stage ++ ").") bin synopsis
diff --git a/hadrian/src/Rules/Register.hs b/hadrian/src/Rules/Register.hs
new file mode 100644
index 0000000000..62023d72e4
--- /dev/null
+++ b/hadrian/src/Rules/Register.hs
@@ -0,0 +1,103 @@
+module Rules.Register (configurePackage, registerPackage) where
+
+import Distribution.ParseUtils
+import Distribution.Version (Version)
+import qualified Distribution.Compat.ReadP as Parse
+import qualified Hadrian.Haskell.Cabal.Parse as Cabal
+import Hadrian.Expression
+import qualified System.Directory as IO
+
+import Base
+import Context
+import Packages
+import Settings
+import Target
+import Utilities
+
+parseCabalName :: String -> Maybe (String, Version)
+parseCabalName = readPToMaybe parse
+ where
+ parse = (,) <$> (parsePackageName <* Parse.char '-') <*> parseOptVersion
+
+-- | Configure a package and build its @setup-config@ file.
+configurePackage :: Context -> Rules ()
+configurePackage context@Context {..} = do
+ root <- buildRootRules
+ root -/- contextDir context -/- "setup-config" %> \_ ->
+ Cabal.configurePackage context
+
+-- | Register a package and initialise the corresponding package database if
+-- need be. Note that we only register packages in 'Stage0' and 'Stage1'.
+registerPackage :: [(Resource, Int)] -> Context -> Rules ()
+registerPackage rs context@Context {..} = when (stage < Stage2) $ do
+ root <- buildRootRules
+
+ -- Initialise the package database.
+ root -/- relativePackageDbPath stage -/- packageDbStamp %> \stamp ->
+ writeFileLines stamp []
+
+ -- TODO: Add proper error handling for partial functions.
+ -- Register a package.
+ root -/- relativePackageDbPath stage -/- "*.conf" %> \conf -> do
+ settings <- libPath context <&> (-/- "settings")
+ platformConstants <- libPath context <&> (-/- "platformConstants")
+ need [settings, platformConstants]
+ let Just pkgName | takeBaseName conf == "rts" = Just "rts"
+ | otherwise = fst <$> parseCabalName (takeBaseName conf)
+ let Just pkg = findPackageByName pkgName
+ isBoot <- (pkg `notElem`) <$> stagePackages Stage0
+ case stage of
+ Stage0 | isBoot -> copyConf rs (context { package = pkg }) conf
+ _ -> buildConf rs (context { package = pkg }) conf
+
+buildConf :: [(Resource, Int)] -> Context -> FilePath -> Action ()
+buildConf _ context@Context {..} _conf = do
+ depPkgIds <- cabalDependencies context
+
+ -- Calling 'need' on @setupConfig@, triggers the package configuration.
+ setupConfig <- pkgSetupConfigFile context
+ need [setupConfig]
+ need =<< mapM (\pkgId -> packageDbPath stage <&> (-/- pkgId <.> "conf")) depPkgIds
+
+ ways <- interpretInContext context (getLibraryWays <> if package == rts then getRtsWays else mempty)
+ need =<< concatMapM (libraryTargets True) [ context { way = w } | w <- ways ]
+
+ -- We might need some package-db resource to limit read/write, see packageRules.
+ path <- buildPath context
+
+ -- Special package cases (these should ideally be rolled into Cabal).
+ when (package == rts) $
+ -- If Cabal knew about "generated-headers", we could read them from the
+ -- 'configuredCabal' information, and just "need" them here.
+ need [ path -/- "DerivedConstants.h"
+ , path -/- "ghcautoconf.h"
+ , path -/- "ghcplatform.h"
+ , path -/- "ghcversion.h"
+ , path -/- "ffi.h" ]
+
+ when (package == integerGmp) $ need [path -/- "ghc-gmp.h"]
+
+ -- Copy and register the package.
+ Cabal.copyPackage context
+ Cabal.registerPackage context
+
+copyConf :: [(Resource, Int)] -> Context -> FilePath -> Action ()
+copyConf rs context@Context {..} conf = do
+ depPkgIds <- fmap stdOutToPkgIds . askWithResources rs $
+ target context (GhcPkg Dependencies stage) [pkgName package] []
+ need =<< mapM (\pkgId -> packageDbPath stage <&> (-/- pkgId <.> "conf")) depPkgIds
+ -- We should unregister if the file exists since @ghc-pkg@ will complain
+ -- about existing package: https://github.com/snowleopard/hadrian/issues/543.
+ -- Also, we don't always do the unregistration + registration to avoid
+ -- repeated work after a full build.
+ -- We do not track 'doesFileExist' since we are going to create the file if
+ -- it is currently missing. TODO: Is this the right thing to do?
+ -- See https://github.com/snowleopard/hadrian/issues/569.
+ unlessM (liftIO $ IO.doesFileExist conf) $ do
+ buildWithResources rs $
+ target context (GhcPkg Unregister stage) [pkgName package] []
+ buildWithResources rs $
+ target context (GhcPkg Copy stage) [pkgName package] [conf]
+ where
+ stdOutToPkgIds :: String -> [String]
+ stdOutToPkgIds = drop 1 . concatMap words . lines
diff --git a/hadrian/src/Rules/Selftest.hs b/hadrian/src/Rules/Selftest.hs
new file mode 100644
index 0000000000..68aa6e3889
--- /dev/null
+++ b/hadrian/src/Rules/Selftest.hs
@@ -0,0 +1,113 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Rules.Selftest (selftestRules) where
+
+import Hadrian.Haskell.Cabal
+import Test.QuickCheck
+
+import Base
+import Context
+import Oracles.ModuleFiles
+import Oracles.Setting
+import Packages
+import Settings
+import Target
+import Utilities
+
+instance Arbitrary Way where
+ arbitrary = wayFromUnits <$> arbitrary
+
+instance Arbitrary WayUnit where
+ arbitrary = arbitraryBoundedEnum
+
+test :: Testable a => a -> Action ()
+test = liftIO . quickCheck
+
+selftestRules :: Rules ()
+selftestRules =
+ "selftest" ~> do
+ testBuilder
+ testChunksOfSize
+ testDependencies
+ testLookupAll
+ testModuleName
+ testPackages
+ testWay
+
+testBuilder :: Action ()
+testBuilder = do
+ putBuild "==== trackArgument"
+ let make = target undefined (Make undefined) undefined undefined
+ test $ forAll (elements ["-j", "MAKEFLAGS=-j", "THREADS="])
+ $ \prefix (NonNegative n) ->
+ not (trackArgument make prefix) &&
+ not (trackArgument make ("-j" ++ show (n :: Int)))
+
+testChunksOfSize :: Action ()
+testChunksOfSize = do
+ putBuild "==== chunksOfSize"
+ test $ chunksOfSize 3 [ "a", "b", "c" , "defg" , "hi" , "jk" ]
+ == [ ["a", "b", "c"], ["defg"], ["hi"], ["jk"] ]
+ test $ \n xs ->
+ let res = chunksOfSize n xs
+ in concat res == xs && all (\r -> length r == 1 || length (concat r) <= n) res
+
+testDependencies :: Action ()
+testDependencies = do
+ putBuild "==== pkgDependencies"
+ let pkgs = ghcPackages \\ [libffi] -- @libffi@ does not have a Cabal file.
+ depLists <- mapM pkgDependencies pkgs
+ test $ and [ deps == sort deps | deps <- depLists ]
+ putBuild "==== Dependencies of the 'ghc-bin' binary"
+ ghcDeps <- pkgDependencies ghc
+ test $ pkgName compiler `elem` ghcDeps
+ stage0Deps <- contextDependencies (vanillaContext Stage0 ghc)
+ stage1Deps <- contextDependencies (vanillaContext Stage1 ghc)
+ stage2Deps <- contextDependencies (vanillaContext Stage2 ghc)
+ test $ vanillaContext Stage0 compiler `notElem` stage1Deps
+ test $ vanillaContext Stage1 compiler `elem` stage1Deps
+ test $ vanillaContext Stage2 compiler `notElem` stage1Deps
+ test $ stage1Deps /= stage0Deps
+ test $ stage1Deps == stage2Deps
+
+testLookupAll :: Action ()
+testLookupAll = do
+ putBuild "==== lookupAll"
+ test $ lookupAll ["b" , "c" ] [("a", 1), ("c", 3), ("d", 4)]
+ == [Nothing, Just (3 :: Int)]
+ test $ forAll dicts $ \dict -> forAll extras $ \extra ->
+ let items = sort $ map fst dict ++ extra
+ in lookupAll items (sort dict) == map (`lookup` dict) items
+ where
+ dicts :: Gen [(Int, Int)]
+ dicts = nubBy (\x y -> fst x == fst y) <$> vector 20
+ extras :: Gen [Int]
+ extras = vector 20
+
+testModuleName :: Action ()
+testModuleName = do
+ putBuild "==== Encode/decode module name"
+ test $ encodeModule "Data/Functor" "Identity.hs" == "Data.Functor.Identity"
+ test $ encodeModule "" "Prelude" == "Prelude"
+
+ test $ decodeModule "Data.Functor.Identity" == ("Data/Functor", "Identity")
+ test $ decodeModule "Prelude" == ("", "Prelude")
+
+ test $ forAll names $ \n -> uncurry encodeModule (decodeModule n) == n
+ where
+ names = intercalate "." <$> listOf1 (listOf1 $ elements "abcABC123_'")
+
+testPackages :: Action ()
+testPackages = do
+ putBuild "==== Check system configuration"
+ win <- windowsHost -- This depends on the @boot@ and @configure@ scripts.
+ putBuild "==== Packages, interpretInContext, configuration flags"
+ forM_ [Stage0 ..] $ \stage -> do
+ pkgs <- stagePackages stage
+ when (win32 `elem` pkgs) . test $ win
+ when (unix `elem` pkgs) . test $ not win
+ test $ pkgs == nubOrd pkgs
+
+testWay :: Action ()
+testWay = do
+ putBuild "==== Read Way, Show Way"
+ test $ \(x :: Way) -> read (show x) == x
diff --git a/hadrian/src/Rules/SourceDist.hs b/hadrian/src/Rules/SourceDist.hs
new file mode 100644
index 0000000000..8bec3f3b26
--- /dev/null
+++ b/hadrian/src/Rules/SourceDist.hs
@@ -0,0 +1,114 @@
+module Rules.SourceDist (sourceDistRules) where
+
+import Hadrian.Oracles.DirectoryContents
+
+import Base
+import Builder
+import Oracles.Setting
+import Rules.Clean
+
+sourceDistRules :: Rules ()
+sourceDistRules = do
+ "source-dist" ~> do
+ -- We clean the source tree first.
+ -- See https://github.com/snowleopard/hadrian/issues/384.
+ -- TODO: Do we still need to clean the tree?
+ cleanSourceTree
+ version <- setting ProjectVersion
+ need ["sdistprep/ghc-" ++ version ++ "-src.tar.xz"]
+ putSuccess "| Done"
+ "sdistprep/ghc-*-src.tar.xz" %> \fname -> do
+ let tarName = takeFileName fname
+ dropTarXz = dropExtension . dropExtension
+ treePath = "sdistprep/ghc" -/- dropTarXz tarName
+ prepareTree treePath
+ runBuilderWithCmdOptions [Cwd "sdistprep/ghc"] (Tar Create)
+ ["cJf", ".." -/- tarName, dropTarXz tarName]
+ ["cJf", ".." -/- tarName] [dropTarXz tarName]
+ "GIT_COMMIT_ID" %> \fname ->
+ writeFileChanged fname =<< setting ProjectGitCommitId
+ "VERSION" %> \fname ->
+ writeFileChanged fname =<< setting ProjectVersion
+
+prepareTree :: FilePath -> Action ()
+prepareTree dest = do
+ mapM_ cpDir srcDirs
+ mapM_ cpFile srcFiles
+ where
+ cpFile a = copyFile a (dest -/- a)
+ cpDir a = copyDirectoryContents (Not excluded) a (dest -/- a)
+ excluded = Or
+ [ Test "//.*"
+ , Test "//#*"
+ , Test "//*-SAVE"
+ , Test "//*.orig"
+ , Test "//*.rej"
+ , Test "//*~"
+ , Test "//autom4te*"
+ , Test "//dist"
+ , Test "//dist-install"
+ , Test "//log"
+ , Test "//stage0"
+ , Test "//stage1"
+ , Test "//stage2"
+ , Test "//stage3"
+ , Test "hadrian/.cabal-sandbox"
+ , Test "hadrian/.stack-work"
+ , Test "hadrian/UserSettings.hs"
+ , Test "hadrian/cabal.sandbox.config"
+ , Test "hadrian/cfg/system.config"
+ , Test "hadrian/bin"
+ , Test "hadrian/dist"
+ , Test "hadrian/dist-newstyle"
+ , Test "libraries//*.buildinfo"
+ , Test "libraries//GNUmakefile"
+ , Test "libraries//config.log"
+ , Test "libraries//config.status"
+ , Test "libraries//configure"
+ , Test "libraries//ghc.mk"
+ , Test "libraries//include/Hs*Config.h"
+ , Test "libraries/dph"
+ , Test "libraries/parallel"
+ , Test "libraries/primitive"
+ , Test "libraries/random"
+ , Test "libraries/stm"
+ , Test "libraries/vector"
+ , Test "mk/build.mk" ]
+ srcDirs =
+ [ "bindisttest"
+ , "compiler"
+ , "distrib"
+ , "docs"
+ , "docs"
+ , "driver"
+ , "ghc"
+ , "hadrian"
+ , "includes"
+ , "iserv"
+ , "libffi"
+ , "libffi-tarballs"
+ , "libraries"
+ , "mk"
+ , "rts"
+ , "rules"
+ , "utils" ]
+ srcFiles =
+ [ "ANNOUNCE"
+ , "GIT_COMMIT_ID"
+ , "HACKING.md"
+ , "INSTALL.md"
+ , "LICENSE"
+ , "MAKEHELP.md"
+ , "Makefile"
+ , "README.md"
+ , "VERSION"
+ , "aclocal.m4"
+ , "boot"
+ , "config.guess"
+ , "config.sub"
+ , "configure"
+ , "configure.ac"
+ , "ghc.mk"
+ , "install-sh"
+ , "packages"
+ , "settings.in" ]
diff --git a/hadrian/src/Rules/Test.hs b/hadrian/src/Rules/Test.hs
new file mode 100644
index 0000000000..f5d6990e69
--- /dev/null
+++ b/hadrian/src/Rules/Test.hs
@@ -0,0 +1,124 @@
+module Rules.Test (testRules) where
+
+import System.Environment
+
+import Base
+import Expression
+import Oracles.Setting
+import Packages
+import Settings
+import Settings.Default
+import Settings.Builders.RunTest
+import Target
+import Utilities
+
+ghcConfigHsPath :: FilePath
+ghcConfigHsPath = "testsuite/mk/ghc-config.hs"
+
+ghcConfigProgPath :: FilePath
+ghcConfigProgPath = "test/bin/ghc-config"
+
+ghcConfigPath :: FilePath
+ghcConfigPath = "test/ghcconfig"
+
+-- TODO: clean up after testing
+testRules :: Rules ()
+testRules = do
+ root <- buildRootRules
+
+ -- | Using program shipped with testsuite to generate ghcconfig file.
+ root -/- ghcConfigProgPath ~> do
+ ghc <- builderPath $ Ghc CompileHs Stage0
+ createDirectory $ takeDirectory (root -/- ghcConfigProgPath)
+ cmd ghc [ghcConfigHsPath, "-o" , root -/- ghcConfigProgPath]
+
+ -- | TODO : Use input test compiler and not just stage2 compiler.
+ root -/- ghcConfigPath ~> do
+ ghcPath <- needFile Stage1 ghc
+ need [root -/- ghcConfigProgPath]
+ cmd [FileStdout $ root -/- ghcConfigPath] (root -/- ghcConfigProgPath)
+ [ghcPath]
+
+ root -/- timeoutPath ~> timeoutProgBuilder
+
+ "validate" ~> do
+ needTestBuilders
+ build $ target (vanillaContext Stage2 compiler) (Make "testsuite/tests") [] []
+
+ "test" ~> do
+ needTestBuilders
+
+ -- TODO : Should we remove the previosly generated config file?
+ -- Prepare Ghc configuration file for input compiler.
+ need [root -/- ghcConfigPath, root -/- timeoutPath]
+
+ -- TODO This approach doesn't work.
+ -- Set environment variables for test's Makefile.
+ env <- sequence
+ [ builderEnvironment "MAKE" $ Make ""
+ , builderEnvironment "TEST_HC" $ Ghc CompileHs Stage2
+ , AddEnv "TEST_HC_OPTS" <$> runTestGhcFlags ]
+
+ makePath <- builderPath $ Make ""
+ top <- topDirectory
+ ghcPath <- (top -/-) <$> builderPath (Ghc CompileHs Stage2)
+ ghcFlags <- runTestGhcFlags
+ checkPprPath <- (top -/-) <$> needFile Stage1 checkPpr
+ annotationsPath <- (top -/-) <$> needFile Stage1 checkApiAnnotations
+
+ -- Set environment variables for test's Makefile.
+ liftIO $ do
+ setEnv "MAKE" makePath
+ setEnv "TEST_HC" ghcPath
+ setEnv "TEST_HC_OPTS" ghcFlags
+ setEnv "CHECK_PPR" checkPprPath
+ setEnv "CHECK_API_ANNOTATIONS" annotationsPath
+
+ -- Execute the test target.
+ buildWithCmdOptions env $ target (vanillaContext Stage2 compiler) RunTest [] []
+
+-- | Build extra programs and libraries required by testsuite
+needTestsuitePackages :: Action ()
+needTestsuitePackages = do
+ targets <- mapM (needFile Stage1) =<< testsuitePackages
+ libPath <- stageLibPath Stage1
+ iservPath <- needFile Stage1 iserv
+ need targets
+ -- | We need to copy iserv bin to lib/bin as this is where testsuite looks
+ -- | for iserv.
+ copyFile iservPath $ libPath -/- "bin/ghc-iserv"
+
+-- | Build the timeout program.
+-- See: https://github.com/ghc/ghc/blob/master/testsuite/timeout/Makefile#L23
+timeoutProgBuilder :: Action ()
+timeoutProgBuilder = do
+ root <- buildRoot
+ windows <- windowsHost
+ if windows
+ then do
+ prog <- programPath =<< programContext Stage1 timeout
+ copyFile prog (root -/- timeoutPath)
+ else do
+ python <- builderPath Python
+ copyFile "testsuite/timeout/timeout.py" (root -/- timeoutPath <.> "py")
+ let script = unlines
+ [ "#!/usr/bin/env sh"
+ , "exec " ++ python ++ " $0.py \"$@\"" ]
+ writeFile' (root -/- timeoutPath) script
+ makeExecutable (root -/- timeoutPath)
+
+needTestBuilders :: Action ()
+needTestBuilders = do
+ needBuilder $ Ghc CompileHs Stage2
+ needBuilder $ GhcPkg Update Stage1
+ needBuilder Hpc
+ needBuilder $ Hsc2Hs Stage1
+ needTestsuitePackages
+
+needFile :: Stage -> Package -> Action FilePath
+needFile stage pkg
+-- TODO (Alp): we might sometimes need more than vanilla!
+-- This should therefore depend on what test ways
+-- we are going to use, I suppose?
+ | isLibrary pkg = pkgConfFile (Context stage pkg profilingDynamic)
+ | otherwise = programPath =<< programContext stage pkg