Skip to content

Commit c5b7d7a

Browse files
committed
managed: persist whether an override is a revision, so overrides can be added after restart
1 parent 140d0c3 commit c5b7d7a

File tree

19 files changed

+142
-85
lines changed

19 files changed

+142
-85
lines changed

lib/internal/env.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
Internal error: A managed override for '${package}' is missing the attribute '${missing}'.
6666
'';
6767

68-
managedOverride = api: package: {version ? null, hash ? null, repo ? null, jailbreak ? null, local ? null}: let
68+
managedOverride = api: package: {version ? null, hash ? null, repo ? null, jailbreak ? null, local ? null, ...}: let
6969
hackage = if repo == null then api.hackage else api.hackageConfGen (unknownHackage package) repo;
7070
in
7171
if version != null && hash != null

packages/hix/lib/Hix/Data/Overrides.hs

+34-4
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,35 @@ import Hix.Data.Version (SourceHash)
1515
import Hix.Managed.Cabal.Data.HackageRepo (HackageName (..))
1616
import Hix.Pretty (hpretty)
1717

18+
data IsRevision =
19+
IsRevision
20+
|
21+
IsNotRevision
22+
deriving stock (Eq, Show)
23+
24+
isRevision :: IsRevision -> Bool
25+
isRevision = \case
26+
IsRevision -> True
27+
IsNotRevision -> False
28+
29+
toIsRevision :: Bool -> IsRevision
30+
toIsRevision = \case
31+
True -> IsRevision
32+
False -> IsNotRevision
33+
34+
instance FromJSON IsRevision where
35+
parseJSON v =
36+
toIsRevision <$> parseJSON v
37+
38+
instance EncodeNix IsRevision where
39+
encodeNix = encodeNix . isRevision
40+
1841
data Override =
1942
Override {
2043
version :: Version,
2144
hash :: SourceHash,
22-
repo :: Maybe HackageName
45+
repo :: Maybe HackageName,
46+
revision :: Maybe IsRevision
2347
}
2448
|
2549
Jailbreak
@@ -30,7 +54,7 @@ data Override =
3054
instance EncodeNix Override where
3155
encodeNix = \case
3256
Override {..} ->
33-
ExprAttrs (static <> foldMap (pure . assoc "repo") repo)
57+
ExprAttrs (static <> foldMap (pure . assoc "repo") repo <> foldMap (pure . assoc "revision") revision)
3458
where
3559
static = [assoc "version" version, assoc "hash" hash]
3660

@@ -43,7 +67,7 @@ instance EncodeNix Override where
4367

4468
override :: Version -> SourceHash -> Override
4569
override version hash =
46-
Override {repo = Nothing, ..}
70+
Override {repo = Nothing, revision = Nothing, ..}
4771

4872
instance FromJSON Override where
4973
parseJSON =
@@ -54,6 +78,7 @@ instance FromJSON Override where
5478
JsonParsec version <- o .: "version"
5579
hash <- o .: "hash"
5680
repo <- o .:? "repo"
81+
revision <- o .:? "revision"
5782
pure Override {..}
5883

5984
jailbreak o = do
@@ -68,13 +93,18 @@ instance FromJSON Override where
6893

6994
instance Pretty Override where
7095
pretty = \case
71-
Override {..} -> pretty version <+> brackets (pretty hash <> foldMap renderRepo repo)
96+
Override {..} ->
97+
pretty version <+> brackets (pretty hash <> foldMap renderRepo repo <> foldMap renderRevision revision)
7298
Jailbreak -> "jailbreak"
7399
Local -> "local"
74100
where
75101
renderRepo (HackageName name) =
76102
hcat [text ",", hpretty name]
77103

104+
renderRevision = \case
105+
IsRevision -> ",rev"
106+
IsNotRevision -> mempty
107+
78108
-- | Overrides can be either for mutable (direct, nonlocal) deps, or for transitive deps, so they must use
79109
-- 'PackageName'.
80110
newtype Overrides =

