{- ----------------------------------------------------------------------------- Copyright 2020 Kevin P. Barry Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------------- -} -- Author: Kevin P. Barry [ta0kira@gmail.com] {-# LANGUAGE Safe #-} module Config.LoadConfig ( Backend(..), LocalConfig(..), Resolver(..), compilerVersion, localConfigPath, loadConfig, rootPath, ) where import Config.Paths import Config.Programs import Control.Monad (when) import Data.Hashable (hash) import Data.List (intercalate,isPrefixOf,isSuffixOf) import Data.Version (showVersion,versionBranch) import GHC.IO.Handle import Numeric (showHex) import System.Directory import System.Exit import System.FilePath import System.IO import System.Posix.Process (ProcessStatus(..),executeFile,forkProcess,getProcessStatus) import System.Posix.Temp (mkstemps) import Paths_zeolite_lang (getDataFileName,version) loadConfig :: IO (Backend,Resolver) loadConfig = do configFile <- localConfigPath isFile <- doesFileExist configFile when (not isFile) $ do hPutStrLn stderr "Zeolite has not been configured. Please run zeolite-setup." exitFailure configString <- readFile configFile lc <- check $ (reads configString :: [(LocalConfig,String)]) pathsFile <- globalPathsPath pathsExists <- doesFileExist pathsFile paths <- if pathsExists then readFile pathsFile >>= return . lines else return [] return (lcBackend lc,addPaths (lcResolver lc) paths) where check [(cm,"")] = return cm check [(cm,"\n")] = return cm check _ = do hPutStrLn stderr "Zeolite configuration is corrupt. Please rerun zeolite-setup." exitFailure rootPath :: IO FilePath rootPath = getDataFileName "" compilerVersion :: String compilerVersion = showVersion version data Backend = UnixBackend { ucCxxBinary :: FilePath, ucCxxOptions :: [String], ucArBinary :: FilePath } deriving (Read,Show) data Resolver = SimpleResolver { srVisibleSystem :: [FilePath], srExtraPaths :: [FilePath] } deriving (Read,Show) data LocalConfig = LocalConfig { lcBackend :: Backend, lcResolver :: Resolver } deriving (Read,Show) localConfigFilename :: FilePath localConfigFilename = ".local-config" globalPathsFilename :: FilePath globalPathsFilename = "global-paths" localConfigPath :: IO FilePath localConfigPath = getDataFileName localConfigFilename >>= canonicalizePath globalPathsPath :: IO FilePath globalPathsPath = getDataFileName globalPathsFilename >>= canonicalizePath addPaths :: Resolver -> [FilePath] -> Resolver addPaths (SimpleResolver ls ps) ps2 = SimpleResolver ls (ps ++ ps2) instance CompilerBackend Backend where runCxxCommand (UnixBackend cb co ab) (CompileToObject s p nm ns ps e) = do objName <- canonicalizePath $ p (takeFileName $ dropExtension s ++ ".o") executeProcess cb $ co ++ otherOptions ++ ["-c", s, "-o", objName] if e then do -- Extra files are put into .a since they will be unconditionally -- included. This prevents unwanted symbol dependencies. arName <- canonicalizePath $ p (takeFileName $ dropExtension s ++ ".a") executeProcess ab ["-q",arName,objName] return arName else return objName where otherOptions = map (("-I" ++) . normalise) ps ++ nsFlag nsFlag | null ns = [] | otherwise = ["-D" ++ nm ++ "=" ++ ns] runCxxCommand (UnixBackend cb co _) (CompileToBinary m ss o ps lf) = do let arFiles = filter (isSuffixOf ".a") ss let otherFiles = filter (not . isSuffixOf ".a") ss executeProcess cb $ co ++ otherOptions ++ m:otherFiles ++ arFiles ++ ["-o", o] return o where otherOptions = lf ++ map ("-I" ++) (map normalise ps) runTestCommand _ (TestCommand b p) = do (outF,outH) <- mkstemps "/tmp/ztest_" ".txt" (errF,errH) <- mkstemps "/tmp/ztest_" ".txt" pid <- forkProcess (execWithCapture outH errH) hClose outH hClose errH status <- getProcessStatus True True pid out <- readFile outF removeFile outF err <- readFile errF removeFile errF let success = case status of Just (Exited ExitSuccess) -> True _ -> False return $ TestCommandResult success (lines out) (lines err) where execWithCapture h1 h2 = do when (not $ null p) $ setCurrentDirectory p hDuplicateTo h1 stdout hDuplicateTo h2 stderr executeFile b True [] Nothing getCompilerHash b = VersionHash $ flip showHex "" $ abs $ hash $ minorVersion ++ show b where minorVersion = show $ take 3 $ versionBranch version executeProcess :: String -> [String] -> IO () executeProcess c os = do hPutStrLn stderr $ "Executing: " ++ intercalate " " (c:os) pid <- forkProcess $ executeFile c True os Nothing status <- getProcessStatus True True pid case status of Just (Exited ExitSuccess) -> return () _ -> exitFailure instance PathResolver Resolver where resolveModule (SimpleResolver ls ps) p m = do let allowGlobal = not (".." `elem` components) m0 <- if allowGlobal && any (\l -> isPrefixOf (l ++ "/") m) ls then getDataFileName m >>= return . (:[]) else return [] let m2 = if allowGlobal then map ( m) ps else [] firstExisting m $ [pm] ++ m0 ++ m2 where components = map stripSlash $ splitPath m stripSlash = reverse . dropWhile (== '/') . reverse resolveBaseModule _ = do let m = "base" m0 <- getDataFileName m firstExisting m [m0] isBaseModule r f = do b <- resolveBaseModule r return (f == b) firstExisting :: FilePath -> [FilePath] -> IO FilePath firstExisting n [] = do -- TODO: Allow error recovery here. hPutStrLn stderr $ "Could not find path " ++ n exitFailure firstExisting n (p:ps) = do isDir <- doesDirectoryExist p if isDir then canonicalizePath p else firstExisting n ps