{-# LANGUAGE CPP, FlexibleInstances #-}
import Control.Exception (SomeException, try)
import Control.Monad
import Data.Char
import Data.List
import Data.Maybe
import Data.Monoid
import Distribution.PackageDescription hiding (buildInfo, includeDirs)
import Distribution.Simple
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.PreProcess
import Distribution.Simple.Program
import Distribution.Simple.Setup hiding (Flag)
import Distribution.System
import System.Environment
#ifdef MIN_VERSION_Cabal
#if MIN_VERSION_Cabal(2,0,0)
#define MIN_VERSION_Cabal_2_0_0
#endif
#endif
-- define these selectively in C files (we are _not_ using HsFFI.h),
-- rather than universally in the ccOptions, because HsFFI.h currently defines them
-- without checking they're already defined and so causes warnings.
uncheckedHsFFIDefines :: [String]
uncheckedHsFFIDefines = ["__STDC_LIMIT_MACROS"]
#ifndef MIN_VERSION_Cabal_2_0_0
mkVersion :: [Int] -> Version
mkVersion ver = Version ver []
versionNumbers :: Version -> [Int]
versionNumbers = versionBranch
mkFlagName :: String -> FlagName
mkFlagName = FlagName
#endif
#if !(MIN_VERSION_Cabal(2,1,0))
lookupFlagAssignment :: FlagName -> FlagAssignment -> Maybe Bool
lookupFlagAssignment = lookup
#endif
llvmVersion :: Version
llvmVersion = mkVersion [12,0]
-- Ordered by decreasing specificty so we will prefer llvm-config-9.0
-- over llvm-config-9 over llvm-config.
llvmConfigNames :: [String]
llvmConfigNames = reverse versionedConfigs ++ ["llvm-config"]
where
versionedConfigs =
map
(\vs -> "llvm-config-" ++ intercalate "." (map show vs))
(tail (inits (versionNumbers llvmVersion)))
findJustBy :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findJustBy f (x:xs) = do
x' <- f x
case x' of
Nothing -> findJustBy f xs
j -> return j
findJustBy _ [] = return Nothing
llvmProgram :: Program
llvmProgram = (simpleProgram "llvm-config") {
programFindLocation = \v p -> findJustBy (\n -> programFindLocation (simpleProgram n) v p) llvmConfigNames,
programFindVersion = \verbosity path ->
let
stripVcsSuffix = takeWhile (\c -> isDigit c || c == '.')
trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse
in findProgramVersion "--version" (stripVcsSuffix . trim) verbosity path
}
getLLVMConfig :: ConfigFlags -> IO ([String] -> IO String)
getLLVMConfig confFlags = do
let verbosity = fromFlag $ configVerbosity confFlags
(program, _, _) <- requireProgramVersion verbosity llvmProgram
(withinVersion llvmVersion)
(configPrograms confFlags)
return $ getProgramOutput verbosity program
addToLdLibraryPath :: String -> IO ()
addToLdLibraryPath path = do
let (ldLibraryPathVar, ldLibraryPathSep) =
case buildOS of
OSX -> ("DYLD_LIBRARY_PATH",":")
_ -> ("LD_LIBRARY_PATH",":")
v <- try $ getEnv ldLibraryPathVar :: IO (Either SomeException String)
setEnv ldLibraryPathVar (path ++ either (const "") (ldLibraryPathSep ++) v)
addLLVMToLdLibraryPath :: ConfigFlags -> IO ()
addLLVMToLdLibraryPath confFlags = do
llvmConfig <- getLLVMConfig confFlags
[libDir] <- liftM lines $ llvmConfig ["--libdir"]
addToLdLibraryPath libDir
-- | These flags are not relevant for us and dropping them allows
-- linking against LLVM build with Clang using GCC
ignoredCxxFlags :: [String]
ignoredCxxFlags = ["-fcolor-diagnostics"] ++ map ("-D" ++) uncheckedHsFFIDefines
ignoredCFlags :: [String]
ignoredCFlags = ["-fcolor-diagnostics"]
-- | Header directories are added separately to configExtraIncludeDirs
isIncludeFlag :: String -> Bool
isIncludeFlag flag = "-I" `isPrefixOf` flag
isWarningFlag :: String -> Bool
isWarningFlag flag = "-W" `isPrefixOf` flag
isIgnoredCFlag :: String -> Bool
isIgnoredCFlag flag = flag `elem` ignoredCFlags || isIncludeFlag flag || isWarningFlag flag
isIgnoredCxxFlag :: String -> Bool
isIgnoredCxxFlag flag = flag `elem` ignoredCxxFlags || isIncludeFlag flag || isWarningFlag flag
main :: IO ()
main = do
let origUserHooks = simpleUserHooks
defaultMainWithHooks origUserHooks {
hookedPrograms = [ llvmProgram ],
confHook = \(genericPackageDescription, hookedBuildInfo) confFlags -> do
llvmConfig <- getLLVMConfig confFlags
llvmCxxFlags <- do
rawLlvmCxxFlags <- llvmConfig ["--cxxflags"]
return . filter (not . isIgnoredCxxFlag) $ words rawLlvmCxxFlags
let stdLib = maybe "stdc++"
(drop (length stdlibPrefix))
(find (isPrefixOf stdlibPrefix) llvmCxxFlags)
where stdlibPrefix = "-stdlib=lib"
includeDirs <- liftM lines $ llvmConfig ["--includedir"]
libDirs <- liftM lines $ llvmConfig ["--libdir"]
[llvmVersion] <- liftM lines $ llvmConfig ["--version"]
let getLibs = liftM (map (fromJust . stripPrefix "-l") . words) . llvmConfig
flags = configConfigurationsFlags confFlags
linkFlag = case lookupFlagAssignment (mkFlagName "shared-llvm") flags of
Nothing -> "--link-shared"
Just shared -> if shared then "--link-shared" else "--link-static"
libs <- getLibs ["--libs", linkFlag]
systemLibs <- getLibs ["--system-libs", linkFlag]
let genericPackageDescription' = genericPackageDescription {
condLibrary = do
libraryCondTree <- condLibrary genericPackageDescription
return libraryCondTree {
condTreeData = condTreeData libraryCondTree <> mempty {
libBuildInfo =
mempty {
ccOptions = llvmCxxFlags,
extraLibs = libs ++ stdLib : systemLibs
}
}
}
}
configFlags' = confFlags {
configExtraLibDirs = libDirs ++ configExtraLibDirs confFlags,
configExtraIncludeDirs = includeDirs ++ configExtraIncludeDirs confFlags
}
addLLVMToLdLibraryPath configFlags'
confHook simpleUserHooks (genericPackageDescription', hookedBuildInfo) configFlags',
hookedPreProcessors =
let origHookedPreprocessors = hookedPreProcessors origUserHooks
#ifdef MIN_VERSION_Cabal_2_0_0
newHsc buildInfo localBuildInfo componentLocalBuildInfo =
#else
newHsc buildInfo localBuildInfo =
#endif
ppUnlit {
platformIndependent = platformIndependent (origHsc buildInfo),
runPreProcessor = \inFiles outFiles verbosity -> do
llvmConfig <- getLLVMConfig (configFlags localBuildInfo)
llvmCFlags <- do
rawLlvmCFlags <- llvmConfig ["--cflags"]
return . filter (not . isIgnoredCFlag) $ words rawLlvmCFlags
let buildInfo' = buildInfo { ccOptions = "-Wno-variadic-macros" : llvmCFlags }
runPreProcessor (origHsc buildInfo') inFiles outFiles verbosity
}
where origHsc buildInfo' =
fromMaybe
ppHsc2hs
(lookup "hsc" origHookedPreprocessors)
buildInfo'
localBuildInfo
#ifdef MIN_VERSION_Cabal_2_0_0
componentLocalBuildInfo
#endif
in [("hsc", newHsc)] ++ origHookedPreprocessors,
buildHook = \packageDesc localBuildInfo userHooks buildFlags ->
do addLLVMToLdLibraryPath (configFlags localBuildInfo)
buildHook origUserHooks packageDesc localBuildInfo userHooks buildFlags,
testHook = \args packageDesc localBuildInfo userHooks testFlags ->
do addLLVMToLdLibraryPath (configFlags localBuildInfo)
testHook origUserHooks args packageDesc localBuildInfo userHooks testFlags
}