packages/hix/lib/Hix/Managed/Build.hs

+24-20
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ module Hix.Managed.Build where
22

33
import Control.Monad (foldM)
44
import qualified Data.Map.Strict as Map
5+
import qualified Data.Set as Set
56
import qualified Data.Text as Text
67
import Distribution.Pretty (Pretty)
78
import Exon (exon)
89
import Text.PrettyPrint (vcat)
910

11+
import Hix.Class.Map (nToMaybe)
1012
import qualified Hix.Color as Color
1113
import qualified Hix.Console
1214
import Hix.Console (color, colors)
1315
import Hix.Data.EnvName (EnvName)
1416
import Hix.Data.Monad (M)
15-
import Hix.Data.Overrides (Overrides)
16-
import qualified Hix.Data.PackageId
17-
import Hix.Data.PackageId (PackageId)
17+
import Hix.Data.Overrides (IsRevision (..), Override (..), Overrides)
18+
import Hix.Data.PackageId (PackageId (..))
1819
import Hix.Data.Version (Version, Versions)
1920
import Hix.Data.VersionBounds (VersionBounds)
2021
import qualified Hix.Log as Log
21-
import Hix.Managed.Data.NixOutput (PackageDerivation (..))
2222
import Hix.Managed.Build.Solve (solveMutation)
2323
import qualified Hix.Managed.Cabal.Changes
2424
import Hix.Managed.Cabal.Config (isNonReinstallableDep, isReinstallableId)
@@ -32,6 +32,7 @@ import qualified Hix.Managed.Data.Mutation
3232
import Hix.Managed.Data.Mutation (BuildMutation (BuildMutation), DepMutation, MutationResult (..))
3333
import qualified Hix.Managed.Data.MutationState
3434
import Hix.Managed.Data.MutationState (MutationState (MutationState), updateBoundsWith)
35+
import Hix.Managed.Data.NixOutput (PackageDerivation (..))
3536
import Hix.Managed.Data.Query (Query (Query))
3637
import qualified Hix.Managed.Data.QueryDep
3738
import Hix.Managed.Data.QueryDep (QueryDep)
@@ -107,12 +108,12 @@ buildVersions ::
107108
Bool ->
108109
Versions ->
109110
[PackageId] ->
110-
M (Overrides, Set PackageId, BuildStatus)
111+
M (Overrides, BuildStatus)
111112
buildVersions builder context description allowRevisions versions overrideVersions = do
112113
logBuildInputs context.env description reinstallable
113-
(result, (overrides, revisions)) <- builder.buildTargets allowRevisions versions reinstallable
114+
(result, overrides) <- builder.buildTargets allowRevisions versions reinstallable
114115
logBuildResult description result
115-
pure (overrides, revisions, buildStatus result)
116+
pure (overrides, buildStatus result)
116117
where
117118
reinstallable = filter isReinstallableId overrideVersions
118119

@@ -121,29 +122,32 @@ buildConstraints ::
121122
EnvContext ->
122123
Text ->
123124
Bool ->
124-
Set PackageId ->
125+
Overrides ->
125126
SolverState ->
126-
M (Maybe (Versions, Overrides, Set PackageId, BuildStatus))
127-
buildConstraints builder context description allowRevisions prevRevisions state =
127+
M (Maybe (Versions, Overrides, BuildStatus))
128+
buildConstraints builder context description allowRevisions prevOverrides state =
128129
solveMutation builder.cabal context.deps prevRevisions state >>= traverse \ changes -> do
129-
(overrides, revisions, status) <-
130+
(overrides, status) <-
130131
buildVersions builder context description allowRevisions changes.versions changes.overrides
131-
pure (changes.versions, overrides, prevRevisions <> revisions, status)
132+
pure (changes.versions, overrides, status)
133+
where
134+
prevRevisions =
135+
Set.fromList $ nToMaybe prevOverrides \cases
136+
name Override {version, revision = Just IsRevision} -> Just PackageId {..}
137+
_ _ -> Nothing
132138

