Skip to content

Commit 9660e7f

Browse files
committed
fixup! Add support for loading multiple components into one repl session
* Add setup.Cabal>=3.11 constraint when multi-repl is enabled (+ test)
1 parent 1b6f66d commit 9660e7f

File tree

11 files changed

+122
-19
lines changed

11 files changed

+122
-19
lines changed

cabal-install-solver/src/Distribution/Solver/Types/ConstraintSource.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ data ConstraintSource =
3636
-- target, when a more specific source is not known.
3737
| ConstraintSourceConfigFlagOrTarget
3838

39+
-- | Constraint introduced by --enable-multi-repl, which requires features
40+
-- from Cabal >= 3.11
41+
| ConstraintSourceMultiRepl
42+
3943
-- | The source of the constraint is not specified.
4044
| ConstraintSourceUnknown
4145

@@ -65,6 +69,8 @@ showConstraintSource ConstraintSourceNonUpgradeablePackage =
6569
showConstraintSource ConstraintSourceFreeze = "cabal freeze"
6670
showConstraintSource ConstraintSourceConfigFlagOrTarget =
6771
"config file, command line flag, or user target"
72+
showConstraintSource ConstraintSourceMultiRepl =
73+
"--enable-multi-repl"
6874
showConstraintSource ConstraintSourceUnknown = "unknown source"
6975
showConstraintSource ConstraintSetupCabalMinVersion =
7076
"minimum version of Cabal used by Setup.hs"

