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
103 changes: 83 additions & 20 deletions Cabal/src/Distribution/Backpack/Configure.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import qualified Distribution.Compat.Graph as Graph
import Distribution.InstalledPackageInfo
( InstalledPackageInfo
, emptyInstalledPackageInfo
, requiredSignatures
)
import qualified Distribution.InstalledPackageInfo as Installed
import Distribution.ModuleName
Expand Down Expand Up @@ -261,6 +262,19 @@ toComponentLocalBuildInfos
packageDependsIndex = PackageIndex.fromList (lefts local_graph)
fullIndex = Graph.fromDistinctList local_graph

let
-- Map from dependency UnitId to its PackageId, built from includes
-- of all ready components. Used to resolve opaque hashed UnitIds
-- in broken-package error messages.
depPkgMap :: Map UnitId PackageId
depPkgMap =
Map.fromList
[ (unDefUnitId (ci_id ci), ci_pkgid ci)
| rc <- graph
, Right instc <- [rc_i rc]
, ci <- instc_includes instc
]

case Graph.broken fullIndex of
[] -> return ()
-- If there are promised dependencies, we don't know what the dependencies
Expand All @@ -270,26 +284,33 @@ toComponentLocalBuildInfos
broken
| not (null promisedPkgDeps) -> return ()
| otherwise ->
-- TODO: ppr this
dieProgress . text $
"The following packages are broken because other"
++ " packages they depend on are missing. These broken "
++ "packages must be rebuilt before they can be used.\n"
-- TODO: Undupe.
++ unlines
[ "installed package "
++ prettyShow (packageId pkg)
++ " is broken due to missing package "
++ intercalate ", " (map prettyShow deps)
| (Left pkg, deps) <- broken
]
++ unlines
[ "planned package "
++ prettyShow (packageId pkg)
++ " is broken due to missing package "
++ intercalate ", " (map prettyShow deps)
| (Right pkg, deps) <- broken
]
dieProgress $
text "The following packages are broken because other"
<+> text "packages they depend on are missing. These broken"
<+> text "packages must be rebuilt before they can be used."
$$ nest
2
( vcat $
[ hang
(text "installed package" <+> pretty (packageId pkg))
4
( text "is broken due to missing package"
<+> hsep (punctuate comma (map pretty deps))
)
| (Left pkg, deps) <- broken
]
++ [ hang
(text "planned package" <+> pretty (packageId pkg))
4
( vcat $
text "is broken due to missing package"
: [ nest 2 (dispMissingDep installedPackageSet depPkgMap dep)
| dep <- deps
]
)
| (Right pkg, deps) <- broken
]
)

-- In this section, we'd like to look at the 'packageDependsIndex'
-- and see if we've picked multiple versions of the same
Expand Down Expand Up @@ -338,6 +359,48 @@ toComponentLocalBuildInfos
-- forM clbis $ \(clbi,deps) -> info verbosity $ "UNIT" ++ hashUnitId (componentUnitId clbi) ++ "\n" ++ intercalate "\n" (map hashUnitId deps)
return (clbis, packageDependsIndex)

-- | Pretty-print a missing dependency, resolving opaque hashed 'UnitId's
-- to their human-readable package id and signature info when possible.
--
-- When an indefinite Backpack package is installed separately (e.g. via
-- nix callCabal2nix), only the indefinite variant (with unfilled signatures)
-- exists in the package DB. The consumer needs an instantiated variant
-- which was never built. The fix is to add both packages to the same
-- cabal project so cabal can fill the signatures.
dispMissingDep
:: InstalledPackageIndex
-- ^ all installed packages
-> Map UnitId PackageId
-- ^ dep UnitId to its PackageId (from includes)
-> UnitId
-- ^ the missing dependency
-> Doc
dispMissingDep installedPkgSet depPkgMap uid =
case Map.lookup uid depPkgMap of
Just pkgid ->
let ipiSigs =
[ sigs
| ipi <- PackageIndex.lookupSourcePackageId installedPkgSet pkgid
, let sigs = requiredSignatures ipi
, not (Set.null sigs)
]
in case ipiSigs of
(sigs : _) ->
pretty pkgid
<+> parens
( text "has unfilled"
<+> (if Set.size sigs > 1 then text "signatures:" else text "signature:")
<+> hsep (punctuate comma (map pretty (Set.toList sigs)))
)
$$ nest
2
( text "The package is installed as indefinite."
$$ text "To use it, rebuild it in the same cabal project as the"
<+> text "consumer so cabal can fill the signatures."
)
[] -> pretty pkgid <+> parens (pretty uid)
Nothing -> pretty uid

