Skip to content

Commit 5b18809

Browse files
committed
Use a separate build cache for each component of a package
1 parent 98c51ca commit 5b18809

File tree

7 files changed

+89
-48
lines changed

7 files changed

+89
-48
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ Bug fixes:
4949
may interfere with benchmarks. It also prevented benchmark output from
5050
being displayed by default. This is now fixed. See
5151
[#3663](https://github.com/commercialhaskell/stack/issues/3663).
52+
* Some unnecessary rebuilds when no files were changed are now avoided, by
53+
having a separate build cache for each component of a package. See
54+
[#3732](https://github.com/commercialhaskell/stack/issues/3732).
5255

5356
## v1.6.3
5457

src/Stack/Build/Cache.hs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import Stack.Types.BuildPlan
5454
import Stack.Types.Compiler
5555
import Stack.Types.Config
5656
import Stack.Types.GhcPkgId
57+
import Stack.Types.NamedComponent
5758
import Stack.Types.Package
5859
import Stack.Types.PackageIdentifier
5960
import Stack.Types.Version
@@ -107,10 +108,26 @@ markExeNotInstalled loc ident = do
107108
ident' <- parseRelFile $ packageIdentifierString ident
108109
liftIO $ ignoringAbsence (removeFile $ dir </> ident')
109110

111+
buildCacheFile :: (HasEnvConfig env, MonadReader env m, MonadThrow m)
112+
=> Path Abs Dir
113+
-> NamedComponent
114+
-> m (Path Abs File)
115+
buildCacheFile dir component = do
116+
cachesDir <- buildCachesDir dir
117+
let nonLibComponent prefix name = prefix <> "-" <> T.unpack name
118+
cacheFileName <- parseRelFile $ case component of
119+
CLib -> "lib"
120+
CExe name -> nonLibComponent "exe" name
121+
CTest name -> nonLibComponent "test" name
122+
CBench name -> nonLibComponent "bench" name
123+
return $ cachesDir </> cacheFileName
124+
110125
-- | Try to read the dirtiness cache for the given package directory.
111126
tryGetBuildCache :: HasEnvConfig env
112-
=> Path Abs Dir -> RIO env (Maybe (Map FilePath FileCacheInfo))
113-
tryGetBuildCache dir = liftM (fmap buildCacheTimes) . $(versionedDecodeFile buildCacheVC) =<< buildCacheFile dir
127+
=> Path Abs Dir
128+
-> NamedComponent
129+
-> RIO env (Maybe (Map FilePath FileCacheInfo))
130+
tryGetBuildCache dir component = liftM (fmap buildCacheTimes) . $(versionedDecodeFile buildCacheVC) =<< buildCacheFile dir component
114131

115132
-- | Try to read the dirtiness cache for the given package directory.
116133
tryGetConfigCache :: HasEnvConfig env
@@ -124,9 +141,11 @@ tryGetCabalMod dir = $(versionedDecodeFile modTimeVC) =<< configCabalMod dir
124141

125142
-- | Write the dirtiness cache for this package's files.
126143
writeBuildCache :: HasEnvConfig env
127-
=> Path Abs Dir -> Map FilePath FileCacheInfo -> RIO env ()
128-
writeBuildCache dir times = do
129-
fp <- buildCacheFile dir
144+
=> Path Abs Dir
145+
-> NamedComponent
146+
-> Map FilePath FileCacheInfo -> RIO env ()
147+
writeBuildCache dir component times = do
148+
fp <- buildCacheFile dir component
130149
$(versionedEncodeFile buildCacheVC) fp BuildCache
131150
{ buildCacheTimes = times
132151
}

src/Stack/Build/Execute.hs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,8 @@ singleBuild ac@ActionContext {..} ee@ExecuteEnv {..} task@Task {..} installedMap
13931393
case taskType of
13941394
TTFiles lp _ -> do -- FIXME should this only be for local packages?
13951395
when enableTests $ unsetTestSuccess pkgDir
1396-
writeBuildCache pkgDir $ lpNewBuildCache lp
1396+
mapM_ (uncurry (writeBuildCache pkgDir))
1397+
(Map.toList $ lpNewBuildCaches lp)
13971398
TTIndex{} -> return ()
13981399

13991400
-- FIXME: only output these if they're in the build plan.
@@ -1595,10 +1596,11 @@ checkForUnlistedFiles (TTFiles lp _) preBuildTime pkgDir = do
15951596
(lpPackage lp)
15961597
(lpCabalFile lp)
15971598
(lpComponents lp)
1598-
(lpNewBuildCache lp)
1599-
unless (null addBuildCache) $
1600-
writeBuildCache pkgDir $
1601-
Map.unions (lpNewBuildCache lp : addBuildCache)
1599+
(lpNewBuildCaches lp)
1600+
forM_ (M.toList addBuildCache) $ \(component, newToCache) -> do
1601+
let cache = Map.findWithDefault Map.empty component (lpNewBuildCaches lp)
1602+
writeBuildCache pkgDir component $
1603+
Map.unions (cache : newToCache)
16021604
return warnings
16031605
checkForUnlistedFiles TTIndex{} _ _ = return []
16041606

src/Stack/Build/Source.hs

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -265,28 +265,36 @@ loadLocalPackage isLocal boptsCli targets (name, lpv) = do
265265
testpkg = resolvePackage testconfig gpkg
266266
benchpkg = resolvePackage benchconfig gpkg
267267

268-
mbuildCache <- tryGetBuildCache $ lpvRoot lpv
269-
270-
(files,_) <- getPackageFilesForTargets pkg (lpvCabalFP lpv) nonLibComponents
271-
272-
(dirtyFiles, newBuildCache) <- checkBuildCache
273-
(fromMaybe Map.empty mbuildCache)
274-
(Set.toList files)
268+
(componentFiles,_) <- getPackageFilesForTargets pkg (lpvCabalFP lpv) nonLibComponents
269+
270+
checkCacheResults <- forM (Map.toList componentFiles) $ \(component, files) -> do
271+
mbuildCache <- tryGetBuildCache (lpvRoot lpv) component
272+
checkCacheResult <- checkBuildCache
273+
(fromMaybe Map.empty mbuildCache)
274+
(Set.toList files)
275+
return (component, checkCacheResult)
276+
277+
let allDirtyFiles =
278+
Set.unions $
279+
map (\(_, (dirtyFiles, _)) -> dirtyFiles) checkCacheResults
280+
newBuildCaches =
281+
M.fromList $
282+
map (\(c, (_, cache)) -> (c, cache)) checkCacheResults
275283

276284
return LocalPackage
277285
{ lpPackage = pkg
278286
, lpTestDeps = packageDeps testpkg
279287
, lpBenchDeps = packageDeps benchpkg
280288
, lpTestBench = btpkg
281-
, lpFiles = files
289+
, lpComponentFiles = componentFiles
282290
, lpForceDirty = boptsForceDirty bopts
283291
, lpDirtyFiles =
284-
if not (Set.null dirtyFiles)
292+
if not (Set.null allDirtyFiles)
285293
then let tryStripPrefix y =
286294
fromMaybe y (stripPrefix (toFilePath $ lpvRoot lpv) y)
287-
in Just $ Set.map tryStripPrefix dirtyFiles
295+
in Just $ Set.map tryStripPrefix allDirtyFiles
288296
else Nothing
289-
, lpNewBuildCache = newBuildCache
297+
, lpNewBuildCaches = newBuildCaches
290298
, lpCabalFile = lpvCabalFP lpv
291299
, lpDir = lpvRoot lpv
292300
, lpWanted = isWanted
@@ -394,15 +402,18 @@ addUnlistedToBuildCache
394402
-> Package
395403
-> Path Abs File
396404
-> Set NamedComponent
397-
-> Map FilePath a
398-
-> RIO env ([Map FilePath FileCacheInfo], [PackageWarning])
399-
addUnlistedToBuildCache preBuildTime pkg cabalFP nonLibComponents buildCache = do
400-
(files,warnings) <- getPackageFilesForTargets pkg cabalFP nonLibComponents
401-
let newFiles =
402-
Set.toList $
403-
Set.map toFilePath files `Set.difference` Map.keysSet buildCache
404-
addBuildCache <- mapM addFileToCache newFiles
405-
return (addBuildCache, warnings)
405+
-> Map NamedComponent (Map FilePath a)
406+
-> RIO env (Map NamedComponent [Map FilePath FileCacheInfo], [PackageWarning])
407+
addUnlistedToBuildCache preBuildTime pkg cabalFP nonLibComponents buildCaches = do
408+
(componentFiles, warnings) <- getPackageFilesForTargets pkg cabalFP nonLibComponents
409+
results <- forM (M.toList componentFiles) $ \(component, files) -> do
410+
let buildCache = M.findWithDefault M.empty component buildCaches
411+
newFiles =
412+
Set.toList $
413+
Set.map toFilePath files `Set.difference` Map.keysSet buildCache
414+
addBuildCache <- mapM addFileToCache newFiles
415+
return ((component, addBuildCache), warnings)
416+
return (M.fromList (map fst results), concatMap snd results)
406417
where
407418
addFileToCache fp = do
408419
mmodTime <- getModTimeMaybe fp
@@ -420,16 +431,18 @@ addUnlistedToBuildCache preBuildTime pkg cabalFP nonLibComponents buildCache = d
420431
-- set of components.
421432
getPackageFilesForTargets
422433
:: HasEnvConfig env
423-
=> Package -> Path Abs File -> Set NamedComponent -> RIO env (Set (Path Abs File), [PackageWarning])
424-
getPackageFilesForTargets pkg cabalFP components = do
434+
=> Package
435+
-> Path Abs File
436+
-> Set NamedComponent
437+
-> RIO env (Map NamedComponent (Set (Path Abs File)), [PackageWarning])
438+
getPackageFilesForTargets pkg cabalFP nonLibComponents = do
425439
(_,compFiles,otherFiles,warnings) <-
426440
getPackageFiles (packageFiles pkg) cabalFP
427-
let filesForComponent cn = Set.map dotCabalGetPath
428-
$ M.findWithDefault mempty cn compFiles
429-
files = Set.unions
430-
$ otherFiles
431-
: map filesForComponent (Set.toList $ Set.insert CLib components)
432-
return (files, warnings)
441+
let components = Set.insert CLib nonLibComponents
442+
componentsFiles =
443+
M.map (\files -> Set.union otherFiles (Set.map dotCabalGetPath files)) $
444+
M.filterWithKey (\component _ -> component `Set.member` components) compFiles
445+
return (componentsFiles, warnings)
433446

434447
-- | Get file modification time, if it exists.
435448
getModTimeMaybe :: MonadIO m => FilePath -> m (Maybe ModTime)

src/Stack/Constants/Config.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Stack.Constants.Config
99
, projectDockerSandboxDir
1010
, configCacheFile
1111
, configCabalMod
12-
, buildCacheFile
12+
, buildCachesDir
1313
, testSuccessFile
1414
, testBuiltFile
1515
, hpcRelativeDir
@@ -32,13 +32,13 @@ objectInterfaceDirL = to $ \env -> -- FIXME is this idomatic lens code?
3232
root = view projectRootL env
3333
in root </> workDir </> $(mkRelDir "odir/")
3434

35-
-- | The filename used for dirtiness check of source files.
36-
buildCacheFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env)
35+
-- | The directory containing the files used for dirtiness check of source files.
36+
buildCachesDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env)
3737
=> Path Abs Dir -- ^ Package directory.
38-
-> m (Path Abs File)
39-
buildCacheFile dir =
38+
-> m (Path Abs Dir)
39+
buildCachesDir dir =
4040
liftM
41-
(</> $(mkRelFile "stack-build-cache"))
41+
(</> $(mkRelDir "stack-build-caches"))
4242
(distDirFromDir dir)
4343

4444
-- | The filename used to mark tests as having succeeded

src/Stack/SDist.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,8 @@ readLocalPackage pkgDir = do
307307
, lpTestBench = Nothing
308308
, lpForceDirty = False
309309
, lpDirtyFiles = Nothing
310-
, lpNewBuildCache = Map.empty
311-
, lpFiles = Set.empty
310+
, lpNewBuildCaches = Map.empty
311+
, lpComponentFiles = Map.empty
312312
, lpComponents = Set.empty
313313
, lpUnbuildable = Set.empty
314314
, lpLocation = PLFilePath $ toFilePath pkgDir

src/Stack/Types/Package.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Stack.Prelude
1313
import qualified Data.ByteString as S
1414
import Data.List
1515
import qualified Data.Map as M
16+
import qualified Data.Set as Set
1617
import Data.Store.Version (VersionConfig)
1718
import Data.Store.VersionTagged (storeVersionConfig)
1819
import Distribution.InstalledPackageInfo (PError)
@@ -246,15 +247,18 @@ data LocalPackage = LocalPackage
246247
-- ^ Nothing == not dirty, Just == dirty. Note that the Set may be empty if
247248
-- we forced the build to treat packages as dirty. Also, the Set may not
248249
-- include all modified files.
249-
, lpNewBuildCache :: !(Map FilePath FileCacheInfo)
250+
, lpNewBuildCaches :: !(Map NamedComponent (Map FilePath FileCacheInfo))
250251
-- ^ current state of the files
251-
, lpFiles :: !(Set (Path Abs File))
252+
, lpComponentFiles :: !(Map NamedComponent (Set (Path Abs File)))
252253
-- ^ all files used by this package
253254
, lpLocation :: !(PackageLocation FilePath)
254255
-- ^ Where this source code came from
255256
}
256257
deriving Show
257258

259+
lpFiles :: LocalPackage -> Set.Set (Path Abs File)
260+
lpFiles = Set.unions . M.elems . lpComponentFiles
261+
258262
-- | A location to install a package into, either snapshot or local
259263
data InstallLocation = Snap | Local
260264
deriving (Show, Eq)

0 commit comments

Comments
 (0)