Skip to content

Commit 94ec44a

Browse files
authored
Merge pull request #5351 from aschmois/master
Remove ModTime check during build (#5125)
2 parents fa00fd8 + 4ced806 commit 94ec44a

File tree

4 files changed

+34
-73
lines changed

4 files changed

+34
-73
lines changed

ChangeLog.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Bug fixes:
3939
* Fix `stack sdist` introducing unneded sublibrary syntax when using
4040
pvp-bounds. See
4141
[#5289](https://github.com/commercialhaskell/stack/issues/5289)
42+
* Fix modified time busting caches by always calculating sha256 digest
43+
during the build process.
44+
[#5125](https://github.com/commercialhaskell/stack/issues/5125)
4245

4346
* Fix `stack test --coverage` when using Cabal 3
4447

@@ -118,7 +121,7 @@ Other enhancements:
118121
prefixes each build log output line with a timestamp.
119122

120123
* Show warning about `local-programs-path` with spaces on windows
121-
when running scripts. See
124+
when running scripts. See
122125
[#5013](https://github.com/commercialhaskell/stack/pull/5013)
123126

124127
* Add `ls dependencies json` which will print dependencies as JSON.

src/Stack/Build/Execute.hs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import qualified Distribution.Text as C
5959
import Distribution.Types.PackageName (mkPackageName)
6060
import Distribution.Types.UnqualComponentName (mkUnqualComponentName)
6161
import Distribution.Version (mkVersion)
62-
import Foreign.C.Types (CTime)
6362
import Path
6463
import Path.CheckInstall
6564
import Path.Extra (toFilePathNoTrailingSep, rejectMissingFile)
@@ -91,7 +90,6 @@ import System.FileLock (withTryFileLock, SharedExclusive (Exclusive),
9190
import qualified System.FilePath as FP
9291
import System.IO.Error (isDoesNotExistError)
9392
import System.PosixCompat.Files (createLink, modificationTime, getFileStatus)
94-
import System.PosixCompat.Time (epochTime)
9593
import RIO.PrettyPrint
9694
import RIO.Process
9795
import Pantry.Internal.Companion
@@ -1601,11 +1599,10 @@ singleBuild ac@ActionContext {..} ee@ExecuteEnv {..} task@Task {..} installedMap
16011599

16021600
-- FIXME: only output these if they're in the build plan.
16031601

1604-
preBuildTime <- liftIO epochTime
16051602
let postBuildCheck _succeeded = do
16061603
mlocalWarnings <- case taskType of
16071604
TTLocalMutable lp -> do
1608-
warnings <- checkForUnlistedFiles taskType preBuildTime pkgDir
1605+
warnings <- checkForUnlistedFiles taskType pkgDir
16091606
-- TODO: Perhaps only emit these warnings for non extra-dep?
16101607
return (Just (lpCabalFile lp, warnings))
16111608
_ -> return Nothing
@@ -1829,12 +1826,11 @@ checkExeStatus platform distDir name = do
18291826
file = T.unpack name
18301827

18311828
-- | Check if any unlisted files have been found, and add them to the build cache.
1832-
checkForUnlistedFiles :: HasEnvConfig env => TaskType -> CTime -> Path Abs Dir -> RIO env [PackageWarning]
1833-
checkForUnlistedFiles (TTLocalMutable lp) preBuildTime pkgDir = do
1829+
checkForUnlistedFiles :: HasEnvConfig env => TaskType -> Path Abs Dir -> RIO env [PackageWarning]
1830+
checkForUnlistedFiles (TTLocalMutable lp) pkgDir = do
18341831
caches <- runMemoizedWith $ lpNewBuildCaches lp
18351832
(addBuildCache,warnings) <-
18361833
addUnlistedToBuildCache
1837-
preBuildTime
18381834
(lpPackage lp)
18391835
(lpCabalFile lp)
18401836
(lpComponents lp)
@@ -1844,7 +1840,7 @@ checkForUnlistedFiles (TTLocalMutable lp) preBuildTime pkgDir = do
18441840
writeBuildCache pkgDir component $
18451841
Map.unions (cache : newToCache)
18461842
return warnings
1847-
checkForUnlistedFiles TTRemotePackage{} _ _ = return []
1843+
checkForUnlistedFiles TTRemotePackage{} _ = return []
18481844

18491845
-- | Implements running a package's tests. Also handles producing
18501846
-- coverage reports if coverage is enabled.

src/Stack/Build/Source.hs

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,13 @@ module Stack.Build.Source
1919

2020
import Stack.Prelude
2121
import qualified Pantry.SHA256 as SHA256
22-
import qualified Data.ByteString as S
2322
import Data.ByteString.Builder (toLazyByteString)
2423
import Conduit (ZipSink (..), withSourceFile)
25-
import qualified Data.Conduit.List as CL
2624
import qualified Distribution.PackageDescription as C
2725
import Data.List
2826
import qualified Data.Map as Map
2927
import qualified Data.Map.Strict as M
3028
import qualified Data.Set as Set
31-
import Foreign.C.Types (CTime)
3229
import Stack.Build.Cache
3330
import Stack.Build.Haddock (shouldHaddockDeps)
3431
import Stack.Build.Target
@@ -41,7 +38,6 @@ import Stack.Types.Package
4138
import Stack.Types.SourceMap
4239
import System.FilePath (takeFileName)
4340
import System.IO.Error (isDoesNotExistError)
44-
import System.PosixCompat.Files (modificationTime, getFileStatus)
4541

4642
-- | loads and returns project packages
4743
projectLocalPackages :: HasEnvConfig env
@@ -406,46 +402,38 @@ checkBuildCache :: forall m. (MonadIO m)
406402
-> m (Set FilePath, Map FilePath FileCacheInfo)
407403
checkBuildCache oldCache files = do
408404
fileTimes <- liftM Map.fromList $ forM files $ \fp -> do
409-
mmodTime <- liftIO (getModTimeMaybe (toFilePath fp))
410-
return (toFilePath fp, mmodTime)
405+
mdigest <- liftIO (getFileDigestMaybe (toFilePath fp))
406+
return (toFilePath fp, mdigest)
411407
liftM (mconcat . Map.elems) $ sequence $
412408
Map.mergeWithKey
413-
(\fp mmodTime fci -> Just (go fp mmodTime (Just fci)))
414-
(Map.mapWithKey (\fp mmodTime -> go fp mmodTime Nothing))
409+
(\fp mdigest fci -> Just (go fp mdigest (Just fci)))
410+
(Map.mapWithKey (\fp mdigest -> go fp mdigest Nothing))
415411
(Map.mapWithKey (\fp fci -> go fp Nothing (Just fci)))
416412
fileTimes
417413
oldCache
418414
where
419-
go :: FilePath -> Maybe CTime -> Maybe FileCacheInfo -> m (Set FilePath, Map FilePath FileCacheInfo)
415+
go :: FilePath -> Maybe SHA256 -> Maybe FileCacheInfo -> m (Set FilePath, Map FilePath FileCacheInfo)
420416
-- Filter out the cabal_macros file to avoid spurious recompilations
421417
go fp _ _ | takeFileName fp == "cabal_macros.h" = return (Set.empty, Map.empty)
422418
-- Common case where it's in the cache and on the filesystem.
423-
go fp (Just modTime') (Just fci)
424-
| fciModTime fci == modTime' = return (Set.empty, Map.singleton fp fci)
425-
| otherwise = do
426-
newFci <- calcFci modTime' fp
427-
let isDirty =
428-
fciSize fci /= fciSize newFci ||
429-
fciHash fci /= fciHash newFci
430-
newDirty = if isDirty then Set.singleton fp else Set.empty
431-
return (newDirty, Map.singleton fp newFci)
419+
go fp (Just digest') (Just fci)
420+
| fciHash fci == digest' = return (Set.empty, Map.singleton fp fci)
421+
| otherwise = return (Set.singleton fp, Map.singleton fp $ FileCacheInfo digest')
432422
-- Missing file. Add it to dirty files, but no FileCacheInfo.
433423
go fp Nothing _ = return (Set.singleton fp, Map.empty)
434424
-- Missing cache. Add it to dirty files and compute FileCacheInfo.
435-
go fp (Just modTime') Nothing = do
436-
newFci <- calcFci modTime' fp
437-
return (Set.singleton fp, Map.singleton fp newFci)
425+
go fp (Just digest') Nothing =
426+
return (Set.singleton fp, Map.singleton fp $ FileCacheInfo digest')
438427

439428
-- | Returns entries to add to the build cache for any newly found unlisted modules
440429
addUnlistedToBuildCache
441430
:: HasEnvConfig env
442-
=> CTime
443-
-> Package
431+
=> Package
444432
-> Path Abs File
445433
-> Set NamedComponent
446434
-> Map NamedComponent (Map FilePath a)
447435
-> RIO env (Map NamedComponent [Map FilePath FileCacheInfo], [PackageWarning])
448-
addUnlistedToBuildCache preBuildTime pkg cabalFP nonLibComponents buildCaches = do
436+
addUnlistedToBuildCache pkg cabalFP nonLibComponents buildCaches = do
449437
(componentFiles, warnings) <- getPackageFilesForTargets pkg cabalFP nonLibComponents
450438
results <- forM (M.toList componentFiles) $ \(component, files) -> do
451439
let buildCache = M.findWithDefault M.empty component buildCaches
@@ -457,13 +445,10 @@ addUnlistedToBuildCache preBuildTime pkg cabalFP nonLibComponents buildCaches =
457445
return (M.fromList (map fst results), concatMap snd results)
458446
where
459447
addFileToCache fp = do
460-
mmodTime <- getModTimeMaybe fp
461-
case mmodTime of
448+
mdigest <- getFileDigestMaybe fp
449+
case mdigest of
462450
Nothing -> return Map.empty
463-
Just modTime' ->
464-
if modTime' < preBuildTime
465-
then Map.singleton fp <$> calcFci modTime' fp
466-
else return Map.empty
451+
Just digest' -> return . Map.singleton fp $ FileCacheInfo digest'
467452

468453
-- | Gets list of Paths for files relevant to a set of components in a package.
469454
-- Note that the library component, if any, is always automatically added to the
@@ -484,34 +469,18 @@ getPackageFilesForTargets pkg cabalFP nonLibComponents = do
484469
M.filterWithKey (\component _ -> component `elem` components) compFiles
485470
return (componentsFiles, warnings)
486471

487-
-- | Get file modification time, if it exists.
488-
getModTimeMaybe :: MonadIO m => FilePath -> m (Maybe CTime)
489-
getModTimeMaybe fp =
472+
-- | Get file digest, if it exists
473+
getFileDigestMaybe :: MonadIO m => FilePath -> m (Maybe SHA256)
474+
getFileDigestMaybe fp = do
490475
liftIO
491476
(catch
492-
(liftM
493-
(Just . modificationTime)
494-
(getFileStatus fp))
477+
(liftM Just . withSourceFile fp $ getDigest)
495478
(\e ->
496479
if isDoesNotExistError e
497480
then return Nothing
498481
else throwM e))
499-
500-
-- | Create FileCacheInfo for a file.
501-
calcFci :: MonadIO m => CTime -> FilePath -> m FileCacheInfo
502-
calcFci modTime' fp = liftIO $
503-
withSourceFile fp $ \src -> do
504-
(size, digest) <- runConduit $ src .| getZipSink
505-
((,)
506-
<$> ZipSink (CL.fold
507-
(\x y -> x + fromIntegral (S.length y))
508-
0)
509-
<*> ZipSink SHA256.sinkHash)
510-
return FileCacheInfo
511-
{ fciModTime = modTime'
512-
, fciSize = FileSize size
513-
, fciHash = digest
514-
}
482+
where
483+
getDigest src = runConduit $ src .| getZipSink (ZipSink SHA256.sinkHash)
515484

516485
-- | Get 'PackageConfig' for package given its name.
517486
getPackageConfig

src/Stack/Types/Package.hs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
module Stack.Types.Package where
1212

1313
import Stack.Prelude
14-
import Foreign.C.Types (CTime)
1514
import qualified RIO.Text as T
1615
import Data.Aeson (ToJSON (..), FromJSON (..), (.=), (.:), object, withObject)
1716
import qualified Data.Map as M
@@ -342,27 +341,21 @@ instance Monoid InstallLocation where
342341
data InstalledPackageLocation = InstalledTo InstallLocation | ExtraGlobal
343342
deriving (Show, Eq)
344343

345-
data FileCacheInfo = FileCacheInfo
346-
{ fciModTime :: !CTime
347-
, fciSize :: !FileSize
348-
, fciHash :: !SHA256
344+
newtype FileCacheInfo = FileCacheInfo
345+
{ fciHash :: SHA256
349346
}
350347
deriving (Generic, Show, Eq, Typeable)
351348
instance NFData FileCacheInfo
352349

353350
-- Provided for storing the BuildCache values in a file. But maybe
354351
-- JSON/YAML isn't the right choice here, worth considering.
355352
instance ToJSON FileCacheInfo where
356-
toJSON (FileCacheInfo time size hash') = object
357-
[ "modtime" .= time
358-
, "size" .= size
359-
, "hash" .= hash'
353+
toJSON (FileCacheInfo hash') = object
354+
[ "hash" .= hash'
360355
]
361356
instance FromJSON FileCacheInfo where
362357
parseJSON = withObject "FileCacheInfo" $ \o -> FileCacheInfo
363-
<$> o .: "modtime"
364-
<*> o .: "size"
365-
<*> o .: "hash"
358+
<$> o .: "hash"
366359

367360
-- | A descriptor from a .cabal file indicating one of the following:
368361
--

0 commit comments

Comments
 (0)