{-# LANGUAGE CPP, FlexibleInstances #-} import Control.Exception (SomeException, try) import Control.Monad import Data.Functor import Data.Maybe import Data.List (isPrefixOf, (\\), intercalate, stripPrefix, find) import Data.Map (Map) import qualified Data.Map as Map import Data.Monoid import Data.Char import Distribution.Simple import Distribution.Simple.PreProcess import Distribution.Simple.Program import Distribution.Simple.Setup hiding (Flag) import Distribution.Simple.LocalBuildInfo import Distribution.PackageDescription import Distribution.Version import System.Environment import Distribution.System #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 = ["__STDC_LIMIT_MACROS"] #ifndef MIN_VERSION_Cabal_2_0_0 mkVersion ver = Version ver [] versionNumbers = versionBranch mkFlagName = FlagName #endif llvmVersion = mkVersion [5,0] llvmConfigNames = [ "llvm-config-" ++ (intercalate "." . map show . versionNumbers $ llvmVersion), "llvm-config" ] 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 class ProgramSearch a where programSearch :: (String -> a) -> a -- this instance is used before Cabal-1.18.0, when programFindLocation took one argument instance Monad m => ProgramSearch (v -> m (Maybe b)) where programSearch checkName = \v -> findJustBy (\n -> checkName n v) llvmConfigNames -- this instance is used for and after Cabal-1.18.0, when programFindLocation took two arguments instance Monad m => ProgramSearch (v -> p -> m (Maybe b)) where programSearch checkName = \v p -> findJustBy (\n -> checkName n v p) llvmConfigNames class OldHookable hook where preHookOld :: (PackageDescription -> LocalBuildInfo -> UserHooks -> TestFlags -> IO ()) -> hook -> hook -- this instance is used before Cabal-1.22.0.0, when testHook took four arguments instance OldHookable (PackageDescription -> LocalBuildInfo -> UserHooks -> TestFlags -> IO ()) where preHookOld f hook = \packageDescription localBuildInfo userHooks testFlags -> do f packageDescription localBuildInfo userHooks testFlags hook packageDescription localBuildInfo userHooks testFlags -- this instance is used for and after Cabal-1.22.0.0, when testHook took four five arguments instance OldHookable (Args -> PackageDescription -> LocalBuildInfo -> UserHooks -> TestFlags -> IO ()) where preHookOld f hook = \args packageDescription localBuildInfo userHooks testFlags -> do f packageDescription localBuildInfo userHooks testFlags hook args packageDescription localBuildInfo userHooks testFlags llvmProgram :: Program llvmProgram = (simpleProgram "llvm-config") { programFindLocation = programSearch (programFindLocation . simpleProgram), 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 configFlags = do let verbosity = fromFlag $ configVerbosity configFlags (program, _, _) <- requireProgramVersion verbosity llvmProgram (withinVersion llvmVersion) (configPrograms configFlags) 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 configFlags = do llvmConfig <- getLLVMConfig configFlags [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 = ["-Wcovered-switch-default", "-fcolor-diagnostics"] ++ map ("-D" ++) uncheckedHsFFIDefines ignoredCFlags :: [String] ignoredCFlags = ["-Wcovered-switch-default", "-Wdelete-non-virtual-dtor", "-fcolor-diagnostics"] -- | Header directories are added separately to configExtraIncludeDirs isIncludeFlag :: String -> Bool isIncludeFlag flag = "-I" `isPrefixOf` flag isIgnoredCFlag :: String -> Bool isIgnoredCFlag flag = flag `elem` ignoredCFlags || isIncludeFlag flag isIgnoredCxxFlag :: String -> Bool isIgnoredCxxFlag flag = flag `elem` ignoredCxxFlags || isIncludeFlag flag main = do let origUserHooks = simpleUserHooks defaultMainWithHooks origUserHooks { hookedPrograms = [ llvmProgram ], confHook = \(genericPackageDescription, hookedBuildInfo) configFlags -> do llvmConfig <- getLLVMConfig configFlags 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@[libDir] <- liftM lines $ llvmConfig ["--libdir"] [llvmVersion] <- liftM lines $ llvmConfig ["--version"] let getLibs = liftM (map (fromJust . stripPrefix "-l") . words) . llvmConfig flags = configConfigurationsFlags configFlags linkFlag = case lookup (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' = configFlags { configExtraLibDirs = libDirs ++ configExtraLibDirs configFlags, configExtraIncludeDirs = includeDirs ++ configExtraIncludeDirs configFlags } 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 PreProcessor { 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 = \packageDescription localBuildInfo userHooks buildFlags -> do addLLVMToLdLibraryPath (configFlags localBuildInfo) buildHook origUserHooks packageDescription localBuildInfo userHooks buildFlags, testHook = preHookOld (\_ localBuildInfo _ _ -> addLLVMToLdLibraryPath (configFlags localBuildInfo)) (testHook origUserHooks) }