Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 64 additions & 18 deletions cabal-install/src/Distribution/Client/ProjectPlanning.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3766,6 +3766,11 @@ pruneInstallPlanPass2 pkgs =
elabBuildTargets elab
++ libTargetsRequiredForRevDeps
++ exeTargetsRequiredForRevDeps
, elabBuildStyle =
if installedUnitId elab `Set.member` mustBuildOnDisk
&& elabBuildStyle elab == BuildInplaceOnly InMemory
then BuildInplaceOnly OnDisk
else elabBuildStyle elab
, elabPkgOrComp =
case elabPkgOrComp elab of
ElabPackage pkg ->
Expand Down Expand Up @@ -3802,10 +3807,13 @@ pruneInstallPlanPass2 pkgs =

libTargetsRequiredForRevDeps =
[ c
| installedUnitId elab `Set.member` hasReverseLibDeps
| installedUnitId elab `Set.member` libDeps
, let c = ComponentTarget (CLibName Cabal.defaultLibName) WholeComponent
, -- Don't enable building for anything which is being build in memory
, -- Don't enable building for anything which is being built in memory,
-- unless it is a (transitive) library dependency of an exe build tool,
-- in which case it must be compiled to disk so the exe can link against it.
elabBuildStyle elab /= BuildInplaceOnly InMemory
|| installedUnitId elab `Set.member` mustBuildOnDisk
]
exeTargetsRequiredForRevDeps =
-- TODO: allow requesting executable with different name
Expand All @@ -3817,35 +3825,73 @@ pruneInstallPlanPass2 pkgs =
elabPkgSourceId elab
)
WholeComponent
| installedUnitId elab `Set.member` hasReverseExeDeps
| installedUnitId elab `Set.member` exeDeps
]

availablePkgs :: Set UnitId
availablePkgs = Set.fromList (map installedUnitId pkgs)

inMemoryTargets :: Set ConfiguredId
inMemoryTargets = do
inMemoryTargets =
Set.fromList
[ configuredId pkg
| InstallPlan.Configured pkg <- pkgs
, -- Exclude packages that must be built on disk (for exe build tools).
-- Their dependents will receive a real -package-id, not a promise.
installedUnitId pkg `Set.notMember` mustBuildOnDisk
, BuildInplaceOnly InMemory <- [elabBuildStyle pkg]
]

hasReverseLibDeps :: Set UnitId
hasReverseLibDeps =
Set.fromList
[ depid
| InstallPlan.Configured pkg <- pkgs
, depid <- elabOrderLibDependencies pkg
]
-- Packages that must be built on disk because they are (transitively)
-- needed as library dependencies by an exe build-tool. Even if such a
-- package was originally marked InMemory for the multi-repl session, it
-- must also produce real on-disk artifacts so the exe can link against it.
-- The repl phase still runs for these packages, so GHCi loads them from
-- source under the same package-id, preserving type identity.
mustBuildOnDisk :: Set UnitId
mustBuildOnDisk = go exeDeps Set.empty
where
go frontier visited
| Set.null frontier = visited
| otherwise =
let newVisited = visited <> frontier
newFrontier =
Set.fromList
[ dep
| uid <- Set.toList frontier
, dep <- Map.findWithDefault [] uid planLibDepMap
]
Set.\\ newVisited
in go newFrontier newVisited

-- Lib and exe deps computed once per in-plan package, shared below.
-- Note: these must be the order-dependency UnitIds, which match the
-- 'installedUnitId' of the units in the plan; in particular for
-- Backpack-instantiated units they include the instantiation, unlike
-- the ComponentIds from 'elabLibDependencies'.
perPkgDeps :: [(UnitId, [UnitId], [UnitId])]
perPkgDeps =
[ ( installedUnitId pkg
, elabOrderLibDependencies pkg
, elabOrderExeDependencies pkg
)
| InstallPlan.Configured pkg <- pkgs
]

hasReverseExeDeps :: Set UnitId
hasReverseExeDeps =
Set.fromList
[ depid
| InstallPlan.Configured pkg <- pkgs
, depid <- elabOrderExeDependencies pkg
]
-- Library-dependency adjacency for in-plan packages only.
planLibDepMap :: Map UnitId [UnitId]
planLibDepMap =
Map.fromList [(uid, ls) | (uid, ls, _) <- perPkgDeps]

-- All packages that appear as a library dep of any in-plan package.
libDeps :: Set UnitId
libDeps =
Set.fromList [d | (_, ls, _) <- perPkgDeps, d <- ls]

-- All packages that appear as an exe dep of any in-plan package.
exeDeps :: Set UnitId
exeDeps =
Set.fromList [d | (_, _, es) <- perPkgDeps, d <- es]