133139
buildMutation ::
134140
EnvBuilder ->
135141
EnvContext ->
136142
MutationState ->
137-
Set PackageId ->
138143
BuildMutation ->
139-
M (Maybe (MutationState, Set PackageId))
140-
buildMutation builder context state prevRevisions BuildMutation {description, solverState, updateBound} =
141-
result <$> buildConstraints builder context description True prevRevisions solverState
144+
M (Maybe MutationState)
145+
buildMutation builder context state BuildMutation {description, solverState, updateBound} =
146+
result <$> buildConstraints builder context description True state.overrides solverState
142147
where
143148
result = \case
144-
Just (versions, overrides, revisions, status) -> do
145-
new <- justSuccess (updateMutationState updateBound versions overrides state) status
146-
pure (new, revisions)
149+
Just (versions, overrides, status) ->
150+
justSuccess (updateMutationState updateBound versions overrides state) status
147151
Nothing -> Nothing
148152

149153
logMutationResult ::
@@ -177,7 +181,7 @@ validateMutation envBuilder context handlers stageState mutation = do
177181
then pure MutationKeep
178182
else handlers.process stageState.ext mutation build
179183

180-
build = buildMutation envBuilder context stageState.state stageState.revisions
184+
build = buildMutation envBuilder context stageState.state
181185

182186
convergeMutations ::
183187
Pretty a =>

