From 57e5ee611ca27480076a30ece26820e7b8a7f867 Mon Sep 17 00:00:00 2001 From: Byron Johnson Date: Fri, 5 Aug 2022 18:33:50 -0600 Subject: [PATCH 1/4] Fix project-local build flags being ignored. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I noticed that running ‘cabal install’ with two separate sets of dynamic / static build flags (e.g. one with none, and one with ‘--enable-shared --enable-executable-dynamic --disable-library-vanilla’) produced packages with the same hash, instead of different hashes. After debugging this issue I found that this command (with no explicit cabal project file) was resulting in these build configuration flags being ignored, because in ProjectPlanning.hs, the sdist was not considered a local package, so the (non-shared) local-package-only configuration was being dropped. This fix ensures that these command-line arguments properly make it through to where they belong in cases like this. --- .../Distribution/Client/ProjectPlanning.hs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index a3464159923..952f68c2952 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -663,6 +663,7 @@ rebuildInstallPlan verbosity projectConfigAllPackages, projectConfigLocalPackages, projectConfigSpecificPackage, + projectPackagesNamed, projectConfigBuildOnly } (compiler, platform, progdb) pkgConfigDB @@ -687,6 +688,7 @@ rebuildInstallPlan verbosity localPackages sourcePackageHashes defaultInstallDirs + projectPackagesNamed projectConfigShared projectConfigAllPackages projectConfigLocalPackages @@ -1345,6 +1347,7 @@ elaborateInstallPlan -> [PackageSpecifier (SourcePackage (PackageLocation loc))] -> Map PackageId PackageSourceHash -> InstallDirs.InstallDirTemplates + -> [PackageVersionConstraint] -> ProjectConfigShared -> PackageConfig -> PackageConfig @@ -1356,6 +1359,7 @@ elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB solverPlan localPackages sourcePackageHashes defaultInstallDirs + extraPackages sharedPackageConfig allPackagesConfig localPackagesConfig @@ -2026,15 +2030,21 @@ elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB $ map packageId $ SolverInstallPlan.reverseDependencyClosure solverPlan - (map PlannedId (Set.toList pkgsLocalToProject)) + (map PlannedId (Set.toList pkgsInplaceToProject)) isLocalToProject :: Package pkg => pkg -> Bool isLocalToProject pkg = Set.member (packageId pkg) pkgsLocalToProject + pkgsInplaceToProject :: Set PackageId + pkgsInplaceToProject = + Set.fromList (catMaybes (map shouldBeLocal localPackages)) + --TODO: localPackages is a misnomer, it's all project packages + -- here is where we decide which ones will be local! + pkgsLocalToProject :: Set PackageId pkgsLocalToProject = - Set.fromList (catMaybes (map shouldBeLocal localPackages)) + Set.fromList (catMaybes (map (isInLocal extraPackages) localPackages)) --TODO: localPackages is a misnomer, it's all project packages -- here is where we decide which ones will be local! @@ -2103,6 +2113,28 @@ shouldBeLocal (SpecificSourcePackage pkg) = case srcpkgSource pkg of LocalUnpackedPackage _ -> Just (packageId pkg) _ -> Nothing +-- Used to determine which packages are affected by local package configuration +-- flags like ‘--enable-shared enable-executable-dynamic --disable-library-vanilla’. +isInLocal :: [PackageVersionConstraint] -> PackageSpecifier (SourcePackage (PackageLocation loc)) -> Maybe PackageId +isInLocal _ NamedPackage{} = Nothing +isInLocal _extraPackages (SpecificSourcePackage pkg) = case srcpkgSource pkg of + LocalUnpackedPackage _ -> Just (packageId pkg) + -- LocalTarballPackage is matched here too, because otherwise ‘sdistize’ + -- produces for ‘localPackages’ in the ‘ProjectBaseContext’ a + -- LocalTarballPackage, and ‘shouldBeLocal’ will make flags like + -- ‘--disable-library-vanilla’ have no effect for a typical + -- ‘cabal install --lib --enable-shared enable-executable-dynamic --disable-library-vanilla’, + -- as these flags would apply to local packages, but the sdist would + -- erroneously not get categorized as a local package, so the flags would be + -- ignored and produce a package with an unchanged hash. + LocalTarballPackage _ -> Just (packageId pkg) + -- TODO: the docs say ‘extra-packages’ is implemented in cabal project + -- files. We can fix that here by checking that the version range matches. + --RemoteTarballPackage _ -> _ + --RepoTarballPackage _ -> _ + --RemoteSourceRepoPackage _ -> _ + _ -> Nothing + -- | Given a 'ElaboratedPlanPackage', report if it matches a 'ComponentName'. matchPlanPkg :: (ComponentName -> Bool) -> ElaboratedPlanPackage -> Bool matchPlanPkg p = InstallPlan.foldPlanPackage (p . ipiComponentName) (matchElabPkg p) From 2f1713dab7761bcdb95b7fc2b0b50b7c5bb65626 Mon Sep 17 00:00:00 2001 From: Byron Johnson Date: Fri, 5 Aug 2022 22:13:42 -0600 Subject: [PATCH 2/4] =?UTF-8?q?Add=20a=20=E2=80=98NonignoredConfigs?= =?UTF-8?q?=E2=80=99=20test=20that=20fails=20without=20our=20fix.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NonignoredConfigs/basic/Basic.hs | 10 ++ .../NonignoredConfigs/basic/basic.cabal | 10 ++ .../NonignoredConfigs/cabal.project | 1 + .../NonignoredConfigs/setup.cabal.out | 28 ++++ .../LinkerOptions/NonignoredConfigs/setup.out | 28 ++++ .../NonignoredConfigs/setup.test.hs | 137 ++++++++++++++++++ 6 files changed, 214 insertions(+) create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/Basic.hs create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/basic.cabal create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/cabal.project create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.cabal.out create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.out create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.test.hs diff --git a/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/Basic.hs b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/Basic.hs new file mode 100644 index 00000000000..d8690293fff --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/Basic.hs @@ -0,0 +1,10 @@ +module Basic where + +funcs :: (a -> b -> c) -> ((a -> b -> c) -> a -> b -> c) -> b -> a -> c +funcs f g = \a b -> (g f) b a + +name :: String +name = "Basic" + +number :: Integer +number = 8 diff --git a/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/basic.cabal b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/basic.cabal new file mode 100644 index 00000000000..bb6263aa939 --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/basic/basic.cabal @@ -0,0 +1,10 @@ +cabal-version: >= 1.10 +name: basic +version: 1.0 +build-type: Simple + +library + default-language: Haskell2010 + build-depends: base + exposed-modules: + Basic diff --git a/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/cabal.project b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/cabal.project new file mode 100644 index 00000000000..6b9fac75bce --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/cabal.project @@ -0,0 +1 @@ +packages: basic diff --git a/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.cabal.out b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.cabal.out new file mode 100644 index 00000000000..4d05743febd --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.cabal.out @@ -0,0 +1,28 @@ +# cabal v2-install +Wrote tarball sdist to /setup.cabal.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - basic-1.0 (lib) (requires build) +Configuring library for basic-1.0.. +Preprocessing library for basic-1.0.. +Building library for basic-1.0.. +Installing library in +# cabal v2-install +Wrote tarball sdist to /setup.cabal.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - basic-1.0 (lib) (requires build) +Configuring library for basic-1.0.. +Preprocessing library for basic-1.0.. +Building library for basic-1.0.. +Installing library in +# cabal v2-install +Wrote tarball sdist to /setup.cabal.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Up to date +# cabal v2-install +Wrote tarball sdist to /setup.cabal.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Up to date diff --git a/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.out b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.out new file mode 100644 index 00000000000..0fb9973dc04 --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.out @@ -0,0 +1,28 @@ +# cabal v2-install +Wrote tarball sdist to /setup.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - basic-1.0 (lib) (requires build) +Configuring library for basic-1.0.. +Preprocessing library for basic-1.0.. +Building library for basic-1.0.. +Installing library in +# cabal v2-install +Wrote tarball sdist to /setup.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - basic-1.0 (lib) (requires build) +Configuring library for basic-1.0.. +Preprocessing library for basic-1.0.. +Building library for basic-1.0.. +Installing library in +# cabal v2-install +Wrote tarball sdist to /setup.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Up to date +# cabal v2-install +Wrote tarball sdist to /setup.dist/work/./basic/dist/sdist/basic-1.0.tar.gz +Resolving dependencies... +Up to date diff --git a/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.test.hs b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.test.hs new file mode 100644 index 00000000000..1558a4b843a --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/NonignoredConfigs/setup.test.hs @@ -0,0 +1,137 @@ +import Test.Cabal.Prelude + +-- This test ensures the following fix holds: +-- > Fix project-local build flags being ignored. +-- > +-- > I noticed that running ‘cabal install’ with two separate sets of dynamic / +-- > static build flags (e.g. one with none, and one with ‘--enable-shared +-- > --enable-executable-dynamic --disable-library-vanilla’) produced packages with +-- > the same hash, instead of different hashes. +-- > +-- > After debugging this issue I found that this command (with no explicit cabal +-- > project file) was resulting in these build configuration flags being ignored, +-- > because in ProjectPlanning.hs, the sdist was not considered a local package, so +-- > the (non-shared) local-package-only configuration was being dropped. +-- > +-- > This fix ensures that these command-line arguments properly make it through to +-- > where they belong in cases like this. +-- +-- Basically, take a simple package, build it under two sets of build flags: +-- > (nothing) +-- > --enable-shared --enable-executable-dynamic --disable-library-vanilla +-- +-- And ensure that whereas before they produced the same hash, now the package +-- hashes produced are different. (And also supplementarily ensure that +-- re-running the same build with the same flags a second time produces a +-- deterministic hash too.) +import Data.List (isInfixOf) +main = setupAndCabalTest $ do + withPackageDb $ do + -- Get env file paths. We'll check 4 files to extract hashes. + env <- getTestEnv + let + workDir = testWorkDir env + dyn1Path = workDir "dyn1.env" + dyn2Path = workDir "dyn2.env" + static1Path = workDir "static1.env" + static2Path = workDir "static2.env" + + -- 4 phases. We'll collect the results in ‘setup.dist/*.env’, and make + -- sure they look fine afterward. + + -- Phase 1: dynamic, first sample. + dyn1Result <- do + withDirectory "basic" $ do + cabal "v2-install" $ + [ + "--lib", + "--package-env=" ++ dyn1Path + ] ++ + [ + "--enable-shared", + "--enable-executable-dynamic", + "--disable-library-vanilla" + ] + + -- Phase 2: static, first sample. + static1Result <- do + withDirectory "basic" $ do + cabal "v2-install" $ + [ + "--lib", + "--package-env=" ++ static1Path + ] ++ + [ + ] + + -- Phase 3: dynamic, second sample. + dyn2Result <- do + withDirectory "basic" $ do + cabal "v2-install" $ + [ + "--lib", + "--package-env=" ++ dyn2Path + ] ++ + [ + "--enable-shared", + "--enable-executable-dynamic", + "--disable-library-vanilla" + ] + + -- Phase 4: static, second sample. + static2Result <- do + withDirectory "basic" $ do + cabal "v2-install" $ + [ + "--lib", + "--package-env=" ++ static2Path + ] ++ + [ + ] + + -- Now read the environment files. + let + extract path = do + contents <- liftIO $ readFile path + let + ls = lines contents + prefix = "package-id basic-1.0-" + basics = map (drop $ length prefix) . filter ("package-id basic-1.0-" `isInfixOf`) $ ls + line <- case basics of + [extraction] -> return extraction + _ -> do + (>> return "ERROR") . assertFailure . unlines $ + [ + "Error: failed to find the ‘basic-1.0’ hash from ‘" ++ path ++ "’.", + "\tMake sure the suffix includes a hash and not just the version.", + "\tAlso make sure there is exactly 1 line starting with ‘" ++ (prefix) ++ "’ (found " ++ (show (length basics)) ++ ")." + ] + return line + dyn1UID <- extract dyn1Path + static1UID <- extract static1Path + dyn2UID <- extract dyn2Path + static2UID <- extract static2Path + + -- First make sure the two samples are deterministic. + -- (Non-essential test.) + when (dyn1UID /= dyn2UID) $ do + assertFailure . unlines $ + [ + "Error: dyn1UID /= dyn2UID: ‘" ++ dyn1UID ++ "’ /= ‘" ++ dyn2UID ++ "’." + ] + when (static1UID /= static2UID) $ do + assertFailure . unlines $ + [ + "Error: dyn1UID /= dyn2UID: ‘" ++ dyn1UID ++ "’ /= ‘" ++ dyn2UID ++ "’." + ] + + -- Now make sure dyn and static are different. What we're mainly testing. + when (dyn1UID == static1UID) $ do + assertFailure . unlines $ + [ + "Error: dyn1UID == static1UID: ‘" ++ dyn1UID ++ "’ == ‘" ++ static1UID ++ "’.", + "\tThese packages should have been configured with different config flags", + "\tproducing different hashes." + ] + + return () From ae1f2e1542c9acbecf2134bc98f534c31aea4035 Mon Sep 17 00:00:00 2001 From: Byron Johnson Date: Wed, 10 Aug 2022 21:45:56 -0600 Subject: [PATCH 3/4] Track build artifacts in installed packages. Let cabal-install know when e.g. dependencies require both dynamic and static build artifacts. --- .../Types/InstalledPackageInfo.hs | 15 +++- .../InstalledPackageInfo/FieldGrammar.hs | 5 ++ .../Types/InstalledPackageInfo/Lens.hs | 20 ++++++ Cabal/src/Distribution/Simple/Register.hs | 7 +- .../cabal-install-solver.cabal | 1 + .../Distribution/Solver/Modular/Builder.hs | 71 +++++++++++-------- .../Distribution/Solver/Modular/Explore.hs | 2 +- .../src/Distribution/Solver/Modular/Index.hs | 4 +- .../Solver/Modular/IndexConversion.hs | 22 ++++-- .../Distribution/Solver/Modular/Linking.hs | 8 +-- .../Distribution/Solver/Modular/Message.hs | 1 + .../src/Distribution/Solver/Modular/Tree.hs | 2 + .../Distribution/Solver/Modular/Validate.hs | 2 +- .../Solver/Types/ArtifactSelection.hs | 70 ++++++++++++++++++ .../src/Distribution/Client/Configure.hs | 6 ++ .../src/Distribution/Client/Install.hs | 1 + .../Client/ProjectConfig/Legacy.hs | 18 ++--- .../Distribution/Client/ProjectPlanning.hs | 5 +- .../src/Distribution/Client/SetupWrapper.hs | 16 ++++- 19 files changed, 218 insertions(+), 58 deletions(-) create mode 100644 cabal-install-solver/src/Distribution/Solver/Types/ArtifactSelection.hs diff --git a/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo.hs b/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo.hs index 0d047db5590..716a9369fb1 100644 --- a/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo.hs +++ b/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo.hs @@ -92,7 +92,13 @@ data InstalledPackageInfo frameworks :: [String], haddockInterfaces :: [FilePath], haddockHTMLs :: [FilePath], - pkgRoot :: Maybe FilePath + pkgRoot :: Maybe FilePath, + -- Artifacts included in this package: + pkgVanillaLib :: Bool, + pkgSharedLib :: Bool, + pkgDynExe :: Bool, + pkgProfLib :: Bool, + pkgProfExe :: Bool } deriving (Eq, Generic, Typeable, Read, Show) @@ -173,5 +179,10 @@ emptyInstalledPackageInfo haddockInterfaces = [], haddockHTMLs = [], pkgRoot = Nothing, - libVisibility = LibraryVisibilityPrivate + libVisibility = LibraryVisibilityPrivate, + pkgVanillaLib = True, + pkgSharedLib = True, + pkgDynExe = True, + pkgProfLib = True, + pkgProfExe = True } diff --git a/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/FieldGrammar.hs b/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/FieldGrammar.hs index f176ea01187..cfbafa41947 100644 --- a/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/FieldGrammar.hs +++ b/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/FieldGrammar.hs @@ -121,6 +121,11 @@ ipiFieldGrammar = mkInstalledPackageInfo <@> monoidalFieldAla "haddock-interfaces" (alaList' FSep FilePathNT) L.haddockInterfaces <@> monoidalFieldAla "haddock-html" (alaList' FSep FilePathNT) L.haddockHTMLs <@> optionalFieldAla "pkgroot" FilePathNT L.pkgRoot + <@> booleanFieldDef "pkg-vanilla-lib" L.pkgVanillaLib True + <@> booleanFieldDef "pkg-shared-lib" L.pkgSharedLib True + <@> booleanFieldDef "pkg-dyn-exe" L.pkgDynExe True + <@> booleanFieldDef "pkg-prof-lib" L.pkgProfLib True + <@> booleanFieldDef "pkg-prof-exe" L.pkgProfExe True where mkInstalledPackageInfo _ Basic {..} = InstalledPackageInfo -- _basicPkgName is not used diff --git a/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/Lens.hs b/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/Lens.hs index 9d1df886370..8bd6e6a2a88 100644 --- a/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/Lens.hs +++ b/Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/Lens.hs @@ -196,3 +196,23 @@ libVisibility :: Lens' InstalledPackageInfo LibraryVisibility libVisibility f s = fmap (\x -> s { T.libVisibility = x }) (f (T.libVisibility s)) {-# INLINE libVisibility #-} +pkgVanillaLib :: Lens' InstalledPackageInfo Bool +pkgVanillaLib f s = fmap (\x -> s { T.pkgVanillaLib = x }) (f (T.pkgVanillaLib s)) +{-# INLINE pkgVanillaLib #-} + +pkgSharedLib :: Lens' InstalledPackageInfo Bool +pkgSharedLib f s = fmap (\x -> s { T.pkgSharedLib = x }) (f (T.pkgSharedLib s)) +{-# INLINE pkgSharedLib #-} + +pkgDynExe :: Lens' InstalledPackageInfo Bool +pkgDynExe f s = fmap (\x -> s { T.pkgDynExe = x }) (f (T.pkgDynExe s)) +{-# INLINE pkgDynExe #-} + +pkgProfLib :: Lens' InstalledPackageInfo Bool +pkgProfLib f s = fmap (\x -> s { T.pkgProfLib = x }) (f (T.pkgProfLib s)) +{-# INLINE pkgProfLib #-} + +pkgProfExe :: Lens' InstalledPackageInfo Bool +pkgProfExe f s = fmap (\x -> s { T.pkgProfExe = x }) (f (T.pkgProfExe s)) +{-# INLINE pkgProfExe #-} + diff --git a/Cabal/src/Distribution/Simple/Register.hs b/Cabal/src/Distribution/Simple/Register.hs index 4a8faaeeeb3..c4abbe73d71 100644 --- a/Cabal/src/Distribution/Simple/Register.hs +++ b/Cabal/src/Distribution/Simple/Register.hs @@ -448,7 +448,12 @@ generalInstalledPackageInfo adjustRelIncDirs pkg abi_hash lib lbi clbi installDi IPI.haddockInterfaces = [haddockdir installDirs haddockName pkg], IPI.haddockHTMLs = [htmldir installDirs], IPI.pkgRoot = Nothing, - IPI.libVisibility = libVisibility lib + IPI.libVisibility = libVisibility lib, + IPI.pkgVanillaLib = withVanillaLib lbi, + IPI.pkgSharedLib = withProfLib lbi, + IPI.pkgDynExe = withSharedLib lbi, + IPI.pkgProfLib = withStaticLib lbi, + IPI.pkgProfExe = withDynExe lbi } where ghc84 = case compilerId $ compiler lbi of diff --git a/cabal-install-solver/cabal-install-solver.cabal b/cabal-install-solver/cabal-install-solver.cabal index 47970ccd0ab..04fdf66570c 100644 --- a/cabal-install-solver/cabal-install-solver.cabal +++ b/cabal-install-solver/cabal-install-solver.cabal @@ -81,6 +81,7 @@ library Distribution.Solver.Modular.Var Distribution.Solver.Modular.Version Distribution.Solver.Modular.WeightedPSQ + Distribution.Solver.Types.ArtifactSelection Distribution.Solver.Types.ComponentDeps Distribution.Solver.Types.ConstraintSource Distribution.Solver.Types.DependencyResolver diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs index 5d196f4fd9f..96c823e64ab 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs @@ -33,6 +33,7 @@ import qualified Distribution.Solver.Modular.PSQ as P import Distribution.Solver.Modular.Tree import qualified Distribution.Solver.Modular.WeightedPSQ as W +import Distribution.Solver.Types.ArtifactSelection import Distribution.Solver.Types.ComponentDeps import Distribution.Solver.Types.PackagePath import Distribution.Solver.Types.Settings @@ -61,18 +62,18 @@ type LinkingState = M.Map (PN, I) [PackagePath] -- -- We also adjust the map of overall goals, and keep track of the -- reverse dependencies of each of the goals. -extendOpen :: QPN -> [FlaggedDep QPN] -> BuildState -> BuildState -extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs +extendOpen :: QPN -> [FlaggedDep QPN] -> ArtifactSelection -> BuildState -> BuildState +extendOpen qpn' gs arts s@(BS { rdeps = gs', open = o' }) = go gs' o' gs where go :: RevDepMap -> [OpenGoal] -> [FlaggedDep QPN] -> BuildState go g o [] = s { rdeps = g, open = o } go g o ((Flagged fn@(FN qpn _) fInfo t f) : ngs) = - go g (FlagGoal fn fInfo t f (flagGR qpn) : o) ngs + go g (FlagGoal fn fInfo t f arts (flagGR qpn) : o) ngs -- Note: for 'Flagged' goals, we always insert, so later additions win. -- This is important, because in general, if a goal is inserted twice, -- the later addition will have better dependency information. go g o ((Stanza sn@(SN qpn _) t) : ngs) = - go g (StanzaGoal sn t (flagGR qpn) : o) ngs + go g (StanzaGoal sn t arts (flagGR qpn) : o) ngs go g o ((Simple (LDep dr (Dep (PkgComponent qpn _) _)) c) : ngs) | qpn == qpn' = -- We currently only add a self-dependency to the graph if it is @@ -84,7 +85,7 @@ extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs ComponentSetup -> go (M.adjust (addIfAbsent (ComponentSetup, qpn')) qpn g) o ngs _ -> go g o ngs | qpn `M.member` g = go (M.adjust (addIfAbsent (c, qpn')) qpn g) o ngs - | otherwise = go (M.insert qpn [(c, qpn')] g) (PkgGoal qpn (DependencyGoal dr) : o) ngs + | otherwise = go (M.insert qpn [(c, qpn')] g) (PkgGoal qpn arts (DependencyGoal dr) : o) ngs -- code above is correct; insert/adjust have different arg order go g o ((Simple (LDep _dr (Ext _ext )) _) : ngs) = go g o ngs go g o ((Simple (LDep _dr (Lang _lang))_) : ngs) = go g o ngs @@ -100,9 +101,9 @@ extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs -- | Given the current scope, qualify all the package names in the given set of -- dependencies and then extend the set of open goals accordingly. -scopedExtendOpen :: QPN -> FlaggedDeps PN -> FlagInfo -> +scopedExtendOpen :: QPN -> FlaggedDeps PN -> FlagInfo -> ArtifactSelection -> BuildState -> BuildState -scopedExtendOpen qpn fdeps fdefs s = extendOpen qpn gs s +scopedExtendOpen qpn fdeps fdefs arts s = extendOpen qpn gs arts s where -- Qualify all package names qfdeps = qualifyDeps (qualifyOptions s) qpn fdeps @@ -117,9 +118,10 @@ scopedExtendOpen qpn fdeps fdefs s = extendOpen qpn gs s -- | Datatype that encodes what to build next data BuildType = - Goals -- ^ build a goal choice node - | OneGoal OpenGoal -- ^ build a node for this goal - | Instance QPN PInfo -- ^ build a tree for a concrete instance + Goals -- ^ build a goal choice node + | OneGoal OpenGoal -- ^ build a node for this goal + | Instance QPN PInfo -- ^ build a tree for a concrete instance + | FailSeed ConflictSet FailReason -- ^ an error occurred while we had access to the package info build :: Linker BuildState -> Tree () QGoalReason build = ana go @@ -143,22 +145,28 @@ addChildren bs@(BS { rdeps = rdm, open = gs, next = Goals }) -- -- For a package, we look up the instances available in the global info, -- and then handle each instance in turn. -addChildren bs@(BS { rdeps = rdm, index = idx, next = OneGoal (PkgGoal qpn@(Q _ pn) gr) }) = +addChildren bs@(BS { rdeps = rdm, index = idx, next = OneGoal (PkgGoal qpn@(Q _ pn) requiredArts gr) }) = case M.lookup pn idx of - Nothing -> FailF - (varToConflictSet (P qpn) `CS.union` goalReasonToConflictSetWithConflict qpn gr) - UnknownPackage + Nothing -> FailF cs UnknownPackage Just pis -> PChoiceF qpn rdm gr (W.fromList (L.map (\ (i, info) -> - ([], POption i Nothing, bs { next = Instance qpn info })) + ([], POption i Nothing, infoBs info)) (M.toList pis))) -- TODO: data structure conversion is rather ugly here + where + infoBs info = bs { next = validateArts (getArts info) $ Instance qpn info } + getArts (PInfo _ _ _ _ arts) = arts + validateArts arts withSuccess + | requiredArts `artsSubsetOf` arts = withSuccess + | otherwise = FailSeed cs (rs arts) + cs = varToConflictSet (P qpn) `CS.union` goalReasonToConflictSetWithConflict qpn gr + rs arts = MissingArtifacts $ requiredArts `artsDifference` arts -- For a flag, we create only two subtrees, and we create them in the order -- that is indicated by the flag default. -addChildren bs@(BS { rdeps = rdm, next = OneGoal (FlagGoal qfn@(FN qpn _) (FInfo b m w) t f gr) }) = +addChildren bs@(BS { rdeps = rdm, next = OneGoal (FlagGoal qfn@(FN qpn _) (FInfo b m w) t f arts gr) }) = FChoiceF qfn rdm gr weak m b (W.fromList - [([if b then 0 else 1], True, (extendOpen qpn t bs) { next = Goals }), - ([if b then 1 else 0], False, (extendOpen qpn f bs) { next = Goals })]) + [([if b then 0 else 1], True, (extendOpen qpn t arts bs) { next = Goals }), + ([if b then 1 else 0], False, (extendOpen qpn f arts bs) { next = Goals })]) where trivial = L.null t && L.null f weak = WeakOrTrivial $ unWeakOrTrivial w || trivial @@ -168,10 +176,10 @@ addChildren bs@(BS { rdeps = rdm, next = OneGoal (FlagGoal qfn@(FN qpn _) (FInfo -- the stanza by replacing the False branch with failure) or preferences -- (try enabling the stanza if possible by moving the True branch first). -addChildren bs@(BS { rdeps = rdm, next = OneGoal (StanzaGoal qsn@(SN qpn _) t gr) }) = +addChildren bs@(BS { rdeps = rdm, next = OneGoal (StanzaGoal qsn@(SN qpn _) t arts gr) }) = SChoiceF qsn rdm gr trivial (W.fromList [([0], False, bs { next = Goals }), - ([1], True, (extendOpen qpn t bs) { next = Goals })]) + ([1], True, (extendOpen qpn t arts bs) { next = Goals })]) where trivial = WeakOrTrivial (L.null t) @@ -179,10 +187,15 @@ addChildren bs@(BS { rdeps = rdm, next = OneGoal (StanzaGoal qsn@(SN qpn _) t gr -- and furthermore we update the set of goals. -- -- TODO: We could inline this above. -addChildren bs@(BS { next = Instance qpn (PInfo fdeps _ fdefs _) }) = - addChildren ((scopedExtendOpen qpn fdeps fdefs bs) +addChildren bs@(BS { next = Instance qpn (PInfo fdeps _ fdefs _ arts) }) = + addChildren ((scopedExtendOpen qpn fdeps fdefs arts bs) { next = Goals }) +-- While building the tree, we detected a failure from information we had while +-- we were aware of the package info. +addChildren (BS { next = FailSeed cs fr }) = + FailF cs fr + {------------------------------------------------------------------------------- Add linking -------------------------------------------------------------------------------} @@ -260,7 +273,7 @@ buildTree idx (IndependentGoals ind) igs = , linkingState = M.empty } where - topLevelGoal qpn = PkgGoal qpn UserGoal + topLevelGoal qpn = PkgGoal qpn noOuts UserGoal qpns | ind = L.map makeIndependent igs | otherwise = L.map (Q (PackagePath DefaultNamespace QualToplevel)) igs @@ -271,16 +284,16 @@ buildTree idx (IndependentGoals ind) igs = -- | Information needed about a dependency before it is converted into a Goal. data OpenGoal = - FlagGoal (FN QPN) FInfo (FlaggedDeps QPN) (FlaggedDeps QPN) QGoalReason - | StanzaGoal (SN QPN) (FlaggedDeps QPN) QGoalReason - | PkgGoal QPN QGoalReason + FlagGoal (FN QPN) FInfo (FlaggedDeps QPN) (FlaggedDeps QPN) ArtifactSelection QGoalReason + | StanzaGoal (SN QPN) (FlaggedDeps QPN) ArtifactSelection QGoalReason + | PkgGoal QPN ArtifactSelection QGoalReason -- | Closes a goal, i.e., removes all the extraneous information that we -- need only during the build phase. close :: OpenGoal -> Goal QPN -close (FlagGoal qfn _ _ _ gr) = Goal (F qfn) gr -close (StanzaGoal qsn _ gr) = Goal (S qsn) gr -close (PkgGoal qpn gr) = Goal (P qpn) gr +close (FlagGoal qfn _ _ _ _ gr) = Goal (F qfn) gr +close (StanzaGoal qsn _ _ gr) = Goal (S qsn) gr +close (PkgGoal qpn _ gr) = Goal (P qpn) gr {------------------------------------------------------------------------------- Auxiliary diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs index 90038a28f5c..0a379969edd 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs @@ -269,7 +269,7 @@ exploreLog mbj enableBj fineGrainedConflicts (CountConflicts countConflicts) idx -- to be merged with the previous one. couldResolveConflicts :: QPN -> POption -> S.Set CS.Conflict -> Maybe ConflictSet couldResolveConflicts currentQPN@(Q _ pn) (POption i@(I v _) _) conflicts = - let (PInfo deps _ _ _) = idx M.! pn M.! i + let (PInfo deps _ _ _ _) = idx M.! pn M.! i qdeps = qualifyDeps (defaultQualifyOptions idx) currentQPN deps couldBeResolved :: CS.Conflict -> Maybe ConflictSet diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Index.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Index.hs index 2f28d12de85..77aae2876ea 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Index.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Index.hs @@ -18,6 +18,7 @@ import Distribution.Solver.Modular.Dependency import Distribution.Solver.Modular.Flag import Distribution.Solver.Modular.Package import Distribution.Solver.Modular.Tree +import Distribution.Solver.Types.ArtifactSelection -- | An index contains information about package instances. This is a nested -- dictionary. Package names are mapped to instances, which in turn is mapped @@ -36,6 +37,7 @@ data PInfo = PInfo (FlaggedDeps PN) (Map ExposedComponent ComponentInfo) FlagInfo (Maybe FailReason) + ArtifactSelection -- Which artifacts are available? (sdists have all.) -- | Info associated with each library and executable in a package instance. data ComponentInfo = ComponentInfo { @@ -64,7 +66,7 @@ defaultQualifyOptions idx = QO { | -- Find all versions of base .. Just is <- [M.lookup base idx] -- .. which are installed .. - , (I _ver (Inst _), PInfo deps _comps _flagNfo _fr) <- M.toList is + , (I _ver (Inst _), PInfo deps _comps _flagNfo _fr _arts) <- M.toList is -- .. and flatten all their dependencies .. , (LDep _ (Dep (PkgComponent dep _) _ci), _comp) <- flattenFlaggedDeps deps ] diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs b/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs index 72d0b8193e3..7f5e2381e32 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs @@ -25,6 +25,8 @@ import Distribution.PackageDescription.Configuration import qualified Distribution.Simple.PackageIndex as SI import Distribution.System +import Distribution.Solver.Types.ArtifactSelection + ( ArtifactSelection(..), allArtifacts, staticOutsOnly, dynOutsOnly ) import Distribution.Solver.Types.ComponentDeps ( Component(..), componentNameToComponent ) import Distribution.Solver.Types.Flag @@ -75,8 +77,8 @@ convIPI' (ShadowPkgs sip) idx = where -- shadowing is recorded in the package info - shadow (pn, i, PInfo fdeps comps fds _) - | sip = (pn, i, PInfo fdeps comps fds (Just Shadowed)) + shadow (pn, i, PInfo fdeps comps fds _ arts) + | sip = (pn, i, PInfo fdeps comps fds (Just Shadowed) arts) shadow x = x -- | Extract/recover the package ID from an installed package info, and convert it to a solver's I. @@ -90,8 +92,8 @@ convId ipi = (pn, I ver $ Inst $ IPI.installedUnitId ipi) convIP :: SI.InstalledPackageIndex -> IPI.InstalledPackageInfo -> (PN, I, PInfo) convIP idx ipi = case traverse (convIPId (DependencyReason pn M.empty S.empty) comp idx) (IPI.depends ipi) of - Left u -> (pn, i, PInfo [] M.empty M.empty (Just (Broken u))) - Right fds -> (pn, i, PInfo fds components M.empty Nothing) + Left u -> (pn, i, PInfo [] M.empty M.empty (Just (Broken u)) mempty) + Right fds -> (pn, i, PInfo fds components M.empty Nothing (ipiToAS ipi)) where -- TODO: Handle sub-libraries and visibility. components = @@ -151,6 +153,16 @@ convIPId dr comp idx ipid = -- NB: something we pick up from the -- InstalledPackageIndex is NEVER an executable +-- | Extract the ArtifactSelection representing which artifacts are available +-- in this installed package. +ipiToAS :: IPI.InstalledPackageInfo -> ArtifactSelection +ipiToAS ipi = mconcat [statics, dynamics] + where + statics :: ArtifactSelection + statics = if any ($ ipi) [IPI.pkgVanillaLib] then staticOutsOnly else mempty + dynamics :: ArtifactSelection + dynamics = if any ($ ipi) [IPI.pkgSharedLib, IPI.pkgDynExe] then dynOutsOnly else mempty + -- | Convert a cabal-install source package index to the simpler, -- more uniform index format of the solver. convSPI' :: OS -> Arch -> CompilerInfo -> Map PN [LabeledPackageConstraint] @@ -238,7 +250,7 @@ convGPD os arch cinfo constraints strfl solveExes pn isPrivate LibraryVisibilityPrivate = True isPrivate LibraryVisibilityPublic = False - in PInfo flagged_deps components fds fr + in PInfo flagged_deps components fds fr allArtifacts -- | Applies the given predicate (for example, testing buildability or -- visibility) to the given component and environment. Values are combined with diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Linking.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Linking.hs index a348aa247b6..3e6346c3801 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Linking.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Linking.hs @@ -98,9 +98,9 @@ validateLinking index = (`runReader` initVS) . go goP :: QPN -> POption -> Validate (Tree d c) -> Validate (Tree d c) goP qpn@(Q _pp pn) opt@(POption i _) r = do vs <- ask - let PInfo deps _ _ _ = vsIndex vs ! pn ! i - qdeps = qualifyDeps (vsQualifyOptions vs) qpn deps - newSaved = M.insert qpn qdeps (vsSaved vs) + let PInfo deps _ _ _ _ = vsIndex vs ! pn ! i + qdeps = qualifyDeps (vsQualifyOptions vs) qpn deps + newSaved = M.insert qpn qdeps (vsSaved vs) case execUpdateState (pickPOption qpn opt qdeps) vs of Left (cs, err) -> return $ Fail cs (DependenciesNotLinked err) Right vs' -> local (const vs' { vsSaved = newSaved }) r @@ -347,7 +347,7 @@ verifyLinkGroup lg = -- if a constructor is added to the datatype we won't notice it here Just i -> do vs <- get - let PInfo _deps _exes finfo _ = vsIndex vs ! lgPackage lg ! i + let PInfo _deps _exes finfo _ _ = vsIndex vs ! lgPackage lg ! i -- TODO: arts can be ignored here, right? flags = M.keys finfo stanzas = [TestStanzas, BenchStanzas] forM_ flags $ \fn -> do diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index eade1c3a1a0..70f36d036aa 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -244,6 +244,7 @@ showFR _ MultipleInstances = " (multiple instances)" showFR c (DependenciesNotLinked msg) = " (dependencies not linked: " ++ msg ++ "; conflict set: " ++ showConflictSet c ++ ")" showFR c CyclicDependencies = " (cyclic dependencies; conflict set: " ++ showConflictSet c ++ ")" showFR _ (UnsupportedSpecVer ver) = " (unsupported spec-version " ++ prettyShow ver ++ ")" +showFR _ (MissingArtifacts arts) = " (missing build artifacts: " ++ prettyShow arts ++ ")" -- The following are internal failures. They should not occur. In the -- interest of not crashing unnecessarily, we still just print an error -- message though. diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Tree.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Tree.hs index 039da4b41b0..194f26e81be 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Tree.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Tree.hs @@ -30,6 +30,7 @@ import Distribution.Solver.Modular.PSQ (PSQ) import Distribution.Solver.Modular.Version import Distribution.Solver.Modular.WeightedPSQ (WeightedPSQ) import qualified Distribution.Solver.Modular.WeightedPSQ as W +import Distribution.Solver.Types.ArtifactSelection import Distribution.Solver.Types.ConstraintSource import Distribution.Solver.Types.Flag import Distribution.Solver.Types.PackagePath @@ -129,6 +130,7 @@ data FailReason = UnsupportedExtension Extension | DependenciesNotLinked String | CyclicDependencies | UnsupportedSpecVer Ver + | MissingArtifacts ArtifactSelection deriving (Eq, Show) -- | Information about a dependency involved in a conflict, for error messages. diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Validate.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Validate.hs index 41a170f1429..1ace3b319dd 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Validate.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Validate.hs @@ -209,7 +209,7 @@ validate = go rComps <- asks requiredComponents qo <- asks qualifyOptions -- obtain dependencies and index-dictated exclusions introduced by the choice - let (PInfo deps comps _ mfr) = idx ! pn ! i + let (PInfo deps comps _ mfr _) = idx ! pn ! i -- qualify the deps in the current scope let qdeps = qualifyDeps qo qpn deps -- the new active constraints are given by the instance we have chosen, diff --git a/cabal-install-solver/src/Distribution/Solver/Types/ArtifactSelection.hs b/cabal-install-solver/src/Distribution/Solver/Types/ArtifactSelection.hs new file mode 100644 index 00000000000..fe2d593ec4f --- /dev/null +++ b/cabal-install-solver/src/Distribution/Solver/Types/ArtifactSelection.hs @@ -0,0 +1,70 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +-- | Provide a type for categorizing artifact requirements. +module Distribution.Solver.Types.ArtifactSelection + ( ArtifactSelection(..) + , ArtifactKind(..) + , allArtifacts + , dynOutsOnly + , staticOutsOnly + , noOuts + , unArtifactSelection + , artsSubsetOf + , artsDifference + ) where + +import Distribution.Solver.Compat.Prelude +import Prelude () + +import qualified Data.Set as S + +import Distribution.Pretty ( Pretty(pretty) ) +import qualified Text.PrettyPrint as PP + +-- | A type for specifying which artifacts are available to be required. +newtype ArtifactSelection = ArtifactSelection (S.Set ArtifactKind) + deriving (Eq, Show, Generic, Semigroup, Monoid) + +instance Pretty ArtifactSelection where + pretty arts + | arts == allArtifacts = PP.text "all artifacts" + | arts == dynOutsOnly = PP.text "dynamic artifacts" + | arts == staticOutsOnly = PP.text "static artifacts" + | arts == noOuts = PP.text "no output artifacts" + | otherwise = PP.text "unknown artifacts" + +-- | Specific kinds of artifacts. +data ArtifactKind + = DynOuts -- ^ Exclude static outputs. + | StaticOuts -- ^ Exclude dynamic outputs. + deriving (Eq, Show, Generic, Ord) + +-- | ArtifactSelection alias: e.g. dynamic and static interface files. +allArtifacts :: ArtifactSelection +allArtifacts = ArtifactSelection $ S.fromList [DynOuts, StaticOuts] + +-- | ArtifactSelection alias: exclude static outputs. +dynOutsOnly :: ArtifactSelection +dynOutsOnly = ArtifactSelection $ S.fromList [DynOuts] + +-- | ArtifactSelection alias: exclude static outputs. +staticOutsOnly :: ArtifactSelection +staticOutsOnly = ArtifactSelection $ S.fromList [StaticOuts] + +-- | ArtifactSelection alias: exclude all artifacts. +noOuts :: ArtifactSelection +noOuts = ArtifactSelection $ S.fromList [] + +-- | Obtain the set of artifact kinds included in this artifact selection. +unArtifactSelection :: ArtifactSelection -> S.Set ArtifactKind +unArtifactSelection (ArtifactSelection set) = set + +-- | Is a selection a subset of another? +artsSubsetOf :: ArtifactSelection -> ArtifactSelection -> Bool +artsSubsetOf = S.isSubsetOf `on` unArtifactSelection + +-- | Return artifacts in the first set not present in the second set. +artsDifference :: ArtifactSelection -> ArtifactSelection -> ArtifactSelection +artsDifference (ArtifactSelection a) (ArtifactSelection b) = + ArtifactSelection $ a `S.difference` b diff --git a/cabal-install/src/Distribution/Client/Configure.hs b/cabal-install/src/Distribution/Client/Configure.hs index 2cbe16096a4..8c65c3d019f 100644 --- a/cabal-install/src/Distribution/Client/Configure.hs +++ b/cabal-install/src/Distribution/Client/Configure.hs @@ -154,6 +154,9 @@ configure verbosity packageDBs repoCtxt comp platform progdb (fromFlagOrDefault (useDistPref defaultSetupScriptOptions) (configDistPref configFlags)) + (fromFlagOrDefault + (setupConfigDynamicDeps defaultSetupScriptOptions) + (configDynExe configFlags)) (chooseCabalVersion configExFlags (flagToMaybe (configCabalVersion configExFlags))) @@ -167,6 +170,7 @@ configureSetupScript :: PackageDBStack -> Platform -> ProgramDb -> FilePath + -> Bool -> VersionRange -> Maybe Lock -> Bool @@ -178,6 +182,7 @@ configureSetupScript packageDBs platform progdb distPref + dynExe cabalVersion lock forceExternal @@ -209,6 +214,7 @@ configureSetupScript packageDBs , useDependenciesExclusive = not defaultSetupDeps && isJust explicitSetupDeps , useVersionMacros = not defaultSetupDeps && isJust explicitSetupDeps , isInteractive = False + , setupConfigDynamicDeps = dynExe } where -- When we are compiling a legacy setup script without an explicit diff --git a/cabal-install/src/Distribution/Client/Install.hs b/cabal-install/src/Distribution/Client/Install.hs index d099ec4dfff..3660924450a 100644 --- a/cabal-install/src/Distribution/Client/Install.hs +++ b/cabal-install/src/Distribution/Client/Install.hs @@ -1059,6 +1059,7 @@ performInstallations verbosity platform progdb distPref + (fromFlagOrDefault (setupConfigDynamicDeps defaultSetupScriptOptions) $ configDynExe configFlags) (chooseCabalVersion configExFlags (libVersion miscOptions)) (Just lock) parallelInstall diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs index 9a311dfcf3b..7b173d5bc00 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs @@ -732,16 +732,16 @@ convertToLegacySharedConfig } configExFlags = ConfigExFlags { - configCabalVersion = projectConfigCabalVersion, - configAppend = mempty, - configBackup = mempty, - configExConstraints = projectConfigConstraints, - configPreferences = projectConfigPreferences, - configSolver = projectConfigSolver, - configAllowOlder = projectConfigAllowOlder, - configAllowNewer = projectConfigAllowNewer, + configCabalVersion = projectConfigCabalVersion, + configAppend = mempty, + configBackup = mempty, + configExConstraints = projectConfigConstraints, + configPreferences = projectConfigPreferences, + configSolver = projectConfigSolver, + configAllowOlder = projectConfigAllowOlder, + configAllowNewer = projectConfigAllowNewer, configWriteGhcEnvironmentFilesPolicy - = projectConfigWriteGhcEnvironmentFilesPolicy + = projectConfigWriteGhcEnvironmentFilesPolicy } installFlags = InstallFlags { diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 952f68c2952..cb7ddd494a7 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2114,7 +2114,7 @@ shouldBeLocal (SpecificSourcePackage pkg) = case srcpkgSource pkg of _ -> Nothing -- Used to determine which packages are affected by local package configuration --- flags like ‘--enable-shared enable-executable-dynamic --disable-library-vanilla’. +-- flags like ‘--enable-shared --enable-executable-dynamic --disable-library-vanilla’. isInLocal :: [PackageVersionConstraint] -> PackageSpecifier (SourcePackage (PackageLocation loc)) -> Maybe PackageId isInLocal _ NamedPackage{} = Nothing isInLocal _extraPackages (SpecificSourcePackage pkg) = case srcpkgSource pkg of @@ -3414,7 +3414,8 @@ setupHsScriptOptions (ReadyPackage elab@ElaboratedConfiguredPackage{..}) useWin32CleanHack = False, --TODO: [required eventually] forceExternalSetupMethod = isParallelBuild, setupCacheLock = Just cacheLock, - isInteractive = False + isInteractive = False, + setupConfigDynamicDeps = elabDynExe } diff --git a/cabal-install/src/Distribution/Client/SetupWrapper.hs b/cabal-install/src/Distribution/Client/SetupWrapper.hs index 1ac82efcbd7..5dc5fb2822b 100644 --- a/cabal-install/src/Distribution/Client/SetupWrapper.hs +++ b/cabal-install/src/Distribution/Client/SetupWrapper.hs @@ -71,7 +71,7 @@ import Distribution.Simple.BuildPaths import Distribution.Simple.Command ( CommandUI(..), commandShowOptions ) import Distribution.Simple.Program.GHC - ( GhcMode(..), GhcOptions(..), renderGhcOptions ) + ( GhcMode(..), GhcDynLinkMode(..), GhcOptions(..), renderGhcOptions ) import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.PackageIndex (InstalledPackageIndex) import qualified Distribution.InstalledPackageInfo as IPI @@ -249,7 +249,13 @@ data SetupScriptOptions = SetupScriptOptions { -- | Is the task we are going to run an interactive foreground task, -- or an non-interactive background task? Based on this flag we -- decide whether or not to delegate ctrl+c to the spawned task - isInteractive :: Bool + isInteractive :: Bool, + + -- Also track build output artifact configuration. + + -- | Pass `-dynamic` to `ghc` so dependencies are dynamically rather than + -- statically linked. + setupConfigDynamicDeps :: Bool } defaultSetupScriptOptions :: SetupScriptOptions @@ -272,7 +278,8 @@ defaultSetupScriptOptions = SetupScriptOptions { useWin32CleanHack = False, forceExternalSetupMethod = False, setupCacheLock = Nothing, - isInteractive = False + isInteractive = False, + setupConfigDynamicDeps = False } workingDir :: SetupScriptOptions -> FilePath @@ -840,6 +847,9 @@ getExternalSetupMethod verbosity options pkg bt = do -- --ghc-option=-v instead! ghcOptVerbosity = Flag (min verbosity normal) , ghcOptMode = Flag GhcModeMake + , ghcOptDynLinkMode = case setupConfigDynamicDeps options'' of + True -> Flag GhcDynamicOnly + False -> Flag GhcStaticOnly , ghcOptInputFiles = toNubListR [setupHs] , ghcOptOutputFile = Flag setupProgFile , ghcOptObjDir = Flag setupDir From 0114ec9ac2b3fb68a69b36a9c70482e9c4486366 Mon Sep 17 00:00:00 2001 From: Byron Johnson Date: Thu, 28 Jul 2022 21:06:24 -0600 Subject: [PATCH 4/4] Add a PackageTest test for missing static files. This demonstrates how cabal-install can fail when it ignores which build artifacts are provided by a package, e.g. static and/or dynamic files. Recent changes add to the InstalledPackageInfo record so that installed packages are more explicit in what is available, so that cabal-install is better able to know when it should rebuild from source, when installed packages were not built from config flags to build the required artifacts. --- .../LinkerOptions/DynDeps/cabal.out | 55 +++++++ .../LinkerOptions/DynDeps/cabal.test.hs | 154 ++++++++++++++++++ .../LinkerOptions/DynDeps/depender/.gitignore | 1 + .../LinkerOptions/DynDeps/depender/Main.hs | 7 + .../DynDeps/depender/cabal.project.in | 4 + .../DynDeps/depender/depender.cabal | 9 + .../LinkerOptions/DynDeps/dynamic/Dynamic.hs | 10 ++ .../DynDeps/dynamic/cabal.project | 1 + .../DynDeps/dynamic/dynamic.cabal | 10 ++ 9 files changed, 251 insertions(+) create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.out create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.test.hs create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/.gitignore create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/Main.hs create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/cabal.project.in create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/depender.cabal create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/Dynamic.hs create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/cabal.project create mode 100644 cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/dynamic.cabal diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.out b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.out new file mode 100644 index 00000000000..c22b8341b28 --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.out @@ -0,0 +1,55 @@ +# cabal v2-configure +# cabal v2-build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - dynamic-1.0 (lib) (first run) +Configuring library for dynamic-1.0.. +Preprocessing library for dynamic-1.0.. +Building library for dynamic-1.0.. +# cabal v2-install +Wrote tarball sdist to /cabal.dist/work/./dynamic/dist/sdist/dynamic-1.0.tar.gz +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - dynamic-1.0 (lib) (requires build) +Configuring library for dynamic-1.0.. +Preprocessing library for dynamic-1.0.. +Building library for dynamic-1.0.. +Installing library in +# cabal v2-sdist +Wrote tarball sdist to /cabal.dist/work/dist/sdist/dynamic-1.0.tar.gz +# cabal v2-configure +# cabal v2-build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - dynamic-1.0 (lib) (requires build) + - depender-1.0 (exe:depender) (first run) +Configuring library for dynamic-1.0.. +Preprocessing library for dynamic-1.0.. +Building library for dynamic-1.0.. +Installing library in +Configuring executable 'depender' for depender-1.0.. +Preprocessing executable 'depender' for depender-1.0.. +Building executable 'depender' for depender-1.0.. +# cabal v2-install +Wrote tarball sdist to /cabal.dist/work/./depender/dist/sdist/depender-1.0.tar.gz +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - dynamic-1.0 (lib) (requires build) + - depender-1.0 (exe:depender) (requires build) +Configuring library for dynamic-1.0.. +Preprocessing library for dynamic-1.0.. +Building library for dynamic-1.0.. +Installing library in +Configuring executable 'depender' for depender-1.0.. +Preprocessing executable 'depender' for depender-1.0.. +Building executable 'depender' for depender-1.0.. +Installing executable depender in +Warning: The directory /cabal.dist/home/.cabal/store/ghc-/incoming/new-/cabal.dist/home/.cabal/store/ghc-/-/bin is not in the system search path. +Warning: installdir is not defined. Set it in your cabal config file or use --installdir=. Using default installdir: "/cabal.dist/home/.cabal/bin" +Symlinking 'depender' to '/cabal.dist/home/.cabal/bin/depender' +# depender +Dynamic's number is 3. diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.test.hs b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.test.hs new file mode 100644 index 00000000000..88a6b759e94 --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/cabal.test.hs @@ -0,0 +1,154 @@ +import Test.Cabal.Prelude + +-- This test shows how 2022-07-28 Archlinux Haskell with a standard ghc and +-- cabal-install fails to build e.g. even attoparsec. Essentially, the system +-- packages strip away static libraries and files, and build with only dynamic +-- files. +-- +-- (ghc-static provides its own custom packagedb location, in e.g. +-- /usr/lib/static-package.conf.d rather than /usr/lib/package.conf.d, which +-- cabal and ghc doesn't know about unless you add it with --package-db. But +-- the haskell-* libraries build with flags like +-- "--enable-shared --enable-executabledynamic --disable-library-vanilla".) +-- +-- Then a vanilla cabal build will see these packages are installed, but when +-- it's trying to build with a "ghc" that has "-static", itthinks the packages +-- installs provide the files, but whereas it would compile if only with +-- "-dynamic", it fails for "-static" with errors like the following: +-- +-- > [1 of 1] Compiling Main ( Main.hs, ../setup.dist/work/depender/dist/build/depender/depender-tmp/Main.o ) +-- > +-- > Main.hs:3:1: error: +-- > Could not find module `Dynamic' +-- > There are files missing in the `dynamic-1.0' package, +-- > try running 'ghc-pkg check'. +-- > Use -v (or `:set -v` in ghci) to see a list of the files searched for. +-- > | +-- > | import qualified Dynamic (number) +-- > | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +-- +-- (A workaround to the system "haskell-*" packages lacking static libraries +-- (ghc-static provides some, though) without a fixed solver is to use stack +-- for everything.) + +import Data.Version +import System.Directory +import System.FilePath + +-- Simulate the above scenario but in our framework (without a cabal with the +-- project-local build flags fix, the test should still pass but not pass the +-- correct dynamic vs static flags through, so on these old cabals this test +-- would pass where it should fail because dynamic is being built with static +-- options enabled too; however, with a new enough cabal but an old GHC, the +-- build artifacts won't be threaded through the IPIs, so it should still fail +-- with an older GHC). +main = do + cabalTest $ do + -- Skip for < GHC 9.6; perhaps 9.6 will depend on a Cabal-syntax that + -- provides the new IPI fields. + skipUnlessGhcVersion ">= 9.6" + env <- getTestEnv + let + dynamicArgs = + [ + "--enable-shared", + "--enable-executable-dynamic", + "--disable-library-vanilla", + "--disable-static", + "--disable-executable-static" + ] + staticArgs = + [ + "--enable-static" + ] + + -- Preprocess configuration. + (sdistDir, sdistRepoDir) <- sdistDirs + projectFilePath <- configureDepender env sdistRepoDir + packageDbPath <- guessPackageDbPath + + -- Now test. + let noBackup = ["--disable-backup"] + withDirectory "dynamic" $ do + -- Use 'dynamicArgs' here. + cabal "v2-configure" $ [] ++ dynamicArgs ++ noBackup + cabal "v2-build" [] + cabal "v2-install" $ ["--lib"] ++ dynamicArgs + cabal "v2-sdist" ["-o", sdistRepoDir, "--builddir", sdistDir] + withDirectory "depender" $ do + -- depender knows of the source package and the installed package. + -- The installed package should only have dynamic files (.dyn_hi, + -- .so), but not any static files (.a, .hi). New ghc-pkg IPI file + -- fields track these, so with a new GHC, a new cabal-install + -- should reject the installed package while building the tree + -- (reason: missing build artifacts) and instead choose the sdist + -- (source package) so that it can figure out its own configuration + -- flags. + -- + -- (For instance, if you comment out the sdist references so that we + -- only see the installed package, you should see an error message + -- like this:) + -- > Error: cabal: Could not resolve dependencies: + -- > [__0] trying: depender-1.0 (user goal) + -- > [__1] next goal: dynamic (dependency of depender) + -- > [__1] rejecting: dynamic-1.0/installed-19c7c1e50b8f1e56115c4f668dfdadd7114fc2c7dad267c2df43028892cc0ff5 (missing build artifacts: static artifacts) + -- > [__1] fail (backjumping, conflict set: depender, dynamic) + -- > After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: depender (3), dynamic (2) + + -- Use 'staticArgs' here. + let projectFileArgs = ["--project-file=" ++ projectFilePath] + let packageDbArgs = ["--package-db=" ++ packageDbPath] + cabal "v2-configure" $ [] ++ projectFileArgs ++ staticArgs ++ noBackup + cabal "v2-build" $ [] ++ projectFileArgs ++ packageDbArgs + + -- Optional: check the output. + cabal "v2-install" $ [] ++ projectFileArgs ++ staticArgs + ran <- runCabalInstalledExe' "depender" [] + assertOutputContains "Dynamic's number is 3." ran + where + sdistDirs = do + env <- getTestEnv + let distDir = testDistDir env + let sdistDir = distDir "dynamic-dist" + let sdistRepoDir = distDir "sdist" + return (sdistDir, sdistRepoDir) + + guessPackageDbPath :: TestM FilePath + guessPackageDbPath = do + env <- getTestEnv + tryProgramVersion <- programVersion <$> requireProgramM ghcProgram + let convertVersion = makeVersion . versionNumbers + ver <- maybe (error "guessPackageDbPath: unknown GHC version") return $ convertVersion <$> tryProgramVersion + liftIO . canonicalizePath $ testCabalDir env "store" "ghc-" ++ (showVersion ver) "package.db" + + -- The purpose of this is to let ‘depender’ know of a *source repo* + -- containing an sdist for ‘dynamic’. (The other part, the IPI, is + -- known through ‘--package-db=’.) + -- + -- Use as e.g. + -- > sed -nEe 's/\{SDIST\}/…path…to…sdist…dir…/g; p' < cabal.project.in > cabal.project + writeProjectFile = writeFile + + -- Set up cabal project file (get its path). + configureDepender env sdistRepoDir = do + projectFilePath <- return $ testWorkDir env "cabal.project.depender" + let + dependerProjectFile :: String + dependerProjectFile = unlines $ + [ + "packages: ./../depender/*.cabal", + "", + "repository my-local-repository", + " url: file+noindex://" ++ sdistRepoDir ++ "#shared-cache" + ] + liftIO $ writeProjectFile projectFilePath dependerProjectFile + + return projectFilePath + + -- Like 'runInstalledExe'' but with a fixed path. + runCabalInstalledExe' :: String -> [String] -> TestM Result + runCabalInstalledExe' exe_name args = do + env <- getTestEnv + defaultRecordMode RecordAll $ do + recordHeader [exe_name] + runM (testCabalDir env "bin" exe_name) args Nothing diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/.gitignore b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/.gitignore new file mode 100644 index 00000000000..79360221e2d --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/.gitignore @@ -0,0 +1 @@ +/cabal.project diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/Main.hs b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/Main.hs new file mode 100644 index 00000000000..e52d310f48e --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/Main.hs @@ -0,0 +1,7 @@ +module Main where + +import qualified Dynamic (number) + +main :: IO () +main = do + putStrLn $ "Dynamic's number is " ++ (show Dynamic.number) ++ "." diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/cabal.project.in b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/cabal.project.in new file mode 100644 index 00000000000..128937b421e --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/cabal.project.in @@ -0,0 +1,4 @@ +packages: ./*.cabal + +repository my-local-repository + url: file+noindex://{SDIST_PATH}#shared-cache diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/depender.cabal b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/depender.cabal new file mode 100644 index 00000000000..1081e29415e --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/depender/depender.cabal @@ -0,0 +1,9 @@ +cabal-version: >= 1.10 +name: depender +version: 1.0 +build-type: Simple + +executable depender + build-depends: dynamic, base + default-language: Haskell2010 + main-is: Main.hs diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/Dynamic.hs b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/Dynamic.hs new file mode 100644 index 00000000000..9d4113f47ad --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/Dynamic.hs @@ -0,0 +1,10 @@ +module Dynamic where + +simple :: (a -> b -> c) -> b -> a -> c +simple f = \a b -> f b a + +name :: String +name = "Dynamic" + +number :: Integer +number = 3 diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/cabal.project b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/cabal.project new file mode 100644 index 00000000000..e85216b030d --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/cabal.project @@ -0,0 +1 @@ +packages: ./*.cabal diff --git a/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/dynamic.cabal b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/dynamic.cabal new file mode 100644 index 00000000000..45295460c13 --- /dev/null +++ b/cabal-testsuite/PackageTests/LinkerOptions/DynDeps/dynamic/dynamic.cabal @@ -0,0 +1,10 @@ +cabal-version: >= 1.10 +name: dynamic +version: 1.0 +build-type: Simple + +library + default-language: Haskell2010 + build-depends: base + exposed-modules: + Dynamic