mapConfiguredPackage
:: (srcpkg -> srcpkg')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Main (main) where

import MyLib (mylib)

main :: IO ()
main = print mylib
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Main (main) where

import MyLib (mylib)

main :: IO ()
main = print (mylib + 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# cabal v2-repl
Resolving dependencies...
Build profile: -w ghc-<GHCVER> -O1
In order, the following will be built:
- foo-0.1.0.0 (lib) (first run)
- foo-0.1.0.0 (exe:foo) (first run)
- foo-0.1.0.0 (interactive) (exe:bar) (first run)
Configuring library for foo-0.1.0.0...
Preprocessing library for foo-0.1.0.0...
Building library for foo-0.1.0.0...
Preprocessing library for foo-0.1.0.0...
Configuring executable 'foo' for foo-0.1.0.0...
Preprocessing executable 'foo' for foo-0.1.0.0...
Building executable 'foo' for foo-0.1.0.0...
Preprocessing executable 'foo' for foo-0.1.0.0...
Configuring executable 'bar' for foo-0.1.0.0...
Preprocessing executable 'bar' for foo-0.1.0.0...
# foo foo
3735929054
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# cabal v2-repl
Resolving dependencies...
Build profile: -w ghc-<GHCVER> -O1
In order, the following will be built:
- foo-0.1.0.0 (lib) (first run)
- foo-0.1.0.0 (exe:foo) (first run)
- foo-0.1.0.0 (interactive) (exe:bar) (first run)
Configuring library for foo-0.1.0.0...
Preprocessing library for foo-0.1.0.0...
Building library for foo-0.1.0.0...
Configuring executable 'foo' for foo-0.1.0.0...
Preprocessing executable 'foo' for foo-0.1.0.0...
Building executable 'foo' for foo-0.1.0.0...
Preprocessing executable 'foo' for foo-0.1.0.0...
Configuring executable 'bar' for foo-0.1.0.0...
Preprocessing executable 'bar' for foo-0.1.0.0...
# foo foo
3735929054
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages: .
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Test.Cabal.Prelude

-- Test for issue #11850: in a multi-repl session, an executable which is a
-- build-tool-depends of another repl target must be compiled to disk together
-- with its library dependencies (not just loaded in memory), so the build
-- tool can be linked and run.
main = do
-- The library is itself a repl target here, as in the original report of
-- issue #11850: before the fix it was loaded in memory only, so building
-- the build tool on disk failed with
-- "cannot satisfy -package-id foo-0.1.0.0-inplace".
cabalTest' "all" $ do
skipUnlessGhcVersion ">= 9.4"
res <- cabalWithStdin "v2-repl" ["--enable-multi-repl", "all"] "print 0xdeadbeef"
-- The tool and its library dep are built on disk, not merely loaded in memory
assertOutputContains "Building executable 'foo' for foo-0.1.0.0" res
-- The multi-repl session is functional (0xdeadbeef = 3735928559)
assertOutputContains "3735928559" res
-- The tool binary exists on disk and was linked against the on-disk library
-- (0xdeadc0de = 3735929054)
withPlan $ do
r <- runPlanExe' "foo" "foo" []
assertOutputContains "3735929054" r

-- The repl targets are the two executables; the library is not a repl
-- target but is still pulled onto disk as a dependency of the build tool.
cabalTest' "exes" $ do
skipUnlessGhcVersion ">= 9.4"
res <- cabalWithStdin "v2-repl" ["--enable-multi-repl", "exe:foo", "exe:bar"] "print 0xdeadbeef"
assertOutputContains "Building executable 'foo' for foo-0.1.0.0" res
-- Both executables are loaded into the multi-repl session
assertOutputContains "Ok, two modules loaded." res
assertOutputContains "3735928559" res
withPlan $ do
r <- runPlanExe' "foo" "foo" []
assertOutputContains "3735929054" r
23 changes: 23 additions & 0 deletions cabal-testsuite/PackageTests/MultiRepl/BuildToolDepends/foo.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
cabal-version: 3.0
name: foo
version: 0.1.0.0
build-type: Simple

library
default-language: Haskell2010
hs-source-dirs: src
exposed-modules: MyLib
build-depends: base

executable foo
default-language: Haskell2010
hs-source-dirs: app
main-is: Main.hs
build-depends: base, foo

executable bar
default-language: Haskell2010
hs-source-dirs: bar
main-is: Main.hs
build-depends: base, foo
build-tool-depends: foo:foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module MyLib (mylib) where

mylib :: Integer
mylib = 0xdeadc0de
13 changes: 13 additions & 0 deletions changelog.d/11850.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
synopsis: "Build `build-tool-depends` executables on disk in multi-repl sessions"
packages: [cabal-install]
prs: 11850
issues: 11850
---

`cabal repl --enable-multi-repl` failed when one repl target had a
`build-tool-depends` on an executable of the same project: the executable and
its library dependencies were only loaded in memory and never compiled to
disk, so the build tool could not be built. The build tool and its transitive
library dependencies are now built on disk, while still being loaded from
source in the repl session.
Loading