From 449fa90d3e472c0c6353ceadd82f805dc866fb32 Mon Sep 17 00:00:00 2001
From: Fendor <power.walross@gmail.com>
Date: Mon, 16 Aug 2021 11:35:39 +0200
Subject: [PATCH] Remove show-build-info command and generate build-info on
 build

Removes 'show-build-info' command from 'lib:Cabal' and replaces it
by generating build-info whenever a build happens.

Add flag '--dump-buildinfo' to signal the build step to dump
build information for the package/component we are currently building.
---
 .../src/Test/QuickCheck/Instances/Cabal.hs    |   8 +-
 .../Distribution/Utils/Structured.hs          |   2 +-
 .../src/Data/TreeDiff/Instances/Cabal.hs      |   2 +
 Cabal/Cabal.cabal                             |   1 +
 Cabal/src/Distribution/Simple.hs              |  32 ------
 Cabal/src/Distribution/Simple/Build.hs        |  52 ++++-----
 Cabal/src/Distribution/Simple/BuildPaths.hs   |   6 +-
 Cabal/src/Distribution/Simple/Setup.hs        | 103 ++++--------------
 .../src/Distribution/Simple/ShowBuildInfo.hs  |  76 +++++++------
 Cabal/src/Distribution/Types/DumpBuildInfo.hs |  15 +++
 .../src/Distribution/Client/Config.hs         |   1 +
 .../Client/ProjectConfig/Legacy.hs            |  42 +++++--
 .../Client/ProjectConfig/Types.hs             |   3 +-
 .../Distribution/Client/ProjectPlanning.hs    |   4 +
 .../Client/ProjectPlanning/Types.hs           |   4 +-
 .../src/Distribution/Client/Setup.hs          |   7 +-
 .../Distribution/Client/ProjectConfig.hs      |   8 +-
 17 files changed, 173 insertions(+), 193 deletions(-)
 create mode 100644 Cabal/src/Distribution/Types/DumpBuildInfo.hs

diff --git a/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs b/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs
index 3d471ea628..b2b6853c60 100644
--- a/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs
+++ b/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs
@@ -25,7 +25,7 @@ import Distribution.ModuleName
 import Distribution.Simple.Compiler                (DebugInfoLevel (..), OptimisationLevel (..), PackageDB (..), ProfDetailLevel (..), knownProfDetailLevels)
 import Distribution.Simple.Flag                    (Flag (..))
 import Distribution.Simple.InstallDirs
-import Distribution.Simple.Setup                   (HaddockTarget (..), TestShowDetails (..))
+import Distribution.Simple.Setup                   (HaddockTarget (..), TestShowDetails (..), DumpBuildInfo)
 import Distribution.SPDX
 import Distribution.System
 import Distribution.Types.Dependency
@@ -486,6 +486,12 @@ instance Arbitrary PackageDB where
                       , SpecificPackageDB <$> arbitraryShortToken
                       ]
 
+-------------------------------------------------------------------------------
+-- DumpBuildInfo
+-------------------------------------------------------------------------------
+
+instance Arbitrary DumpBuildInfo where
+    arbitrary = arbitraryBoundedEnum
 
 -------------------------------------------------------------------------------
 -- Helpers
diff --git a/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs b/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs
index 1d715af86e..9ad401b4b1 100644
--- a/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs
+++ b/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs
@@ -29,7 +29,7 @@ tests = testGroup "Distribution.Utils.Structured"
     , testCase "GenericPackageDescription" $
       md5Check (Proxy :: Proxy GenericPackageDescription) 0xa164cbe5092a1cd31da1f15358d1537a
     , testCase "LocalBuildInfo" $
-      md5Check (Proxy :: Proxy LocalBuildInfo) 0xac70971ea59d30aab7e4b6dafc9113d4
+      md5Check (Proxy :: Proxy LocalBuildInfo) 0x9ce83e4aec3b2fa6d7f999dbc32c2a33
 #endif
     ]
 
