@@ -42,7 +42,6 @@ module Development.IDE.Core.Shake(
42
42
RuleBody (.. ),
43
43
define , defineNoDiagnostics ,
44
44
defineEarlyCutoff ,
45
- defineOnDisk , needOnDisk , needOnDisks ,
46
45
defineNoFile , defineEarlyCutOffNoFile ,
47
46
getDiagnostics ,
48
47
mRunLspT , mRunLspTCallback ,
@@ -63,7 +62,6 @@ module Development.IDE.Core.Shake(
63
62
Priority (.. ),
64
63
updatePositionMapping ,
65
64
deleteValue , recordDirtyKeys ,
66
- OnDiskRule (.. ),
67
65
WithProgressFunc , WithIndefiniteProgressFunc ,
68
66
ProgressEvent (.. ),
69
67
DelayedAction , mkDelayedAction ,
@@ -168,6 +166,7 @@ import qualified "list-t" ListT
168
166
import OpenTelemetry.Eventlog
169
167
import qualified StmContainers.Map as STM
170
168
import System.FilePath hiding (makeRelative )
169
+ import System.IO.Unsafe (unsafePerformIO )
171
170
import System.Time.Extra
172
171
173
172
data Log
@@ -1026,6 +1025,10 @@ usesWithStale key files = do
1026
1025
-- whether the rule succeeded or not.
1027
1026
mapM (lastValue key) files
1028
1027
1028
+ useWithoutDependency :: IdeRule k v
1029
+ => k -> NormalizedFilePath -> Action (Maybe v )
1030
+ useWithoutDependency key file =
1031
+ (\ [A value] -> currentValue value) <$> applyWithoutDependency [Q (key, file)]
1029
1032
1030
1033
data RuleBody k v
1031
1034
= Rule (k -> NormalizedFilePath -> Action (Maybe BS. ByteString , IdeResult v ))
@@ -1044,28 +1047,28 @@ defineEarlyCutoff
1044
1047
-> Rules ()
1045
1048
defineEarlyCutoff recorder (Rule op) = addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do
1046
1049
extras <- getShakeExtras
1047
- let diagnostics diags = do
1050
+ let diagnostics ver diags = do
1048
1051
traceDiagnostics diags
1049
- updateFileDiagnostics recorder file (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
1052
+ updateFileDiagnostics recorder file ver (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
1050
1053
defineEarlyCutoff' diagnostics (==) key file old mode $ const $ op key file
1051
1054
defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do
1052
- let diagnostics diags = do
1055
+ let diagnostics _ver diags = do
1053
1056
traceDiagnostics diags
1054
1057
mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleNoDiagHasDiag ) diags
1055
1058
defineEarlyCutoff' diagnostics (==) key file old mode $ const $ second (mempty ,) <$> op key file
1056
1059
defineEarlyCutoff recorder RuleWithCustomNewnessCheck {.. } =
1057
1060
addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode ->
1058
1061
otTracedAction key file mode traceA $ \ traceDiagnostics -> do
1059
- let diagnostics diags = do
1062
+ let diagnostics _ver diags = do
1060
1063
traceDiagnostics diags
1061
1064
mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleCustomNewnessHasDiag ) diags
1062
1065
defineEarlyCutoff' diagnostics newnessCheck key file old mode $
1063
1066
const $ second (mempty ,) <$> build key file
1064
1067
defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \ (Q (key, file)) (old :: Maybe BS. ByteString ) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do
1065
1068
extras <- getShakeExtras
1066
- let diagnostics diags = do
1069
+ let diagnostics ver diags = do
1067
1070
traceDiagnostics diags
1068
- updateFileDiagnostics recorder file (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
1071
+ updateFileDiagnostics recorder file ver (Key key) extras . map (\ (_,y,z) -> (y,z)) $ diags
1069
1072
defineEarlyCutoff' diagnostics (==) key file old mode $ op key file
1070
1073
1071
1074
defineNoFile :: IdeRule k v => Recorder (WithPriority Log ) -> (k -> Action v ) -> Rules ()
@@ -1080,7 +1083,7 @@ defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnost
1080
1083
1081
1084
defineEarlyCutoff'
1082
1085
:: forall k v . IdeRule k v
1083
- => ([FileDiagnostic ] -> Action () ) -- ^ update diagnostics
1086
+ => (TextDocumentVersion -> [FileDiagnostic ] -> Action () ) -- ^ update diagnostics
1084
1087
-- | compare current and previous for freshness
1085
1088
-> (BS. ByteString -> BS. ByteString -> Bool )
1086
1089
-> k
@@ -1099,8 +1102,9 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
1099
1102
case v of
1100
1103
-- No changes in the dependencies and we have
1101
1104
-- an existing successful result.
1102
- Just (v@ Succeeded {}, diags) -> do
1103
- doDiagnostics $ Vector. toList diags
1105
+ Just (v@ (Succeeded _ x), diags) -> do
1106
+ ver <- estimateFileVersionUnsafely state key (Just x) file
1107
+ doDiagnostics (vfsVersion =<< ver) $ Vector. toList diags
1104
1108
return $ Just $ RunResult ChangedNothing old $ A v
1105
1109
_ -> return Nothing
1106
1110
_ ->
@@ -1120,18 +1124,13 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
1120
1124
\ (e :: SomeException ) -> do
1121
1125
pure (Nothing , ([ideErrorText file $ T. pack $ show e | not $ isBadDependency e],Nothing ))
1122
1126
1123
- modTime <- case eqT @ k @ GetModificationTime of
1124
- Just Refl -> pure res
1125
- Nothing
1126
- | file == emptyFilePath -> pure Nothing
1127
- | otherwise -> liftIO $ (currentValue . fst =<< ) <$> atomicallyNamed " define - read 2" (getValues state GetModificationTime file)
1128
-
1127
+ ver <- estimateFileVersionUnsafely state key res file
1129
1128
(bs, res) <- case res of
1130
1129
Nothing -> do
1131
1130
pure (toShakeValue ShakeStale bs, staleV)
1132
- Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded modTime v)
1131
+ Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded ver v)
1133
1132
liftIO $ atomicallyNamed " define - write" $ setValues state key file res (Vector. fromList diags)
1134
- doDiagnostics diags
1133
+ doDiagnostics (vfsVersion =<< ver) diags
1135
1134
let eq = case (bs, fmap decodeShakeValue old) of
1136
1135
(ShakeResult a, Just (ShakeResult b)) -> cmp a b
1137
1136
(ShakeStale a, Just (ShakeStale b)) -> cmp a b
@@ -1144,117 +1143,74 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
1144
1143
A res
1145
1144
liftIO $ atomicallyNamed " define - dirtyKeys" $ modifyTVar' dirtyKeys (HSet. delete $ toKey key file)
1146
1145
return res
1146
+ where
1147
+ -- Highly unsafe helper to compute the version of a file
1148
+ -- without creating a dependency on the GetModificationTime rule
1149
+ -- (and without creating cycles in the build graph).
1150
+ estimateFileVersionUnsafely
1151
+ :: forall k v
1152
+ . IdeRule k v
1153
+ => Values
1154
+ -> k
1155
+ -> Maybe v
1156
+ -> NormalizedFilePath
1157
+ -> Action (Maybe FileVersion )
1158
+ estimateFileVersionUnsafely state _k v fp
1159
+ | fp == emptyFilePath = pure Nothing
1160
+ | Just Refl <- eqT @ k @ GetModificationTime = pure v
1161
+ -- GetModificationTime depends on these rules, so avoid creating a cycle
1162
+ | Just Refl <- eqT @ k @ AddWatchedFile = pure Nothing
1163
+ | Just Refl <- eqT @ k @ IsFileOfInterest = pure Nothing
1164
+ -- GetFileExists gets called for missing files
1165
+ | Just Refl <- eqT @ k @ GetFileExists = pure Nothing
1166
+ -- For all other rules - compute the version properly without:
1167
+ -- * creating a dependency: If everything depends on GetModificationTime, we lose early cutoff
1168
+ -- * creating bogus "file does not exists" diagnostics
1169
+ | otherwise = useWithoutDependency (GetModificationTime_ False ) fp
1147
1170
1148
1171
traceA :: A v -> String
1149
1172
traceA (A Failed {}) = " Failed"
1150
1173
traceA (A Stale {}) = " Stale"
1151
1174
traceA (A Succeeded {}) = " Success"
1152
1175
1153
- -- | Rule type, input file
1154
- data QDisk k = QDisk k NormalizedFilePath
1155
- deriving (Eq , Generic )
1156
-
1157
- instance Hashable k => Hashable (QDisk k )
1158
-
1159
- instance NFData k => NFData (QDisk k )
1160
-
1161
- instance Show k => Show (QDisk k ) where
1162
- show (QDisk k file) =
1163
- show k ++ " ; " ++ fromNormalizedFilePath file
1164
-
1165
- type instance RuleResult (QDisk k ) = Bool
1166
-
1167
- data OnDiskRule = OnDiskRule
1168
- { getHash :: Action BS. ByteString
1169
- -- This is used to figure out if the state on disk corresponds to the state in the Shake
1170
- -- database and we can therefore avoid rerunning. Often this can just be the file hash but
1171
- -- in some cases we can be more aggressive, e.g., for GHC interface files this can be the ABI hash which
1172
- -- is more stable than the hash of the interface file.
1173
- -- An empty bytestring indicates that the state on disk is invalid, e.g., files are missing.
1174
- -- We do not use a Maybe since we have to deal with encoding things into a ByteString anyway in the Shake DB.
1175
- , runRule :: Action (IdeResult BS. ByteString )
1176
- -- The actual rule code which produces the new hash (or Nothing if the rule failed) and the diagnostics.
1177
- }
1178
-
1179
- -- This is used by the DAML compiler for incremental builds. Right now this is not used by
1180
- -- ghcide itself but that might change in the future.
1181
- -- The reason why this code lives in ghcide and in particular in this module is that it depends quite heavily on
1182
- -- the internals of this module that we do not want to expose.
1183
- defineOnDisk
1184
- :: (Shake. ShakeValue k , RuleResult k ~ () )
1185
- => Recorder (WithPriority Log )
1186
- -> (k -> NormalizedFilePath -> OnDiskRule )
1187
- -> Rules ()
1188
- defineOnDisk recorder act = addRule $
1189
- \ (QDisk key file) (mbOld :: Maybe BS. ByteString ) mode -> do
1190
- extras <- getShakeExtras
1191
- let OnDiskRule {.. } = act key file
1192
- let validateHash h
1193
- | BS. null h = Nothing
1194
- | otherwise = Just h
1195
- let runAct = actionCatch runRule $
1196
- \ (e :: SomeException ) -> pure ([ideErrorText file $ T. pack $ displayException e | not $ isBadDependency e], Nothing )
1197
- case mbOld of
1198
- Nothing -> do
1199
- (diags, mbHash) <- runAct
1200
- updateFileDiagnostics recorder file (Key key) extras $ map (\ (_,y,z) -> (y,z)) diags
1201
- pure $ RunResult ChangedRecomputeDiff (fromMaybe " " mbHash) (isJust mbHash)
1202
- Just old -> do
1203
- current <- validateHash <$> (actionCatch getHash $ \ (_ :: SomeException ) -> pure " " )
1204
- if mode == RunDependenciesSame && Just old == current && not (BS. null old)
1205
- then
1206
- -- None of our dependencies changed, we’ve had a successful run before and
1207
- -- the state on disk matches the state in the Shake database.
1208
- pure $ RunResult ChangedNothing (fromMaybe " " current) (isJust current)
1209
- else do
1210
- (diags, mbHash) <- runAct
1211
- updateFileDiagnostics recorder file (Key key) extras $ map (\ (_,y,z) -> (y,z)) diags
1212
- let change
1213
- | mbHash == Just old = ChangedRecomputeSame
1214
- | otherwise = ChangedRecomputeDiff
1215
- pure $ RunResult change (fromMaybe " " mbHash) (isJust mbHash)
1216
-
1217
- needOnDisk :: (Shake. ShakeValue k , RuleResult k ~ () ) => k -> NormalizedFilePath -> Action ()
1218
- needOnDisk k file = do
1219
- successfull <- apply1 (QDisk k file)
1220
- liftIO $ unless successfull $ throwIO $ BadDependency (show k)
1221
-
1222
- needOnDisks :: (Shake. ShakeValue k , RuleResult k ~ () ) => k -> [NormalizedFilePath ] -> Action ()
1223
- needOnDisks k files = do
1224
- successfulls <- apply $ map (QDisk k) files
1225
- liftIO $ unless (and successfulls) $ throwIO $ BadDependency (show k)
1226
-
1227
1176
updateFileDiagnostics :: MonadIO m
1228
1177
=> Recorder (WithPriority Log )
1229
1178
-> NormalizedFilePath
1179
+ -> TextDocumentVersion
1230
1180
-> Key
1231
1181
-> ShakeExtras
1232
1182
-> [(ShowDiagnostic ,Diagnostic )] -- ^ current results
1233
1183
-> m ()
1234
- updateFileDiagnostics recorder fp k ShakeExtras {diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, lspEnv} current = liftIO $ do
1235
- modTime <- (currentValue . fst =<< ) <$> atomicallyNamed " diagnostics - read" (getValues state GetModificationTime fp)
1184
+ updateFileDiagnostics recorder fp ver k ShakeExtras {diagnostics, hiddenDiagnostics, publishedDiagnostics, debouncer, lspEnv} current =
1185
+ liftIO $ withTrace (" update diagnostics " <> fromString(fromNormalizedFilePath fp)) $ \ addTag -> do
1186
+ addTag " key" (show k)
1236
1187
let (currentShown, currentHidden) = partition ((== ShowDiag ) . fst ) current
1237
1188
uri = filePathToUri' fp
1238
- ver = vfsVersion =<< modTime
1239
- update new store = setStageDiagnostics uri ver (T. pack $ show k) new store
1189
+ addTagUnsafe :: String -> String -> String -> a -> a
1190
+ addTagUnsafe msg t x v = unsafePerformIO(addTag (msg <> t) x) `seq` v
1191
+ update :: (forall a . String -> String -> a -> a ) -> [Diagnostic ] -> STMDiagnosticStore -> STM [Diagnostic ]
1192
+ update addTagUnsafe new store = addTagUnsafe " count" (show $ Prelude. length new) $ setStageDiagnostics addTagUnsafe uri ver (T. pack $ show k) new store
1193
+ addTag " version" (show ver)
1240
1194
mask_ $ do
1241
1195
-- Mask async exceptions to ensure that updated diagnostics are always
1242
1196
-- published. Otherwise, we might never publish certain diagnostics if
1243
1197
-- an exception strikes between modifyVar but before
1244
1198
-- publishDiagnosticsNotification.
1245
- newDiags <- liftIO $ atomicallyNamed " diagnostics - update" $ update (map snd currentShown) diagnostics
1246
- _ <- liftIO $ atomicallyNamed " diagnostics - hidden" $ update (map snd currentHidden) hiddenDiagnostics
1199
+ newDiags <- liftIO $ atomicallyNamed " diagnostics - update" $ update (addTagUnsafe " shown " ) ( map snd currentShown) diagnostics
1200
+ _ <- liftIO $ atomicallyNamed " diagnostics - hidden" $ update (addTagUnsafe " hidden " ) ( map snd currentHidden) hiddenDiagnostics
1247
1201
let uri = filePathToUri' fp
1248
1202
let delay = if null newDiags then 0.1 else 0
1249
- registerEvent debouncer delay uri $ do
1203
+ registerEvent debouncer delay uri $ withTrace ( " report diagnostics " <> fromString (fromNormalizedFilePath fp)) $ \ tag -> do
1250
1204
join $ mask_ $ do
1251
1205
lastPublish <- atomicallyNamed " diagnostics - publish" $ STM. focus (Focus. lookupWithDefault [] <* Focus. insert newDiags) uri publishedDiagnostics
1252
1206
let action = when (lastPublish /= newDiags) $ case lspEnv of
1253
1207
Nothing -> -- Print an LSP event.
1254
1208
logWith recorder Info $ LogDiagsDiffButNoLspEnv (map (fp, ShowDiag ,) newDiags)
1255
- Just env -> LSP. runLspT env $
1209
+ Just env -> LSP. runLspT env $ do
1210
+ liftIO $ tag " count" (show $ Prelude. length newDiags)
1211
+ liftIO $ tag " key" (show k)
1256
1212
LSP. sendNotification LSP. STextDocumentPublishDiagnostics $
1257
- LSP. PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)
1213
+ LSP. PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)
1258
1214
return action
1259
1215
1260
1216
newtype Priority = Priority Double
@@ -1276,26 +1232,33 @@ type STMDiagnosticStore = STM.Map NormalizedUri StoreItem
1276
1232
getDiagnosticsFromStore :: StoreItem -> [Diagnostic ]
1277
1233
getDiagnosticsFromStore (StoreItem _ diags) = concatMap SL. fromSortedList $ Map. elems diags
1278
1234
1279
- updateSTMDiagnostics :: STMDiagnosticStore
1280
- -> NormalizedUri -> TextDocumentVersion -> DiagnosticsBySource
1281
- -> STM [LSP. Diagnostic ]
1282
- updateSTMDiagnostics store uri mv newDiagsBySource =
1235
+ updateSTMDiagnostics ::
1236
+ (forall a . String -> String -> a -> a ) ->
1237
+ STMDiagnosticStore ->
1238
+ NormalizedUri ->
1239
+ TextDocumentVersion ->
1240
+ DiagnosticsBySource ->
1241
+ STM [LSP. Diagnostic ]
1242
+ updateSTMDiagnostics addTag store uri mv newDiagsBySource =
1283
1243
getDiagnosticsFromStore . fromJust <$> STM. focus (Focus. alter update *> Focus. lookup ) uri store
1284
1244
where
1285
1245
update (Just (StoreItem mvs dbs))
1246
+ | addTag " previous version" (show mvs) $
1247
+ addTag " previous count" (show $ Prelude. length $ filter (not . null ) $ Map. elems dbs) False = undefined
1286
1248
| mvs == mv = Just (StoreItem mv (newDiagsBySource <> dbs))
1287
1249
update _ = Just (StoreItem mv newDiagsBySource)
1288
1250
1289
1251
-- | Sets the diagnostics for a file and compilation step
1290
1252
-- if you want to clear the diagnostics call this with an empty list
1291
1253
setStageDiagnostics
1292
- :: NormalizedUri
1254
+ :: (forall a . String -> String -> a -> a )
1255
+ -> NormalizedUri
1293
1256
-> TextDocumentVersion -- ^ the time that the file these diagnostics originate from was last edited
1294
1257
-> T. Text
1295
1258
-> [LSP. Diagnostic ]
1296
1259
-> STMDiagnosticStore
1297
1260
-> STM [LSP. Diagnostic ]
1298
- setStageDiagnostics uri ver stage diags ds = updateSTMDiagnostics ds uri ver updatedDiags
1261
+ setStageDiagnostics addTag uri ver stage diags ds = updateSTMDiagnostics addTag ds uri ver updatedDiags
1299
1262
where
1300
1263
! updatedDiags = Map. singleton (Just stage) $! SL. toSortedList diags
1301
1264
0 commit comments