Skip to content

Commit 6aa9f7a

Browse files
authored
Merge pull request #3047 from commercialhaskell/2904-aggressive-unregister
Aggressive unregister for #2904
2 parents 0f61375 + 6104a0a commit 6aa9f7a

File tree

4 files changed

+81
-29
lines changed

4 files changed

+81
-29
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ Bug fixes:
109109
([#2977](https://github.com/commercialhaskell/stack/issues/2977))
110110
* Added support for GHC 8's slightly different warning format for
111111
dumping warnings from logs.
112+
* Work around a bug in Cabal/GHC in which package IDs are not unique
113+
for different source code, leading to Stack not always rebuilding
114+
packages depending on local packages which have
115+
changed. ([#2904](https://github.com/commercialhaskell/stack/issues/2904))
112116

113117
## 1.3.2
114118

src/Stack/Build/ConstructPlan.hs

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ module Stack.Build.ConstructPlan
1515
( constructPlan
1616
) where
1717

18-
import Control.Arrow ((&&&))
1918
import Control.Exception.Lifted
2019
import Control.Monad
2120
import Control.Monad.IO.Class
2221
import Control.Monad.Logger
2322
import Control.Monad.RWS.Strict
23+
import Control.Monad.State.Strict (execState)
2424
import Control.Monad.Trans.Resource
2525
import Data.Either
2626
import Data.Function
@@ -156,7 +156,6 @@ constructPlan :: forall env m. (StackM env m, HasEnvConfig env)
156156
-> m Plan
157157
constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackage0 sourceMap installedMap = do
158158
$logDebug "Constructing the build plan"
159-
let locallyRegistered = Map.fromList $ map (dpGhcPkgId &&& dpPackageIdent) localDumpPkgs
160159
getVersions0 <- getPackageVersionsIO
161160

162161
econfig <- view envConfigL
@@ -186,7 +185,7 @@ constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackag
186185
return $ takeSubset Plan
187186
{ planTasks = tasks
188187
, planFinals = M.fromList finals
189-
, planUnregisterLocal = mkUnregisterLocal tasks dirtyReason locallyRegistered sourceMap
188+
, planUnregisterLocal = mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap
190189
, planInstallExes =
191190
if boptsInstallExes $ bcoBuildOpts baseConfigOpts0
192191
then installExes
@@ -219,29 +218,78 @@ constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackag
219218
-- or local packages.
220219
toolMap = getToolMap mbp0
221220

221+
-- | State to be maintained during the calculation of local packages
222+
-- to unregister.
223+
data UnregisterState = UnregisterState
224+
{ usToUnregister :: !(Map GhcPkgId (PackageIdentifier, Text))
225+
, usKeep :: ![DumpPackage () () ()]
226+
, usAnyAdded :: !Bool
227+
}
228+
222229
-- | Determine which packages to unregister based on the given tasks and
223230
-- already registered local packages
224231
mkUnregisterLocal :: Map PackageName Task
232+
-- ^ Tasks
225233
-> Map PackageName Text
226-
-> Map GhcPkgId PackageIdentifier
234+
-- ^ Reasons why packages are dirty and must be rebuilt
235+
-> [DumpPackage () () ()]
236+
-- ^ Local package database dump
227237
-> SourceMap
228-
-> Map GhcPkgId (PackageIdentifier, Maybe Text)
229-
mkUnregisterLocal tasks dirtyReason locallyRegistered sourceMap =
230-
Map.unions $ map toUnregisterMap $ Map.toList locallyRegistered
238+
-> Map GhcPkgId (PackageIdentifier, Text)
239+
mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap =
240+
-- We'll take multiple passes through the local packages. This
241+
-- will allow us to detect that a package should be unregistered,
242+
-- as well as all packages directly or transitively depending on
243+
-- it.
244+
loop Map.empty localDumpPkgs
231245
where
232-
toUnregisterMap (gid, ident) =
233-
case M.lookup name tasks of
234-
Nothing ->
235-
case M.lookup name sourceMap of
236-
Just (PSUpstream _ Snap _ _ _) -> Map.singleton gid
237-
( ident
238-
, Just "Switching to snapshot installed package"
239-
)
240-
_ -> Map.empty
241-
Just _ -> Map.singleton gid
242-
( ident
243-
, Map.lookup name dirtyReason
244-
)
246+
loop toUnregister keep
247+
-- If any new packages were added to the unregister Map, we
248+
-- need to loop through the remaining packages again to detect
249+
-- if a transitive dependency is being unregistered.
250+
| usAnyAdded us = loop (usToUnregister us) (usKeep us)
251+
-- Nothing added, so we've already caught them all. Return the
252+
-- Map we've already calculated.
253+
| otherwise = usToUnregister us
254+
where
255+
-- Run the unregister checking function on all packages we
256+
-- currently think we'll be keeping.
257+
us = execState (mapM_ go keep) UnregisterState
258+
{ usToUnregister = toUnregister
259+
, usKeep = []
260+
, usAnyAdded = False
261+
}
262+
263+
go dp = do
264+
us <- get
265+
case go' (usToUnregister us) ident deps of
266+
-- Not unregistering, add it to the keep list
267+
Nothing -> put us { usKeep = dp : usKeep us }
268+
-- Unregistering, add it to the unregister Map and
269+
-- indicate that a package was in fact added to the
270+
-- unregister Map so we loop again.
271+
Just reason -> put us
272+
{ usToUnregister = Map.insert gid (ident, reason) (usToUnregister us)
273+
, usAnyAdded = True
274+
}
275+
where
276+
gid = dpGhcPkgId dp
277+
ident = dpPackageIdent dp
278+
deps = dpDepends dp
279+
280+
go' toUnregister ident deps
281+
-- If we're planning on running a task on it, then it must be
282+
-- unregistered
283+
| Just _ <- Map.lookup name tasks
284+
= Just $ fromMaybe undefined $ Map.lookup name dirtyReason
285+
-- Check if we're no longer using the local version
286+
| Just (PSUpstream _ Snap _ _ _) <- Map.lookup name sourceMap
287+
= Just "Switching to snapshot installed package"
288+
-- Check if a dependency is going to be unregistered
289+
| (dep, _):_ <- mapMaybe (`Map.lookup` toUnregister) deps
290+
= Just $ "Dependency being unregistered: " <> packageIdentifierText dep
291+
-- None of the above, keep it!
292+
| otherwise = Nothing
245293
where
246294
name = packageIdentifierName ident
247295

src/Stack/Build/Execute.hs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ printPlan plan = do
140140
[] -> $logInfo "No packages would be unregistered."
141141
xs -> do
142142
$logInfo "Would unregister locally:"
143-
forM_ xs $ \(ident, mreason) -> $logInfo $ T.concat
143+
forM_ xs $ \(ident, reason) -> $logInfo $ T.concat
144144
[ T.pack $ packageIdentifierString ident
145-
, case mreason of
146-
Nothing -> ""
147-
Just reason -> T.concat
145+
, if T.null reason
146+
then ""
147+
else T.concat
148148
[ " ("
149149
, reason
150150
, ")"
@@ -591,13 +591,13 @@ executePlan' installedMap0 targets plan ee@ExecuteEnv {..} = do
591591
[] -> return ()
592592
ids -> do
593593
localDB <- packageDatabaseLocal
594-
forM_ ids $ \(id', (ident, mreason)) -> do
594+
forM_ ids $ \(id', (ident, reason)) -> do
595595
$logInfo $ T.concat
596596
[ T.pack $ packageIdentifierString ident
597597
, ": unregistering"
598-
, case mreason of
599-
Nothing -> ""
600-
Just reason -> T.concat
598+
, if T.null reason
599+
then ""
600+
else T.concat
601601
[ " ("
602602
, reason
603603
, ")"

src/Stack/Types/Build.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ data Plan = Plan
447447
{ planTasks :: !(Map PackageName Task)
448448
, planFinals :: !(Map PackageName Task)
449449
-- ^ Final actions to be taken (test, benchmark, etc)
450-
, planUnregisterLocal :: !(Map GhcPkgId (PackageIdentifier, Maybe Text))
450+
, planUnregisterLocal :: !(Map GhcPkgId (PackageIdentifier, Text))
451451
-- ^ Text is reason we're unregistering, for display only
452452
, planInstallExes :: !(Map Text InstallLocation)
453453
-- ^ Executables that should be installed after successful building

0 commit comments

Comments
 (0)