packages/hix/lib/Hix/Managed/Build/Mutation.hs

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,18 @@ updateConstraints impl candidate state =
5656
-- TODO If we'd use the @retract@ field from @DepMutation@ and the target bound here, we could probably use a universal
5757
-- bounds updater without leaking implementation...investigate.
5858
buildCandidate ::
59-
(BuildMutation -> M (Maybe (MutationState, Set PackageId))) ->
59+
(BuildMutation -> M (Maybe MutationState)) ->
6060
(Version -> VersionBounds -> VersionBounds) ->
6161
(MutableId -> PackageId -> MutationConstraints -> MutationConstraints) ->
6262
SolverState ->
6363
MutableDep ->
6464
Version ->
65-
M (Maybe (MutableId, SolverState, MutationState, Set PackageId))
65+
M (Maybe (MutableId, SolverState, MutationState))
6666
buildCandidate build updateStateBound updateConstraintBound solverState package version = do
6767
Log.debug [exon|Mutation constraints for #{showP candidate}: #{showP mutationSolverState.constraints}|]
6868
fmap result <$> build (candidateMutation mutationSolverState candidate updateStateBound)
6969
where
70-
result (newState, revisions) = (candidate, newSolverState newState, newState, revisions)
70+
result newState = (candidate, newSolverState newState, newState)
7171

7272
candidate = MutableId {name = package, version}
7373

packages/hix/lib/Hix/Managed/Build/Single.hs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Hix.Managed.Build.Single where
22

33
import Hix.Data.Monad (M)
4+
import Hix.Data.Overrides (Overrides)
45
import Hix.Data.VersionBounds (exactVersion)
56
import Hix.Managed.Build (buildConstraints)
67
import Hix.Managed.Cabal.Data.SolverState (solverState)
@@ -18,10 +19,11 @@ buildVersions ::
1819
EnvContext ->
1920
Text ->
2021
MutableVersions ->
22+
Maybe Overrides ->
2123
M BuildStatus
22-
buildVersions builder context description versions =
23-
buildConstraints builder context description False [] solver <&> \case
24-
Just (_, _, _, status) -> status
24+
buildVersions builder context description versions prevOverrides =
25+
buildConstraints builder context description False (fold prevOverrides) solver <&> \case
26+
Just (_, _, status) -> status
2527
Nothing -> Failure
2628
where
2729
solver = solverState context.solverBounds context.deps (fromVersions exactVersion versions) def

packages/hix/lib/Hix/Managed/Build/Solve.hs

+18-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Text.PrettyPrint (hang, ($$), (<+>))
99

1010
import Hix.Class.Map (nRestrictKeys)
1111
import Hix.Data.Monad (M)
12-
import Hix.Data.PackageId (PackageId (PackageId))
12+
import Hix.Data.PackageId (PackageId)
1313
import Hix.Data.Version (packageIdVersions)
1414
import qualified Hix.Log as Log
1515
import qualified Hix.Managed.Cabal.Changes
@@ -28,6 +28,18 @@ logNonReinstallable :: NonEmpty PackageId -> M ()
2828
logNonReinstallable ids =
2929
Log.verbose [exon|NOTE: Cabal solver suggested new versions for non-reinstallable packages: #{showPL ids}|]
3030

31+
-- | Forcing revisions means that any package that has a revision in the Hackage snapshot will be treated as an
32+
-- override, i.e. it will be built from Hackage despite having the same version as the one installed in nixpkgs.
33+
--
34+
-- The benefit of doing this is that often nixpkgs will be outdated in comparison with Hackage, and therefore have
35+
-- tighter dependency bounds.
36+
-- When Cabal resolves a plan based on revised bounds in packages whose versions match nixpkgs, but not their revisions,
37+
-- the nix build will fail with bounds errors, requiring a restart with revisions.
38+
--
39+
-- On the other hand, in many situations (like lower bound mutations), this is entirely irrelevant; in others, the
40+
-- original bounds might just work for the current build; and most often nixpkgs actually has the latest revision, which
41+
-- we cannot observe at this point.
42+
-- Therefore, this feature is disabled until it can be refined.
3143
checkRevision ::
3244
Bool ->
3345
CabalHandlers ->
@@ -62,13 +74,13 @@ processSolverPlan forceRevisions cabal deps prevRevisions SolverPlan {..} = do
6274
where
6375
projectDeps = nRestrictKeys mutablePIds versions
6476
versions = packageIdVersions (overrides ++ installed)
65-
overrides = filter notLocal (changes ++ forcedRevisions ++ reusedRevisions)
77+
overrides = changes ++ forcedRevisions ++ reusedRevisions
78+
-- If a package has been selected for revision during a prior build, add it to the overrides despite its matching
79+
-- version.
80+
-- This simply ensures that the revision procedure can be skipped in this build, since the same version will likely
81+
-- cause the same dependency bounds error that triggered the revision.
6682
(reusedRevisions, installed) = partition (flip Set.member prevRevisions) noForcedRevisions
6783
(noForcedRevisions, forcedRevisions) = partitionEithers (checkRevision forceRevisions cabal <$> matching)
68-
-- notLocal PackageId {name} = not (isLocalPackage deps.local name)
69-
-- TODO I assumed that targets hadn't been part of EnvDeps.local for a long time, so this shouldn't be effective
70-
-- anymore, but verify anyway!
71-
notLocal PackageId {} = True
7284
mutablePIds = Set.fromList (depName <$> Set.toList deps.mutable)
7385

7486
-- TODO probably best to store the revisions in the SolverState

packages/hix/lib/Hix/Managed/Build/Target.hs

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module Hix.Managed.Build.Target where
22

33
import Control.Monad.Trans.State.Strict (StateT (runStateT))
4-
import qualified Data.Map.Strict as Map
54
import Exon (exon)
65
import Path (Abs, Dir, Path)
76

@@ -26,7 +25,7 @@ import Hix.Managed.Data.Targets (Targets, firstMTargets)
2625
import Hix.Managed.Handlers.AvailableVersions (AvailableVersionsHandlers (..))
2726
import Hix.Managed.Handlers.SourceHash (SourceHashHandlers)
2827
import Hix.Managed.Handlers.StateFile (StateFileHandlers)
29-
import Hix.Managed.Overrides (packageOverride, packageOverrides)
28+
import Hix.Managed.Overrides (packageOverrideRegular, packageOverrides, packageRevision)
3029
import Hix.Managed.StateFile (writeBuildStateFor, writeSolverStateFor)
3130

3231
data BuilderResources =
@@ -76,7 +75,7 @@ suggestRevision resources _ pkg = \cases
7675
Nothing (BoundsError _)
7776
| Just package <- failedPackageId pkg
7877
-> do
79-
override <- packageOverride resources.hackage [] package
78+
override <- packageRevision resources.hackage [] package
8079
pure (Just RetryPackage {package, ..})
8180
_ _ -> pure Nothing
8281

@@ -92,9 +91,13 @@ suggestNothing _ _ _ _ =
9291
latestVersionFor :: BuilderResources -> PackageName -> M (Maybe RetryPackage)
9392
latestVersionFor resources target =
9493
resources.versions.latest target >>= traverse \ latest -> do
95-
override <- packageOverride resources.hackage [] PackageId {name = target, version = latest}
94+
override <- packageOverrideRegular resources.hackage [] PackageId {name = target, version = latest}
9695
pure RetryPackage {package = PackageId {name = target, version = override.version}, ..}
9796

97+
-- | This might seem wrong at first glance – it immediately jailbreaks the entire package even though a newer revision
98+
-- might relax just the right bounds and leave the rest intact.
99+
-- However, at this point bounds are entirely useless, since a) we already incorporated proper bounds in our plan by
100+
-- running the solver, and b) nix cannot select between different versions anyway.
98101
suggestJailbreakAndLatestVersion ::
99102
BuilderResources ->
100103
FailureCounts ->
@@ -130,11 +133,11 @@ buildTargets ::
130133
Bool ->
131134
Versions ->
132135
[PackageId] ->
133-
M (BuildResult, (Overrides, Set PackageId))
136+
M (BuildResult, Overrides)
134137
buildTargets builder allowRevisions _ overrideVersions = do
135138
overrides <- packageOverrides builder.global.hackage builder.localUnavailable overrideVersions
136139
let build target = buildAdaptive (buildWithOverrides builder.global builder.env target) suggest
137140
s0 = (overrides, [])
138-
second (second Map.keysSet) <$> runStateT (firstMTargets (BuildSuccess []) buildUnsuccessful build builder.targets) s0
141+
second fst <$> runStateT (firstMTargets (BuildSuccess []) buildUnsuccessful build builder.targets) s0
139142
where
140143
suggest = if allowRevisions then suggestRevision builder.global else suggestNothing

