From c2e6eaeddd5357d8be278f56aa00ccb01ff31239 Mon Sep 17 00:00:00 2001
From: Patrick Palka <patrick@parcs.ath.cx>
Date: Mon, 16 Jul 2012 16:58:40 -0400
Subject: [PATCH] Consider usage files in the GHCi recompilation checker
A module now gets recompiled if its usage files have changed.
---
compiler/main/DriverPipeline.hs | 9 ++++-
compiler/main/GHC.hs | 1 +
compiler/main/GhcMake.hs | 85 ++++++++++++++++++++++++++++++---------
compiler/main/HscTypes.lhs | 2 +
4 files changed, 78 insertions(+), 19 deletions(-)
diff --git a/compiler/main/DriverPipeline.hs b/compiler/main/DriverPipeline.hs
index 4770679..0cc6097 100644
|
a
|
b
|
|
| 210 | 210 | return [DotO stub_o] |
| 211 | 211 | |
| 212 | 212 | let hs_unlinked = [BCOs comp_bc modBreaks] |
| 213 | | unlinked_time = ms_hs_date summary |
| | 213 | |
| | 214 | usage_file_mtimes = map snd (ms_usage_files summary) |
| | 215 | unlinked_time = |
| | 216 | maximum (ms_hs_date summary : catMaybes usage_file_mtimes) |
| 214 | 217 | -- Why do we use the timestamp of the source file here, |
| 215 | 218 | -- rather than the current time? This works better in |
| 216 | 219 | -- the case where the local clock is out of sync |
| 217 | 220 | -- with the filesystem's clock. It's just as accurate: |
| 218 | 221 | -- if the source is modified, then the linkable will |
| 219 | 222 | -- be out of date. |
| | 223 | -- However, make sure to consider the mtimes of the module's |
| | 224 | -- usage files, too. |
| | 225 | |
| 220 | 226 | let linkable = LM unlinked_time this_mod |
| 221 | 227 | (hs_unlinked ++ stub_o) |
| 222 | 228 | return (Just linkable) |
| … |
… |
|
| 946 | 952 | ms_hspp_buf = hspp_buf, |
| 947 | 953 | ms_location = location4, |
| 948 | 954 | ms_hs_date = src_timestamp, |
| | 955 | ms_usage_files = [], |
| 949 | 956 | ms_obj_date = Nothing, |
| 950 | 957 | ms_textual_imps = imps, |
| 951 | 958 | ms_srcimps = src_imps } |
diff --git a/compiler/main/GHC.hs b/compiler/main/GHC.hs
index bedb300..383dff8 100644
|
a
|
b
|
|
| 882 | 882 | -- want. (Thus it doesn't matter what the timestamp |
| 883 | 883 | -- for the (nonexistent) source file is.) |
| 884 | 884 | ms_hs_date = currentTime, |
| | 885 | ms_usage_files = [], |
| 885 | 886 | ms_obj_date = Nothing, |
| 886 | 887 | -- Only handling the single-module case for now, so no imports. |
| 887 | 888 | ms_srcimps = [], |
diff --git a/compiler/main/GhcMake.hs b/compiler/main/GhcMake.hs
index 322c631..77146da 100644
|
a
|
b
|
|
| 253 | 253 | 2 (ppr mg)) |
| 254 | 254 | |
| 255 | 255 | setSession hsc_env{ hsc_HPT = emptyHomePackageTable } |
| 256 | | (upsweep_ok, modsUpswept) |
| | 256 | (upsweep_ok, modsUpswept, modsNotUpswept) |
| 257 | 257 | <- upsweep pruned_hpt stable_mods cleanup mg |
| 258 | 258 | |
| 259 | 259 | -- Make modsDone be the summaries for each home module now |
| … |
… |
|
| 262 | 262 | |
| 263 | 263 | let modsDone = reverse modsUpswept |
| 264 | 264 | |
| | 265 | -- The upswept modules were possibly changed, so update the module graph. |
| | 266 | modifySession $ \hsc_env -> hsc_env |
| | 267 | { hsc_mod_graph = modsUpswept ++ modsNotUpswept } |
| | 268 | |
| 265 | 269 | -- Try and do linking in some form, depending on whether the |
| 266 | 270 | -- upsweep was completely or only partially successful. |
| 267 | 271 | |
| … |
… |
|
| 502 | 506 | all stableObject (imports m) |
| 503 | 507 | && old linkable does not exist, or is == on-disk .o |
| 504 | 508 | && date(on-disk .o) > date(.hs) |
| | 509 | && date(linkableTime) > usage file dates |
| 505 | 510 | |
| 506 | 511 | stableBCO m = |
| 507 | 512 | all stable (imports m) |
| 508 | 513 | && date(BCO) > date(.hs) |
| | 514 | && date(linkableTime) > usage file dates |
| 509 | 515 | @ |
| 510 | 516 | |
| 511 | 517 | These properties embody the following ideas: |
| … |
… |
|
| 567 | 573 | where |
| 568 | 574 | same_as_prev t = case lookupUFM hpt (ms_mod_name ms) of |
| 569 | 575 | Just hmi | Just l <- hm_linkable hmi |
| 570 | | -> isObjectLinkable l && t == linkableTime l |
| | 576 | -> isObjectLinkable l && t == linkableTime l && |
| | 577 | usage_files_ok ms l |
| 571 | 578 | _other -> True |
| 572 | 579 | -- why '>=' rather than '>' above? If the filesystem stores |
| 573 | 580 | -- times to the nearset second, we may occasionally find that |
| … |
… |
|
| 584 | 591 | | otherwise = case lookupUFM hpt (ms_mod_name ms) of |
| 585 | 592 | Just hmi | Just l <- hm_linkable hmi -> |
| 586 | 593 | not (isObjectLinkable l) && |
| 587 | | linkableTime l >= ms_hs_date ms |
| | 594 | linkableTime l >= ms_hs_date ms && |
| | 595 | usage_files_ok ms l |
| 588 | 596 | _other -> False |
| 589 | 597 | |
| | 598 | -- Check whether the usage files' mtimes are <= the linkableTime. |
| | 599 | -- An mtime of Nothing means a usage file has been deleted; in that case |
| | 600 | -- the module is marked unstable. |
| | 601 | usage_files_ok ms linkable = all (maybe False (<= linkableTime linkable)) |
| | 602 | (map snd (ms_usage_files ms)) |
| | 603 | |
| 590 | 604 | -- ----------------------------------------------------------------------------- |
| 591 | 605 | -- |
| 592 | 606 | -- | The upsweep |
| … |
… |
|
| 602 | 616 | -> (HscEnv -> IO ()) -- ^ How to clean up unwanted tmp files |
| 603 | 617 | -> [SCC ModSummary] -- ^ Mods to do (the worklist) |
| 604 | 618 | -> m (SuccessFlag, |
| | 619 | [ModSummary], |
| 605 | 620 | [ModSummary]) |
| 606 | 621 | -- ^ Returns: |
| 607 | 622 | -- |
| 608 | 623 | -- 1. A flag whether the complete upsweep was successful. |
| 609 | 624 | -- 2. The 'HscEnv' in the monad has an updated HPT |
| 610 | 625 | -- 3. A list of modules which succeeded loading. |
| | 626 | -- 4. A list of modules which weren't loaded. |
| 611 | 627 | |
| 612 | 628 | upsweep old_hpt stable_mods cleanup sccs = do |
| 613 | | (res, done) <- upsweep' old_hpt [] sccs 1 (length sccs) |
| 614 | | return (res, reverse done) |
| | 629 | (res, done, notDone) <- upsweep' old_hpt [] sccs 1 (length sccs) |
| | 630 | return (res, reverse done, flattenSCCs notDone) |
| 615 | 631 | where |
| 616 | 632 | |
| 617 | 633 | upsweep' _old_hpt done |
| 618 | 634 | [] _ _ |
| 619 | | = return (Succeeded, done) |
| | 635 | = return (Succeeded, done, []) |
| 620 | 636 | |
| 621 | 637 | upsweep' _old_hpt done |
| 622 | | (CyclicSCC ms:_) _ _ |
| | 638 | mg@(CyclicSCC ms:_) _ _ |
| 623 | 639 | = do dflags <- getSessionDynFlags |
| 624 | 640 | liftIO $ fatalErrorMsg dflags (cyclicModuleErr ms) |
| 625 | | return (Failed, done) |
| | 641 | return (Failed, done, mg) |
| 626 | 642 | |
| 627 | 643 | upsweep' old_hpt done |
| 628 | | (AcyclicSCC mod:mods) mod_index nmods |
| | 644 | mg@(AcyclicSCC mod:mods) mod_index nmods |
| 629 | 645 | = do -- putStrLn ("UPSWEEP_MOD: hpt = " ++ |
| 630 | 646 | -- show (map (moduleUserString.moduleName.mi_module.hm_iface) |
| 631 | 647 | -- (moduleEnvElts (hsc_HPT hsc_env))) |
| … |
… |
|
| 645 | 661 | return (Just mod_info) |
| 646 | 662 | |
| 647 | 663 | case mb_mod_info of |
| 648 | | Nothing -> return (Failed, done) |
| | 664 | Nothing -> return (Failed, done, mg) |
| 649 | 665 | Just mod_info -> do |
| 650 | 666 | let this_mod = ms_mod_name mod |
| 651 | 667 | |
| … |
… |
|
| 663 | 679 | old_hpt1 | isBootSummary mod = old_hpt |
| 664 | 680 | | otherwise = delFromUFM old_hpt this_mod |
| 665 | 681 | |
| 666 | | done' = mod:done |
| | 682 | -- Update the usage files of this mod with data from the |
| | 683 | -- newly generated HMI |
| | 684 | let usages = mi_usages (hm_iface mod_info) |
| | 685 | usage_files = [(file,Just mtime) | UsageFile file mtime <- usages] |
| | 686 | |
| | 687 | mod' = mod { ms_usage_files = usage_files } |
| | 688 | done' = mod':done |
| 667 | 689 | |
| 668 | 690 | -- fixup our HomePackageTable after we've finished compiling |
| 669 | 691 | -- a mutually-recursive loop. See reTypecheckLoop, below. |
| 670 | | hsc_env2 <- liftIO $ reTypecheckLoop hsc_env1 mod done' |
| | 692 | hsc_env2 <- liftIO $ reTypecheckLoop hsc_env1 mod' done' |
| 671 | 693 | setSession hsc_env2 |
| 672 | 694 | |
| 673 | 695 | upsweep' old_hpt1 done' mods (mod_index+1) nmods |
| … |
… |
|
| 1194 | 1216 | = do |
| 1195 | 1217 | let location = ms_location old_summary |
| 1196 | 1218 | |
| | 1219 | (usage_files_ok,usage_files) <- checkUsageFiles old_summary |
| 1197 | 1220 | src_timestamp <- get_src_timestamp |
| 1198 | 1221 | -- The file exists; we checked in getRootSummary above. |
| 1199 | 1222 | -- If it gets removed subsequently, then this |
| … |
… |
|
| 1201 | 1224 | -- behaviour. |
| 1202 | 1225 | |
| 1203 | 1226 | -- return the cached summary if the source didn't change |
| 1204 | | if ms_hs_date old_summary == src_timestamp |
| | 1227 | summary <- if ms_hs_date old_summary == src_timestamp && usage_files_ok |
| 1205 | 1228 | then do -- update the object-file timestamp |
| 1206 | 1229 | obj_timestamp <- |
| 1207 | 1230 | if isObjectTarget (hscTarget (hsc_dflags hsc_env)) |
| … |
… |
|
| 1212 | 1235 | else |
| 1213 | 1236 | new_summary src_timestamp |
| 1214 | 1237 | |
| | 1238 | -- Update the usage file mtimes if a usage file has been altered. |
| | 1239 | return $ if usage_files_ok |
| | 1240 | then summary |
| | 1241 | else summary { ms_usage_files = usage_files } |
| | 1242 | |
| 1215 | 1243 | | otherwise |
| 1216 | 1244 | = do src_timestamp <- get_src_timestamp |
| 1217 | 1245 | new_summary src_timestamp |
| … |
… |
|
| 1251 | 1279 | ms_hspp_buf = Just buf, |
| 1252 | 1280 | ms_srcimps = srcimps, ms_textual_imps = the_imps, |
| 1253 | 1281 | ms_hs_date = src_timestamp, |
| | 1282 | ms_usage_files = [], |
| 1254 | 1283 | ms_obj_date = obj_timestamp }) |
| 1255 | 1284 | |
| 1256 | 1285 | findSummaryBySourceFile :: [ModSummary] -> FilePath -> Maybe ModSummary |
| … |
… |
|
| 1282 | 1311 | let location = ms_location old_summary |
| 1283 | 1312 | src_fn = expectJust "summariseModule" (ml_hs_file location) |
| 1284 | 1313 | |
| | 1314 | (usage_files_ok,usage_files) <- checkUsageFiles old_summary |
| | 1315 | |
| 1285 | 1316 | -- check the modification time on the source file, and |
| 1286 | 1317 | -- return the cached summary if it hasn't changed. If the |
| 1287 | 1318 | -- file has disappeared, we need to call the Finder again. |
| 1288 | | case maybe_buf of |
| 1289 | | Just (_,t) -> check_timestamp old_summary location src_fn t |
| | 1319 | maybe_ms <- case maybe_buf of |
| | 1320 | Just (_,t) -> check_timestamp old_summary location src_fn t usage_files_ok |
| 1290 | 1321 | Nothing -> do |
| 1291 | 1322 | m <- tryIO (getModificationUTCTime src_fn) |
| 1292 | 1323 | case m of |
| 1293 | | Right t -> check_timestamp old_summary location src_fn t |
| | 1324 | Right t -> |
| | 1325 | check_timestamp old_summary location src_fn t usage_files_ok |
| 1294 | 1326 | Left e | isDoesNotExistError e -> find_it |
| 1295 | 1327 | | otherwise -> ioError e |
| 1296 | 1328 | |
| | 1329 | -- Update the usage file mtimes if a usage file has been altered. |
| | 1330 | return $ case maybe_ms of |
| | 1331 | Nothing -> Nothing |
| | 1332 | Just ms | usage_files_ok -> Just ms |
| | 1333 | | otherwise -> Just ms { ms_usage_files = usage_files } |
| | 1334 | |
| 1297 | 1335 | | otherwise = find_it |
| 1298 | 1336 | where |
| 1299 | 1337 | dflags = hsc_dflags hsc_env |
| 1300 | 1338 | |
| 1301 | 1339 | hsc_src = if is_boot then HsBootFile else HsSrcFile |
| 1302 | 1340 | |
| 1303 | | check_timestamp old_summary location src_fn src_timestamp |
| 1304 | | | ms_hs_date old_summary == src_timestamp = do |
| | 1341 | check_timestamp old_summary location src_fn src_timestamp usage_files_ok |
| | 1342 | | ms_hs_date old_summary == src_timestamp, usage_files_ok = do |
| 1305 | 1343 | -- update the object-file timestamp |
| 1306 | 1344 | obj_timestamp <- |
| 1307 | 1345 | if isObjectTarget (hscTarget (hsc_dflags hsc_env)) |
| … |
… |
|
| 1377 | 1415 | ms_srcimps = srcimps, |
| 1378 | 1416 | ms_textual_imps = the_imps, |
| 1379 | 1417 | ms_hs_date = src_timestamp, |
| | 1418 | ms_usage_files = [], |
| 1380 | 1419 | ms_obj_date = obj_timestamp })) |
| 1381 | 1420 | |
| 1382 | 1421 | |
| | 1422 | checkUsageFiles :: ModSummary |
| | 1423 | -> IO (Bool, [(FilePath,Maybe UTCTime)]) |
| | 1424 | -- ^ returns whether any usage file has been altered, |
| | 1425 | -- and the entire list of usage files with their mtimes updated |
| | 1426 | checkUsageFiles summary = do |
| | 1427 | let (usage_files,old_mtimes) = unzip (ms_usage_files summary) |
| | 1428 | mtimes <- mapM modificationTimeIfExists usage_files |
| | 1429 | |
| | 1430 | return (mtimes == old_mtimes, zip usage_files mtimes) |
| | 1431 | |
| 1383 | 1432 | getObjTimestamp :: ModLocation -> Bool -> IO (Maybe UTCTime) |
| 1384 | 1433 | getObjTimestamp location is_boot |
| 1385 | 1434 | = if is_boot then return Nothing |
diff --git a/compiler/main/HscTypes.lhs b/compiler/main/HscTypes.lhs
index 343df00..813c19b 100644
|
a
|
b
|
|
| 1813 | 1813 | ms_hsc_src :: HscSource, -- ^ The module source either plain Haskell, hs-boot or external core |
| 1814 | 1814 | ms_location :: ModLocation, -- ^ Location of the various files belonging to the module |
| 1815 | 1815 | ms_hs_date :: UTCTime, -- ^ Timestamp of source file |
| | 1816 | ms_usage_files :: [(FilePath,Maybe UTCTime)], -- ^ Usage files and their mtimes |
| 1816 | 1817 | ms_obj_date :: Maybe UTCTime, -- ^ Timestamp of object, if we have one |
| 1817 | 1818 | ms_srcimps :: [Located (ImportDecl RdrName)], -- ^ Source imports of the module |
| 1818 | 1819 | ms_textual_imps :: [Located (ImportDecl RdrName)], -- ^ Non-source imports of the module from the module *text* |
| … |
… |
|
| 1865 | 1866 | ppr ms |
| 1866 | 1867 | = sep [text "ModSummary {", |
| 1867 | 1868 | nest 3 (sep [text "ms_hs_date = " <> text (show (ms_hs_date ms)), |
| | 1869 | text "ms_usage_files = " <> text (show (ms_usage_files ms)), |
| 1868 | 1870 | text "ms_mod =" <+> ppr (ms_mod ms) |
| 1869 | 1871 | <> text (hscSourceString (ms_hsc_src ms)) <> comma, |
| 1870 | 1872 | text "ms_textual_imps =" <+> ppr (ms_textual_imps ms), |