forked from purescript/purescript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRebuild.hs
More file actions
190 lines (170 loc) · 6.93 KB
/
Rebuild.hs
File metadata and controls
190 lines (170 loc) · 6.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TemplateHaskell #-}
module Language.PureScript.Ide.Rebuild
( rebuildFileSync
, rebuildFileAsync
, rebuildFile
) where
import Protolude
import "monad-logger" Control.Monad.Logger
import qualified Data.List as List
import qualified Data.Map.Lazy as M
import Data.Maybe (fromJust)
import qualified Data.Set as S
import qualified Language.PureScript as P
import Language.PureScript.Errors.JSON
import Language.PureScript.Ide.Error
import Language.PureScript.Ide.Logging
import Language.PureScript.Ide.State
import Language.PureScript.Ide.Types
import Language.PureScript.Ide.Util
-- | Given a filepath performs the following steps:
--
-- * Reads and parses a PureScript module from the filepath.
--
-- * Builds a dependency graph for the parsed module from the already loaded
-- ExternsFiles.
--
-- * Attempts to find an FFI definition file for the module by looking
-- for a file with the same filepath except for a .js extension.
--
-- * Passes all the created artifacts to @rebuildModule@.
--
-- * If the rebuilding succeeds, returns a @RebuildSuccess@ with the generated
-- warnings, and if rebuilding fails, returns a @RebuildError@ with the
-- generated errors.
rebuildFile
:: (Ide m, MonadLogger m, MonadError IdeError m)
=> FilePath
-- ^ The file to rebuild
-> (ReaderT IdeEnvironment (LoggingT IO) () -> m ())
-- ^ A runner for the second build with open exports
-> m Success
rebuildFile path runOpenBuild = do
input <- ideReadFile path
m <- case snd <$> P.parseModuleFromFile identity (path, input) of
Left parseError -> throwError
. RebuildError
. toJSONErrors False P.Error
$ P.MultipleErrors [P.toPositionedError parseError]
Right m -> pure m
-- Externs files must be sorted ahead of time, so that they get applied
-- correctly to the 'Environment'.
externs <- logPerf (labelTimespec "Sorting externs") (sortExterns m =<< getExternFiles)
outputDirectory <- confOutputPath . ideConfiguration <$> ask
-- For rebuilding, we want to 'RebuildAlways', but for inferring foreign
-- modules using their file paths, we need to specify the path in the 'Map'.
let filePathMap = M.singleton (P.getModuleName m) (Left P.RebuildAlways)
foreigns <- P.inferForeignModules (M.singleton (P.getModuleName m) (Right path))
let makeEnv = MakeActionsEnv outputDirectory filePathMap foreigns False
-- Rebuild the single module using the cached externs
(result, warnings) <- logPerf (labelTimespec "Rebuilding Module") $
liftIO
. P.runMake P.defaultOptions
. P.rebuildModule (buildMakeActions
>>= shushProgress $ makeEnv) externs $ m
case result of
Left errors -> throwError (RebuildError (toJSONErrors False P.Error errors))
Right _ -> do
runOpenBuild (rebuildModuleOpen makeEnv externs m)
pure (RebuildSuccess (toJSONErrors False P.Warning warnings))
rebuildFileAsync
:: forall m. (Ide m, MonadLogger m, MonadError IdeError m)
=> FilePath -> m Success
rebuildFileAsync fp = rebuildFile fp asyncRun
where
asyncRun :: ReaderT IdeEnvironment (LoggingT IO) () -> m ()
asyncRun action = do
env <- ask
let ll = confLogLevel (ideConfiguration env)
void (liftIO (async (runLogger ll (runReaderT action env))))
rebuildFileSync
:: forall m. (Ide m, MonadLogger m, MonadError IdeError m)
=> FilePath -> m Success
rebuildFileSync fp = rebuildFile fp syncRun
where
syncRun :: ReaderT IdeEnvironment (LoggingT IO) () -> m ()
syncRun action = do
env <- ask
let ll = confLogLevel (ideConfiguration env)
void (liftIO (runLogger ll (runReaderT action env)))
-- | Rebuilds a module but opens up its export list first and stores the result
-- inside the rebuild cache
rebuildModuleOpen
:: (Ide m, MonadLogger m)
=> MakeActionsEnv
-> [P.ExternsFile]
-> P.Module
-> m ()
rebuildModuleOpen makeEnv externs m = void $ runExceptT $ do
(openResult, _) <- liftIO
. P.runMake P.defaultOptions
. P.rebuildModule (buildMakeActions
>>= shushProgress
>>= shushCodegen
$ makeEnv) externs $ openModuleExports m
case openResult of
Left _ ->
throwError (GeneralError "Failed when rebuilding with open exports")
Right result -> do
$(logDebug)
("Setting Rebuild cache: " <> P.runModuleName (P.efModuleName result))
cacheRebuild result
-- | Parameters we can access while building our @MakeActions@
data MakeActionsEnv =
MakeActionsEnv
{ maeOutputDirectory :: FilePath
, maeFilePathMap :: ModuleMap (Either P.RebuildPolicy FilePath)
, maeForeignPathMap :: ModuleMap FilePath
, maePrefixComment :: Bool
}
-- | Builds the default @MakeActions@ from a @MakeActionsEnv@
buildMakeActions :: MakeActionsEnv -> P.MakeActions P.Make
buildMakeActions MakeActionsEnv{..} =
P.buildMakeActions
maeOutputDirectory
maeFilePathMap
maeForeignPathMap
maePrefixComment
-- | Shuts the compiler up about progress messages
shushProgress :: P.MakeActions P.Make -> MakeActionsEnv -> P.MakeActions P.Make
shushProgress ma _ =
ma { P.progress = \_ -> pure () }
-- | Stops any kind of codegen (also silences errors about missing or unused FFI
-- files though)
shushCodegen :: P.MakeActions P.Make -> MakeActionsEnv -> P.MakeActions P.Make
shushCodegen ma MakeActionsEnv{..} =
ma { P.codegen = \_ _ _ -> pure () }
-- | Returns a topologically sorted list of dependent ExternsFiles for the given
-- module. Throws an error if there is a cyclic dependency within the
-- ExternsFiles
sortExterns
:: (Ide m, MonadError IdeError m)
=> P.Module
-> ModuleMap P.ExternsFile
-> m [P.ExternsFile]
sortExterns m ex = do
sorted' <- runExceptT
. P.sortModules
. (:) m
. map mkShallowModule
. M.elems
. M.delete (P.getModuleName m) $ ex
case sorted' of
Left err ->
throwError (RebuildError (toJSONErrors False P.Error err))
Right (sorted, graph) -> do
let deps = fromJust (List.lookup (P.getModuleName m) graph)
pure $ mapMaybe getExtern (deps `inOrderOf` map P.getModuleName sorted)
where
mkShallowModule P.ExternsFile{..} =
P.Module (P.internalModuleSourceSpan "<rebuild>") [] efModuleName (map mkImport efImports) Nothing
mkImport (P.ExternsImport mn it iq) =
P.ImportDeclaration mn it iq
getExtern mn = M.lookup mn ex
-- Sort a list so its elements appear in the same order as in another list.
inOrderOf :: (Ord a) => [a] -> [a] -> [a]
inOrderOf xs ys = let s = S.fromList xs in filter (`S.member` s) ys
-- | Removes a modules export list.
openModuleExports :: P.Module -> P.Module
openModuleExports (P.Module ss cs mn decls _) = P.Module ss cs mn decls Nothing