module Idris.Package where
import System.Directory
import System.Directory (copyFile, createDirectoryIfMissing)
import System.Exit
import System.FilePath (addExtension, addTrailingPathSeparator, dropExtension,
hasExtension, normalise, takeDirectory, takeExtension,
takeFileName, (</>))
import System.IO
import System.Process
import Util.System
import Control.Monad
import Control.Monad.Trans.Except (runExceptT)
import Control.Monad.Trans.State.Strict (execStateT)
import Data.Either (partitionEithers)
import Data.Either (partitionEithers)
import Data.List
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)
import Idris.AbsSyntax
import Idris.Core.TT
import Idris.Error (ifail)
import Idris.IBC
import Idris.IdrisDoc
import Idris.Imports
import Idris.Main (idris, idrisMain)
import Idris.Output (pshow)
import Idris.Output
import Idris.Parser (loadModule)
import Idris.REPL
import Idris.Package.Common
import Idris.Package.Parser
import IRTS.System
getPkgDesc :: FilePath -> IO PkgDesc
getPkgDesc = parseDesc
buildPkg :: [Opt]
-> Bool
-> (Bool, FilePath)
-> IO ()
buildPkg copts warnonly (install, fp) = do
pkgdesc <- parseDesc fp
dir <- getCurrentDirectory
let idx' = pkgIndex $ pkgname pkgdesc
idx = PkgIndex $ case opt getIBCSubDir copts of
(ibcsubdir:_) -> ibcsubdir </> idx'
[] -> idx'
oks <- mapM (testLib warnonly (pkgname pkgdesc)) (libdeps pkgdesc)
when (and oks) $ do
m_ist <- inPkgDir pkgdesc $ do
make (makefile pkgdesc)
case (execout pkgdesc) of
Nothing -> do
case mergeOptions copts (idx : NoREPL : Verbose 1 : idris_opts pkgdesc) of
Left emsg -> do
putStrLn emsg
exitWith (ExitFailure 1)
Right opts -> do
auditPackage (AuditIPkg `elem` opts) pkgdesc
buildMods opts (modules pkgdesc)
Just o -> do
let exec = dir </> o
case mergeOptions copts (idx : NoREPL : Verbose 1 : Output exec : idris_opts pkgdesc) of
Left emsg -> do
putStrLn emsg
exitWith (ExitFailure 1)
Right opts -> do
auditPackage (AuditIPkg `elem` opts) pkgdesc
buildMain opts (idris_main pkgdesc)
case m_ist of
Nothing -> exitWith (ExitFailure 1)
Just ist -> do
case errSpan ist of
Just _ -> exitWith (ExitFailure 1)
_ -> return ()
when install $ installPkg (opt getIBCSubDir copts) pkgdesc
where
buildMain opts (Just mod) = buildMods opts [mod]
buildMain _ Nothing = do
putStrLn "Can't build an executable: No main module given"
exitWith (ExitFailure 1)
checkPkg :: [Opt]
-> Bool
-> Bool
-> FilePath
-> IO ()
checkPkg copts warnonly quit fpath = do
pkgdesc <- parseDesc fpath
oks <- mapM (testLib warnonly (pkgname pkgdesc)) (libdeps pkgdesc)
when (and oks) $ do
res <- inPkgDir pkgdesc $ do
make (makefile pkgdesc)
case mergeOptions copts (NoREPL : Verbose 1 : idris_opts pkgdesc) of
Left emsg -> do
putStrLn emsg
exitWith (ExitFailure 1)
Right opts -> do
auditPackage (AuditIPkg `elem` opts) pkgdesc
buildMods opts (modules pkgdesc)
when quit $ case res of
Nothing -> exitWith (ExitFailure 1)
Just res' -> do
case errSpan res' of
Just _ -> exitWith (ExitFailure 1)
_ -> return ()
replPkg :: [Opt]
-> FilePath
-> Idris ()
replPkg copts fp = do
orig <- getIState
runIO $ checkPkg copts False False fp
pkgdesc <- runIO $ parseDesc fp
case mergeOptions copts (idris_opts pkgdesc) of
Left emsg -> ifail emsg
Right opts -> do
putIState orig
dir <- runIO getCurrentDirectory
runIO $ setCurrentDirectory $ dir </> sourcedir pkgdesc
runMain opts (idris_main pkgdesc)
runIO $ setCurrentDirectory dir
where
toPath n = foldl1' (</>) $ splitOn "." n
runMain opts (Just mod) = do
let f = toPath (showCG mod)
idrisMain ((Filename f) : opts)
runMain _ Nothing =
iputStrLn "Can't start REPL: no main module given"
cleanPkg :: [Opt]
-> FilePath
-> IO ()
cleanPkg copts fp = do
pkgdesc <- parseDesc fp
dir <- getCurrentDirectory
inPkgDir pkgdesc $ do
clean (makefile pkgdesc)
mapM_ rmIBC (modules pkgdesc)
rmIdx (pkgname pkgdesc)
case execout pkgdesc of
Nothing -> return ()
Just s -> rmExe $ dir </> s
documentPkg :: [Opt]
-> (Bool,FilePath)
-> IO ()
documentPkg copts (install,fp) = do
pkgdesc <- parseDesc fp
cd <- getCurrentDirectory
let pkgDir = cd </> takeDirectory fp
outputDir = cd </> pkgname pkgdesc ++ "_doc"
popts = NoREPL : Verbose 1 : idris_opts pkgdesc
mods = modules pkgdesc
fs = map (foldl1' (</>) . splitOn "." . showCG) mods
setCurrentDirectory $ pkgDir </> sourcedir pkgdesc
make (makefile pkgdesc)
setCurrentDirectory pkgDir
case mergeOptions copts popts of
Left emsg -> do
putStrLn emsg
exitWith (ExitFailure 1)
Right opts -> do
let run l = runExceptT . execStateT l
load [] = return ()
load (f:fs) = do loadModule f IBC_Building; load fs
loader = do
idrisMain opts
addImportDir (sourcedir pkgdesc)
load fs
idrisImplementation <- run loader idrisInit
setCurrentDirectory cd
case idrisImplementation of
Left err -> do
putStrLn $ pshow idrisInit err
exitWith (ExitFailure 1)
Right ist -> do
iDocDir <- getIdrisDocDir
pkgDocDir <- makeAbsolute (iDocDir </> pkgname pkgdesc)
let out_dir = if install then pkgDocDir else outputDir
when install $ do
putStrLn $ unwords ["Attempting to install IdrisDocs for", pkgname pkgdesc, "in:", out_dir]
docRes <- generateDocs ist mods out_dir
case docRes of
Right _ -> return ()
Left msg -> do
putStrLn msg
exitWith (ExitFailure 1)
testPkg :: [Opt]
-> FilePath
-> IO ExitCode
testPkg copts fp = do
pkgdesc <- parseDesc fp
ok <- mapM (testLib True (pkgname pkgdesc)) (libdeps pkgdesc)
if and ok
then do
(m_ist, exitCode) <- inPkgDir pkgdesc $ do
make (makefile pkgdesc)
(tmpn, tmph) <- tempfile ".idr"
hPutStrLn tmph $
"module Test_______\n" ++
concat ["import " ++ show m ++ "\n" | m <- modules pkgdesc]
++ "namespace Main\n"
++ " main : IO ()\n"
++ " main = do "
++ concat [ show t ++ "\n "
| t <- idris_tests pkgdesc]
hClose tmph
(tmpn', tmph') <- tempfile ""
hClose tmph'
let popts = (Filename tmpn : NoREPL : Verbose 1 : Output tmpn' : idris_opts pkgdesc)
case mergeOptions copts popts of
Left emsg -> do
putStrLn emsg
exitWith (ExitFailure 1)
Right opts -> do
m_ist <- idris opts
let texe = if isWindows then addExtension tmpn' ".exe" else tmpn'
exitCode <- rawSystem texe []
return (m_ist, exitCode)
case m_ist of
Nothing -> exitWith (ExitFailure 1)
Just ist -> do
case errSpan ist of
Just _ -> exitWith (ExitFailure 1)
_ -> return exitCode
else return (ExitFailure 1)
installPkg :: [String]
-> PkgDesc
-> IO ()
installPkg altdests pkgdesc = inPkgDir pkgdesc $ do
d <- getIdrisLibDir
let destdir = case altdests of
[] -> d
(d':_) -> d'
case (execout pkgdesc) of
Nothing -> do
mapM_ (installIBC destdir (pkgname pkgdesc)) (modules pkgdesc)
installIdx destdir (pkgname pkgdesc)
Just o -> return ()
mapM_ (installObj destdir (pkgname pkgdesc)) (objs pkgdesc)
auditPackage :: Bool -> PkgDesc -> IO ()
auditPackage False _ = return ()
auditPackage True ipkg = do
cwd <- getCurrentDirectory
let ms = map (sourcedir ipkg </>) $ map (toPath . showCG) (modules ipkg)
ms' <- mapM makeAbsolute ms
ifiles <- getIdrisFiles cwd
let ifiles' = map dropExtension ifiles
not_listed <- mapM makeRelativeToCurrentDirectory (ifiles' \\ ms')
putStrLn $ unlines $
["Warning: The following modules are not listed in your iPkg file:\n"]
++ map (\m -> unwords ["-", m]) not_listed
++ ["\nModules that are not listed, are not installed."]
where
toPath n = foldl1' (</>) $ splitOn "." n
getIdrisFiles :: FilePath -> IO [FilePath]
getIdrisFiles dir = do
contents <- getDirectoryContents dir
let contents' = filter (\fname -> fname /= "." && fname /= "..") contents
files <- forM contents (findRest dir)
return $ filter (isIdrisFile) (concat files)
isIdrisFile :: FilePath -> Bool
isIdrisFile fp = takeExtension fp == ".idr" || takeExtension fp == ".lidr"
findRest :: FilePath -> FilePath -> IO [FilePath]
findRest dir fn = do
path <- makeAbsolute (dir </> fn)
isDir <- doesDirectoryExist path
if isDir
then getIdrisFiles path
else return [path]
buildMods :: [Opt] -> [Name] -> IO (Maybe IState)
buildMods opts ns = do let f = map (toPath . showCG) ns
idris (map Filename f ++ opts)
where
toPath n = foldl1' (</>) $ splitOn "." n
testLib :: Bool -> String -> String -> IO Bool
testLib warn p f
= do d <- getIdrisCRTSDir
gcc <- getCC
(tmpf, tmph) <- tempfile ""
hClose tmph
let libtest = d </> "libtest.c"
e <- rawSystem gcc [libtest, "-l" ++ f, "-o", tmpf]
case e of
ExitSuccess -> return True
_ -> do if warn
then do putStrLn $ "Not building " ++ p ++
" due to missing library " ++ f
return False
else fail $ "Missing library " ++ f
rmIBC :: Name -> IO ()
rmIBC m = rmFile $ toIBCFile m
rmIdx :: String -> IO ()
rmIdx p = do let f = pkgIndex p
ex <- doesFileExist f
when ex $ rmFile f
rmExe :: String -> IO ()
rmExe p = do
fn <- return $ if isWindows && not (hasExtension p)
then addExtension p ".exe" else p
rmFile fn
toIBCFile (UN n) = str n ++ ".ibc"
toIBCFile (NS n ns) = foldl1' (</>) (reverse (toIBCFile n : map str ns))
installIBC :: String -> String -> Name -> IO ()
installIBC dest p m = do
let f = toIBCFile m
let destdir = dest </> p </> getDest m
putStrLn $ "Installing " ++ f ++ " to " ++ destdir
createDirectoryIfMissing True destdir
copyFile f (destdir </> takeFileName f)
return ()
where
getDest (UN n) = ""
getDest (NS n ns) = foldl1' (</>) (reverse (getDest n : map str ns))
installIdx :: String -> String -> IO ()
installIdx dest p = do
let f = pkgIndex p
let destdir = dest </> p
putStrLn $ "Installing " ++ f ++ " to " ++ destdir
createDirectoryIfMissing True destdir
copyFile f (destdir </> takeFileName f)
return ()
installObj :: String -> String -> String -> IO ()
installObj dest p o = do
let destdir = addTrailingPathSeparator (dest </> p)
putStrLn $ "Installing " ++ o ++ " to " ++ destdir
createDirectoryIfMissing True destdir
copyFile o (destdir </> takeFileName o)
return ()
#ifdef mingw32_HOST_OS
mkDirCmd = "mkdir "
#else
mkDirCmd = "mkdir -p "
#endif
inPkgDir :: PkgDesc -> IO a -> IO a
inPkgDir pkgdesc action =
do dir <- getCurrentDirectory
when (sourcedir pkgdesc /= "") $
do putStrLn $ "Entering directory `" ++ ("." </> sourcedir pkgdesc) ++ "'"
setCurrentDirectory $ dir </> sourcedir pkgdesc
res <- action
when (sourcedir pkgdesc /= "") $
do putStrLn $ "Leaving directory `" ++ ("." </> sourcedir pkgdesc) ++ "'"
setCurrentDirectory dir
return res
make :: Maybe String -> IO ()
make Nothing = return ()
make (Just s) = do rawSystem "make" ["-f", s]
return ()
clean :: Maybe String -> IO ()
clean Nothing = return ()
clean (Just s) = do rawSystem "make" ["-f", s, "clean"]
return ()
mergeOptions :: [Opt]
-> [Opt]
-> Either String [Opt]
mergeOptions copts popts =
case partitionEithers (map chkOpt (normaliseOpts copts)) of
([], copts') -> Right $ copts' ++ popts
(es, _) -> Left $ genErrMsg es
where
normaliseOpts :: [Opt] -> [Opt]
normaliseOpts = filter filtOpt
filtOpt :: Opt -> Bool
filtOpt (PkgBuild _) = False
filtOpt (PkgInstall _) = False
filtOpt (PkgClean _) = False
filtOpt (PkgCheck _) = False
filtOpt (PkgREPL _) = False
filtOpt (PkgDocBuild _) = False
filtOpt (PkgDocInstall _) = False
filtOpt (PkgTest _) = False
filtOpt _ = True
chkOpt :: Opt -> Either String Opt
chkOpt o@(OLogging _) = Right o
chkOpt o@(OLogCats _) = Right o
chkOpt o@(DefaultTotal) = Right o
chkOpt o@(DefaultPartial) = Right o
chkOpt o@(WarnPartial) = Right o
chkOpt o@(WarnReach) = Right o
chkOpt o@(IBCSubDir _) = Right o
chkOpt o@(ImportDir _ ) = Right o
chkOpt o@(UseCodegen _) = Right o
chkOpt o@(Verbose _) = Right o
chkOpt o@(AuditIPkg) = Right o
chkOpt o@(DumpHighlights) = Right o
chkOpt o = Left (unwords ["\t", show o, "\n"])
genErrMsg :: [String] -> String
genErrMsg es = unlines
[ "Not all command line options can be used to override package options."
, "\nThe only changeable options are:"
, "\t--log <lvl>, --total, --warnpartial, --warnreach, --warnipkg"
, "\t--ibcsubdir <path>, -i --idrispath <path>"
, "\t--logging-categories <cats>"
, "\t--highlight"
, "\nThe options need removing are:"
, unlines es
]