module Dib.Builders.C (
CTargetInfo(CTargetInfo, outputName, targetName, srcDir, outputLocation, compiler, linker, archiver, inFileOption, outFileOption, commonCompileFlags, cCompileFlags, cxxCompileFlags, linkFlags, archiverFlags, includeDirs, extraCompileDeps, extraLinkDeps, exclusions, staticLibrary),
BuildLocation(InPlace, BuildDir, ObjAndBinDirs),
makeCTarget,
makeCleanTarget,
makeBuildDirs,
emptyConfig,
defaultGCCConfig,
defaultGXXConfig,
defaultClangConfig
) where
import Dib.Gatherers
import Dib.Target
import Dib.Types
import Dib.Util
import Dib.Scanners.CDepScanner
import Data.List as L
import Data.Monoid
import Data.Word
import System.Process (system)
import System.Directory as D
import System.FilePath as F
import qualified Data.Digest.CRC32 as Hash
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified System.Console.ANSI as ANSI
data CTargetInfo = CTargetInfo {
outputName :: T.Text,
targetName :: T.Text,
srcDir :: T.Text,
outputLocation :: BuildLocation,
compiler :: T.Text,
linker :: T.Text,
archiver :: T.Text,
inFileOption :: T.Text,
outFileOption :: T.Text,
includeOption :: T.Text,
commonCompileFlags :: T.Text,
cCompileFlags :: T.Text,
cxxCompileFlags :: T.Text,
linkFlags :: T.Text,
archiverFlags :: T.Text,
includeDirs :: [T.Text],
extraCompileDeps :: [T.Text],
extraLinkDeps :: [T.Text],
exclusions :: [T.Text],
staticLibrary :: Bool
}
cTargetHash :: CTargetInfo -> Target -> Word32
cTargetHash info _ =
Hash.crc32 $ TE.encodeUtf8 $ T.intercalate "^" [
"srcDir",
srcDir info,
"compiler",
compiler info,
"linker",
linker info,
"archiver",
archiver info,
"inFileOption",
inFileOption info,
"outFileOption",
outFileOption info,
"includeOption",
includeOption info,
"commonCompileFlags",
commonCompileFlags info,
"cCompileFlags",
cCompileFlags info,
"cxxCompileFlags",
cxxCompileFlags info,
"linkFlags",
linkFlags info,
"archiverFlags",
archiverFlags info,
"includeDirs",
T.intercalate "^^" $ includeDirs info,
"extraCompileDeps",
T.intercalate "^^" $ extraCompileDeps info,
"extraLinkDeps",
T.intercalate "^^" $ extraLinkDeps info,
"exclusions",
T.intercalate "^^" $ exclusions info,
"staticLibrary",
if staticLibrary info then "True" else "False"
]
data BuildLocation =
InPlace
| BuildDir T.Text
| ObjAndBinDirs T.Text T.Text
emptyConfig :: CTargetInfo
emptyConfig = CTargetInfo {
outputName = "",
targetName = "",
srcDir = "",
outputLocation = InPlace,
compiler = "",
linker = "",
archiver = "",
inFileOption = "",
outFileOption = "",
includeOption = "",
commonCompileFlags = "",
cCompileFlags = "",
cxxCompileFlags = "",
linkFlags = "",
archiverFlags = "",
includeDirs = [],
extraCompileDeps = [],
extraLinkDeps = [],
exclusions = [],
staticLibrary = False
}
defaultGCCConfig :: CTargetInfo
defaultGCCConfig = emptyConfig {
compiler = "gcc",
linker = "gcc",
archiver = "ar",
inFileOption = "-c",
outFileOption = "-o",
includeOption = "-I",
archiverFlags = "rs"
}
defaultGXXConfig :: CTargetInfo
defaultGXXConfig = defaultGCCConfig {
compiler = "gcc",
linker = "g++"
}
defaultClangConfig :: CTargetInfo
defaultClangConfig = defaultGCCConfig {
compiler = "clang",
linker = "clang"
}
massageFilePath :: T.Text -> T.Text
massageFilePath path = T.replace "\\" "_" $ T.replace "/" "_" path
remapObjFile :: BuildLocation -> T.Text -> T.Text
remapObjFile InPlace file = file
remapObjFile (BuildDir dir) file = dir `T.snoc` F.pathSeparator <> massageFilePath file
remapObjFile (ObjAndBinDirs objDir _) file = objDir `T.snoc` F.pathSeparator <> massageFilePath file
remapBinFile :: BuildLocation -> T.Text -> T.Text
remapBinFile InPlace file = file
remapBinFile (BuildDir dir) file = dir `T.snoc` F.pathSeparator <> file
remapBinFile (ObjAndBinDirs _ binDir) file = binDir `T.snoc` F.pathSeparator <> file
makeBuildDirs :: CTargetInfo -> IO ()
makeBuildDirs info = do
let helper InPlace = return ()
helper (BuildDir dir) = D.createDirectoryIfMissing True (T.unpack dir)
helper (ObjAndBinDirs objDir binDir) = D.createDirectoryIfMissing True (T.unpack objDir) >> D.createDirectoryIfMissing True (T.unpack binDir)
helper (outputLocation info)
return ()
excludeFiles :: [T.Text] -> T.Text -> Bool
excludeFiles excl file = L.any (`T.isSuffixOf` file) excl
getCorrectCompileFlags :: CTargetInfo -> T.Text -> T.Text
getCorrectCompileFlags info source = if ".c" `T.isSuffixOf` source then cCompileFlags info else cxxCompileFlags info
makeCTarget :: CTargetInfo -> Target
makeCTarget info =
let includeDirString = includeOption info <> T.intercalate (" " <> includeOption info) (includeDirs info)
makeBuildString source target = T.unpack $ T.concat [compiler info, " ", inFileOption info, " ", source, " ", outFileOption info, " ", target, " ", includeDirString, " ", commonCompileFlags info, " ", getCorrectCompileFlags info source]
makeLinkString sources target = T.unpack $ T.concat [linker info, " ", T.unwords sources, " ", outFileOption info, " ", target, " ", linkFlags info]
makeArchiveString sources target = T.unpack $ T.concat [archiver info, " ", archiverFlags info, " ", target, " ", T.unwords sources]
buildCmd (ManyToOne sources target) = do
let sourceFile = head sources
let buildString = makeBuildString sourceFile target
ANSI.setSGR [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.White]
putStr "Building: "
ANSI.setSGR [ANSI.Reset]
putStrLn $ T.unpack sourceFile
exitCode <- system buildString
handleExitCode exitCode target buildString
buildCmd _ = return $ Left "Unhandled SrcTransform."
linkCmd (ManyToOne sources target) = do
let linkString = makeLinkString sources target
ANSI.setSGR [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.White]
putStr "Linking: "
ANSI.setSGR [ANSI.Reset]
putStrLn $ T.unpack target
exitCode <- system linkString
handleExitCode exitCode target linkString
linkCmd _ = return $ Left "Unhandled SrcTransform."
archiveCmd (ManyToOne sources target) = do
let archiveString = makeArchiveString sources target
ANSI.setSGR [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.White]
putStr "Archiving: "
ANSI.setSGR [ANSI.Reset]
putStrLn $ T.unpack target
exitCode <- system archiveString
handleExitCode exitCode target archiveString
archiveCmd _ = return $ Left "Unhandled SrcTransform."
buildDirGatherer = makeCommandGatherer $ makeBuildDirs info
cppStage = Stage "compile" (map (changeExt "o" (outputLocation info))) (cDepScanner (map T.unpack $ includeDirs info)) (extraCompileDeps info) buildCmd
linkStage = Stage "link" (combineTransforms (remapBinFile (outputLocation info) $ outputName info)) return (extraLinkDeps info) linkCmd
archiveStage = Stage "archive" (combineTransforms (remapBinFile (outputLocation info) $ outputName info)) return [] archiveCmd
in Target (targetName info) (cTargetHash info) [] [cppStage, if staticLibrary info then archiveStage else linkStage] [buildDirGatherer, makeFileTreeGatherer (srcDir info) (matchExtensionsExcluded [".cpp", ".c"] [excludeFiles $ exclusions info])]
changeExt :: T.Text -> BuildLocation -> SrcTransform -> SrcTransform
changeExt newExt buildLoc (OneToOne input _) = OneToOne input $ remapObjFile buildLoc $ T.dropWhileEnd (/='.') input <> newExt
changeExt _ _ _ = undefined
combineTransforms :: T.Text -> [SrcTransform] -> [SrcTransform]
combineTransforms target transforms = [ManyToOne (L.sort sources) target]
where sources = foldl' (\acc (OneToOne input _) -> acc ++ [input]) [] transforms
makeCleanTarget :: CTargetInfo -> Target
makeCleanTarget info =
let cleanCmd (OneToOne input _) = do
ANSI.setSGR [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.White]
putStr "Removing: "
ANSI.setSGR [ANSI.Reset]
putStrLn $ T.unpack input
D.removeFile (T.unpack input)
return $ Right $ OneToOne "" ""
cleanCmd _ = error "Should never hit this."
objDir InPlace = srcDir info
objDir (BuildDir dir) = dir
objDir (ObjAndBinDirs objDir _) = objDir
programFile InPlace = outputName info
programFile (BuildDir dir) = dir `T.snoc` F.pathSeparator <> outputName info
programFile (ObjAndBinDirs _ binDir) = binDir `T.snoc` F.pathSeparator <> outputName info
cleanStage = Stage "clean" id return [] cleanCmd
objectGatherer = makeFileTreeGatherer (objDir $ outputLocation info) (matchExtension ".o")
programGatherer = makeSingleFileGatherer (programFile $ outputLocation info)
in Target ("clean-" <> targetName info) (const 0) [] [cleanStage] [objectGatherer, programGatherer]