packages/hix/lib/Hix/Managed/Data/Mutation.hs

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Distribution.Pretty (Pretty (pretty))
44
import qualified Text.PrettyPrint as PrettyPrint
55
import Text.PrettyPrint (parens, (<+>))
66

7-
import Hix.Data.PackageId (PackageId)
87
import Hix.Data.Version (Version)
98
import Hix.Data.VersionBounds (VersionBounds)
109
import Hix.Managed.Cabal.Data.SolverState (SolverState)
@@ -41,7 +40,6 @@ data MutationResult s =
4140
candidate :: MutableId,
4241
changed :: Bool,
4342
state :: MutationState,
44-
revisions :: Set PackageId,
4543
ext :: s
4644
}
4745
|

packages/hix/lib/Hix/Managed/Data/StageState.hs

+1-2
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,11 @@ data StageState a s =
9494
success :: Map MutableDep BuildSuccess,
9595
failed :: [DepMutation a],
9696
state :: MutationState,
97-
revisions :: Set PackageId,
9897
iterations :: Word,
9998
ext :: s
10099
}
101100
deriving stock (Eq, Show)
102101

103102
initStageState :: Initial MutationState -> s -> StageState a s
104103
initStageState (Initial state) ext =
105-
StageState {success = [], failed = [], revisions = [], iterations = 0, ..}
104+
StageState {success = [], failed = [], iterations = 0, ..}

0 commit comments

Comments
 (0)