diff --git a/Cabal/src/Distribution/Simple/Program/Db.hs b/Cabal/src/Distribution/Simple/Program/Db.hs index 929b3dd6372..70c21bd9102 100644 --- a/Cabal/src/Distribution/Simple/Program/Db.hs +++ b/Cabal/src/Distribution/Simple/Program/Db.hs @@ -33,6 +33,7 @@ module Distribution.Simple.Program.Db -- ** Query and manipulate the program db , addKnownProgram , addKnownPrograms + , clearUnconfiguredPrograms , prependProgramSearchPath , prependProgramSearchPathNoLogging , lookupKnownProgram @@ -201,6 +202,14 @@ addKnownProgram prog = addKnownPrograms :: [Program] -> ProgramDb -> ProgramDb addKnownPrograms progs progdb = foldl' (flip addKnownProgram) progdb progs +-- | Drop all unconfigured programs from a 'ProgramDb', retaining only +-- configured programs, the search path, and environment overrides. +-- +-- This mirrors round-tripping via the @'Binary' 'ProgramDb'@ instance, which +-- drops unconfigured programs. +clearUnconfiguredPrograms :: ProgramDb -> ProgramDb +clearUnconfiguredPrograms progdb = progdb{unconfiguredProgs = Map.empty} + lookupKnownProgram :: String -> ProgramDb -> Maybe Program lookupKnownProgram name = fmap (\(p, _, _) -> p) . Map.lookup name . unconfiguredProgs @@ -258,8 +267,14 @@ prependProgramSearchPathNoLogging -> ProgramDb -> ProgramDb prependProgramSearchPathNoLogging extraPaths extraEnv db = - let db' = modifyProgramSearchPath (nub . (map ProgramSearchPathDir extraPaths ++)) db - db'' = db'{progOverrideEnv = extraEnv ++ progOverrideEnv db'} + let db' = + if null extraPaths + then db -- skip work if nothing to do + else modifyProgramSearchPath (nub . (map ProgramSearchPathDir extraPaths ++)) db + db'' = + if null extraEnv + then db' -- skip work if nothing to do + else db'{progOverrideEnv = extraEnv ++ progOverrideEnv db'} in db'' -- | User-specify this path. Basically override any path information diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 5ec48a249aa..4584bf9fe39 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -500,6 +500,16 @@ rebuildProjectConfig $ projectConfigProvenance projectConfig ] +-- | Configure the compiler. This results in a program database that contains +-- the **configured** compiler (which is stored in a cache) +-- and **unconfigured** related programs (cannot be cached, as unconfigured). +-- +-- This will be re-run when the compiler or @hc-pkg@ change, and when the +-- program search path or @extra-prog-path@ or @program-locations@ change. +-- +-- In the case of @GHC@, we configure @ghc@ and @ghc-pkg@, and provide +-- unconfigured attendant programs such as @hsc2hs@, @haddock@ and toolchain +-- programs such as @ar@, @ld@. See 'Distribution.Simple.GHC.configure'. configureCompiler :: Verbosity -> DistDirLayout @@ -564,11 +574,15 @@ configureCompiler monitorFiles (programsMonitorFiles progdb') return result - -- Now, **outside** of the caching logic of 'rerunIfChanged', add on - -- auxiliary unconfigured programs to the ProgramDb (e.g. hc-pkg, haddock, ar, ld...). + -- Now, **outside** of the caching logic of 'rerunIfChanged': + -- + -- 1. Call 'clearUnconfiguredPrograms' to ensure the consistency between + -- the first run (in-memory) and when deserialising from cache. + -- 2. Add on auxiliary unconfigured programs to the ProgramDb + -- (e.g. hsc2hs, haddock, ar, ld...). -- -- See Note [Caching the result of configuring the compiler] - finalProgDb <- liftIO $ Cabal.configCompilerProgDb verbosity hc hcProgDb hcPkg + finalProgDb <- liftIO $ Cabal.configCompilerProgDb verbosity hc (clearUnconfiguredPrograms hcProgDb) hcPkg return (hc, plat, finalProgDb) where hcFlavor = flagToMaybe projectConfigHcFlavor @@ -597,6 +611,9 @@ To solve this, we cache the ProgramDb containing the compiler (which will be a configured program, hence properly serialised/deserialised), and then re-compute any attendant unconfigured programs (such as hc-pkg, haddock or build tools such as ar, ld) using 'configCompilerProgDb'. +We also call 'clearUnconfiguredPrograms' on the ProgramDb returned by +'rerunIfChanged', so that the first-run (in-memory) result behaves the same as +the cache hit result: always drop unconfigured programs. Another idea would be to simply eagerly configure all unconfigured programs, as was originally attempted. But this doesn't work, for a couple of reasons: @@ -740,14 +757,14 @@ rebuildInstallPlan :: ProjectConfig -> (Compiler, Platform, ProgramDb) -> Rebuild () - phaseConfigurePrograms projectConfig (_, _, compilerprogdb) = do + phaseConfigurePrograms projectConfig (_, _, compilerProgDb) = do -- Users are allowed to specify program locations independently for -- each package (e.g. to use a particular version of a pre-processor -- for some packages). However they cannot do this for the compiler -- itself as that's just not going to work. So we check for this. liftIO $ checkBadPerPackageCompilerPaths - (configuredPrograms compilerprogdb) + (configuredPrograms compilerProgDb) (getMapMappend (projectConfigSpecificPackage projectConfig)) -- TODO: [required eventually] find/configure other programs that the @@ -891,7 +908,7 @@ rebuildInstallPlan , projectConfigSpecificPackage , projectConfigBuildOnly } - (compiler, platform, progdb) + (compiler, platform, compilerProgDb) pkgConfigDB solverPlan localPackages = do @@ -906,13 +923,16 @@ rebuildInstallPlan defaultInstallDirs <- liftIO $ userInstallDirTemplates compiler let installDirs = fmap Cabal.fromFlag $ (fmap Flag defaultInstallDirs) <> (projectConfigInstallDirs projectConfigShared) + -- Configure the compiler ProgramDb now (once for the entire project), + -- to avoid repeatedly doing this once per package. + configuredCompilerProgDb <- liftIO $ configureAllKnownPrograms verbosity compilerProgDb (elaboratedPlan, elaboratedShared) <- liftIO . runLogProgress verbosity $ elaborateInstallPlan verbosity platform compiler - progdb + configuredCompilerProgDb pkgConfigDB distDirLayout cabalStoreDirLayout @@ -1631,6 +1651,7 @@ elaborateInstallPlan -> Platform -> Compiler -> ProgramDb + -- ^ __Configured__ compiler program database (ghc, ghc-pkg, haddock, ld, etc) -> Maybe PkgConfigDb -> DistDirLayout -> StoreDirLayout @@ -1647,7 +1668,7 @@ elaborateInstallPlan verbosity platform compiler - compilerprogdb + compilerProgDb pkgConfigDB distDirLayout@DistDirLayout{..} storeDirLayout@StoreDirLayout{storePackageDBStack} @@ -1666,7 +1687,7 @@ elaborateInstallPlan ElaboratedSharedConfig { pkgConfigPlatform = platform , pkgConfigCompiler = compiler - , pkgConfigCompilerProgs = compilerprogdb + , pkgConfigCompilerProgs = compilerProgDb , pkgConfigReplOptions = mempty } @@ -2365,7 +2386,7 @@ elaborateInstallPlan okProfDyn = profilingDynamicSupportedOrUnknown compiler profExe = perPkgOptionFlag pkgid False packageConfigProf - elabBuildOptions = Cabal.adjustBuildOptions compiler compilerprogdb elabBuildOptionsRaw + elabBuildOptions = Cabal.adjustBuildOptions compiler compilerProgDb elabBuildOptionsRaw ( elabProfExeDetail , elabProfLibDetail @@ -2386,7 +2407,7 @@ elaborateInstallPlan elabProgramPaths = Map.fromList [ (programId prog, programPath prog) - | prog <- configuredPrograms compilerprogdb + | prog <- configuredPrograms compilerProgDb ] <> perPkgOptionMapLast pkgid packageConfigProgramPaths elabProgramArgs = @@ -2408,14 +2429,14 @@ elaborateInstallPlan (++) ( Map.fromList [ (programId prog, args) - | prog <- configuredPrograms compilerprogdb + | prog <- configuredPrograms compilerProgDb , let args = programOverrideArgs $ addHaddockIfDocumentationEnabled prog , not (null args) ] ) (perPkgOptionMapMappend pkgid packageConfigProgramArgs) elabProgramPathExtra = perPkgOptionNubList pkgid packageConfigProgramPathExtra - elabConfiguredPrograms = configuredPrograms compilerprogdb + elabConfiguredPrograms = configuredPrograms compilerProgDb elabConfigureScriptArgs = perPkgOptionList pkgid packageConfigConfigureArgs elabExtraLibDirs = perPkgOptionList pkgid packageConfigExtraLibDirs elabExtraLibDirsStatic = perPkgOptionList pkgid packageConfigExtraLibDirsStatic diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs b/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs index adbd8a85f5e..9a7134b04ee 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs @@ -188,9 +188,10 @@ data ElaboratedSharedConfig = ElaboratedSharedConfig { pkgConfigPlatform :: Platform , pkgConfigCompiler :: Compiler -- TODO: [code cleanup] replace with CompilerInfo , pkgConfigCompilerProgs :: ProgramDb - -- ^ The programs that the compiler configured (e.g. for GHC, the progs - -- ghc & ghc-pkg). Once constructed, only the 'configuredPrograms' are - -- used. + -- ^ All known programs configured once for the project: the compiler + -- (e.g. ghc & ghc-pkg) plus associated tools (hsc2hs, haddock, hpc, + -- runghc) and toolchain programs (ar, ld, strip). Once constructed, + -- only the 'configuredPrograms' are used. , pkgConfigReplOptions :: ReplOptions } deriving (Show, Generic) diff --git a/cabal-install/src/Distribution/Client/SetupWrapper.hs b/cabal-install/src/Distribution/Client/SetupWrapper.hs index 52f0a7774db..abbeb8ae426 100644 --- a/cabal-install/src/Distribution/Client/SetupWrapper.hs +++ b/cabal-install/src/Distribution/Client/SetupWrapper.hs @@ -612,8 +612,8 @@ setupWrapper -> IO (SetupRunnerRes setupSpec) setupWrapper verbosity options mpkg cmd getCommonFlags getFlags getExtraArgs wrapperArgs = do let allowInLibrary = case wrapperArgs of - NotInLibrary -> Don'tAllowInLibrary InLibraryArgs {} -> AllowInLibrary + NotInLibrary -> Don'tAllowInLibrary ASetup (setup :: Setup kind) <- getSetup verbosity options mpkg allowInLibrary let version = setupVersion setup flags <- getFlags version @@ -643,21 +643,30 @@ setupWrapper verbosity options mpkg cmd getCommonFlags getFlags getExtraArgs wra InLibraryArgs libArgs -> case libArgs of InLibraryConfigureArgs elabSharedConfig elabReadyPkg -> do - -- See (1)(a) in Note [Constructing the ProgramDb] + -- Start from the pre-configured compiler ProgramDb, augmented + -- with all builtin programs (restored as unconfigured). + -- This ensures: + -- (a) configureAllKnownPrograms inside configureFinal skips + -- compiler programs (already configured at project level), + -- (b) builtin preprocessors like alex and happy are present as + -- unconfigured programs, so configureFinal's + -- configureAllKnownPrograms can find them using the + -- per-package search path (respecting extra-prog-path). + -- See (1) in Note [Constructing the ProgramDb]. + + -- Apply per-package user-supplied program args/paths. + -- See (2)(a) in Note [Constructing the ProgramDb] baseProgDb <- + -- Use 'mkProgramDb' to pass user-supplied per-package + -- program options (--PROG-options=...). + mkProgramDb verbHandles flags + (restoreProgramDb builtinPrograms $ + pkgConfigCompilerProgs elabSharedConfig) + setupProgDb <- prependProgramSearchPath verbosity (useExtraPathEnv options) - (useExtraEnvOverrides options) =<< - mkProgramDb verbHandles flags -- Passes user-supplied arguments to e.g. GHC - (restoreProgramDb builtinPrograms $ - useProgramDb options) -- Recall that 'useProgramDb' is set to 'pkgConfigCompilerProgs' - -- See (2) in Note [Constructing the ProgramDb] - setupProgDb <- - configCompilerProgDb - verbosity - (pkgConfigCompiler elabSharedConfig) + (useExtraEnvOverrides options) baseProgDb - Nothing -- we use configProgramPaths instead lbi0 <- InLibrary.configure (InLibrary.libraryConfigureInputsFromElabPackage @@ -670,7 +679,7 @@ setupWrapper verbosity options mpkg cmd getCommonFlags getFlags getExtraArgs wra ) flags let progs0 = LBI.withPrograms lbi0 - -- See (1)(b) in Note [Constructing the ProgramDb] + -- See (2)(b) in Note [Constructing the ProgramDb] progs1 <- updatePathProgDb verbosity progs0 let lbi = @@ -715,7 +724,24 @@ includes the call to 'configCompilerEx'. To obtain a program database with all the required information, we do a few things: - (1) + (1) We retrieve the pre-configured compiler program database (typically + containing ghc, ghc-pkg, haddock, and toolchain programs such as ar, ld), + and restore all builtin programs (alex, happy, hsc2hs, ...) as unconfigured + entries on top of it. + + This serves two purposes: + + (a) The compiler programs (ghc, ghc-pkg, haddock, hsc2hs, ...) that were + already configured at the project level appear in 'configuredProgs'. + The 'configureAllKnownPrograms' call inside the Cabal per-package + 'configureFinal' skips them, saving redundant work. + + (b) Builtin preprocessors (e.g. alex, happy) are NOT configured at the + project level; restoring them here means the call to + 'configureAllKnownPrograms' in 'configureFinal' can find them using + the per-package search path (including 'extra-prog-path'). + + (2) (a) When building a package with internal build tools, we must ensure that these build tools are available in PATH, with appropriate environment variable overrides for their data directory. To do this, we call @@ -724,12 +750,6 @@ things: (b) Moreover, these programs must be available in the search paths for the compiler itself, in case they are run at compile-time (e.g. with a Template Haskell splice). We achieve this using 'updatePathProgDb'. - - (2) Given the compiler, we must compute the ProgramDb of programs that are - specified alongside the compiler, such as ghc-pkg, haddock, and toolchain - programs such as ar, ld. - - We do this using the function 'configCompilerProgDb'. -} -- ------------------------------------------------------------ diff --git a/changelog.d/conf-compiler-progs.md b/changelog.d/conf-compiler-progs.md new file mode 100644 index 00000000000..2f273c9c38b --- /dev/null +++ b/changelog.d/conf-compiler-progs.md @@ -0,0 +1,17 @@ +--- +synopsis: Pre-configure compiler program database +packages: [Cabal, cabal-install] +prs: 11768 +--- + +The compiler program database, containing `ghc`, `ghc-pkg`, `haddock` and various +toolchain programs (such as `ar`, `ld`) is now configured ahead of time within +`cabal-install`, so that we don't have to re-configure all of those programs +once for every package. + +See the pre-existing Note [Caching the result of configuring the compiler] in +Distribution.Client.ProjectPlanning and Note [Constructing the ProgramDb] +in Distribution.Client.SetupWrapper for additional details. + +This required a tiny change to `Cabal` to define and expose +`clearUnconfiguredPrograms :: ProgramDb -> ProgramDb` for use in `cabal-install`.