11module Language.PureScript.Make.Actions
22 ( MakeActions (.. )
33 , RebuildPolicy (.. )
4+ , RebuildReason (.. )
45 , ProgressMessage (.. )
56 , renderProgressMessage
7+ , renderProgressVerboseMessage
8+ , printProgress
9+ , progressWithFile
610 , buildMakeActions
11+ , makeOutputFilename
712 , checkForeignDecls
813 , cacheDbFile
914 , readCacheDb'
@@ -13,7 +18,7 @@ module Language.PureScript.Make.Actions
1318
1419import Prelude
1520
16- import Control.Monad (guard , unless , when )
21+ import Control.Monad (guard , unless , void , when )
1722import Control.Monad.Error.Class (MonadError (.. ))
1823import Control.Monad.IO.Class (MonadIO (.. ))
1924import Control.Monad.Reader (asks )
@@ -31,7 +36,8 @@ import Data.Set qualified as S
3136import Data.Text qualified as T
3237import Data.Text.IO qualified as TIO
3338import Data.Text.Encoding qualified as TE
34- import Data.Time.Clock (UTCTime )
39+ import Data.Time (formatTime , defaultTimeLocale )
40+ import Data.Time.Clock (UTCTime , NominalDiffTime )
3541import Data.Version (showVersion )
3642import Language.JavaScript.Parser qualified as JS
3743import Language.PureScript.AST (SourcePos (.. ))
@@ -44,10 +50,11 @@ import Language.PureScript.Crash (internalError)
4450import Language.PureScript.CST qualified as CST
4551import Language.PureScript.Docs.Prim qualified as Docs.Prim
4652import Language.PureScript.Docs.Types qualified as Docs
47- import Language.PureScript.Errors (MultipleErrors , SimpleErrorMessage (.. ), errorMessage , errorMessage' )
53+ import Language.PureScript.Errors (MultipleErrors , SimpleErrorMessage (.. ), errorMessage , errorMessage' , nonEmpty , ErrorMessage ( .. ), onErrorMessages , replaceSpanName , runMultipleErrors )
4854import Language.PureScript.Externs (ExternsFile , externsFileName )
49- import Language.PureScript.Make.Monad (Make , copyFile , getCurrentTime , getTimestamp , getTimestampMaybe , hashFile , makeIO , readExternsFile , readJSONFile , readTextFile , setTimestamp , writeCborFile , writeJSONFile , writeTextFile )
55+ import Language.PureScript.Make.Monad (Make , copyFile , getCurrentTime , getTimestamp , getTimestampMaybe , hashFile , makeIO , readExternsFile , readWarningsFile , readJSONFile , readTextFile , setTimestamp , writeCborFile , writeJSONFile , writeTextFile , removeFileIfExists )
5056import Language.PureScript.Make.Cache (CacheDb , ContentHash , cacheDbIsCurrentVersion , fromCacheDbVersioned , normaliseForCache , toCacheDbVersioned )
57+ import Language.PureScript.Make.ExternsDiff qualified as ED
5158import Language.PureScript.Names (Ident (.. ), ModuleName , runModuleName )
5259import Language.PureScript.Options (CodegenTarget (.. ), Options (.. ))
5360import Language.PureScript.Pretty.Common (SMap (.. ))
@@ -57,47 +64,115 @@ import SourceMap.Types (Mapping(..), Pos(..), SourceMapping(..))
5764import System.Directory (getCurrentDirectory )
5865import System.FilePath ((</>) , makeRelative , splitPath , normalise , splitDirectories )
5966import System.FilePath.Posix qualified as Posix
60- import System.IO (stderr )
67+ import System.IO (stderr , IOMode (.. ))
68+ import Language.PureScript.AST.Declarations (ErrorMessageHint (.. ))
69+ import Control.Concurrent.Lifted (newMVar , putMVar , takeMVar )
70+ import GHC.IO.StdHandles (withFile )
71+ import GHC.IO.Handle (hFlush )
72+ import Numeric (showFFloat )
6173
6274-- | Determines when to rebuild a module
6375data RebuildPolicy
6476 -- | Never rebuild this module
6577 = RebuildNever
6678 -- | Always rebuild this module
6779 | RebuildAlways
80+ deriving (Show , Ord , Eq )
81+
82+ -- | Specifies reason for module compilation while incremental build.
83+ data RebuildReason
84+ -- | Compiled because of RebuildAlways policy
85+ = RebuildAlwaysPolicy
86+ -- | Compiled because of no previously built result available
87+ | NoCached
88+ -- | Compiled because of no previously built result for one of dependencies is available.
89+ | NoCachedDependency
90+ -- | Compiled because the module has changed since its previous compilation.
91+ | CacheOutdated
92+ -- | Compiled because has later dependency (that previously has been built after the module).
93+ | LaterDependency ModuleName
94+ -- | Compiled because of (the first found) changed reference in a dependency.
95+ | UpstreamRef ED. DiffRef
6896 deriving (Show , Eq , Ord )
6997
7098-- | Progress messages from the make process
7199data ProgressMessage
72- = CompilingModule ModuleName (Maybe (Int , Int ))
100+ = CompilingModule ModuleName (Maybe (Int , Int )) RebuildReason
73101 -- ^ Compilation started for the specified module
74102 | SkippingModule ModuleName (Maybe (Int , Int ))
75- deriving (Show , Eq , Ord )
103+ | ModuleCompiled ModuleName (Maybe (Int , Int )) NominalDiffTime (Maybe ED. ExternsDiff ) MultipleErrors
104+ | ModuleFailed ModuleName (Maybe (Int , Int )) MultipleErrors
105+ deriving (Show )
76106
77- -- | Render a progress message
78- renderProgressMessage :: T. Text -> ProgressMessage -> T. Text
79- renderProgressMessage infx msg = case msg of
80- CompilingModule mn mi ->
107+ renderProgressIndex :: Maybe (Int , Int ) -> T. Text
108+ renderProgressIndex = maybe " " $ \ (start, end) ->
109+ let start' = T. pack (show start)
110+ end' = T. pack (show end)
111+ preSpace = T. replicate (T. length end' - T. length start') " "
112+ in " [" <> preSpace <> start' <> " of " <> end' <> " ] "
113+
114+ sSuffix :: Int -> T. Text
115+ sSuffix n = if n > 1 then " s" else " "
116+
117+ renderProgressVerboseMessage :: T. Text -> ProgressMessage -> T. Text
118+ renderProgressVerboseMessage infx msg = case msg of
119+ CompilingModule mn mi br ->
81120 T. concat
82121 [ renderProgressIndex mi
83122 , " Compiling "
84123 , infx
85124 , runModuleName mn
125+ , (flip (<>) " )" . (<>) " (rebuild reason: " . T. pack . show ) br
86126 ]
87127 SkippingModule mn mi ->
88128 T. concat
89- [renderProgressIndex mi
129+ [ renderProgressIndex mi
90130 , " Skipping "
91131 , infx
92132 , runModuleName mn
93133 ]
94- where
95- renderProgressIndex :: Maybe (Int , Int ) -> T. Text
96- renderProgressIndex = maybe " " $ \ (start, end) ->
97- let start' = T. pack (show start)
98- end' = T. pack (show end)
99- preSpace = T. replicate (T. length end' - T. length start') " "
100- in " [" <> preSpace <> start' <> " of " <> end' <> " ] "
134+ ModuleCompiled mn mi time extDiff warnings ->
135+ T. concat
136+ [ renderProgressIndex mi
137+ , " Compiled "
138+ , infx
139+ , runModuleName mn
140+ , " in " <> (T. pack . toMs) time <> " ms"
141+ , if nonEmpty warnings
142+ then
143+ " with "
144+ <> (T. pack . show ) wLen
145+ <> " warning"
146+ <> sSuffix wLen
147+ else " "
148+ , maybe " " (flip (<>) " )" . (<>) " (changed refs: " . T. pack . show . ED. edRefs) extDiff
149+ ]
150+ where
151+ wLen = length $ runMultipleErrors warnings
152+ toMs ndt = showFFloat (Just 3 ) (realToFrac ndt * 1000 :: Double ) " "
153+ ModuleFailed mn mi errors ->
154+ T. concat
155+ [ renderProgressIndex mi
156+ , " Failed to compile "
157+ , infx
158+ , runModuleName mn
159+ , " with " <> (T. pack . show ) eLen <> " error" <> sSuffix eLen
160+ ]
161+ where
162+ eLen = length $ runMultipleErrors errors
163+
164+ -- | Render a progress message
165+ -- infix in used, i.g in docs generation.
166+ renderProgressMessage :: T. Text -> ProgressMessage -> Maybe T. Text
167+ renderProgressMessage infx msg = case msg of
168+ CompilingModule mn mi _ ->
169+ Just $ T. concat
170+ [ renderProgressIndex mi
171+ , " Compiling "
172+ , infx
173+ , runModuleName mn
174+ ]
175+ _ -> Nothing
101176
102177-- | Actions that require implementations when running in "make" mode.
103178--
@@ -119,13 +194,17 @@ data MakeActions m = MakeActions
119194 -- externs file, or if any of the requested codegen targets were not produced
120195 -- the last time this module was compiled, this function must return Nothing;
121196 -- this indicates that the module will have to be recompiled.
122- , updateOutputTimestamp :: ModuleName -> m Bool
197+ , updateOutputTimestamp :: ModuleName -> Maybe UTCTime -> m Bool
123198 -- ^ Updates the modification time of existing output files to mark them as
124199 -- actual.
125200 , readExterns :: ModuleName -> m (FilePath , Maybe ExternsFile )
126201 -- ^ Read the externs file for a module as a string and also return the actual
127202 -- path for the file.
128- , codegen :: CF. Module CF. Ann -> Docs. Module -> ExternsFile -> SupplyT m ()
203+ , readWarnings :: (ModuleName , FilePath ) -> m (FilePath , Maybe MultipleErrors )
204+ -- ^ Read the file with cached warnings for a module and also return the
205+ -- actual path for the warnings file. It also requires module's filePath to place
206+ -- it in source spans to personalize warnings.
207+ , codegen :: CF. Module CF. Ann -> Docs. Module -> ExternsFile -> MultipleErrors -> SupplyT m ()
129208 -- ^ Run the code generator for the module and write any required output files.
130209 , ffiCodegen :: CF. Module CF. Ann -> m ()
131210 -- ^ Check ffi and print it in the output directory.
@@ -149,6 +228,20 @@ data MakeActions m = MakeActions
149228cacheDbFile :: FilePath -> FilePath
150229cacheDbFile = (</> " cache-db.json" )
151230
231+ warningsFileName :: FilePath
232+ warningsFileName = " warnings.cbor"
233+
234+ replaceSpanNameInErrors :: FilePath -> MultipleErrors -> MultipleErrors
235+ replaceSpanNameInErrors fp =
236+ onErrorMessages replace
237+ where
238+ replaceSpan = replaceSpanName fp
239+ replaceHint = \ case
240+ PositionedError ss -> PositionedError (replaceSpan <$> ss)
241+ RelatedPositions ss -> RelatedPositions (replaceSpan <$> ss)
242+ h -> h
243+ replace (ErrorMessage hints e) = ErrorMessage (replaceHint <$> hints) e
244+
152245readCacheDb'
153246 :: (MonadIO m , MonadError MultipleErrors m )
154247 => FilePath
@@ -179,6 +272,42 @@ writePackageJson' outputDir = writeJSONFile (outputDir </> "package.json") $ obj
179272 [ " type" .= String " module"
180273 ]
181274
275+ makeOutputFilename :: FilePath -> ModuleName -> String -> FilePath
276+ makeOutputFilename outputDir mn fn =
277+ let filePath = T. unpack (runModuleName mn)
278+ in outputDir </> filePath </> fn
279+
280+ printProgress :: ProgressMessage -> Make ()
281+ printProgress = liftIO . maybe (pure () ) (TIO. hPutStr stderr . (<> " \n " )) . renderProgressMessage " "
282+
283+ toLogTime :: UTCTime -> T. Text
284+ toLogTime = T. pack . formatTime defaultTimeLocale " %Y-%m-%d %H:%M:%S.%3q"
285+
286+ withLogTime :: UTCTime -> T. Text -> T. Text
287+ withLogTime time =
288+ (<>) (toLogTime time <> " - " )
289+
290+ progressWithFile :: FilePath -> Bool -> Make (ProgressMessage -> Make () )
291+ progressWithFile logFilePath cleanFile = do
292+ lock <- newMVar ()
293+ let mode = if cleanFile then WriteMode else AppendMode
294+ curTime <- getCurrentTime
295+ let initMsg = " Starting new build"
296+ liftIO $ withFile logFilePath mode $ \ handle ->
297+ TIO. hPutStrLn handle (withLogTime curTime initMsg)
298+ pure (logToFile lock)
299+ where
300+ -- TODO: what is better to use sync output vs hFlush handle?
301+ logToFile lock pm = void $ liftIO $ do
302+ takeMVar lock
303+ -- msg <- getLogMessage pm
304+ curTime <- getCurrentTime
305+ let msg = withLogTime curTime (renderProgressVerboseMessage " " pm)
306+ liftIO $ withFile logFilePath AppendMode $ \ handle -> do
307+ TIO. hPutStrLn handle msg
308+ hFlush handle
309+ putMVar lock ()
310+
182311-- | A set of make actions that read and write modules from the given directory.
183312buildMakeActions
184313 :: FilePath
@@ -196,6 +325,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
196325 getOutputTimestamp
197326 updateOutputTimestamp
198327 readExterns
328+ readWarnings
199329 codegen
200330 ffiCodegen
201331 progress
@@ -223,9 +353,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
223353 return $ Right $ M. fromList pathsWithInfo
224354
225355 outputFilename :: ModuleName -> String -> FilePath
226- outputFilename mn fn =
227- let filePath = T. unpack (runModuleName mn)
228- in outputDir </> filePath </> fn
356+ outputFilename = makeOutputFilename outputDir
229357
230358 targetFilename :: ModuleName -> CodegenTarget -> FilePath
231359 targetFilename mn = \ case
@@ -262,33 +390,44 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
262390 then Just externsTimestamp
263391 else Nothing
264392
265- updateOutputTimestamp :: ModuleName -> Make Bool
266- updateOutputTimestamp mn = do
267- curTime <- getCurrentTime
393+ updateOutputTimestamp :: ModuleName -> Maybe UTCTime -> Make Bool
394+ updateOutputTimestamp mn mbTime = do
395+ curTime <- maybe getCurrentTime pure mbTime
268396 ok <- setTimestamp (outputFilename mn externsFileName) curTime
269- -- Then update timestamps of all actual codegen targets.
397+ _ <- setTimestamp (outputFilename mn warningsFileName) curTime
398+ -- then update all actual codegen targets
270399 codegenTargets <- asks optionsCodegenTargets
271400 let outputPaths = fmap (targetFilename mn) (S. toList codegenTargets)
272401 results <- traverse (flip setTimestamp curTime) outputPaths
273- -- If something goes wrong (any of targets doesn't exit, a file system
274- -- error), return False.
402+ -- if something goes wrong, something failed to update, return Nothing
275403 pure $ and (ok : results)
276404
277405 readExterns :: ModuleName -> Make (FilePath , Maybe ExternsFile )
278406 readExterns mn = do
279407 let path = outputDir </> T. unpack (runModuleName mn) </> externsFileName
280408 (path, ) <$> readExternsFile path
281409
410+ readWarnings :: (ModuleName , FilePath ) -> Make (FilePath , Maybe MultipleErrors )
411+ readWarnings (mn, fp) = do
412+ let path = outputDir </> T. unpack (runModuleName mn) </> warningsFileName
413+ (path, ) . fmap (replaceSpanNameInErrors fp) <$> readWarningsFile path
414+
282415 outputPrimDocs :: Make ()
283416 outputPrimDocs = do
284417 codegenTargets <- asks optionsCodegenTargets
285418 when (S. member Docs codegenTargets) $ for_ Docs.Prim. primModules $ \ docsMod@ Docs. Module {.. } ->
286419 writeJSONFile (outputFilename modName " docs.json" ) docsMod
287420
288- codegen :: CF. Module CF. Ann -> Docs. Module -> ExternsFile -> SupplyT Make ()
289- codegen m docs exts = do
421+ codegen :: CF. Module CF. Ann -> Docs. Module -> ExternsFile -> MultipleErrors -> SupplyT Make ()
422+ codegen m docs exts warnings = do
290423 let mn = CF. moduleName m
291424 lift $ writeCborFile (outputFilename mn externsFileName) exts
425+ let warningsFile = outputFilename mn warningsFileName
426+ lift $ if nonEmpty warnings then
427+ -- Remove spanName from the errors
428+ writeCborFile warningsFile (replaceSpanNameInErrors " " warnings)
429+ else
430+ removeFileIfExists warningsFile
292431 codegenTargets <- lift $ asks optionsCodegenTargets
293432 when (S. member CoreFn codegenTargets) $ do
294433 let coreFnFile = targetFilename mn CoreFn
@@ -354,7 +493,8 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
354493 requiresForeign = not . null . CF. moduleForeign
355494
356495 progress :: ProgressMessage -> Make ()
357- progress = liftIO . TIO. hPutStr stderr . (<> " \n " ) . renderProgressMessage " "
496+ progress msg = do
497+ printProgress msg
358498
359499 readCacheDb :: Make CacheDb
360500 readCacheDb = readCacheDb' outputDir
0 commit comments