-- Build ComponentLocalBuildInfo for each component we are going
-- to build.
--
Expand Down
24 changes: 24 additions & 0 deletions cabal-testsuite/PackageTests/Backpack/Fail5/Fail5.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
cabal-version: 3.0
name: Fail5
version: 0.1.0.0
build-type: Simple

library framework
signatures: App
hs-source-dirs: repo/framework-0.1.0.0/src-sig
build-depends: base
default-language: Haskell2010
visibility: public

library app-impl
exposed-modules: App
hs-source-dirs: repo/app-impl-0.1.0.0
build-depends: base
default-language: Haskell2010
visibility: public

executable consumer
main-is: Main.hs
build-depends: base, Fail5:framework, Fail5:app-impl
hs-source-dirs: repo/consumer-0.1.0.0
default-language: Haskell2010
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module App where
appMessage :: String
appMessage = "Hello from app-impl"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cabal-version: 3.0
name: app-impl
version: 0.1.0.0
build-type: Simple

library
exposed-modules: App
build-depends: base
default-language: Haskell2010
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import App (appMessage)
main :: IO ()
main = putStrLn appMessage
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cabal-version: 3.0
name: consumer
version: 0.1.0.0
build-type: Simple

executable consumer
main-is: Main.hs
build-depends: base, framework, app-impl
default-language: Haskell2010
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
cabal-version: 3.0
name: framework
version: 0.1.0.0
build-type: Simple

library
signatures: App
hs-source-dirs: src-sig
build-depends: base
default-language: Haskell2010
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
signature App where
appMessage :: String
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Setup configure
Configuring consumer-0.1.0.0...
Error:
The following packages are broken because other packages they depend on are missing. These broken packages must be rebuilt before they can be used.
planned package consumer-0.1.0.0
is broken due to missing package
framework-0.1.0.0 (has unfilled signature: App)
The package is installed as indefinite.
To use it, rebuild it in the same cabal project as the consumer so cabal can fill the signatures.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Setup configure
Configuring consumer-0.1.0.0...
Error:
The following packages are broken because other packages they depend on are missing. These broken packages must be rebuilt before they can be used.
planned package consumer-0.1.0.0
is broken due to missing package
framework-0.1.0.0 (has unfilled signature: App)
The package is installed as indefinite.
To use it, rebuild it in the same cabal project as the consumer so cabal can fill the signatures.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Test.Cabal.Prelude
main = setupAndCabalTest $ do
withPackageDb $ do
-- Install framework WITHOUT instantiation (simulates nix callCabal2nix)
recordMode DoNotRecord $
withDirectory "repo/framework-0.1.0.0" $ setup_install []
-- Install the App implementation package separately
recordMode DoNotRecord $
withDirectory "repo/app-impl-0.1.0.0" $ setup_install []
-- Configure consumer — should fail because the instantiated
-- framework (with App=app-impl:App) was never built.
-- The exact error message is checked via the .out file.
withDirectory "repo/consumer-0.1.0.0" $ do
fails $ setup' "configure" []
return ()
12 changes: 12 additions & 0 deletions changelog.d/backpack-broken-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
synopsis: "Improve Backpack broken-package error to show unfilled signatures"
packages: [Cabal]
prs: 11669
---

When an indefinite Backpack package is installed separately (e.g. via
nix `callCabal2nix`), configuring a consumer that depends on it would
show an opaque hashed UnitId like
`framework-0.1.0.0+95RTb42ZWxa9J13cUStM0q`. The error now shows the
package name, which signatures are unfilled, and advises rebuilding
in the same cabal project so cabal can fill them.
Loading