souffle-haskell-1.0.0: scripts/import_souffle_headers.hs
{-# LANGUAGE DataKinds, TypeFamilies, DeriveGeneric, DeriveAnyClass #-}
module Main ( main ) where
-- NOTE: This is a helper script for importing all Souffle headers into
-- this repository. This is done in order to make Haskell libraries that
-- use souffle-haskell "self-contained", meaning users of the packages
-- using those libraries are not required to have souffle (headers) installed.
import System.Directory
import System.FilePath
import System.Process
import Control.Monad
import Control.Monad.Extra
import Control.Applicative
import Data.List
import Data.Maybe
import Data.Void
import GHC.Generics
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
import qualified Language.Souffle.Interpreted as Souffle
data Includes = Includes FilePath FilePath
deriving (Eq, Show, Generic, Souffle.Marshal)
newtype TopLevelInclude = TopLevelInclude FilePath
deriving (Eq, Show, Generic, Souffle.Marshal)
newtype RequiredInclude = RequiredInclude FilePath
deriving (Eq, Show, Generic, Souffle.Marshal)
data Handle = Handle
instance Souffle.Program Handle where
type ProgramFacts Handle = [TopLevelInclude, Includes, RequiredInclude]
programName = const "required_include"
instance Souffle.Fact Includes where
factName = const "includes"
instance Souffle.Fact TopLevelInclude where
factName = const "top_level_include"
instance Souffle.Fact RequiredInclude where
factName = const "required_include"
run :: String -> IO ()
run = callCommand
runWithResult :: String -> IO String
runWithResult s = case words s of
(program:args) -> readProcess program args ""
_ -> error "Passed empty string to 'runWithResult'. Aborting."
headerDir :: FilePath
headerDir = "cbits/souffle/"
main :: IO ()
main = do
pwd <- getCurrentDirectory
gitRoot <- getGitRootDirectory
when (pwd /= gitRoot) $
putStrLn "You need to run this script in the root directory of this repo! Aborting."
run "git submodule update --init --recursive"
run $ "rm -rf " <> headerDir <> "*.h"
run $ "cp souffle/LICENSE " <> headerDir
files <- copyHeaders
putStrLn "Replace 'install-includes' in your package.yaml with the following:"
putStrLn . unlines $ map (" - souffle/" <>) files
getGitRootDirectory :: IO FilePath
getGitRootDirectory =
filter (/= '\n') <$> runWithResult "git rev-parse --show-toplevel"
parseIncludes :: String -> [FilePath]
parseIncludes s = either (const []) catMaybes $ P.runParser parser "" s where
parser :: P.Parsec Void String [Maybe FilePath]
parser = many (includeParser <|> skipRestOfLine) <* P.eof
includeParser = do
P.chunk "#include" *> P.space1
P.lookAhead (P.char '"' <|> P.char '<') >>= \case
'"' -> do
include <- P.between quotes quotes $ P.takeWhile1P Nothing (/= '"')
void skipRestOfLine
pure $ Just include
_ -> pure Nothing
quotes = P.char '"'
skipRestOfLine = Nothing <$ (P.takeWhileP Nothing (/= '\n') *> P.newline)
parseIncludesInHeader :: FilePath -> IO [Includes]
parseIncludesInHeader file = f <$> readFile file where
f = map ((file `Includes`) . normalizeFilePath dir) . parseIncludes
dir = takeDirectory file
normalizeFilePath :: FilePath -> FilePath -> FilePath
normalizeFilePath dir file = normalize $ dir </> file' where
file' = if "souffle/" `isPrefixOf` file then file \\ "souffle/" else file
normalize = withExplodedPath (reverse . removeParentDirRefs . reverse)
removeParentDirRefs = \case
("../":_:xs) -> removeParentDirRefs xs
(x:xs) -> x:removeParentDirRefs xs
[] -> []
copyHeaders :: IO [FilePath]
copyHeaders = do
headers <- filter (".h" `isSuffixOf`) . lines
<$> runWithResult "find souffle -type f"
includes <- concatMapM parseIncludesInHeader headers
traverse copyHeader =<< computeRequiredIncludes includes
computeRequiredIncludes :: [Includes] -> IO [FilePath]
computeRequiredIncludes includes = do
cfg <- Souffle.defaultConfig
let config = cfg { Souffle.cfgDatalogDir = "./scripts" }
requiredIncludes <- Souffle.runSouffleWith config $
Souffle.init Handle >>= \case
Nothing -> error "Failed to load Souffle program. Aborting."
Just prog -> do
Souffle.addFacts prog [ TopLevelInclude "souffle/src/SouffleInterface.h"
, TopLevelInclude "souffle/src/CompiledSouffle.h"
]
Souffle.addFacts prog includes
Souffle.run prog
Souffle.getFacts prog
pure $ map (\(RequiredInclude include) -> include) requiredIncludes
copyHeader :: FilePath -> IO FilePath
copyHeader file = do
header <- head . filter (file `isSuffixOf`) . lines
<$> runWithResult "find souffle/ -type f"
let header' = withExplodedPath (drop 2) header
dir = headerDir </> takeDirectory header'
destination = replaceDirectory header' dir
createDirectoryIfMissing True dir
copyFile header destination
pure header'
withExplodedPath :: ([FilePath] -> [FilePath]) -> FilePath -> FilePath
withExplodedPath f = joinPath . f . splitPath