-- Copyright (c) 2010-2018 Brett Lajzer -- See LICENSE for license information. -- | This is the command-line executable "dib". Since Dib proper is a -- library, one doesn't need this, but it makes dealing with build -- scripts quite a bit easier. module Main where import Paths_dib (version) import GHC.IO.Exception import Control.Monad import Data.Maybe import Data.Time.Clock.POSIX import Data.Version (showVersion) import System.Process (system) import qualified System.Directory as D import System.Environment (getArgs) import System.FilePath as F import System.Info import System.IO unixExe :: String unixExe = ".dib/dib" windowsExe :: String windowsExe = ".dib/dib.exe" correctExe :: String correctExe = if os == "mingw32" then windowsExe else unixExe correctExtension :: String correctExtension = if os == "mingw32" then ".exe" else "" -- | Location to copy the dib.hs script to and rename it. Get around -- an issue with building on Windows. tmpDibScript :: String tmpDibScript = "dib-tmp.hs" -- | The file that stores the timestamp for dib.hs timestampFile :: String timestampFile = ".dib/timestamp" -- | The file that stores the version of the dib library versionFile :: String versionFile = ".dib/version" -- | The command line for building dib.hs buildString :: String buildString = "ghc -o dib" ++ correctExtension ++ " -O2 -XOverloadedStrings -rtsopts -threaded -outputdir . " ++ tmpDibScript -- | A basic dib script. This is the script saved when running "dib init" defaultScript :: String defaultScript = "\ \module Main where\n\n\ \import Dib\n\ \import qualified Data.Text as T\n\n\ \targets = []\n\n\ \main = dib targets\n" compilerToConfig :: String -> String compilerToConfig "gcc" = "defaultGCCConfig" compilerToConfig "g++" = "defaultGXXConfig" compilerToConfig "clang" = "defaultClangConfig" compilerToConfig template = error $ "Error: unknown template \"" ++ template ++ "\"" -- | Makes a C Builder. Takes a name and source directory. cBuilderScript :: String -> String -> String -> String cBuilderScript name compiler srcDir = "\ \module Main where\n\n\ \import Dib\n\ \import Dib.Builders.C\n\ \import qualified Data.Text as T\n\n" ++ "projectInfo = " ++ compilerToConfig compiler ++ " {\n" ++ " outputName = \"" ++ name ++ "\",\n" ++ " targetName = \"" ++ name ++ "\",\n" ++ " srcDir = \"" ++ srcDir ++ "\",\n" ++ " commonCompileFlags = \"\",\n" ++ " cCompileFlags = \"\",\n" ++ " cxxCompileFlags = \"\",\n" ++ " linkFlags = \"\",\n" ++ " outputLocation = ObjAndBinDirs \"obj\" \".\",\n" ++ " includeDirs = [\"" ++ srcDir ++ "\"]\n" ++ "}\n\n" ++ "project = makeCTarget projectInfo\n\ \clean = makeCleanTarget projectInfo\n\n\ \targets = [project, clean]\n\n\ \main = dib targets" -- | Recurses up the file path until it finds a directory with a dib.hs in it -- Once it finds a dib.hs, it returns the path. findDib :: FilePath -> IO (Maybe FilePath) findDib lastPath = do let dibPath = lastPath "dib.hs" hasDib <- D.doesFileExist dibPath if hasDib then return $ Just dibPath else do curPath <- D.canonicalizePath $ lastPath ".." if curPath /= lastPath then findDib curPath else return Nothing -- | Recurses up the file path until it finds a directory with a dib.hs in it -- Once it finds a dib.hs, it builds and runs it. findAndRunDib :: String -> IO ExitCode findAndRunDib args = do curPath <- D.getCurrentDirectory dibPath <- findDib curPath if isJust dibPath then do D.setCurrentDirectory $ F.dropFileName $ fromJust dibPath retVal <- buildAndRunDib args D.setCurrentDirectory curPath return retVal else do putStrLn "Error: can't find dib.hs." putStrLn "Suggestion: use 'dib --init ...' to create one." putStrLn " Empty Project: 'dib --init empty'" putStrLn " C Project: 'dib --init c (gcc|g++|clang) '" return (ExitFailure 255) getDibCalendarTimeStr :: IO String getDibCalendarTimeStr = do modTime <- D.getModificationTime "dib.hs" let calTime = (fromIntegral.fromEnum.utcTimeToPOSIXSeconds) modTime :: Integer return $ show calTime checkDibTimestamps :: IO Bool checkDibTimestamps = do dibUnixExists <- D.doesFileExist unixExe dibWinExists <- D.doesFileExist windowsExe if dibUnixExists || dibWinExists then do calTimeStr <- getDibCalendarTimeStr storedCalTime <- getStoredCalTime storedVersion <- getStoredVersion let versionStr = showVersion version return $ calTimeStr /= storedCalTime || versionStr /= storedVersion else return True getStoredCalTime :: IO String getStoredCalTime = getStoredTokenFileContents timestampFile getStoredVersion :: IO String getStoredVersion = getStoredTokenFileContents versionFile getStoredTokenFileContents :: String -> IO String getStoredTokenFileContents f = do fileExists <- D.doesFileExist f if fileExists then do file <- openFile f ReadMode tokenStr <- hGetLine file hClose file return tokenStr else return "" processExitCode :: ExitCode -> IO () processExitCode ExitSuccess = return () processExitCode (ExitFailure n) = error $ "Error " ++ show n ++ " building dib.hs." rebuild :: Bool -> IO () rebuild needToRebuild = when needToRebuild $ do D.copyFile "dib.hs" $ ".dib/" ++ tmpDibScript D.setCurrentDirectory ".dib" exitCode <- system buildString D.setCurrentDirectory ".." processExitCode exitCode calTimeStr <- getDibCalendarTimeStr tsFile <- openFile timestampFile WriteMode hPutStr tsFile calTimeStr hClose tsFile let versionStr = showVersion version verFile <- openFile versionFile WriteMode hPutStr verFile versionStr hClose verFile requoteArg :: String -> String requoteArg arg = requoteArgInternal arg False where requoteArgInternal ('=':xs) False = '=' : '\"' : requoteArgInternal xs True requoteArgInternal [] True = "\"" requoteArgInternal [] False = [] requoteArgInternal (x:xs) e = x : requoteArgInternal xs e buildAndRunDib :: String -> IO ExitCode buildAndRunDib args = do D.createDirectoryIfMissing False ".dib" needToRebuild <- checkDibTimestamps rebuild needToRebuild let quotes = if os =="mingw32" then "\"\"" else "" system $ quotes ++ correctExe ++ quotes ++ " +RTS -N -RTS " ++ args shouldHandleInit :: [String] -> Bool shouldHandleInit args = not (null args) && head args == "--init" argsToBuildScript :: [String] -> String argsToBuildScript ["c", name, compiler, srcDir] = cBuilderScript name compiler srcDir argsToBuildScript ["empty"] = defaultScript argsToBuildScript [] = defaultScript argsToBuildScript _ = error "Error: Unknown template name." handleInit :: [String] -> IO Bool handleInit args = if shouldHandleInit args then do dibInCurDir <- D.doesFileExist "dib.hs" if dibInCurDir then error "Error: dib.hs already exists in current directory." else do curPath <- D.getCurrentDirectory dibPath <- findDib curPath let initPrefix = if isNothing dibPath then "" else "Warning: dib.hs exists in a parent directory, are you sure you want to do this?\n" let buildScript = argsToBuildScript $ tail args putStrLn $ initPrefix ++ "Initializing dib.hs..." withFile "dib.hs" WriteMode (\h -> hPutStr h buildScript >> return True) else return False main :: IO ExitCode main = do hSetBuffering stdout LineBuffering hSetBuffering stderr LineBuffering args <- getArgs inited <- handleInit args if inited then return ExitSuccess else findAndRunDib (unwords $ map requoteArg args)