From 586e93a053052fd41a3a44622ac501f9bae35b55 Mon Sep 17 00:00:00 2001 From: sheaf Date: Thu, 27 Aug 2026 13:30:52 +0200 Subject: [PATCH] Allow minor Cabal version bumps without recompiling We now don't store the specific Cabal library version used to provide the Setup CLI, which allows us to re-use an install plan made with another Cabal library version that is still compatible (e.g. across a minor version bump). Fixes #11416 --- .../src/Distribution/Client/Configure.hs | 20 +-- .../Client/ProjectBuilding/UnpackedPackage.hs | 15 +- .../Distribution/Client/ProjectPlanOutput.hs | 2 +- .../Distribution/Client/ProjectPlanning.hs | 33 ++--- .../Client/ProjectPlanning/SetupPolicy.hs | 34 ++--- .../Client/ProjectPlanning/Types.hs | 75 +++++++--- .../src/Distribution/Client/SetupWrapper.hs | 135 +++++++++++------- changelog.d/T11416.md | 20 +++ 8 files changed, 208 insertions(+), 126 deletions(-) create mode 100644 changelog.d/T11416.md diff --git a/cabal-install/src/Distribution/Client/Configure.hs b/cabal-install/src/Distribution/Client/Configure.hs index 6a4e3d0016c..9b4e31177b7 100644 --- a/cabal-install/src/Distribution/Client/Configure.hs +++ b/cabal-install/src/Distribution/Client/Configure.hs @@ -42,7 +42,8 @@ import Distribution.Client.Setup , filterConfigureFlags ) import Distribution.Client.SetupWrapper - ( SetupRunnerArgs (NotInLibrary) + ( SetupDependencies (..) + , SetupRunnerArgs (NotInLibrary) , SetupScriptOptions (..) , defaultSetupScriptOptions , setupWrapper @@ -288,14 +289,15 @@ configureSetupScript , useExtraEnvOverrides = [] , setupCacheLock = lock , useWin32CleanHack = False - , -- If we have explicit setup dependencies, list them; otherwise, we give - -- the empty list of dependencies; ideally, we would fix the version of - -- Cabal here, so that we no longer need the special case for that in - -- `compileSetupExecutable` in `externalSetupMethod`, but we don't yet - -- know the version of Cabal at this point, but only find this there. - -- Therefore, for now, we just leave this blank. - useDependencies = fromMaybe [] explicitSetupDeps - , useDependenciesExclusive = not defaultSetupDeps && isJust explicitSetupDeps + , -- If the @custom-setup@ stanza has an explicit list of dependencies, + -- use those exclusively. Otherwise, the list is not exhaustive, and + -- we have a special case in 'SetupWrapper' to go looking for a Cabal + -- library. We don't yet know its version, so for now we leave the list + -- of implicit dependencies blank. + useSetupDependencies = + case explicitSetupDeps of + Just deps | not defaultSetupDeps -> ExplicitSetupDeps deps + _ -> ImplicitSetupDeps (fromMaybe [] explicitSetupDeps) , useVersionMacros = not defaultSetupDeps && isJust explicitSetupDeps , isInteractive = False , isMainLibOrExeComponent = True diff --git a/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs b/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs index 30d235bbcde..9f6b202830c 100644 --- a/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs +++ b/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs @@ -667,7 +667,7 @@ buildInplaceUnpackedPackage Simple -> listSimple Hooks -> listSdist `ifNullThen` listSimple _ - | elabSetupScriptCliVersion pkg >= mkVersion [1, 17] -> + | setupCliVersion (elabSetupScriptCliVersion pkg) >= mkVersion [1, 17] -> listSdist `ifNullThen` listSimple | otherwise -> listSimple @@ -689,18 +689,19 @@ buildInplaceUnpackedPackage buildResult whenReconfigure :: IO InLibraryLBI -> IO InLibraryLBI - whenReconfigure action = + whenReconfigure configure_action = case buildStatus of - BuildStatusConfigure _ -> action + BuildStatusConfigure _ -> configure_action _ -> do -- We are skipping reconfiguration, so we recover the -- 'LocalBuildInfo' persisted by the previous 'configure'. mbOldLBI <- Cabal.tryGetPersistBuildConfig (Just srcdir) builddir case mbOldLBI of - -- #11942: if the previous LocalBuildInfo was written by an - -- external Setup.hs with an incompatible Cabal library version, - -- then we must continue to use the external setup method. - Left Cabal.ConfigStateFileBadVersion{} -> return NotInLibraryNoLBI + -- #11942: the previous 'LocalBuildInfo' was written by an + -- incompatible Cabal (either an external Setup.hs or a different + -- version of cabal-install). We can't use it at all, so we must + -- re-configure. + Left Cabal.ConfigStateFileBadVersion{} -> configure_action -- Other errors reflect genuine problems: re-throw them. Left err -> throwIO err Right lbi_wo_programs -> do diff --git a/cabal-install/src/Distribution/Client/ProjectPlanOutput.hs b/cabal-install/src/Distribution/Client/ProjectPlanOutput.hs index 82e90976013..7ba35583ca0 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanOutput.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanOutput.hs @@ -220,7 +220,7 @@ encodePlanAsJson distDirLayout elaboratedInstallPlan elaboratedSharedConfig = -- with an old lib:Cabal version. buildInfoFileLocation :: J.Pair buildInfoFileLocation - | elabSetupScriptCliVersion elab < mkVersion [3, 7, 0, 0] = + | setupCliVersion (elabSetupScriptCliVersion elab) < mkVersion [3, 7, 0, 0] = "build-info" J..= J.Null | otherwise = "build-info" J..= J.String (getSymbolicPath $ buildInfoPref $ makeSymbolicPath dist_dir) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index ef290ff1599..4e1001bd658 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -3917,25 +3917,24 @@ setupHsScriptOptions builddir cacheLock = SetupScriptOptions - { useCabalVersion = thisVersion elabSetupScriptCliVersion - , useCabalSpecVersion = + { useCabalVersion = thisVersion cliVersion + , -- NB: for build-type: Hooks we don't want to commit to a Cabal + -- version, as all that should matter is the version of Cabal-hooks, + -- not of Cabal. + useCabalSpecVersion = if PD.buildType elabPkgDescription == PD.Hooks - then -- NB: we don't want to commit to a Cabal version here: - -- - all that should matter for Hooks build-type is the - -- version of Cabal-hooks, not of Cabal, - -- - if we commit to a Cabal version, the logic in - Nothing - else Just elabSetupScriptCliVersion + then Nothing + else Just cliVersion , useCompiler = Just pkgConfigCompiler , usePlatform = Just pkgConfigPlatform , usePackageDB = elabSetupPackageDBStack , usePackageIndex = Nothing - , useDependencies = - [ (uid, srcid) - | (ConfiguredId srcid (Just (CLibName LMainLibName)) uid, _) <- - elabSetupDependencies elab - ] - , useDependenciesExclusive = True + , useSetupDependencies = + ExplicitSetupDeps + [ (uid, srcid) + | (ConfiguredId srcid (Just (CLibName LMainLibName)) uid, _) <- + elabSetupDependencies elab + ] , useVersionMacros = elabSetupScriptStyle == SetupCustomExplicitDeps , useProgramDb = pkgConfigCompilerProgs , useDistPref = builddir @@ -3960,6 +3959,8 @@ setupHsScriptOptions -- everything else is not a main lib or exe component ElabComponent _ -> False } + where + cliVersion = setupCliVersion elabSetupScriptCliVersion -- | To be used for the input for elaborateInstallPlan. -- @@ -4241,7 +4242,7 @@ setupHsBuildFlags par_strat elab _ common = , buildProgramArgs = mempty -- unused, set at configure time , buildNumJobs = mempty -- TODO: [nice to have] sometimes want to use toFlag (Just numBuildJobs), , buildUseSemaphore = - if elabSetupScriptCliVersion elab >= mkVersion [3, 11, 0, 0] + if setupCliVersion (elabSetupScriptCliVersion elab) >= mkVersion [3, 11, 0, 0] then -- Cabal 3.11 is the first version that supports parallelism semaphores par_strat else mempty @@ -4250,7 +4251,7 @@ setupHsBuildFlags par_strat elab _ common = setupHsBuildArgs :: ElaboratedConfiguredPackage -> [String] setupHsBuildArgs elab@(ElaboratedConfiguredPackage{elabPkgOrComp = ElabPackage _}) -- Fix for #3335, don't pass build arguments if it's not supported - | elabSetupScriptCliVersion elab >= mkVersion [1, 17] = + | setupCliVersion (elabSetupScriptCliVersion elab) >= mkVersion [1, 17] = map (showComponentTarget (packageId elab)) (elabBuildTargets elab) | otherwise = [] diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning/SetupPolicy.hs b/cabal-install/src/Distribution/Client/ProjectPlanning/SetupPolicy.hs index e3a6595ecdb..1ac2e31b4ea 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning/SetupPolicy.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning/SetupPolicy.hs @@ -7,20 +7,8 @@ -- functions for both phases together here so at least you can see all of it -- in one place. -- --- There are four major cases for Setup.hs handling: --- --- 1. @build-type@ Custom with a @custom-setup@ section --- 2. @build-type@ Custom without a @custom-setup@ section --- 3. @build-type@ not Custom with @cabal-version > $our-cabal-version@ --- 4. @build-type@ not Custom with @cabal-version <= $our-cabal-version@ --- --- It's also worth noting that packages specifying @cabal-version: >= 1.23@ --- or later that have @build-type@ Custom will always have a @custom-setup@ --- section. Therefore in case 2, the specified @cabal-version@ will always be --- less than 1.23. --- --- In cases 1 and 2 we obviously have to build an external Setup.hs script, --- while in case 4 we can use the internal library API. +-- There are four major cases for Setup.hs handling; see the documentation of +-- 'SetupScriptStyle' in Distribution.Client.ProjectPlanning.Types. -- -- @since 3.12.0.0 module Distribution.Client.ProjectPlanning.SetupPolicy @@ -34,7 +22,10 @@ where import Distribution.Client.Compat.Prelude import Prelude () -import Distribution.Client.ProjectPlanning.Types (SetupScriptStyle (..)) +import Distribution.Client.ProjectPlanning.Types + ( SetupCliVersion (..) + , SetupScriptStyle (..) + ) import Distribution.Client.SolverInstallPlan (SolverPlanPackage) import Distribution.Solver.Types.ComponentDeps (ComponentDeps) import qualified Distribution.Solver.Types.ComponentDeps as CD @@ -192,24 +183,25 @@ packageSetupScriptSpecVersion -> PackageDescription -> Graph.Graph NonSetupLibDepSolverPlanPackage -> ComponentDeps [SolverId] - -> Version + -> SetupCliVersion -- We're going to be using the internal Cabal library, so the spec version of -- that is simply the version of the Cabal library that cabal-install has been -- built with. packageSetupScriptSpecVersion SetupNonCustomInternalLib _ _ _ = - cabalVersion + InternalCabalLib -- If we happen to be building the Cabal lib itself then because that -- bootstraps itself then we use the version of the lib we're building. packageSetupScriptSpecVersion SetupCustomImplicitDeps pkg _ _ | packageName pkg == cabalPkgname = - packageVersion pkg + ExternalCabalLib $ packageVersion pkg -- In all other cases we have a look at what version of the Cabal lib the -- solver picked. Or if it didn't depend on Cabal at all (which is very rare) -- then we look at the .cabal file to see what spec version it declares. packageSetupScriptSpecVersion _ pkg libDepGraph deps = - case find ((cabalPkgname ==) . packageName) setupLibDeps of - Just dep -> packageVersion dep - Nothing -> mkVersion (cabalSpecMinimumLibraryVersion (specVersion pkg)) + ExternalCabalLib $ + case find ((cabalPkgname ==) . packageName) setupLibDeps of + Just dep -> packageVersion dep + Nothing -> mkVersion (cabalSpecMinimumLibraryVersion (specVersion pkg)) where setupLibDeps = maybe [] (map packageId) (Graph.closure libDepGraph (CD.setupDeps deps)) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs b/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs index 89fc0e54bdf..15d91232441 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs @@ -57,6 +57,8 @@ module Distribution.Client.ProjectPlanning.Types -- * Setup script , SetupScriptStyle (..) + , SetupCliVersion (..) + , setupCliVersion ) where import Distribution.Client.Compat.Prelude @@ -102,7 +104,7 @@ import Distribution.Simple.Setup , ReplOptions , TestShowDetails ) -import Distribution.Simple.Utils (ordNub) +import Distribution.Simple.Utils (cabalVersion, ordNub) import Distribution.Solver.Types.ComponentDeps (ComponentDeps) import qualified Distribution.Solver.Types.ComponentDeps as CD import Distribution.Solver.Types.OptionalStanza @@ -313,15 +315,10 @@ data ElaboratedConfiguredPackage = ElaboratedConfiguredPackage elabSetupScriptStyle :: SetupScriptStyle -- ^ One of four modes for how we build and interact with the Setup.hs - -- script, based on whether it's a build-type Custom, with or without - -- explicit deps and the cabal spec version the .cabal file needs. - , elabSetupScriptCliVersion :: Version - -- ^ The version of the Cabal command line interface that we are using - -- for this package. This is typically the version of the Cabal lib - -- that the Setup.hs is built against. - -- - -- TODO: We might want to turn this into a enum, - -- yet different enum than 'CabalSpecVersion'. + -- script, based on whether it's a build-type Custom or Hooks, with or + -- without explicit deps, and the cabal spec version the .cabal file needs. + , elabSetupScriptCliVersion :: SetupCliVersion + -- ^ The Cabal library version used to provide the Setup CLI. , -- Build time related: elabConfigureTargets :: [ComponentTarget] , elabBuildTargets :: [ComponentTarget] @@ -914,26 +911,62 @@ componentOptionalStanza _ = Nothing -- | There are four major cases for Setup.hs handling: -- --- 1. @build-type@ Custom with a @custom-setup@ section +-- 1. @build-type@ Custom or Hooks with a @custom-setup@ section -- 2. @build-type@ Custom without a @custom-setup@ section --- 3. @build-type@ not Custom with @cabal-version > $our-cabal-version@ --- 4. @build-type@ not Custom with @cabal-version <= $our-cabal-version@ +-- 3. @build-type@ neither Custom nor Hooks, with +-- @cabal-version > $our-cabal-version@ +-- 4. @build-type@ neither Custom nor Hooks, with +-- @cabal-version <= $our-cabal-version@ -- -- It's also worth noting that packages specifying @cabal-version: >= 1.23@ -- or later that have @build-type@ Custom will always have a @custom-setup@ -- section. Therefore in case 2, the specified @cabal-version@ will always be -- less than 1.23. -- --- In cases 1 and 2 we obviously have to build an external Setup.hs script, --- while in case 4 we can use the internal library API. In case 3 we also have --- to build an external Setup.hs script because the package needs a later --- Cabal lib version than we can support internally. +-- In cases 1 and 2 we obviously have to compile an external program: a +-- Setup.hs script for build-type Custom, and the hooks executable for +-- build-type Hooks (with a possible fallback to a Setup.hs). +-- In case 3 we also have to build an external Setup.hs script, because the +-- package needs a later Cabal lib version than we can support internally. +-- Only in case 4 can we use the internal library API alone. data SetupScriptStyle - = SetupCustomExplicitDeps - | SetupCustomImplicitDeps - | SetupNonCustomExternalLib - | SetupNonCustomInternalLib + = -- | @build-type: Custom@ (or @Hooks@) with explicit @setup-depends@ + SetupCustomExplicitDeps + | -- | @build-type: Custom@ without an explicit @setup-depends@ + SetupCustomImplicitDeps + | -- | Non-Custom/Hooks build-type, but we fall back to an external @Setup.hs@ + -- in order to satisfy Cabal version constraints. + SetupNonCustomExternalLib + | -- | Non-Custom/Hooks build type: Cabal provides the Setup.hs CLI internally. + SetupNonCustomInternalLib deriving (Eq, Show, Generic) instance Binary SetupScriptStyle instance Structured SetupScriptStyle + +-- | The version of the Cabal library used to provide the Setup CLI. +-- +-- The version corresponds to the 'SetupScriptStyle' we use: for +-- 'SetupNonCustomInternalLib' we use the Cabal library that @cabal-install@ was +-- built against, and for every other 'SetupScripStyle' we pick the version +-- chosen by the solver to compile the Setup script. +data SetupCliVersion + = -- | Use the Cabal library version that @cabal-install@ was linked against + -- to provide the Setup CLI. + InternalCabalLib -- NB: this very carefully __does not__ store a version number. + -- + -- This is because of #11416: the install plan is cached across @cabal-install@ + -- invocations, so we should not pin a Cabal library version which would + -- go stale when doing a minor @cabal-install@ upgrade. + | -- | Use the Cabal library version picked by the solver to provide + -- the Setup CLI. + ExternalCabalLib !Version + deriving (Eq, Show, Generic) + +instance Binary SetupCliVersion +instance Structured SetupCliVersion + +-- | The version of the Cabal library used to provide the Setup CLI. +setupCliVersion :: SetupCliVersion -> Version +setupCliVersion InternalCabalLib = cabalVersion +setupCliVersion (ExternalCabalLib version) = version diff --git a/cabal-install/src/Distribution/Client/SetupWrapper.hs b/cabal-install/src/Distribution/Client/SetupWrapper.hs index 69b9b2dc980..4feb5e1827b 100644 --- a/cabal-install/src/Distribution/Client/SetupWrapper.hs +++ b/cabal-install/src/Distribution/Client/SetupWrapper.hs @@ -31,6 +31,8 @@ module Distribution.Client.SetupWrapper , RightFlagsForPhase , setupWrapper , SetupScriptOptions (..) + , SetupDependencies (..) + , setupDependencies , defaultSetupScriptOptions , externalSetupMethod ) where @@ -292,17 +294,45 @@ data SetupMethod (kind :: SetupKind) where -- -- See also the discussion at https://github.com/haskell/cabal/pull/3094 +-- | The dependencies to use to compile an external @Setup@ executable. +data SetupDependencies + = -- | An exhaustive set of dependencies to compile the Setup script against. + -- + -- These are used __exclusively__, hiding every other package in the + -- package databases. + -- + -- Whether the @Cabal@ library is among these is the package's business: a + -- @custom-setup@ stanza may leave it out if its @Setup.hs@ does not need + -- it. + ExplicitSetupDeps [(ComponentId, PackageId)] + | -- | A non-exhaustive set of dependencies to compile the Setup script against. + -- + -- We keep all other packages in the package databases visible as well. + -- + -- We also go looking for a @Cabal@ library in the package databases, adding + -- it to this list of dependencies if it doesn't already contain one. + ImplicitSetupDeps [(ComponentId, PackageId)] + +-- | The dependencies to use to compile an external @Setup@ executable, whether +-- or not they are to be used exclusively. +-- +-- Prefer matching on 'SetupDependencies' where the distinction matters. +setupDependencies :: SetupDependencies -> [(ComponentId, PackageId)] +setupDependencies (ExplicitSetupDeps deps) = deps +setupDependencies (ImplicitSetupDeps deps) = deps + -- | @SetupScriptOptions@ are options used to configure and run 'Setup', as -- opposed to options given to the Cabal command at runtime. data SetupScriptOptions = SetupScriptOptions { useCabalVersion :: VersionRange - -- ^ The version of the Cabal library to use (if 'useDependenciesExclusive' - -- is not set). A suitable version of the Cabal library must be installed - -- (or for some build-types be the one cabal-install was built with). + -- ^ The acceptable Cabal library version range for this Setup script. -- - -- The version found also determines the version of the Cabal specification - -- that we us for talking to the Setup.hs, unless overridden by - -- 'useCabalSpecVersion'. + -- This determines: + -- - whether the Cabal library that cabal-install is linked against can + -- provide the Setup CLI, + -- - which Cabal library to go looking for in package databases when + -- 'useSetupDependencies' is 'ImplicitSetupDeps', which in turn decides + -- which Cabal specification we use to talk with the @Setup@ executable. , useCabalSpecVersion :: Maybe Version -- ^ This is the version of the Cabal specification that we believe that -- this package uses. This affects the semantics and in particular the @@ -310,9 +340,7 @@ data SetupScriptOptions = SetupScriptOptions -- -- This is similar to 'useCabalVersion' but instead of probing the system -- for a version of the /Cabal library/ you just say exactly which version - -- of the /spec/ we will use. Using this also avoids adding the Cabal - -- library as an additional dependency, so add it to 'useDependencies' - -- if needed. + -- of the /spec/ we will use. , useCompiler :: Maybe Compiler , usePlatform :: Maybe Platform , usePackageDB :: PackageDBStackCWD @@ -328,23 +356,9 @@ data SetupScriptOptions = SetupScriptOptions -- -- * @'Just' v@ means \"set the environment variable's value to @v@\". -- * 'Nothing' means \"unset the environment variable\". - , useDependencies :: [(ComponentId, PackageId)] - -- ^ List of dependencies to use when building Setup.hs. - , useDependenciesExclusive :: Bool - -- ^ Is the list of setup dependencies exclusive? - -- - -- When this is @False@, if we compile the Setup.hs script we do so with the - -- list in 'useDependencies' but all other packages in the environment are - -- also visible. A suitable version of @Cabal@ library (see - -- 'useCabalVersion') is also added to the list of dependencies, unless - -- 'useDependencies' already contains a Cabal dependency. - -- - -- When @True@, only the 'useDependencies' packages are used, with other - -- packages in the environment hidden. - -- - -- This feature is here to support the setup stanza in .cabal files that - -- specifies explicit (and exclusive) dependencies, as well as the old - -- style with no dependencies. + , useSetupDependencies :: SetupDependencies + -- ^ The dependencies to use when building Setup.hs, and whether they are to + -- be used exclusively. , useVersionMacros :: Bool -- ^ Should we build the Setup.hs with CPP version macros available? -- We turn this on when we have a setup stanza in .cabal that declares @@ -393,8 +407,7 @@ defaultSetupScriptOptions = , usePlatform = Nothing , usePackageDB = [GlobalPackageDB, UserPackageDB] , usePackageIndex = Nothing - , useDependencies = [] - , useDependenciesExclusive = False + , useSetupDependencies = ImplicitSetupDeps [] , useVersionMacros = False , useProgramDb = emptyProgramDb , useDistPref = defaultDistPref @@ -510,9 +523,13 @@ withSetupMethod verbosity options pkg buildType' allowInLibrary with Nothing -> False withExternalSetupMethod = do debug verbosity $ "Using external setup method with build-type " ++ show buildType' - debug verbosity $ - "Using explicit dependencies: " - ++ show (useDependenciesExclusive options) + debug verbosity $ case useSetupDependencies options of + ExplicitSetupDeps deps -> + "Using explicit dependencies: " + ++ show (map (prettyShow . snd) deps) + ImplicitSetupDeps deps -> + "Using implicit dependencies: " + ++ show (map (prettyShow . snd) deps) with <$> compileExternalExe verbosity options pkg buildType' WantSetup runSetupMethod :: WithCallStack (SetupMethod GeneralSetup -> SetupRunner UseGeneralSetup) @@ -915,14 +932,14 @@ compileExternalExe verbosity options pkg bt wantedExe = do platform = fromMaybe buildPlatform (usePlatform options) -- | Extract the Cabal library version from 'SetupScriptOptions' if it is --- already determined: either by the solver via 'useDependencies', or directly --- via 'useCabalSpecVersion' (used for build-type: Custom packages whose --- setup-depends does not include a transitive Cabal dependency). +-- already determined: either by the solver via 'useSetupDependencies', or +-- directly via 'useCabalSpecVersion' (used for build-type: Custom packages +-- whose setup-depends does not include a transitive Cabal dependency). cabalLibFromOptions :: SetupScriptOptions -> Maybe (Version, Maybe ComponentId) cabalLibFromOptions options = - case find (isCabalPkgId . snd) (useDependencies options) of + case find (isCabalPkgId . snd) (setupDependencies (useSetupDependencies options)) of Just (unitId, pkgId) -> Just (pkgVersion pkgId, Just unitId) Nothing -> case useCabalSpecVersion options of @@ -1260,18 +1277,16 @@ compileSetupX (\ipkgid -> [(ipkgid, cabalPkgid)]) maybeCabalLibInstalledPkgId - -- With 'useDependenciesExclusive' and Custom build type, - -- we enforce the deps specified, so only the given ones can be used. - -- Otherwise we add on a dep on the Cabal library - -- (unless 'useDependencies' already contains one). - selectedDeps - | (useDependenciesExclusive options' && (bt /= Hooks)) - -- NB: to compile build-type: Hooks packages, we need Cabal - -- in order to compile @main = defaultMainWithSetupHooks setupHooks@. - || any (isCabalPkgId . snd) (useDependencies options') - = useDependencies options' - | otherwise = - useDependencies options' ++ cabalDep + setupDeps = useSetupDependencies options' + hideAllPackages = case setupDeps of + ExplicitSetupDeps{} -> True + ImplicitSetupDeps{} -> False + + selectedDeps = case setupDeps of + ExplicitSetupDeps ds -> ds + ImplicitSetupDeps ds + | any (isCabalPkgId . snd) ds -> ds + | otherwise -> ds ++ cabalDep addRenaming (ipid, _) = -- Assert 'DefUnitId' invariant ( Backpack.DefiniteUnitId (unsafeMkDefUnitId (newSimpleUnitId ipid)) @@ -1295,8 +1310,8 @@ compileSetupX Hooks -> toNubListR [sameDirectory] _ -> mempty , ghcOptPackageDBs = pkgDbs - , ghcOptHideAllPackages = Flag (useDependenciesExclusive options') - , ghcOptCabal = Flag (useDependenciesExclusive options') + , ghcOptHideAllPackages = Flag hideAllPackages + , ghcOptCabal = Flag hideAllPackages , ghcOptPackages = toNubListR $ map addRenaming selectedDeps -- With 'useVersionMacros', use a version CPP macros .h file. , ghcOptCppIncludes = @@ -1313,6 +1328,19 @@ compileSetupX -- when compiling a simple Setup.hs file. , ghcOptExtensionMap = Map.fromList . Simple.compilerExtensions $ compiler } + -- Sanity check: if we generated Setup.hs ourselves (the non-Custom case), + -- make sure we provide a Cabal dependency when building it. + -- This allows us to report a custom message for #11416 instead of giving + -- the user a confusing GHC error. + when (hideAllPackages && bt /= Custom && not (any (isCabalPkgId . snd) selectedDeps)) $ + die' verbosity $ unwords + [ "internal error: no Cabal library is available to compile the" + , what + , "executable for the" + , prettyShow bt + , "package" + , prettyShow pkgId ++ "." + ] let ghcCmdLine = renderGhcOptions compiler platform ghcOptions when (useVersionMacros options') $ rewriteFileEx verbosity (i cppMacrosFile) $ @@ -1347,8 +1375,13 @@ isBasePkgId (PackageIdentifier pname _) = pname == mkPackageName "base" -- Hopefully we can get rid of all of this before long, simplifying this -- annoyingly complex module. -- --- The v1 code path corresponds to 'useDependencies' being unset --- (no pre-computed dependencies by the solver). +-- These functions locate a Cabal library by searching the package databases. +-- They are reached whenever the 'SetupScriptOptions' do not already determine +-- one, i.e. when no Cabal library appears in 'useSetupDependencies' and +-- 'useCabalSpecVersion' is unset. In practice that is the v1 code path, which +-- leaves 'useCabalSpecVersion' unset; note that it is not the same thing as +-- 'ImplicitSetupDeps', which v1 uses only for packages without an explicit +-- @custom-setup@ stanza. -- | **v1-only** -- diff --git a/changelog.d/T11416.md b/changelog.d/T11416.md new file mode 100644 index 00000000000..36097e13f1c --- /dev/null +++ b/changelog.d/T11416.md @@ -0,0 +1,20 @@ +--- +synopsis: Allow minor Cabal version bumps without recompiling +packages: [cabal-install] +prs: 12289 +issues: 11416 +--- + +Fix a bug in which changing the version of `cabal-install` without clearing +`dist-newstyle` could cause subsequent builds to fail with an error of the form: + +```txt +setup.hs:2:1: error: [GHC-87110] + Could not load module 'Distribution.Simple'. + It is a member of the hidden package 'Cabal-x.y.z'. + Perhaps you need to add 'Cabal' to the build-depends in your .cabal file. +``` + +A safeguard is also put in place to provide a more understandable error should +this ever arise again, instead of the nonsense GHC error above which provides a +non-actionable hint.