3Description : Functionality for working with Idris packages.
6Maintainer : The Idris Community.
9module Idris.Package where
11import System.Directory
12import System.Directory (copyFile, createDirectoryIfMissing)
14import System.FilePath (addExtension, addTrailingPathSeparator, dropExtension,
15 hasExtension, takeDirectory, takeExtension,
23import Control.Monad.Trans.Except (runExceptT)
24import Control.Monad.Trans.State.Strict (execStateT)
25import Data.Either (partitionEithers)
27import Data.List.Split (splitOn)
31import Idris.Error (ifail)
35import Idris.Main (idris, idrisMain)
38import Idris.Parser (loadModule)
40import Idris.Package.Common
41import Idris.Package.Parser
46-- * read the package description
47-- * check all the library dependencies exist
48-- * invoke the makefile if there is one
49-- * invoke idris on each module, with idris_opts
50-- * install everything into datadir/pname, if install flag is set
52getPkgDesc :: FilePath -> IO PkgDesc
55-- --------------------------------------------------------- [ Build Packages ]
57-- | Run the package through the idris compiler.
58buildPkg :: [Opt] -- ^ Command line options
59 -> Bool -- ^ Provide Warnings
60 -> (Bool, FilePath) -- ^ (Should we install, Location of iPKG file)
62buildPkg copts warnonly (install, fp) = do
63 pkgdesc <- parseDesc fp
64 dir <- getCurrentDirectory
65 let idx' = pkgIndex $ pkgname pkgdesc
66 idx = PkgIndex $ case opt getIBCSubDir copts of
67 (ibcsubdir:_) -> ibcsubdir </> idx'
69 oks <- mapM (testLib warnonly (pkgname pkgdesc)) (libdeps pkgdesc)
71 m_ist <- inPkgDir pkgdesc $ do
73 make (makefile pkgdesc)
74 case (execout pkgdesc) of
76 case mergeOptions copts (idx : NoREPL : Verbose 1 : idris_opts pkgdesc) of
79 exitWith (ExitFailure 1)
81 auditPackage (AuditIPkg `elem` opts) pkgdesc
82 buildMods opts (modules pkgdesc)
85 case mergeOptions copts (idx : NoREPL : Verbose 1 : Output exec : idris_opts pkgdesc) of
88 exitWith (ExitFailure 1)
90 auditPackage (AuditIPkg `elem` opts) pkgdesc
91 buildMain opts (idris_main pkgdesc)
93 Nothing -> exitWith (ExitFailure 1)
95 -- Quit with error code if there was a problem
97 Just _ -> exitWith (ExitFailure 1)
99 when install $ installPkg (opt getIBCSubDir copts) pkgdesc
101 buildMain opts (Just mod) = buildMods opts [mod]
102 buildMain _ Nothing = do
103 putStrLn "Can't build an executable: No main module given"
104 exitWith (ExitFailure 1)
106-- --------------------------------------------------------- [ Check Packages ]
108-- | Type check packages only
110-- This differs from build in that executables are not built, if the
111-- package contains an executable.
112checkPkg :: [Opt] -- ^ Command line Options
113 -> Bool -- ^ Show Warnings
114 -> Bool -- ^ quit on failure
115 -> FilePath -- ^ Path to ipkg file.
117checkPkg copts warnonly quit fpath = do
118 pkgdesc <- parseDesc fpath
119 oks <- mapM (testLib warnonly (pkgname pkgdesc)) (libdeps pkgdesc)
121 res <- inPkgDir pkgdesc $ do
122 make (makefile pkgdesc)
124 case mergeOptions copts (NoREPL : Verbose 1 : idris_opts pkgdesc) of
127 exitWith (ExitFailure 1)
129 auditPackage (AuditIPkg `elem` opts) pkgdesc
130 buildMods opts (modules pkgdesc)
131 when quit $ case res of
132 Nothing -> exitWith (ExitFailure 1)
135 Just _ -> exitWith (ExitFailure 1)
138-- ------------------------------------------------------------------- [ REPL ]
140-- | Check a package and start a REPL.
142-- This function only works with packages that have a main module.
144replPkg :: [Opt] -- ^ Command line Options
145 -> FilePath -- ^ Path to ipkg file.
149 runIO $ checkPkg copts False False fp
150 pkgdesc <- runIO $ parseDesc fp -- bzzt, repetition!
152 case mergeOptions copts (idris_opts pkgdesc) of
153 Left emsg -> ifail emsg
157 dir <- runIO getCurrentDirectory
158 runIO $ setCurrentDirectory $ dir </> sourcedir pkgdesc
159 runMain opts (idris_main pkgdesc)
160 runIO $ setCurrentDirectory dir
163 toPath n = foldl1' (</>) $ splitOn "." n
165 runMain opts (Just mod) = do
166 let f = toPath (showCG mod)
167 idrisMain ((Filename f) : opts)
169 iputStrLn "Can't start REPL: no main module given"
171-- --------------------------------------------------------------- [ Cleaning ]
173-- | Clean Package build files
174cleanPkg :: [Opt] -- ^ Command line options.
175 -> FilePath -- ^ Path to ipkg file.
177cleanPkg copts fp = do
178 pkgdesc <- parseDesc fp
179 dir <- getCurrentDirectory
180 inPkgDir pkgdesc $ do
181 clean (makefile pkgdesc)
182 mapM_ rmIBC (modules pkgdesc)
183 rmIdx (pkgname pkgdesc)
184 case execout pkgdesc of
186 Just s -> rmExe $ dir </> s
188-- ------------------------------------------------------ [ Generate IdrisDoc ]
191-- | Generate IdrisDoc for package
192-- TODO: Handle case where module does not contain a matching namespace
193-- E.g. from prelude.ipkg: IO, Prelude.Chars, Builtins
195-- Issue number #1572 on the issue tracker
196-- https://github.com/idris-lang/Idris-dev/issues/1572
197documentPkg :: [Opt] -- ^ Command line options.
198 -> (Bool,FilePath) -- ^ (Should we install?, Path to ipkg file).
200documentPkg copts (install,fp) = do
201 pkgdesc <- parseDesc fp
202 cd <- getCurrentDirectory
203 let pkgDir = cd </> takeDirectory fp
204 outputDir = cd </> unPkgName (pkgname pkgdesc) ++ "_doc"
205 popts = NoREPL : Verbose 1 : idris_opts pkgdesc
206 mods = modules pkgdesc
207 fs = map (foldl1' (</>) . splitOn "." . showCG) mods
208 setCurrentDirectory $ pkgDir </> sourcedir pkgdesc
209 make (makefile pkgdesc)
210 setCurrentDirectory pkgDir
211 case mergeOptions copts popts of
214 exitWith (ExitFailure 1)
216 let run l = runExceptT . execStateT l
218 load (f:fs) = do loadModule f IBC_Building; load fs
221 addImportDir (sourcedir pkgdesc)
223 idrisImplementation <- run loader idrisInit
224 setCurrentDirectory cd
225 case idrisImplementation of
227 putStrLn $ pshow idrisInit err
228 exitWith (ExitFailure 1)
230 iDocDir <- getIdrisDocDir
231 pkgDocDir <- makeAbsolute (iDocDir </> unPkgName (pkgname pkgdesc))
232 let out_dir = if install then pkgDocDir else outputDir
234 putStrLn $ unwords ["Attempting to install IdrisDocs for", show $ pkgname pkgdesc, "in:", out_dir]
236 docRes <- generateDocs ist mods out_dir
241 exitWith (ExitFailure 1)
243-- ------------------------------------------------------------------- [ Test ]
245-- | Build a package with a sythesized main function that runs the tests
246testPkg :: [Opt] -- ^ Command line options.
247 -> FilePath -- ^ Path to ipkg file.
250 pkgdesc <- parseDesc fp
251 ok <- mapM (testLib True (pkgname pkgdesc)) (libdeps pkgdesc)
254 (m_ist, exitCode) <- inPkgDir pkgdesc $ do
255 make (makefile pkgdesc)
256 -- Get a temporary file to save the tests' source in
257 (tmpn, tmph) <- tempfile ".idr"
259 "module Test_______\n" ++
260 concat ["import " ++ show m ++ "\n" | m <- modules pkgdesc]
261 ++ "namespace Main\n"
264 ++ concat [ show t ++ "\n "
265 | t <- idris_tests pkgdesc]
267 (tmpn', tmph') <- tempfile ""
269 let popts = (Filename tmpn : NoREPL : Verbose 1 : Output tmpn' : idris_opts pkgdesc)
270 case mergeOptions copts popts of
273 exitWith (ExitFailure 1)
276 let texe = if isWindows then addExtension tmpn' ".exe" else tmpn'
277 exitCode <- rawSystem texe []
278 return (m_ist, exitCode)
280 Nothing -> exitWith (ExitFailure 1)
282 -- Quit with error code if problem building
284 Just _ -> exitWith (ExitFailure 1)
286 else return (ExitFailure 1)
288-- ----------------------------------------------------------- [ Installation ]
291installPkg :: [String] -- ^ Alternate install location
292 -> PkgDesc -- ^ iPKG file.
294installPkg altdests pkgdesc = inPkgDir pkgdesc $ do
296 let destdir = case altdests of
299 case (execout pkgdesc) of
301 mapM_ (installIBC destdir (pkgname pkgdesc)) (modules pkgdesc)
302 installIdx destdir (pkgname pkgdesc)
303 Just o -> return () -- do nothing, keep executable locally, for noe
305 mapM_ (installObj destdir (pkgname pkgdesc)) (objs pkgdesc)
307-- ---------------------------------------------------------- [ Helper Methods ]
308-- Methods for building, testing, installing, and removal of idris
311auditPackage :: Bool -> PkgDesc -> IO ()
312auditPackage False _ = return ()
313auditPackage True ipkg = do
314 cwd <- getCurrentDirectory
316 let ms = map (sourcedir ipkg </>) $ map (toPath . showCG) (modules ipkg)
317 ms' <- mapM makeAbsolute ms
319 ifiles <- getIdrisFiles cwd
321 let ifiles' = map dropExtension ifiles
323 not_listed <- mapM makeRelativeToCurrentDirectory (ifiles' \\ ms')
326 ["Warning: The following modules are not listed in your iPkg file:\n"]
327 ++ map (\m -> unwords ["-", m]) not_listed
328 ++ ["\nModules that are not listed, are not installed."]
331 toPath n = foldl1' (</>) $ splitOn "." n
333 getIdrisFiles :: FilePath -> IO [FilePath]
334 getIdrisFiles dir = do
335 contents <- getDirectoryContents dir
337 -- [ NOTE ] Directory >= 1.2.5.0 introduced `listDirectory` but later versions of directory appear to be causing problems with ghc 7.10.3 and cabal 1.22 in travis. Let's reintroduce the old ranges for directory to be sure.
339 files <- forM contents (findRest dir)
340 return $ filter (isIdrisFile) (concat files)
342 isIdrisFile :: FilePath -> Bool
343 isIdrisFile fp = takeExtension fp == ".idr" || takeExtension fp == ".lidr"
345 findRest :: FilePath -> FilePath -> IO [FilePath]
347 path <- makeAbsolute (dir </> fn)
348 isDir <- doesDirectoryExist path
350 then getIdrisFiles path
353buildMods :: [Opt] -> [Name] -> IO (Maybe IState)
354buildMods opts ns = do let f = map (toPath . showCG) ns
355 idris (map Filename f ++ opts)
357 toPath n = foldl1' (</>) $ splitOn "." n
359testLib :: Bool -> PkgName -> String -> IO Bool
361 = do d <- getIdrisCRTSDir
363 (tmpf, tmph) <- tempfile ""
365 let libtest = d </> "libtest.c"
366 e <- rawSystem gcc [libtest, "-l" ++ f, "-o", tmpf]
368 ExitSuccess -> return True
370 then do putStrLn $ "Not building " ++ show p ++
371 " due to missing library " ++ f
373 else fail $ "Missing library " ++ f
375rmIBC :: Name -> IO ()
376rmIBC m = rmFile $ toIBCFile m
378rmIdx :: PkgName -> IO ()
379rmIdx p = do let f = pkgIndex p
380 ex <- doesFileExist f
383rmExe :: String -> IO ()
385 fn <- return $ if isWindows && not (hasExtension p)
386 then addExtension p ".exe" else p
389toIBCFile (UN n) = str n ++ ".ibc"
390toIBCFile (NS n ns) = foldl1' (</>) (reverse (toIBCFile n : map str ns))
392installIBC :: String -> PkgName -> Name -> IO ()
393installIBC dest p m = do
395 let destdir = dest </> unPkgName p </> getDest m
396 putStrLn $ "Installing " ++ f ++ " to " ++ destdir
397 createDirectoryIfMissing True destdir
398 copyFile f (destdir </> takeFileName f)
402 getDest (NS n ns) = foldl1' (</>) (reverse (getDest n : map str ns))
404installIdx :: String -> PkgName -> IO ()
405installIdx dest p = do
407 let destdir = dest </> unPkgName p
408 putStrLn $ "Installing " ++ f ++ " to " ++ destdir
409 createDirectoryIfMissing True destdir
410 copyFile f (destdir </> takeFileName f)
413installObj :: String -> PkgName -> String -> IO ()
414installObj dest p o = do
415 let destdir = addTrailingPathSeparator (dest </> unPkgName p)
416 putStrLn $ "Installing " ++ o ++ " to " ++ destdir
417 createDirectoryIfMissing True destdir
418 copyFile o (destdir </> takeFileName o)
421#ifdef mingw32_HOST_OS
424mkDirCmd = "mkdir -p "
427inPkgDir :: PkgDesc -> IO a -> IO a
428inPkgDir pkgdesc action =
429 do dir <- getCurrentDirectory
430 when (sourcedir pkgdesc /= "") $
431 do putStrLn $ "Entering directory `" ++ ("." </> sourcedir pkgdesc) ++ "'"
432 setCurrentDirectory $ dir </> sourcedir pkgdesc
434 when (sourcedir pkgdesc /= "") $
435 do putStrLn $ "Leaving directory `" ++ ("." </> sourcedir pkgdesc) ++ "'"
436 setCurrentDirectory dir
439-- ------------------------------------------------------- [ Makefile Commands ]
440-- | Invoke a Makefile's default target.
441make :: Maybe String -> IO ()
442make Nothing = return ()
443make (Just s) = do rawSystem "make" ["-f", s]
446-- | Invoke a Makefile's clean target.
447clean :: Maybe String -> IO ()
448clean Nothing = return ()
449clean (Just s) = do rawSystem "make" ["-f", s, "clean"]
452-- | Merge an option list representing the command line options into
453-- those specified for a package description.
455-- This is not a complete union between the two options sets. First,
456-- to prevent important package specified options from being
457-- overwritten. Second, the semantics for this merge are not fully
460-- A discussion for this is on the issue tracker:
461-- https://github.com/idris-lang/Idris-dev/issues/1448
463mergeOptions :: [Opt] -- ^ The command line options
464 -> [Opt] -- ^ The package options
465 -> Either String [Opt]
466mergeOptions copts popts =
467 case partitionEithers (map chkOpt (normaliseOpts copts)) of
468 ([], copts') -> Right $ copts' ++ popts
469 (es, _) -> Left $ genErrMsg es
471 normaliseOpts :: [Opt] -> [Opt]
472 normaliseOpts = filter filtOpt
474 filtOpt :: Opt -> Bool
475 filtOpt (PkgBuild _) = False
476 filtOpt (PkgInstall _) = False
477 filtOpt (PkgClean _) = False
478 filtOpt (PkgCheck _) = False
479 filtOpt (PkgREPL _) = False
480 filtOpt (PkgDocBuild _) = False
481 filtOpt (PkgDocInstall _) = False
482 filtOpt (PkgTest _) = False
485 chkOpt :: Opt -> Either String Opt
486 chkOpt o@(OLogging _) = Right o
487 chkOpt o@(OLogCats _) = Right o
488 chkOpt o@(DefaultTotal) = Right o
489 chkOpt o@(DefaultPartial) = Right o
490 chkOpt o@(WarnPartial) = Right o
491 chkOpt o@(WarnReach) = Right o
492 chkOpt o@(IBCSubDir _) = Right o
493 chkOpt o@(ImportDir _ ) = Right o
494 chkOpt o@(UseCodegen _) = Right o
495 chkOpt o@(Verbose _) = Right o
496 chkOpt o@(AuditIPkg) = Right o
497 chkOpt o@(DumpHighlights) = Right o
498 chkOpt o = Left (unwords ["\t", show o, "\n"])
500 genErrMsg :: [String] -> String
501 genErrMsg es = unlines
502 [ "Not all command line options can be used to override package options."
503 , "\nThe only changeable options are:"
504 , "\t--log <lvl>, --total, --warnpartial, --warnreach, --warnipkg"
505 , "\t--ibcsubdir <path>, -i --idrispath <path>"
506 , "\t--logging-categories <cats>"
508 , "\nThe options need removing are:"
512-- --------------------------------------------------------------------- [ EOF ]
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list