diff options
author | Ben Gamari <ben@smart-cactus.org> | 2018-10-23 14:20:13 -0400 |
---|---|---|
committer | Ben Gamari <ben@smart-cactus.org> | 2018-10-23 14:20:13 -0400 |
commit | 94756201349685a34c4495addd3484fdfcc8b498 (patch) | |
tree | fd4a9cee20d3c2b79f56ded7e02fb0c01b26b6c9 /hadrian/src/Rules | |
parent | 575b35f4cdc18045bccd42d341d6f25d95c0696c (diff) | |
parent | 45f3bff7016a2a0cd9a5455a882ced984655e90b (diff) | |
download | haskell-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.hs | 294 | ||||
-rw-r--r-- | hadrian/src/Rules/Clean.hs | 34 | ||||
-rw-r--r-- | hadrian/src/Rules/Compile.hs | 83 | ||||
-rw-r--r-- | hadrian/src/Rules/Configure.hs | 57 | ||||
-rw-r--r-- | hadrian/src/Rules/Dependencies.hs | 35 | ||||
-rw-r--r-- | hadrian/src/Rules/Documentation.hs | 210 | ||||
-rw-r--r-- | hadrian/src/Rules/Generate.hs | 501 | ||||
-rw-r--r-- | hadrian/src/Rules/Gmp.hs | 123 | ||||
-rw-r--r-- | hadrian/src/Rules/Libffi.hs | 109 | ||||
-rw-r--r-- | hadrian/src/Rules/Library.hs | 305 | ||||
-rw-r--r-- | hadrian/src/Rules/Nofib.hs | 57 | ||||
-rw-r--r-- | hadrian/src/Rules/Program.hs | 77 | ||||
-rw-r--r-- | hadrian/src/Rules/Register.hs | 103 | ||||
-rw-r--r-- | hadrian/src/Rules/Selftest.hs | 113 | ||||
-rw-r--r-- | hadrian/src/Rules/SourceDist.hs | 114 | ||||
-rw-r--r-- | hadrian/src/Rules/Test.hs | 124 |
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 |