{-# LANGUAGE CPP #-}
module Foreign.Hoppy.Setup (
ProjectConfig (..),
cppMain,
cppUserHooks,
hsMain,
hsUserHooks,
) where
import Control.Monad (forM_, unless, when)
import Data.List (isInfixOf)
import Distribution.InstalledPackageInfo (libraryDirs)
#if MIN_VERSION_Cabal(2,0,0)
import Distribution.Package (mkPackageName)
#else
import Distribution.Package (PackageName (PackageName))
#endif
import Distribution.PackageDescription (
HookedBuildInfo,
PackageDescription,
emptyBuildInfo,
extraLibDirs,
)
import Distribution.Simple (defaultMainWithHooks, simpleUserHooks)
import Distribution.Simple.LocalBuildInfo (
LocalBuildInfo,
absoluteInstallDirs,
buildDir,
installedPkgs,
libdir,
withPrograms,
)
import Distribution.Simple.PackageIndex (lookupPackageName)
import Distribution.Simple.Program (
getProgramOutput,
runDbProgram,
runProgram,
)
import Distribution.Simple.Program.Find (
ProgramSearchPathEntry (ProgramSearchPathDefault),
findProgramOnSearchPath,
)
import Distribution.Simple.Program.Types (
ConfiguredProgram,
Program,
ProgramLocation (FoundOnSystem),
simpleConfiguredProgram,
simpleProgram,
)
import Distribution.Simple.Setup (
CopyDest (CopyTo, NoCopyDest),
RegisterFlags,
buildVerbosity,
cleanVerbosity,
configVerbosity,
copyDest,
copyVerbosity,
flagToMaybe,
fromFlagOrDefault,
installDistPref,
installVerbosity,
regInPlace,
regVerbosity,
)
import Distribution.Simple.UserHooks (
UserHooks (
buildHook,
hookedPrograms,
cleanHook,
copyHook,
instHook,
regHook,
postConf,
preBuild,
preCopy,
preInst,
preReg,
preRepl,
preTest
),
)
#if MIN_VERSION_Cabal(2,0,0)
import Distribution.Simple.Utils (die')
#else
import Distribution.Simple.Utils (die)
#endif
import Distribution.Simple.Utils (info)
import Distribution.Verbosity (Verbosity, normal)
import System.Directory (createDirectoryIfMissing, doesFileExist, removeFile)
import System.FilePath ((</>), takeDirectory)
data Language = Cpp | Haskell
data ProjectConfig = ProjectConfig
{ generatorExecutableName :: FilePath
, cppPackageName :: String
, cppSourcesDir :: FilePath
, hsSourcesDir :: FilePath
, interfaceName :: Maybe String
}
getGeneratorProgram :: ProjectConfig -> Program
getGeneratorProgram project = simpleProgram $ generatorExecutableName project
getInterfaceArg :: ProjectConfig -> [String]
getInterfaceArg project = case interfaceName project of
Just name -> ["--interface", name]
Nothing -> []
cppMain :: ProjectConfig -> IO ()
cppMain project = defaultMainWithHooks $ cppUserHooks project
cppUserHooks :: ProjectConfig -> UserHooks
cppUserHooks project =
simpleUserHooks
{ hookedPrograms = [generatorProgram, makeProgram]
, postConf = \args flags pkgDesc localBuildInfo -> do
let verbosity = fromFlagOrDefault normal $ configVerbosity flags
cppConfigure project verbosity localBuildInfo generatorProgram
postConf simpleUserHooks args flags pkgDesc localBuildInfo
, buildHook = \pkgDesc localBuildInfo hooks flags -> do
buildHook simpleUserHooks pkgDesc localBuildInfo hooks flags
let verbosity = fromFlagOrDefault normal $ buildVerbosity flags
cppBuild verbosity localBuildInfo
, copyHook = \pkgDesc localBuildInfo hooks flags -> do
copyHook simpleUserHooks pkgDesc localBuildInfo hooks flags
let verbosity = fromFlagOrDefault normal $ copyVerbosity flags
dest = fromFlagOrDefault NoCopyDest $ copyDest flags
cppInstall verbosity pkgDesc localBuildInfo dest
, instHook = \pkgDesc localBuildInfo hooks flags -> do
instHook simpleUserHooks pkgDesc localBuildInfo hooks flags
let verbosity = fromFlagOrDefault normal $ installVerbosity flags
dest = maybe NoCopyDest CopyTo $
flagToMaybe $ installDistPref flags
cppInstall verbosity pkgDesc localBuildInfo dest
, regHook = \pkgDesc localBuildInfo hooks flags -> do
regHook simpleUserHooks pkgDesc localBuildInfo hooks flags
let verbosity = fromFlagOrDefault normal $ regVerbosity flags
cppRegister verbosity pkgDesc localBuildInfo flags
, cleanHook = \pkgDesc z hooks flags -> do
cleanHook simpleUserHooks pkgDesc z hooks flags
let verbosity = fromFlagOrDefault normal $ cleanVerbosity flags
cppClean project verbosity
}
where generatorProgram = getGeneratorProgram project
makeProgram :: Program
makeProgram = simpleProgram "make"
cppConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> Program -> IO ()
cppConfigure project verbosity localBuildInfo generatorProgram = do
let programDb = withPrograms localBuildInfo
sourcesDir = cppSourcesDir project
createDirectoryIfMissing True sourcesDir
runDbProgram verbosity generatorProgram programDb $
getInterfaceArg project ++ ["--gen-cpp", sourcesDir]
maybeConfigureProgram <- findConfigure
case maybeConfigureProgram of
Just configureProgram -> runProgram verbosity configureProgram []
Nothing -> return ()
where findConfigure :: IO (Maybe ConfiguredProgram)
findConfigure = do
hasConfigure <- doesFileExist "configure"
return $ if hasConfigure
then Just $ simpleConfiguredProgram "configure" $ FoundOnSystem "./configure"
else Nothing
cppBuild :: Verbosity -> LocalBuildInfo -> IO ()
cppBuild verbosity localBuildInfo = do
hasMakefile <- doesFileExist "Makefile"
unless hasMakefile $
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity
#else
die
#endif
"No Makefile found."
let programDb = withPrograms localBuildInfo
runDbProgram verbosity makeProgram programDb []
cppInstall :: Verbosity -> PackageDescription -> LocalBuildInfo -> CopyDest -> IO ()
cppInstall verbosity pkgDesc localBuildInfo dest = do
hasMakefile <- doesFileExist "Makefile"
unless hasMakefile $
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity
#else
die
#endif
"No Makefile found."
let programDb = withPrograms localBuildInfo
libDir = libdir $ absoluteInstallDirs pkgDesc localBuildInfo dest
createDirectoryIfMissing True libDir
runDbProgram verbosity makeProgram programDb ["install", "libdir=" ++ libDir]
cppRegister :: Verbosity -> PackageDescription -> LocalBuildInfo -> RegisterFlags -> IO ()
cppRegister verbosity _pkgDesc localBuildInfo flags = do
hasMakefile <- doesFileExist "Makefile"
unless hasMakefile $
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity
#else
die
#endif
"No Makefile found."
when (fromFlagOrDefault False (regInPlace flags)) $ do
let programDb = withPrograms localBuildInfo
libDir = buildDir localBuildInfo
createDirectoryIfMissing True libDir
runDbProgram verbosity makeProgram programDb ["install", "libdir=" ++ libDir]
cppClean :: ProjectConfig -> Verbosity -> IO ()
cppClean project verbosity = do
removeGeneratedFiles Cpp project verbosity
hasMakefile <- doesFileExist "Makefile"
unless hasMakefile $
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity
#else
die
#endif
"No Makefile found."
make <- findSystemProgram verbosity "make"
runProgram verbosity make ["clean"]
findSystemProgram :: Verbosity -> FilePath -> IO ConfiguredProgram
findSystemProgram verbosity basename = do
maybePath <-
#if MIN_VERSION_Cabal(1,24,0)
fmap (fmap fst) $
#endif
findProgramOnSearchPath verbosity [ProgramSearchPathDefault] basename
case maybePath of
Just path -> return $ simpleConfiguredProgram basename $ FoundOnSystem path
Nothing ->
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity $
#else
die $
#endif
"Couldn't find program " ++ show basename ++ "."
hsMain :: ProjectConfig -> IO ()
hsMain project = defaultMainWithHooks $ hsUserHooks project
hsUserHooks :: ProjectConfig -> UserHooks
hsUserHooks project =
simpleUserHooks
{ hookedPrograms = [generatorProgram]
, postConf = \args flags pkgDesc localBuildInfo -> do
postConf simpleUserHooks args flags pkgDesc localBuildInfo
let verbosity = fromFlagOrDefault normal $ configVerbosity flags
hsConfigure project verbosity localBuildInfo generatorProgram
, preBuild = \_ _ -> addLibDir
, preTest = \_ _ -> addLibDir
, preCopy = \_ _ -> addLibDir
, preInst = \_ _ -> addLibDir
, preReg = \_ _ -> addLibDir
, preRepl = \_ _ -> addLibDir
, cleanHook = \pkgDesc z hooks flags -> do
cleanHook simpleUserHooks pkgDesc z hooks flags
let verbosity = fromFlagOrDefault normal $ cleanVerbosity flags
hsClean project verbosity
}
where generatorProgram = getGeneratorProgram project
hsCppLibDirFile :: FilePath
hsCppLibDirFile = "dist/build/hoppy-cpp-libdir"
hsConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> Program -> IO ()
hsConfigure project verbosity localBuildInfo generatorProgram = do
libDir <- lookupCppLibDir
storeCppLibDir libDir
generateSources
where lookupCppLibDir = do
let packageName = cppPackageName project
cppPkg <- case lookupPackageName (installedPkgs localBuildInfo) $
#if MIN_VERSION_Cabal(2,0,0)
mkPackageName packageName
#else
PackageName packageName
#endif
of
[(_, [pkg])] -> return pkg
results ->
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity $
#else
die $
#endif
"Failed to find a unique " ++ packageName ++
" installation. Found " ++ show results ++ "."
case filter (\x -> packageName `isInfixOf` x) $ libraryDirs cppPkg of
[libDir] -> return libDir
libDirs ->
#if MIN_VERSION_Cabal(2,0,0)
die' verbosity $
#else
die $
#endif
"Expected a single library directory for " ++ packageName ++
", got " ++ show libDirs ++ "."
storeCppLibDir libDir = do
createDirectoryIfMissing True $ takeDirectory hsCppLibDirFile
writeFile hsCppLibDirFile libDir
generateSources = do
let programDb = withPrograms localBuildInfo
sourcesDir = hsSourcesDir project
createDirectoryIfMissing True sourcesDir
runDbProgram verbosity generatorProgram programDb $
getInterfaceArg project ++ ["--gen-hs", sourcesDir]
addLibDir :: IO HookedBuildInfo
addLibDir = do
libDir <- readFile hsCppLibDirFile
return (Just emptyBuildInfo {extraLibDirs = [libDir]}, [])
hsClean :: ProjectConfig -> Verbosity -> IO ()
hsClean = removeGeneratedFiles Haskell
removeGeneratedFiles :: Language -> ProjectConfig -> Verbosity -> IO ()
removeGeneratedFiles language project verbosity = do
let (listArg, sourcesDir) = case language of
Cpp -> ("--list-cpp-files", cppSourcesDir)
Haskell -> ("--list-hs-files", hsSourcesDir)
generatorProgram <- findSystemProgram verbosity $ generatorExecutableName project
generatedFiles <-
lines <$> getProgramOutput verbosity
generatorProgram
(getInterfaceArg project ++ [listArg])
forM_ generatedFiles $ \file -> do
let path = sourcesDir project </> file
exists <- doesFileExist path
when exists $ do
info verbosity $ "Removing " ++ path ++ "."
removeFile path