diff --git a/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs b/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs
index 40765a6764..98bb586d50 100644
--- a/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs
+++ b/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs
@@ -29,6 +29,7 @@ import Distribution.Simple.Setup                   (HaddockTarget, TestShowDetai
 import Distribution.System
 import Distribution.Types.AbiHash                  (AbiHash)
 import Distribution.Types.ComponentId              (ComponentId)
+import Distribution.Types.DumpBuildInfo            (DumpBuildInfo)
 import Distribution.Types.PackageVersionConstraint
 import Distribution.Types.UnitId                   (DefUnitId, UnitId)
 import Distribution.Utils.NubList                  (NubList)
@@ -74,6 +75,7 @@ instance ToExpr CompilerId
 instance ToExpr ComponentId
 instance ToExpr DebugInfoLevel
 instance ToExpr DefUnitId
+instance ToExpr DumpBuildInfo
 instance ToExpr ExeDependency
 instance ToExpr Executable
 instance ToExpr ExecutableScope
diff --git a/Cabal/Cabal.cabal b/Cabal/Cabal.cabal
index 8158d4640e..3991c48321 100644
--- a/Cabal/Cabal.cabal
+++ b/Cabal/Cabal.cabal
@@ -201,6 +201,7 @@ library
     Distribution.Types.ComponentInclude
     Distribution.Types.ConfVar
     Distribution.Types.Dependency
+    Distribution.Types.DumpBuildInfo
     Distribution.Types.ExeDependency
     Distribution.Types.LegacyExeDependency
     Distribution.Types.PkgconfigDependency
diff --git a/Cabal/src/Distribution/Simple.hs b/Cabal/src/Distribution/Simple.hs
index 1eb958a035..fe19558db1 100644
--- a/Cabal/src/Distribution/Simple.hs
+++ b/Cabal/src/Distribution/Simple.hs
@@ -104,7 +104,6 @@ import Distribution.Compat.Directory        (makeAbsolute)
 import Distribution.Compat.Environment      (getEnvironment)
 import Distribution.Compat.GetShortPathName (getShortPathName)
 
-import qualified Data.ByteString.Lazy as B
 import Data.List       (unionBy, (\\))
 
 import Distribution.PackageDescription.Parsec
@@ -179,7 +178,6 @@ defaultMainHelper hooks args = topHandler $ do
       [configureCommand progs `commandAddAction`
         \fs as -> configureAction hooks fs as >> return ()
       ,buildCommand     progs `commandAddAction` buildAction        hooks
-      ,showBuildInfoCommand progs `commandAddAction` showBuildInfoAction    hooks
       ,replCommand      progs `commandAddAction` replAction         hooks
       ,installCommand         `commandAddAction` installAction      hooks
       ,copyCommand            `commandAddAction` copyAction         hooks
@@ -264,36 +262,6 @@ buildAction hooks flags args = do
                (return lbi { withPrograms = progs })
                hooks flags' { buildArgs = args } args
 
-showBuildInfoAction :: UserHooks -> ShowBuildInfoFlags -> Args -> IO ()
-showBuildInfoAction hooks flags args = do
-  let buildFlags = buildInfoBuildFlags flags
-  distPref <- findDistPrefOrDefault (buildDistPref buildFlags)
-  let verbosity = fromFlag $ buildVerbosity buildFlags
-  lbi <- getBuildConfig hooks verbosity distPref
-  let buildFlags' =
-        buildFlags { buildDistPref = toFlag distPref
-                   , buildCabalFilePath = maybeToFlag (cabalFilePath lbi)
-                   }
-
-  progs <- reconfigurePrograms verbosity
-             (buildProgramPaths buildFlags')
-             (buildProgramArgs buildFlags')
-             (withPrograms lbi)
-
-  pbi <- preBuild hooks args buildFlags'
-  let lbi' = lbi { withPrograms = progs }
-      pkg_descr0 = localPkgDescr lbi'
-      pkg_descr = updatePackageDescription pbi pkg_descr0
-      -- TODO: Somehow don't ignore build hook?
-
-  buildInfoByteString <- showBuildInfo pkg_descr lbi' flags
-
-  case buildInfoOutputFile flags of
-    Nothing -> B.putStr buildInfoByteString
-    Just fp -> B.writeFile fp buildInfoByteString
-
-  postBuild hooks args buildFlags' pkg_descr lbi'
-
 replAction :: UserHooks -> ReplFlags -> Args -> IO ()
 replAction hooks flags args = do
   distPref <- findDistPrefOrDefault (replDistPref flags)
diff --git a/Cabal/src/Distribution/Simple/Build.hs b/Cabal/src/Distribution/Simple/Build.hs
index 7c5bd280d8..78bf79feae 100644
--- a/Cabal/src/Distribution/Simple/Build.hs
+++ b/Cabal/src/Distribution/Simple/Build.hs
@@ -19,7 +19,7 @@
 --
 
 module Distribution.Simple.Build (
-    build, showBuildInfo, repl,
+    build, repl,
     startInterpreter,
 
     initialBuildSteps,
@@ -87,11 +87,10 @@ import Distribution.Version (thisVersion)
 import Distribution.Compat.Graph (IsNode(..))
 
 import Control.Monad
-import Data.ByteString.Lazy (ByteString)
-import qualified Data.ByteString.Lazy.Char8 as LBS
 import qualified Data.Set as Set
+import qualified Data.ByteString.Lazy as LBS
 import System.FilePath ( (</>), (<.>), takeDirectory )
-import System.Directory ( getCurrentDirectory )
+import System.Directory ( getCurrentDirectory, removeFile, doesFileExist )
 
 -- -----------------------------------------------------------------------------
 -- |Build the libraries and executables in this package.
@@ -129,32 +128,33 @@ build pkg_descr lbi flags suffixes = do
     mb_ipi <- buildComponent verbosity (buildNumJobs flags) pkg_descr
                    lbi' suffixes comp clbi distPref
     return (maybe index (Index.insert `flip` index) mb_ipi)
+
+  when shouldDumpBuildInfo $ do
+    -- Changing this line might break consumers of the dumped build info.
+    -- Announce changes on mailing lists!
+    let activeTargets = allTargetsInBuildOrder' pkg_descr lbi
+    info verbosity $ "Dump build information for: "
+                  ++ intercalate ", "
+                      (map (showComponentName . componentLocalName . targetCLBI)
+                          activeTargets)
+    pwd <- getCurrentDirectory
+    let (warns, json) = mkBuildInfo pwd pkg_descr lbi flags activeTargets
+        buildInfoText = renderJson json
+    unless (null warns) $
+      warn verbosity $ "Encountered warnings while dumping build-info:\n"
+                    ++ unlines warns
+    LBS.writeFile (buildInfoPref distPref) buildInfoText
+
+  when (not shouldDumpBuildInfo) $ do
+    -- Remove existing build-info.json as it might be outdated now.
+    exists <- doesFileExist (buildInfoPref distPref)
+    when exists $ removeFile (buildInfoPref distPref)
+
   return ()
  where
   distPref  = fromFlag (buildDistPref flags)
   verbosity = fromFlag (buildVerbosity flags)
-
-
-showBuildInfo :: PackageDescription  -- ^ Mostly information from the .cabal file
-  -> LocalBuildInfo                  -- ^ Configuration information
-  -> ShowBuildInfoFlags              -- ^ Flags that the user passed to build
-  -> IO ByteString
-showBuildInfo pkg_descr lbi flags = do
-  let buildFlags = buildInfoBuildFlags flags
-      verbosity = fromFlag (buildVerbosity buildFlags)
-  targets <- readTargetInfos verbosity pkg_descr lbi (buildArgs buildFlags)
-  pwd <- getCurrentDirectory
-  let targetsToBuild = neededTargetsInBuildOrder' pkg_descr lbi (map nodeKey targets)
-      result
-        | fromFlag (buildInfoComponentsOnly flags) =
-            let components = map (mkComponentInfo pwd pkg_descr lbi . targetCLBI)
-                                 targetsToBuild
-              in LBS.unlines $ map renderJson components
-        | otherwise =
-            let json = mkBuildInfo pwd pkg_descr lbi buildFlags targetsToBuild
-              in renderJson json
-  return result
-
+  shouldDumpBuildInfo = fromFlagOrDefault NoDumpBuildInfo (configDumpBuildInfo (configFlags lbi)) == DumpBuildInfo
 
 repl     :: PackageDescription  -- ^ Mostly information from the .cabal file
          -> LocalBuildInfo      -- ^ Configuration information
diff --git a/Cabal/src/Distribution/Simple/BuildPaths.hs b/Cabal/src/Distribution/Simple/BuildPaths.hs
index 86df023663..733953747c 100644
--- a/Cabal/src/Distribution/Simple/BuildPaths.hs
+++ b/Cabal/src/Distribution/Simple/BuildPaths.hs
@@ -15,7 +15,7 @@
 
 module Distribution.Simple.BuildPaths (
     defaultDistPref, srcPref,
-    haddockDirName, hscolourPref, haddockPref,
+    buildInfoPref, haddockDirName, hscolourPref, haddockPref,
     autogenPackageModulesDir,
     autogenComponentModulesDir,
 
@@ -67,6 +67,10 @@ srcPref distPref = distPref </> "src"
 hscolourPref :: HaddockTarget -> FilePath -> PackageDescription -> FilePath
 hscolourPref = haddockPref
 
+-- | Build info json file, generated in every build
+buildInfoPref :: FilePath -> FilePath
+buildInfoPref distPref = distPref </> "build-info.json"
+
 -- | This is the name of the directory in which the generated haddocks
 -- should be stored. It does not include the @<dist>/doc/html@ prefix.
 haddockDirName :: HaddockTarget -> PackageDescription -> FilePath
diff --git a/Cabal/src/Distribution/Simple/Setup.hs b/Cabal/src/Distribution/Simple/Setup.hs
index 8f0ad64de1..6ed08e7ed3 100644
--- a/Cabal/src/Distribution/Simple/Setup.hs
+++ b/Cabal/src/Distribution/Simple/Setup.hs
@@ -45,7 +45,7 @@ module Distribution.Simple.Setup (
   HaddockFlags(..),  emptyHaddockFlags,  defaultHaddockFlags,  haddockCommand,
   HscolourFlags(..), emptyHscolourFlags, defaultHscolourFlags, hscolourCommand,
   BuildFlags(..),    emptyBuildFlags,    defaultBuildFlags,    buildCommand,
-  ShowBuildInfoFlags(..),                defaultShowBuildFlags, showBuildInfoCommand,
+  DumpBuildInfo(..),
   ReplFlags(..),                         defaultReplFlags,     replCommand,
   ReplOptions(..),
   CleanFlags(..),    emptyCleanFlags,    defaultCleanFlags,    cleanCommand,
@@ -99,6 +99,7 @@ import Distribution.Simple.InstallDirs
 import Distribution.Verbosity
 import Distribution.Utils.NubList
 import Distribution.Types.ComponentId
+import Distribution.Types.DumpBuildInfo
 import Distribution.Types.GivenComponent
 import Distribution.Types.Module
 import Distribution.Types.PackageVersionConstraint
@@ -274,6 +275,11 @@ data ConfigFlags = ConfigFlags {
       -- ^Halt and show an error message indicating an error in flag assignment
     configRelocatable :: Flag Bool, -- ^ Enable relocatable package built
     configDebugInfo :: Flag DebugInfoLevel,  -- ^ Emit debug info.
+    configDumpBuildInfo :: Flag DumpBuildInfo,
+      -- ^ Should we dump available build information on build?
+      -- Dump build information to disk before attempting to build,
+      -- tooling can parse these files and use them to compile the
+      -- source files themselves.
     configUseResponseFiles :: Flag Bool,
       -- ^ Whether to use response files at all. They're used for such tools
       -- as haddock, or ld.
@@ -343,6 +349,7 @@ instance Eq ConfigFlags where
     && equal configFlagError
     && equal configRelocatable
     && equal configDebugInfo
+    && equal configDumpBuildInfo
     && equal configUseResponseFiles
     where
       equal f = on (==) f a b
@@ -393,6 +400,7 @@ defaultConfigFlags progDb = emptyConfigFlags {
     configFlagError    = NoFlag,
     configRelocatable  = Flag False,
     configDebugInfo    = Flag NoDebugInfo,
+    configDumpBuildInfo = NoFlag,
     configUseResponseFiles = NoFlag
   }
 
@@ -561,6 +569,17 @@ configureOptions showOrParseArgs =
                 "Don't emit debug info"
          ]
 
+      , multiOption "build-info"
+         configDumpBuildInfo
+         (\v flags -> flags { configDumpBuildInfo = v })
+         [noArg (Flag DumpBuildInfo) []
+                ["enable-build-info"]
+                "Enable build information generation during project building",
+          noArg (Flag NoDumpBuildInfo) []
+                ["disable-build-info"]
+                "Disable build information generation during project building"
+         ]
+
       ,option "" ["library-for-ghci"]
          "compile library for use with GHCi"
          configGHCiLib (\v flags -> flags { configGHCiLib = v })
@@ -2183,88 +2202,6 @@ optionNumJobs get set =
             | otherwise -> Right (Just n)
           _             -> Left "The jobs value should be a number or '$ncpus'"
 
-
--- ------------------------------------------------------------
--- * show-build-info command flags
--- ------------------------------------------------------------
-
-data ShowBuildInfoFlags = ShowBuildInfoFlags
-  { buildInfoBuildFlags     :: BuildFlags
-  , buildInfoOutputFile     :: Maybe FilePath
-  , buildInfoComponentsOnly :: Flag Bool
-  -- ^ If 'True' then only print components, each separated by a newline
-  } deriving (Show, Typeable)
-
-defaultShowBuildFlags  :: ShowBuildInfoFlags
-defaultShowBuildFlags =
-    ShowBuildInfoFlags
-      { buildInfoBuildFlags     = defaultBuildFlags
-      , buildInfoOutputFile     = Nothing
-      , buildInfoComponentsOnly = Flag False
-      }
-
-showBuildInfoCommand :: ProgramDb -> CommandUI ShowBuildInfoFlags
-showBuildInfoCommand progDb = CommandUI
-  { commandName         = "show-build-info"
-  , commandSynopsis     = "Emit details about how a package would be built."
-  , commandDescription  = Just $ \_ -> wrapText $
-         "Components encompass executables, tests, and benchmarks.\n"
-      ++ "\n"
-      ++ "Affected by configuration options, see `configure`.\n"
-  , commandNotes        = Just $ \pname ->
-       "Examples:\n"
-        ++ "  " ++ pname ++ " show-build-info      "
-        ++ "    All the components in the package\n"
-        ++ "  " ++ pname ++ " show-build-info foo       "
-        ++ "    A component (i.e. lib, exe, test suite)\n\n"
-        ++ programFlagsDescription progDb
---TODO: re-enable once we have support for module/file targets
---        ++ "  " ++ pname ++ " show-build-info Foo.Bar   "
---        ++ "    A module\n"
---        ++ "  " ++ pname ++ " show-build-info Foo/Bar.hs"
---        ++ "    A file\n\n"
---        ++ "If a target is ambiguous it can be qualified with the component "
---        ++ "name, e.g.\n"
---        ++ "  " ++ pname ++ " show-build-info foo:Foo.Bar\n"
---        ++ "  " ++ pname ++ " show-build-info testsuite1:Foo/Bar.hs\n"
-  , commandUsage        = usageAlternatives "show-build-info" $
-      [ "[FLAGS]"
-      , "COMPONENTS [FLAGS]"
-      ]
-  , commandDefaultFlags = defaultShowBuildFlags
-  , commandOptions      = \showOrParseArgs ->
-      parseBuildFlagsForShowBuildInfoFlags showOrParseArgs progDb
-      ++
-      [ option [] ["buildinfo-json-output"]
-                "Write the result to the given file instead of stdout"
-                buildInfoOutputFile (\v flags -> flags { buildInfoOutputFile = v })
-                (reqArg' "FILE" Just (maybe [] pure))
-      , option [] ["buildinfo-components-only"]
-                  "Print out only the component info, each separated by a newline"
-                  buildInfoComponentsOnly (\v flags -> flags { buildInfoComponentsOnly = v})
-                  trueArg
-      ]
-
-  }
-
-parseBuildFlagsForShowBuildInfoFlags :: ShowOrParseArgs -> ProgramDb -> [OptionField ShowBuildInfoFlags]
-parseBuildFlagsForShowBuildInfoFlags showOrParseArgs progDb =
-  map
-      (liftOption
-        buildInfoBuildFlags
-          (\bf flags -> flags { buildInfoBuildFlags = bf } )
-      )
-      buildFlags
-  where
-    buildFlags = buildOptions progDb showOrParseArgs
-      ++
-      [ optionVerbosity
-        buildVerbosity (\v flags -> flags { buildVerbosity = v })
-
-      , optionDistPref
-        buildDistPref (\d flags -> flags { buildDistPref = d }) showOrParseArgs
-      ]
-
 -- ------------------------------------------------------------
 -- * Other Utils
 -- ------------------------------------------------------------
diff --git a/Cabal/src/Distribution/Simple/ShowBuildInfo.hs b/Cabal/src/Distribution/Simple/ShowBuildInfo.hs
index fbbebbe9e0..9bfda3eadd 100644
--- a/Cabal/src/Distribution/Simple/ShowBuildInfo.hs
+++ b/Cabal/src/Distribution/Simple/ShowBuildInfo.hs
@@ -2,7 +2,7 @@
 -- This module defines a simple JSON-based format for exporting basic
 -- information about a Cabal package and the compiler configuration Cabal
 -- would use to build it. This can be produced with the
--- @cabal new-show-build-info@ command.
+-- @cabal build --enable-build-info@ command.
 --
 --
 -- This format is intended for consumption by external tooling and should
@@ -14,7 +14,7 @@
 -- Below is an example of the output this module produces,
 --
 -- @
--- { "cabal-version": "1.23.0.0",
+-- { "cabal-lib-version": "1.23.0.0",
 --   "compiler": {
 --     "flavour": "GHC",
 --     "compiler-id": "ghc-7.10.2",
@@ -34,7 +34,10 @@
 -- }
 -- @
 --
--- The @cabal-version@ property provides the version of the Cabal library
+-- The output format needs to be validated against 'doc/json-schemas/build-info.schema.json'.
+-- If the format changes, update the schema as well!
+--
+-- The @cabal-lib-version@ property provides the version of the Cabal library
 -- which generated the output. The @compiler@ property gives some basic
 -- information about the compiler Cabal would use to compile the package.
 --
@@ -56,8 +59,11 @@
 
 {-# LANGUAGE OverloadedStrings #-}
 
-module Distribution.Simple.ShowBuildInfo
-  ( mkBuildInfo, mkBuildInfo', mkCompilerInfo, mkComponentInfo ) where
+module Distribution.Simple.ShowBuildInfo (
+  mkBuildInfo, mkBuildInfo', mkCompilerInfo, mkComponentInfo
+  ) where
+
+import System.FilePath
 
 import Distribution.Compat.Prelude
 import Prelude ()
@@ -68,12 +74,14 @@ import qualified Distribution.Simple.Program.GHC as GHC
 import Distribution.PackageDescription
 import Distribution.Compiler
 import Distribution.Verbosity
-import Distribution.Simple.Compiler
-import Distribution.Simple.LocalBuildInfo
+import Distribution.Simple.Compiler (Compiler, showCompilerId, compilerFlavor)
 import Distribution.Simple.Program
 import Distribution.Simple.Setup
 import Distribution.Simple.Utils (cabalVersion)
 import Distribution.Utils.Json
+import Distribution.Types.Component
+import Distribution.Types.ComponentLocalBuildInfo
+import Distribution.Types.LocalBuildInfo
 import Distribution.Types.TargetInfo
 import Distribution.Text
 import Distribution.Pretty
@@ -86,11 +94,13 @@ mkBuildInfo
   -> LocalBuildInfo      -- ^ Configuration information
   -> BuildFlags          -- ^ Flags that the user passed to build
   -> [TargetInfo]
-  -> Json
-mkBuildInfo wdir pkg_descr lbi _flags targetsToBuild =
-  JsonObject $
-    mkBuildInfo' (mkCompilerInfo (withPrograms lbi) (compiler lbi))
-                 (map (mkComponentInfo wdir pkg_descr lbi . targetCLBI) targetsToBuild)
+  -> ([String], Json)    -- ^ Json representation of buildinfo alongside generated warnings
+mkBuildInfo wdir pkg_descr lbi _flags targetsToBuild = (warnings, JsonObject buildInfoFields)
+  where
+    buildInfoFields = mkBuildInfo' (mkCompilerInfo (withPrograms lbi) (compiler lbi)) componentInfos
+    componentInfosWithWarnings = map (mkComponentInfo wdir pkg_descr lbi . targetCLBI) targetsToBuild
+    componentInfos = map snd componentInfosWithWarnings
+    warnings = concatMap fst componentInfosWithWarnings
 
 -- | A variant of 'mkBuildInfo' if you need to call 'mkCompilerInfo' and
 -- 'mkComponentInfo' yourself.
@@ -98,21 +108,21 @@ mkBuildInfo'
   :: Json   -- ^ The 'Json' from 'mkCompilerInfo'
   -> [Json] -- ^ The 'Json' from 'mkComponentInfo'
   -> [(String, Json)]
-mkBuildInfo' cmplrInfo componentInfos =
-    [ "cabal-version" .= JsonString (display cabalVersion)
-    , "compiler"      .= cmplrInfo
-    , "components"    .= JsonArray componentInfos
-    ]
+mkBuildInfo' compilerInfo componentInfos =
+  [ "cabal-lib-version" .= JsonString (display cabalVersion)
+  , "compiler"          .= compilerInfo
+  , "components"        .= JsonArray componentInfos
+  ]
 
 mkCompilerInfo :: ProgramDb -> Compiler -> Json
-mkCompilerInfo programDb cmplr = JsonObject
-  [ "flavour"     .= JsonString (prettyShow $ compilerFlavor cmplr)
-  , "compiler-id" .= JsonString (showCompilerId cmplr)
+mkCompilerInfo programDb compilerInfo = JsonObject
+  [ "flavour"     .= JsonString (prettyShow $ compilerFlavor compilerInfo)
+  , "compiler-id" .= JsonString (showCompilerId compilerInfo)
   , "path"        .= path
   ]
   where
     path = maybe JsonNull (JsonString . programPath)
-            $ (flavorToProgram . compilerFlavor $ cmplr)
+            $ (flavorToProgram . compilerFlavor $ compilerInfo)
             >>= flip lookupProgram programDb
 
     flavorToProgram :: CompilerFlavor -> Maybe Program
@@ -122,20 +132,22 @@ mkCompilerInfo programDb cmplr = JsonObject
     flavorToProgram JHC   = Just jhcProgram
     flavorToProgram _     = Nothing
 
-mkComponentInfo :: FilePath -> PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> Json
-mkComponentInfo wdir pkg_descr lbi clbi = JsonObject $
+mkComponentInfo :: FilePath -> PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> ([String], Json)
+mkComponentInfo wdir pkg_descr lbi clbi = (warnings, JsonObject $
   [ "type"          .= JsonString compType
   , "name"          .= JsonString (prettyShow name)
   , "unit-id"       .= JsonString (prettyShow $ componentUnitId clbi)
-  , "compiler-args" .= JsonArray (map JsonString $ getCompilerArgs bi lbi clbi)
+  , "compiler-args" .= JsonArray (map JsonString compilerArgs)
   , "modules"       .= JsonArray (map (JsonString . display) modules)
   , "src-files"     .= JsonArray (map JsonString sourceFiles)
   , "hs-src-dirs"   .= JsonArray (map (JsonString . prettyShow) $ hsSourceDirs bi)
-  , "src-dir"       .= JsonString wdir
-  ] <> cabalFile
+  , "src-dir"       .= JsonString (addTrailingPathSeparator wdir)
+  ] <> cabalFile)
   where
+    (warnings, compilerArgs) = getCompilerArgs bi lbi clbi
     name = componentLocalName clbi
     bi = componentBuildInfo comp
+    -- If this error happens, a cabal invariant has been violated
     comp = fromMaybe (error $ "mkBuildInfo: no component " ++ prettyShow name) $ lookupComponent pkg_descr name
     compType = case comp of
       CLib _   -> "lib"
@@ -176,13 +188,15 @@ getCompilerArgs
   :: BuildInfo
   -> LocalBuildInfo
   -> ComponentLocalBuildInfo
-  -> [String]
+  -> ([String], [String])
 getCompilerArgs bi lbi clbi =
   case compilerFlavor $ compiler lbi of
-      GHC   -> ghc
-      GHCJS -> ghc
-      c     -> error $ "ShowBuildInfo.getCompilerArgs: Don't know how to get "++
-                       "build arguments for compiler "++show c
+      GHC   -> ([], ghc)
+      GHCJS -> ([], ghc)
+      c     ->
+        ( ["ShowBuildInfo.getCompilerArgs: Don't know how to get build "
+          ++ " arguments for compiler " ++ show c]
+        , [])
   where
     -- This is absolutely awful
     ghc = GHC.renderGhcOptions (compiler lbi) (hostPlatform lbi) baseOpts
diff --git a/Cabal/src/Distribution/Types/DumpBuildInfo.hs b/Cabal/src/Distribution/Types/DumpBuildInfo.hs
new file mode 100644
index 0000000000..5657a65aa7
--- /dev/null
+++ b/Cabal/src/Distribution/Types/DumpBuildInfo.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+module Distribution.Types.DumpBuildInfo
+    ( DumpBuildInfo(..)
+    ) where
+
+import Distribution.Compat.Prelude
+
+data DumpBuildInfo
+  = NoDumpBuildInfo
+  | DumpBuildInfo
+  deriving (Read, Show, Eq, Ord, Enum, Bounded, Generic, Typeable)
+
+instance Binary DumpBuildInfo
+instance Structured DumpBuildInfo
diff --git a/cabal-install/src/Distribution/Client/Config.hs b/cabal-install/src/Distribution/Client/Config.hs
index 1c2df3dc71..c5ca5f1811 100644
--- a/cabal-install/src/Distribution/Client/Config.hs
+++ b/cabal-install/src/Distribution/Client/Config.hs
@@ -409,6 +409,7 @@ instance Semigroup SavedConfig where
         configFlagError           = combine configFlagError,
         configRelocatable         = combine configRelocatable,
         configUseResponseFiles    = combine configUseResponseFiles,
+        configDumpBuildInfo       = combine configDumpBuildInfo,
         configAllowDependingOnPrivateLibs =
             combine configAllowDependingOnPrivateLibs
         }
diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs
index 0aca7ac2a4..38852d0fd0 100644
--- a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs
+++ b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs
@@ -54,7 +54,7 @@ import Distribution.Simple.Setup
          , HaddockFlags(..), haddockOptions, defaultHaddockFlags
          , TestFlags(..), testOptions', defaultTestFlags
          , BenchmarkFlags(..), benchmarkOptions', defaultBenchmarkFlags
-         , programDbPaths', splitArgs
+         , programDbPaths', splitArgs, DumpBuildInfo (NoDumpBuildInfo, DumpBuildInfo)
          )
 import Distribution.Client.NixStyleOptions (NixStyleFlags (..))
 import Distribution.Client.ProjectFlags (ProjectFlags (..), projectFlagsOptions, defaultProjectFlags)
@@ -437,6 +437,7 @@ convertLegacyPerPackageFlags configFlags installFlags
       configCoverage            = coverage,
       configLibCoverage         = libcoverage, --deprecated
       configDebugInfo           = packageConfigDebugInfo,
+      configDumpBuildInfo       = packageConfigDumpBuildInfo,
       configRelocatable         = packageConfigRelocatable
     } = configFlags
     packageConfigProgramPaths   = MapLast    (Map.fromList configProgramPaths)
@@ -724,6 +725,7 @@ convertToLegacyAllPackageConfig
       configRelocatable         = mempty,
       configDebugInfo           = mempty,
       configUseResponseFiles    = mempty,
+      configDumpBuildInfo       = mempty,
       configAllowDependingOnPrivateLibs = mempty
     }
 
@@ -797,6 +799,7 @@ convertToLegacyPerPackageConfig PackageConfig {..} =
       configRelocatable         = packageConfigRelocatable,
       configDebugInfo           = packageConfigDebugInfo,
       configUseResponseFiles    = mempty,
+      configDumpBuildInfo       = packageConfigDumpBuildInfo,
       configAllowDependingOnPrivateLibs = mempty
     }
 
@@ -1083,6 +1086,7 @@ legacyPackageConfigFieldDescrs =
           dispFlagAssignment parsecFlagAssignment
           configConfigurationsFlags
           (\v conf -> conf { configConfigurationsFlags = v })
+      , overrideDumpBuildInfo
       ]
   . filterFields
       [ "with-compiler", "with-hc-pkg"
@@ -1180,6 +1184,23 @@ legacyPackageConfigFieldDescrs =
         (toFlag <$> parsec <|> pure mempty)
         configHcFlavor (\v flags -> flags { configHcFlavor = v })
 
+    overrideDumpBuildInfo =
+      liftField configDumpBuildInfo
+                (\v flags -> flags { configDumpBuildInfo = v }) $
+      let name = "build-info" in
+      FieldDescr name
+        (\f -> case f of
+                 Flag NoDumpBuildInfo -> Disp.text "False"
+                 Flag DumpBuildInfo   -> Disp.text "True"
+                 _                    -> Disp.empty)
+        (\line str _ -> case () of
+         _ |  str == "False" -> ParseOk [] (Flag NoDumpBuildInfo)
+           |  str == "True"  -> ParseOk [] (Flag DumpBuildInfo)
+           | lstr == "false" -> ParseOk [caseWarning name] (Flag NoDumpBuildInfo)
+           | lstr == "true"  -> ParseOk [caseWarning name] (Flag DumpBuildInfo)
+           | otherwise       -> ParseFailed (NoParse name line)
+           where
+             lstr = lowercase str)
 
     -- TODO: [code cleanup] The following is a hack. The "optimization" and
     -- "debug-info" fields are OptArg, and viewAsFieldDescr fails on that.
@@ -1202,13 +1223,11 @@ legacyPackageConfigFieldDescrs =
            |  str == "0"     -> ParseOk [] (Flag NoOptimisation)
            |  str == "1"     -> ParseOk [] (Flag NormalOptimisation)
            |  str == "2"     -> ParseOk [] (Flag MaximumOptimisation)
-           | lstr == "false" -> ParseOk [caseWarning] (Flag NoOptimisation)
-           | lstr == "true"  -> ParseOk [caseWarning] (Flag NormalOptimisation)
+           | lstr == "false" -> ParseOk [caseWarning name] (Flag NoOptimisation)
+           | lstr == "true"  -> ParseOk [caseWarning name] (Flag NormalOptimisation)
            | otherwise       -> ParseFailed (NoParse name line)
            where
-             lstr = lowercase str
-             caseWarning = PWarning $
-               "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")
+             lstr = lowercase str)
 
     overrideFieldDebugInfo =
       liftField configDebugInfo (\v flags -> flags { configDebugInfo = v }) $
@@ -1227,13 +1246,14 @@ legacyPackageConfigFieldDescrs =
            |  str == "1"     -> ParseOk [] (Flag MinimalDebugInfo)
            |  str == "2"     -> ParseOk [] (Flag NormalDebugInfo)
            |  str == "3"     -> ParseOk [] (Flag MaximalDebugInfo)
-           | lstr == "false" -> ParseOk [caseWarning] (Flag NoDebugInfo)
-           | lstr == "true"  -> ParseOk [caseWarning] (Flag NormalDebugInfo)
+           | lstr == "false" -> ParseOk [caseWarning name] (Flag NoDebugInfo)
+           | lstr == "true"  -> ParseOk [caseWarning name] (Flag NormalDebugInfo)
            | otherwise       -> ParseFailed (NoParse name line)
            where
-             lstr = lowercase str
-             caseWarning = PWarning $
-               "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")
+             lstr = lowercase str)
+
+    caseWarning name = PWarning $
+      "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'."
 
     prefixTest name | "test-" `isPrefixOf` name = name
                     | otherwise = "test-" ++ name
diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Types.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Types.hs
index 92850b2792..eb32ce2741 100644
--- a/cabal-install/src/Distribution/Client/ProjectConfig/Types.hs
+++ b/cabal-install/src/Distribution/Client/ProjectConfig/Types.hs
@@ -59,7 +59,7 @@ import Distribution.Simple.Compiler
          ( Compiler, CompilerFlavor
          , OptimisationLevel(..), ProfDetailLevel, DebugInfoLevel(..) )
 import Distribution.Simple.Setup
-         ( Flag, HaddockTarget(..), TestShowDetails(..) )
+         ( Flag, HaddockTarget(..), TestShowDetails(..), DumpBuildInfo (..) )
 import Distribution.Simple.InstallDirs
          ( PathTemplate )
 import Distribution.Utils.NubList
@@ -271,6 +271,7 @@ data PackageConfig
        packageConfigCoverage            :: Flag Bool,
        packageConfigRelocatable         :: Flag Bool,
        packageConfigDebugInfo           :: Flag DebugInfoLevel,
+       packageConfigDumpBuildInfo       :: Flag DumpBuildInfo,
        packageConfigRunTests            :: Flag Bool, --TODO: [required eventually] use this
        packageConfigDocumentation       :: Flag Bool, --TODO: [required eventually] use this
        -- Haddock options
diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs
index 4113f5f1fb..1745f25632 100644
--- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs
+++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs
@@ -115,6 +115,8 @@ import           Distribution.ModuleName
 import           Distribution.Package
 import           Distribution.Types.AnnotatedId
 import           Distribution.Types.ComponentName
+import           Distribution.Types.DumpBuildInfo
+                   ( DumpBuildInfo (..) )
 import           Distribution.Types.LibraryName
 import           Distribution.Types.GivenComponent
   (GivenComponent(..))
@@ -1838,6 +1840,7 @@ elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB
         elabStripLibs     = perPkgOptionFlag pkgid False packageConfigStripLibs
         elabStripExes     = perPkgOptionFlag pkgid False packageConfigStripExes
         elabDebugInfo     = perPkgOptionFlag pkgid NoDebugInfo packageConfigDebugInfo
+        elabDumpBuildInfo = perPkgOptionFlag pkgid NoDumpBuildInfo packageConfigDumpBuildInfo
 
         -- Combine the configured compiler prog settings with the user-supplied
         -- config. For the compiler progs any user-supplied config was taken
@@ -3468,6 +3471,7 @@ setupHsConfigureFlags (ReadyPackage elab@ElaboratedConfiguredPackage{..})
     configStripExes           = toFlag elabStripExes
     configStripLibs           = toFlag elabStripLibs
     configDebugInfo           = toFlag elabDebugInfo
+    configDumpBuildInfo       = toFlag elabDumpBuildInfo
 
     configConfigurationsFlags = elabFlagAssignment
     configConfigureArgs       = elabConfigureScriptArgs
diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs b/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs
index 2d5eecdd35..48d3fd6a62 100644
--- a/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs
+++ b/cabal-install/src/Distribution/Client/ProjectPlanning/Types.hs
@@ -95,7 +95,8 @@ import           Distribution.Simple.LocalBuildInfo
                    ( ComponentName(..), LibraryName(..) )
 import qualified Distribution.Simple.InstallDirs as InstallDirs
 import           Distribution.Simple.InstallDirs (PathTemplate)
-import           Distribution.Simple.Setup (HaddockTarget, TestShowDetails, ReplOptions)
+import           Distribution.Simple.Setup
+                   ( HaddockTarget, TestShowDetails, DumpBuildInfo (..), ReplOptions )
 import           Distribution.Version
 
 import qualified Distribution.Solver.Types.ComponentDeps as CD
@@ -261,6 +262,7 @@ data ElaboratedConfiguredPackage
        elabStripLibs            :: Bool,
        elabStripExes            :: Bool,
        elabDebugInfo            :: DebugInfoLevel,
+       elabDumpBuildInfo        :: DumpBuildInfo,
 
        elabProgramPaths          :: Map String FilePath,
        elabProgramArgs           :: Map String [String],
diff --git a/cabal-install/src/Distribution/Client/Setup.hs b/cabal-install/src/Distribution/Client/Setup.hs
index 13cd1001fb..033932a0e7 100644
--- a/cabal-install/src/Distribution/Client/Setup.hs
+++ b/cabal-install/src/Distribution/Client/Setup.hs
@@ -450,7 +450,7 @@ filterConfigureFlags :: ConfigFlags -> Version -> ConfigFlags
 filterConfigureFlags flags cabalLibVersion
   -- NB: we expect the latest version to be the most common case,
   -- so test it first.
-  | cabalLibVersion >= mkVersion [2,5,0]  = flags_latest
+  | cabalLibVersion >= mkVersion [3,7,0]  = flags_latest
   -- The naming convention is that flags_version gives flags with
   -- all flags *introduced* in version eliminated.
   -- It is NOT the latest version of Cabal library that
@@ -483,7 +483,10 @@ filterConfigureFlags flags cabalLibVersion
 
     flags_3_7_0 = flags_latest {
         -- Cabal < 3.7 does not know about --extra-lib-dirs-static
-        configExtraLibDirsStatic = []
+        configExtraLibDirsStatic = [],
+
+        -- Cabal < 3.7 does not understand '--enable-build-info' or '--disable-build-info'
+        configDumpBuildInfo = NoFlag
       }
 
     flags_2_5_0 = flags_3_7_0 {
diff --git a/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs b/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs
index 95b7e1ccb7..fc9d0c36f7 100644
--- a/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs
+++ b/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs
@@ -559,7 +559,7 @@ instance Arbitrary PackageConfig where
         <*> arbitrary <*> arbitrary
         <*> arbitrary <*> arbitrary
         <*> arbitrary <*> arbitrary
-        <*> arbitrary <*> arbitrary
+        <*> arbitrary <*> arbitrary <*> arbitrary
         <*> arbitrary <*> arbitrary
         <*> arbitraryFlag arbitraryShortToken
         <*> arbitrary
@@ -618,6 +618,7 @@ instance Arbitrary PackageConfig where
                          , packageConfigCoverage = x25
                          , packageConfigRelocatable = x26
                          , packageConfigDebugInfo = x27
+                         , packageConfigDumpBuildInfo = x27_1
                          , packageConfigRunTests = x28
                          , packageConfigDocumentation = x29
                          , packageConfigHaddockHoogle = x30
@@ -674,6 +675,7 @@ instance Arbitrary PackageConfig where
                       , packageConfigCoverage = x25'
                       , packageConfigRelocatable = x26'
                       , packageConfigDebugInfo = x27'
+                      , packageConfigDumpBuildInfo = x27_1'
                       , packageConfigRunTests = x28'
                       , packageConfigDocumentation = x29'
                       , packageConfigHaddockHoogle = x30'
@@ -703,7 +705,7 @@ instance Arbitrary PackageConfig where
           (x10', x11', x12', x13', x14'),
           (x15', x16', x53', x17', x18', x19')),
          ((x20', x20_1', x21', x22', x23', x24'),
-          (x25', x26', x27', x28', x29'),
+          (x25', x26', x27', x27_1', x28', x29'),
           (x30', x31', x32', (x33', x33_1'), x34'),
           (x35', x36', x37', x38', x43', x39'),
           (x40', x41'),
@@ -717,7 +719,7 @@ instance Arbitrary PackageConfig where
                   map NonEmpty x18,
                   x19)),
                ((x20, x20_1, x21, x22, x23, x24),
-                 (x25, x26, x27, x28, x29),
+                 (x25, x26, x27, x27_1, x28, x29),
                  (x30, x31, x32, (x33, x33_1), x34),
                  (x35, x36, fmap NonEmpty x37, x38, x43, fmap NonEmpty x39),
                  (x40, x41),
-- 
GitLab