cabal-install/src/Distribution/Client/CmdRepl.hs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import Distribution.Client.CmdErrorMessages
3636
targetSelectorRefersToPkgs,
3737
renderComponentKind, renderListCommaAnd, renderListSemiAnd,
3838
componentKind, sortGroupOn, Plural(..) )
39+
import Distribution.Client.Targets
40+
( UserConstraint(..), UserConstraintScope(..) )
3941
import Distribution.Client.TargetProblem
4042
( TargetProblem(..) )
4143
import qualified Distribution.Client.InstallPlan as InstallPlan
@@ -65,9 +67,13 @@ import Distribution.Compiler
6567
import Distribution.Simple.Compiler
6668
( Compiler, compilerCompatVersion )
6769
import Distribution.Package
68-
( Package(..), packageName, UnitId, installedUnitId )
70+
( Package(..), packageName, mkPackageName, UnitId, installedUnitId )
6971
import Distribution.Solver.Types.SourcePackage
7072
( SourcePackage(..) )
73+
import Distribution.Solver.Types.ConstraintSource
74+
( ConstraintSource(ConstraintSourceMultiRepl) )
75+
import Distribution.Solver.Types.PackageConstraint
76+
( PackageProperty(PackagePropertyVersion) )
7177
import Distribution.Types.BuildInfo
7278
( BuildInfo(..), emptyBuildInfo )
7379
import Distribution.Types.ComponentName
@@ -81,7 +87,7 @@ import Distribution.Types.Library
8187
import Distribution.Types.Version
8288
( Version, mkVersion )
8389
import Distribution.Types.VersionRange
84-
( anyVersion )
90+
( anyVersion, orLaterVersion )
8591
import Distribution.Utils.Generic
8692
( safeHead )
8793
import Distribution.Verbosity
@@ -115,7 +121,7 @@ import Distribution.Client.ReplFlags
115121
topReplOptions )
116122
import Distribution.Simple.Flag ( Flag(Flag), fromFlagOrDefault )
117123
import Distribution.Client.ProjectConfig
118-
( ProjectConfigShared(projectConfigMultiRepl),
124+
( ProjectConfigShared(projectConfigMultiRepl, projectConfigConstraints),
119125
ProjectConfig(projectConfigShared) )
120126

121127

@@ -183,8 +189,8 @@ multiReplDecision ctx compiler flags =
183189
-- up to date, selects that part of the plan needed by the given or implicit
184190
-- repl target and then executes the plan.
185191
--
186-
-- Compared to @build@ the difference is that only one target is allowed
187-
-- (given or implicit) and the target type is repl rather than build. The
192+
-- Compared to @build@ the difference is that multiple targets are handled
193+
-- specially and the target type is repl rather than build. The
188194
-- general plan execution infrastructure handles both build and repl targets.
189195
--
190196
-- For more details on how this works, see the module
@@ -228,13 +234,24 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
228234

229235
updateContextAndWriteProjectFile ctx scriptPath scriptExecutable
230236

231-
(originalComponent, baseCtx') <- if null (envPackages replEnvFlags)
232-
then return (Nothing, baseCtx)
237+
-- If multi-repl is used, we need a Cabal recent enough to handle it.
238+
-- We need to do this before solving, but the compiler version is only known
239+
-- after solving (phaseConfigureCompiler), so instead of using
240+
-- multiReplDecision we just check the flag.
241+
let baseCtx' = if fromFlagOrDefault False $
242+
projectConfigMultiRepl (projectConfigShared $ projectConfig baseCtx)
243+
<> replUseMulti
244+
then baseCtx & lProjectConfig . lProjectConfigShared . lProjectConfigConstraints
245+
%~ (multiReplCabalConstraint:)
246+
else baseCtx
247+
248+
(originalComponent, baseCtx'') <- if null (envPackages replEnvFlags)
249+
then return (Nothing, baseCtx')
233250
else
234251
-- Unfortunately, the best way to do this is to let the normal solver
235252
-- help us resolve the targets, but that isn't ideal for performance,
236253
-- especially in the no-project case.
237-
withInstallPlan (lessVerbose verbosity) baseCtx $ \elaboratedPlan sharedConfig -> do
254+
withInstallPlan (lessVerbose verbosity) baseCtx' $ \elaboratedPlan sharedConfig -> do
238255
-- targets should be non-empty map, but there's no NonEmptyMap yet.
239256
targets <- validatedTargets (projectConfigShared (projectConfig ctx)) (pkgConfigCompiler sharedConfig) elaboratedPlan targetSelectors
240257

@@ -243,9 +260,9 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
243260
originalDeps = installedUnitId <$> InstallPlan.directDeps elaboratedPlan unitId
244261
oci = OriginalComponentInfo unitId originalDeps
245262
pkgId = fromMaybe (error $ "cannot find " ++ prettyShow unitId) $ packageId <$> InstallPlan.lookup elaboratedPlan unitId
246-
baseCtx' = addDepsToProjectTarget (envPackages replEnvFlags) pkgId baseCtx
263+
baseCtx'' = addDepsToProjectTarget (envPackages replEnvFlags) pkgId baseCtx'
247264

248-
return (Just oci, baseCtx')
265+
return (Just oci, baseCtx'')
249266

250267
-- Now, we run the solver again with the added packages. While the graph
251268
-- won't actually reflect the addition of transitive dependencies,
@@ -255,9 +272,9 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
255272
-- In addition, to avoid a *third* trip through the solver, we are
256273
-- replicating the second half of 'runProjectPreBuildPhase' by hand
257274
-- here.
258-
(buildCtx, compiler, replOpts', targets) <- withInstallPlan verbosity baseCtx' $
275+
(buildCtx, compiler, replOpts', targets) <- withInstallPlan verbosity baseCtx'' $
259276
\elaboratedPlan elaboratedShared' -> do
260-
let ProjectBaseContext{..} = baseCtx'
277+
let ProjectBaseContext{..} = baseCtx''
261278

262279
-- Recalculate with updated project.
263280
targets <- validatedTargets (projectConfigShared projectConfig) (pkgConfigCompiler elaboratedShared') elaboratedPlan targetSelectors
@@ -296,7 +313,7 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
296313
-- Multi Repl implemention see: https://well-typed.com/blog/2023/03/cabal-multi-unit/ for
297314
-- a high-level overview about how everything fits together.
298315
if Set.size (distinctTargetComponents targets) > 1
299-
then withTempDirectoryEx verbosity (TempFileOptions keepTempFiles) distDir "multi-out-" $ \dir' -> do
316+
then withTempDirectoryEx verbosity (TempFileOptions keepTempFiles) distDir "multi-out" $ \dir' -> do
300317
-- multi target repl
301318
dir <- makeAbsolute dir'
302319
-- Modify the replOptions so that the ./Setup repl command will write options
@@ -306,12 +323,12 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
306323
_ -> usingGhciScript compiler projectRoot replOpts'
307324

308325
let buildCtx' = buildCtx & lElaboratedShared . lPkgConfigReplOptions .~ replOpts''
309-
printPlan verbosity baseCtx' buildCtx'
326+
printPlan verbosity baseCtx'' buildCtx'
310327

311328
-- The project build phase will call `./Setup repl` but write the options
312329
-- out into a file without starting a repl.
313-
buildOutcomes <- runProjectBuildPhase verbosity baseCtx' buildCtx'
314-
runProjectPostBuildPhase verbosity baseCtx' buildCtx' buildOutcomes
330+
buildOutcomes <- runProjectBuildPhase verbosity baseCtx'' buildCtx'
331+
runProjectPostBuildPhase verbosity baseCtx'' buildCtx' buildOutcomes
315332

316333
-- calculate PATH, we construct a PATH which is the union of all paths from
317334
-- the units which have been loaded. This is not quite right but usually works fine.
@@ -354,10 +371,10 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
354371
_ -> usingGhciScript compiler projectRoot replOpts'
355372

356373
let buildCtx' = buildCtx & lElaboratedShared . lPkgConfigReplOptions .~ replOpts''
357-
printPlan verbosity baseCtx' buildCtx'
374+
printPlan verbosity baseCtx'' buildCtx'
358375

359-
buildOutcomes <- runProjectBuildPhase verbosity baseCtx' buildCtx'
360-
runProjectPostBuildPhase verbosity baseCtx' buildCtx' buildOutcomes
376+
buildOutcomes <- runProjectBuildPhase verbosity baseCtx'' buildCtx'
377+
runProjectPostBuildPhase verbosity baseCtx'' buildCtx' buildOutcomes
361378
where
362379

363380
combine_search_paths paths =
@@ -391,6 +408,17 @@ replAction flags@NixStyleFlags { extraFlags = r@ReplFlags{..} , ..} targetString
391408

392409
return targets
393410

411+
-- This is the constraint setup.Cabal>=3.11. 3.11 is when Cabal options
412+
-- used for multi-repl were introduced.
413+
-- Idelly we'd apply this constraint only on the closure of repl targets,
414+
-- but that would require another solver run for marginal advantages that
415+
-- will further shrink as 3.11 is adopted.
416+
multiReplCabalConstraint =
417+
( UserConstraint
418+
(UserAnySetupQualifier (mkPackageName "Cabal"))
419+
(PackagePropertyVersion $ orLaterVersion $ mkVersion [3,11])
420+
, ConstraintSourceMultiRepl )
421+
394422
-- | First version of GHC which supports multiple home packages
395423
minMultipleHomeUnitsVersion :: Version
396424
minMultipleHomeUnitsVersion = mkVersion [9, 4]
@@ -685,3 +713,15 @@ lPkgConfigReplOptions f s = fmap (\x -> s { pkgConfigReplOptions = x }) (f (pkgC
685713
lReplOptionsFlags :: Lens' ReplOptions [String]
686714
lReplOptionsFlags f s = fmap (\x -> s { replOptionsFlags = x }) (f (replOptionsFlags s))
687715
{-# inline lReplOptionsFlags #-}
716+
717+
lProjectConfig :: Lens' ProjectBaseContext ProjectConfig
718+
lProjectConfig f s = fmap (\x -> s { projectConfig = x }) (f (projectConfig s))
719+
{-# inline lProjectConfig #-}
720+
721+
lProjectConfigShared :: Lens' ProjectConfig ProjectConfigShared
722+
lProjectConfigShared f s = fmap (\x -> s { projectConfigShared = x }) (f (projectConfigShared s))
723+
{-# inline lProjectConfigShared #-}
724+
725+
lProjectConfigConstraints :: Lens' ProjectConfigShared [(UserConstraint, ConstraintSource)]
726+
lProjectConfigConstraints f s = fmap (\x -> s { projectConfigConstraints = x }) (f (projectConfigConstraints s))
727+
{-# inline lProjectConfigConstraints #-}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# cabal v2-update
2+
Downloading the latest package list from test-local-repo
3+
# cabal v2-repl
4+
Resolving dependencies...
5+
Error: cabal: Could not resolve dependencies:
6+
[__0] trying: pkg-a-0 (user goal)
7+
[__1] next goal: pkg-a:setup.Cabal (dependency of pkg-a)
8+
[__1] rejecting: pkg-a:setup.Cabal-<VERSION>/installed-<HASH>, pkg-a:setup.Cabal-3.8.0.0 (constraint from --enable-multi-repl requires >=3.11)
9+
[__1] fail (backjumping, conflict set: pkg-a, pkg-a:setup.Cabal)
10+
After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: pkg-a:setup.Cabal (3), pkg-a (2)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages: pkg-a/*.cabal
2+
packages: pkg-b/*.cabal
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Test.Cabal.Prelude
2+
3+
main = cabalTest $ withRepo "repo" $ do
4+
skipUnlessGhcVersion ">= 9.4"
5+
void $ fails $ cabalWithStdin "v2-repl" ["--keep-temp-files","--enable-multi-repl","pkg-a", "pkg-b"] ""
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Foo where
2+
3+
foo :: Int
4+
foo = 42
5+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cabal-version: 2.2
2+
name: pkg-a
3+
version: 0
4+
build-type: Custom
5+
6+
custom-setup
7+
setup-depends:
8+
base >= 4 && < 5,
9+
Cabal < 3.11
10+
11+
library
12+
default-language: Haskell2010
13+
build-depends: base
14+
exposed-modules: Foo
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Bar (foo, bar) where
2+
3+
import Foo (foo)
4+
5+
bar :: Int
6+
bar = 0xdeadc0de
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cabal-version: 2.2
2+
name: pkg-b
3+
version: 0
4+
5+
library
6+
default-language: Haskell2010
7+
build-depends: base, pkg-a
8+
exposed-modules: Bar

0 commit comments

Comments
 (0)