fficxx-0.7.0.0: src/FFICXX/Generate/Builder.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module FFICXX.Generate.Builder where
import Control.Monad (void, when)
import qualified Data.ByteString.Lazy.Char8 as L
import Data.Char (toUpper)
import Data.Digest.Pure.MD5 (md5)
import Data.Foldable (for_)
import qualified Data.Text as T
import FFICXX.Generate.Code.Cabal (buildCabalFile, buildJSONFile)
import FFICXX.Generate.Config
( FFICXXConfig (..),
SimpleBuilderConfig (..),
)
import qualified FFICXX.Generate.ContentMaker as C
import FFICXX.Generate.Dependency
( findModuleUnitImports,
mkPackageConfig,
)
import FFICXX.Generate.Dependency.Graph
( constructDepGraph,
findDepCycles,
gatherHsBootSubmodules,
)
import FFICXX.Generate.Type.Cabal
( AddCInc (..),
AddCSrc (..),
Cabal (..),
CabalName (..),
)
import FFICXX.Generate.Type.Class (hasProxy)
import FFICXX.Generate.Type.Module
( ClassImportHeader (..),
ClassModule (..),
PackageConfig (..),
TemplateClassImportHeader (..),
TemplateClassModule (..),
TopLevelImportHeader (..),
)
import FFICXX.Generate.Util (moduleDirFile)
import FFICXX.Runtime.CodeGen.Cxx (HeaderName (..))
import Language.Haskell.Exts.Pretty (prettyPrint)
import System.Directory
( copyFile,
createDirectoryIfMissing,
doesFileExist,
)
import System.FilePath (splitExtension, (<.>), (</>))
import System.IO (IOMode (..), hPutStrLn, withFile)
import System.Process (readProcess)
macrofy :: String -> String
macrofy = map ((\x -> if x == '-' then '_' else x) . toUpper)
simpleBuilder :: FFICXXConfig -> SimpleBuilderConfig -> IO ()
simpleBuilder cfg sbc = do
putStrLn "----------------------------------------------------"
putStrLn "-- fficxx code generation for Haskell-C++ binding --"
putStrLn "----------------------------------------------------"
let SimpleBuilderConfig
topLevelMod
mumap
cabal
classes
toplevelfunctions
templates
extralibs
cxxopts
extramods
staticFiles =
sbc
pkgname = cabal_pkgname cabal
putStrLn ("Generating " <> unCabalName pkgname)
let workingDir = fficxxconfig_workingDir cfg
installDir = fficxxconfig_installBaseDir cfg
staticDir = fficxxconfig_staticFileDir cfg
pkgconfig@(PkgConfig mods cihs tih tcms _tcihs _ _) =
mkPackageConfig
(pkgname, findModuleUnitImports mumap)
(classes, toplevelfunctions, templates, extramods)
(cabal_additional_c_incs cabal)
(cabal_additional_c_srcs cabal)
cabalFileName = unCabalName pkgname <.> "cabal"
jsonFileName = unCabalName pkgname <.> "json"
allClasses = fmap (Left . tcihTClass) templates ++ fmap Right classes
depCycles =
findDepCycles $
constructDepGraph allClasses toplevelfunctions
-- for now, put this function here
-- This function is a little ad hoc, only for Interface.hs.
-- But as of now, we support hs-boot for ordinary class only.
mkHsBootCandidateList :: [ClassModule] -> [ClassModule]
mkHsBootCandidateList ms =
let hsbootSubmods = gatherHsBootSubmodules depCycles
in filter (\c -> cmModule c <.> "Interface" `elem` hsbootSubmods) ms
hsbootlst = mkHsBootCandidateList mods
--
createDirectoryIfMissing True workingDir
createDirectoryIfMissing True installDir
createDirectoryIfMissing True (installDir </> "src")
createDirectoryIfMissing True (installDir </> "csrc")
--
putStrLn "Copying static files"
mapM_ (\x -> copyFileWithMD5Check (staticDir </> x) (installDir </> x)) staticFiles
--
putStrLn "Generating Cabal file"
buildCabalFile cabal topLevelMod pkgconfig extralibs cxxopts (workingDir </> cabalFileName)
--
putStrLn "Generating JSON file"
buildJSONFile cabal topLevelMod pkgconfig extralibs cxxopts (workingDir </> jsonFileName)
--
putStrLn "Generating Header file"
let gen :: FilePath -> String -> IO ()
gen file str =
let path = workingDir </> file in withFile path WriteMode (flip hPutStrLn str)
gen (unCabalName pkgname <> "Type.h") (C.buildTypeDeclHeader (map cihClass cihs))
for_ cihs $ \hdr ->
gen
(unHdrName (cihSelfHeader hdr))
(C.buildDeclHeader (unCabalName pkgname) hdr)
gen
(tihHeaderFileName tih <.> "h")
(C.buildTopLevelHeader (unCabalName pkgname) tih)
putStrLn "Generating Cpp file"
for_ cihs (\hdr -> gen (cihSelfCpp hdr) (C.buildDefMain hdr))
gen (tihHeaderFileName tih <.> "cpp") (C.buildTopLevelCppDef tih)
--
putStrLn "Generating Additional Header/Source"
for_ (cabal_additional_c_incs cabal) (\(AddCInc hdr txt) -> gen hdr txt)
for_ (cabal_additional_c_srcs cabal) (\(AddCSrc hdr txt) -> gen hdr txt)
--
putStrLn "Generating RawType.hs"
for_ mods $ \m ->
gen
(cmModule m <.> "RawType" <.> "hs")
(prettyPrint (C.buildRawTypeHs m))
--
putStrLn "Generating FFI.hsc"
for_ mods $ \m ->
gen
(cmModule m <.> "FFI" <.> "hsc")
(prettyPrint (C.buildFFIHsc m))
--
putStrLn "Generating Interface.hs"
for_ mods $ \m ->
gen
(cmModule m <.> "Interface" <.> "hs")
(prettyPrint (C.buildInterfaceHs mempty depCycles m))
--
putStrLn "Generating Cast.hs"
for_ mods $ \m ->
gen
(cmModule m <.> "Cast" <.> "hs")
(prettyPrint (C.buildCastHs m))
--
putStrLn "Generating Implementation.hs"
for_ mods $ \m ->
gen
(cmModule m <.> "Implementation" <.> "hs")
(prettyPrint (C.buildImplementationHs mempty m))
--
putStrLn "Generating Proxy.hs"
for_ mods $ \m ->
when (hasProxy . cihClass . cmCIH $ m) $
gen (cmModule m <.> "Proxy" <.> "hs") (prettyPrint (C.buildProxyHs m))
--
putStrLn "Generating Template.hs"
for_ tcms $ \m ->
gen
(tcmModule m <.> "Template" <.> "hs")
(prettyPrint (C.buildTemplateHs m))
--
putStrLn "Generating TH.hs"
for_ tcms $ \m ->
gen
(tcmModule m <.> "TH" <.> "hs")
(prettyPrint (C.buildTHHs m))
--
-- TODO: Template.hs-boot need to be generated as well
putStrLn "Generating hs-boot file"
-- This is a hack since haskell-src-exts always codegen () => instead of empty
-- string for an empty context, which have different meanings in hs-boot file.
-- Therefore, we get rid of them.
let hsBootHackClearEmptyContexts = T.unpack . T.replace "() =>" "" . T.pack
for_ hsbootlst $ \m -> do
gen
(cmModule m <.> "Interface" <.> "hs-boot")
(hsBootHackClearEmptyContexts $ prettyPrint (C.buildInterfaceHsBoot depCycles m))
--
putStrLn "Generating Module summary file"
for_ mods $ \m ->
gen
(cmModule m <.> "hs")
(prettyPrint (C.buildModuleHs m))
--
putStrLn "Generating Top-level Ordinary Module"
gen (topLevelMod <.> "Ordinary" <.> "hs") (prettyPrint (C.buildTopLevelOrdinaryHs (topLevelMod <> ".Ordinary") (mods, tcms) tih))
--
putStrLn "Generating Top-level Template Module"
gen
(topLevelMod <.> "Template" <.> "hs")
(prettyPrint (C.buildTopLevelTemplateHs (topLevelMod <> ".Template") tih))
--
putStrLn "Generating Top-level TH Module"
gen
(topLevelMod <.> "TH" <.> "hs")
(prettyPrint (C.buildTopLevelTHHs (topLevelMod <> ".TH") tih))
--
putStrLn "Generating Top-level Module"
gen
(topLevelMod <.> "hs")
(prettyPrint (C.buildTopLevelHs topLevelMod (mods, tcms)))
--
putStrLn "Copying generated files to target directory"
touch (workingDir </> "LICENSE")
copyFileWithMD5Check (workingDir </> cabalFileName) (installDir </> cabalFileName)
copyFileWithMD5Check (workingDir </> jsonFileName) (installDir </> jsonFileName)
copyFileWithMD5Check (workingDir </> "LICENSE") (installDir </> "LICENSE")
copyCppFiles workingDir (C.csrcDir installDir) (unCabalName pkgname) pkgconfig
for_ mods (copyModule workingDir (C.srcDir installDir))
for_ tcms (copyTemplateModule workingDir (C.srcDir installDir))
putStrLn "Copying Ordinary"
moduleFileCopy workingDir (C.srcDir installDir) $ topLevelMod <.> "Ordinary" <.> "hs"
moduleFileCopy workingDir (C.srcDir installDir) $ topLevelMod <.> "Template" <.> "hs"
moduleFileCopy workingDir (C.srcDir installDir) $ topLevelMod <.> "TH" <.> "hs"
moduleFileCopy workingDir (C.srcDir installDir) $ topLevelMod <.> "hs"
putStrLn "----------------------------------------------------"
putStrLn "-- Code generation has been completed. Enjoy! --"
putStrLn "----------------------------------------------------"
-- | some dirty hack. later, we will do it with more proper approcah.
touch :: FilePath -> IO ()
touch fp = void (readProcess "touch" [fp] "")
copyFileWithMD5Check :: FilePath -> FilePath -> IO ()
copyFileWithMD5Check src tgt = do
b <- doesFileExist tgt
if b
then do
srcmd5 <- md5 <$> L.readFile src
tgtmd5 <- md5 <$> L.readFile tgt
if srcmd5 == tgtmd5 then return () else copyFile src tgt
else copyFile src tgt
copyCppFiles :: FilePath -> FilePath -> String -> PackageConfig -> IO ()
copyCppFiles wdir ddir cprefix (PkgConfig _ cihs tih _ _tcihs acincs acsrcs) = do
let thfile = cprefix <> "Type.h"
tlhfile = tihHeaderFileName tih <.> "h"
tlcppfile = tihHeaderFileName tih <.> "cpp"
copyFileWithMD5Check (wdir </> thfile) (ddir </> thfile)
doesFileExist (wdir </> tlhfile)
>>= flip when (copyFileWithMD5Check (wdir </> tlhfile) (ddir </> tlhfile))
doesFileExist (wdir </> tlcppfile)
>>= flip when (copyFileWithMD5Check (wdir </> tlcppfile) (ddir </> tlcppfile))
for_ cihs $ \header -> do
let hfile = unHdrName (cihSelfHeader header)
cppfile = cihSelfCpp header
copyFileWithMD5Check (wdir </> hfile) (ddir </> hfile)
copyFileWithMD5Check (wdir </> cppfile) (ddir </> cppfile)
for_ acincs $ \(AddCInc header _) ->
copyFileWithMD5Check (wdir </> header) (ddir </> header)
for_ acsrcs $ \(AddCSrc csrc _) ->
copyFileWithMD5Check (wdir </> csrc) (ddir </> csrc)
moduleFileCopy :: FilePath -> FilePath -> FilePath -> IO ()
moduleFileCopy wdir ddir fname = do
let (fnamebody, fnameext) = splitExtension fname
(mdir, mfile) = moduleDirFile fnamebody
origfpath = wdir </> fname
(mfile', _mext') = splitExtension mfile
newfpath = ddir </> mdir </> mfile' <> fnameext
b <- doesFileExist origfpath
when b $ do
createDirectoryIfMissing True (ddir </> mdir)
copyFileWithMD5Check origfpath newfpath
copyModule :: FilePath -> FilePath -> ClassModule -> IO ()
copyModule wdir ddir m = do
let modbase = cmModule m
moduleFileCopy wdir ddir $ modbase <> ".hs"
moduleFileCopy wdir ddir $ modbase <> ".RawType.hs"
moduleFileCopy wdir ddir $ modbase <> ".FFI.hsc"
moduleFileCopy wdir ddir $ modbase <> ".Interface.hs"
moduleFileCopy wdir ddir $ modbase <> ".Cast.hs"
moduleFileCopy wdir ddir $ modbase <> ".Implementation.hs"
moduleFileCopy wdir ddir $ modbase <> ".Interface.hs-boot"
when (hasProxy . cihClass . cmCIH $ m) $
moduleFileCopy wdir ddir $
modbase <> ".Proxy.hs"
copyTemplateModule :: FilePath -> FilePath -> TemplateClassModule -> IO ()
copyTemplateModule wdir ddir m = do
let modbase = tcmModule m
moduleFileCopy wdir ddir $ modbase <> ".Template.hs"
moduleFileCopy wdir ddir $ modbase <> ".TH.hs"