2module Build.Dependencies (getSortedDependencies, readDeps) where
4import Control.Applicative
5import Control.Monad.Error
6import qualified Control.Monad.State as State
7import qualified Data.Aeson as Json
8import qualified Data.ByteString.Lazy.Char8 as BSC
9import qualified Data.Graph as Graph
10import qualified Data.List as List
11import qualified Data.Map as Map
12import qualified Data.Maybe as Maybe
13import qualified Data.Set as Set
14import System.Directory
15import System.FilePath as FP
17import Build.Print (failure)
19import qualified SourceSyntax.Module as Module
20import qualified Parse.Parse as Parse
21import qualified Elm.Internal.Paths as Path
22import qualified Elm.Internal.Name as N
23import qualified Elm.Internal.Dependencies as Deps
25getSortedDependencies :: [FilePath] -> Module.Interfaces -> FilePath -> IO [String]
26getSortedDependencies srcDirs builtIns root =
27 do extras <- extraDependencies
28 let allSrcDirs = srcDirs ++ Maybe.fromMaybe [] extras
29 result <- runErrorT $ readAllDeps allSrcDirs builtIns root
31 Right deps -> sortDeps deps
32 Left err -> failure err
34extraDependencies :: IO (Maybe [FilePath])
36 do exists <- doesFileExist Path.dependencyFile
37 if not exists then return Nothing else Just <$> getPaths
40 raw <- BSC.readFile Path.dependencyFile
41 case Json.eitherDecode raw of
42 Right (Deps.Mini deps) -> mapM validate deps
44 failure $ "Error reading the " ++ Path.dependencyFile ++ " file:\n" ++ err
46 validate (name,version) = do
47 let path = Path.dependencyDirectory </> toPath name version
48 exists <- doesDirectoryExist path
49 if exists then return path else failure (notFound name version)
51 toPath name version = N.toFilePath name </> show version
53 notFound name version =
55 [ "Your " ++ Path.dependencyFile ++ " file says you depend on library"
56 , show name ++ " " ++ show version ++ " but it was not found."
57 , "You may need to install it with:"
59 , " elm-get install " ++ show name ++ " " ++ show version ]
61type Deps = (FilePath, String, [String])
63sortDeps :: [Deps] -> IO [String]
66 then return (concat sccs)
67 else failure $ msg ++ unlines (map show mistakes)
69 sccs = map Graph.flattenSCC $ Graph.stronglyConnComp depends
71 mistakes = filter (\scc -> length scc > 1) sccs
72 msg = "A cyclical module dependency or was detected in:\n"
74readAllDeps :: [FilePath] -> Module.Interfaces -> FilePath -> ErrorT String IO [Deps]
75readAllDeps srcDirs rawBuiltIns filePath =
76 State.evalStateT (go Nothing filePath) Set.empty
78 builtIns :: Set.Set String
79 builtIns = Set.fromList $ Map.keys rawBuiltIns
81 go :: Maybe String -> FilePath -> State.StateT (Set.Set String) (ErrorT String IO) [Deps]
82 go parentModuleName filePath = do
83 filePath' <- lift $ findSrcFile parentModuleName srcDirs filePath
84 (moduleName, deps) <- lift $ readDeps filePath'
86 let realDeps = Set.difference (Set.fromList deps) builtIns
87 newDeps = Set.difference (Set.filter (not . isNative) realDeps) seen
88 State.put (Set.insert moduleName (Set.union newDeps seen))
89 rest <- mapM (go (Just moduleName) . toFilePath) (Set.toList newDeps)
90 return ((makeRelative "." filePath', moduleName, Set.toList realDeps) : concat rest)
92readDeps :: FilePath -> ErrorT String IO (String, [String])
94 txt <- lift $ readFile path
95 case Parse.dependencies txt of
96 Left err -> throwError $ msg ++ show err
97 where msg = "Error resolving dependencies in " ++ path ++ ":\n"
100findSrcFile :: Maybe String -> [FilePath] -> FilePath -> ErrorT String IO FilePath
101findSrcFile parentModuleName dirs path =
102 foldr tryDir notFound dirs
105 let path' = dir </> path
106 exists <- liftIO $ doesFileExist path'
112 case parentModuleName of
113 Just name -> "module '" ++ name ++ "'"
114 Nothing -> "the main module"
116 notFound = throwError $ unlines
117 [ "When finding the imports declared in " ++ parentModuleName' ++ ", could not find file: " ++ path
118 , " If you created this module, but it is in a subdirectory that does not"
119 , " exactly match the module name, you may need to use the --src-dir flag."
121 , " If it is part of a 3rd party library, it needs to be declared"
122 , " as a dependency in your project's " ++ Path.dependencyFile ++ " file."
125isNative :: String -> Bool
126isNative name = List.isPrefixOf "Native." name
128toFilePath :: String -> FilePath
129toFilePath name = map swapDots name ++ ext
133 ext = if isNative name then ".js" else ".elm"
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