Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new-show-build-info command #5954

Closed
wants to merge 43 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1e9de7f
Add show-build-info command
bgamari Aug 16, 2015
0418c08
Rebase work of cfraz89 and bgamari
cfraz89 May 28, 2018
533d6bf
Remove unused import
fendor Mar 20, 2019
0469b47
Adopt coding style and edit comments
fendor Mar 25, 2019
c917a06
Adopt coding style
fendor Mar 25, 2019
dae46d9
Use prett show to show component name
fendor Mar 25, 2019
b0ecf2d
Add help message for show-build-info cmd
fendor Mar 27, 2019
3ff9276
Use mempty as the clientInstallFlags
fendor Apr 2, 2019
73c438d
Remove Command WriteAutogenFiles
fendor Apr 3, 2019
dc93de6
Adapt naming to fit to plan.json
fendor Apr 3, 2019
8c61157
Integrate PR change requests
fendor Apr 11, 2019
c677b5b
Add flags for new-show-build-info
fendor Apr 12, 2019
17cb804
Clean up code a bit
fendor Apr 13, 2019
316676b
Add first draft for tests
fendor Apr 25, 2019
167bc72
Update the correct cabal-install.cabal
fendor Apr 25, 2019
0b0bd5d
Implement basic test for new-show-build-info
fendor Apr 25, 2019
59d1f06
Improve format and clean up code
fendor May 6, 2019
669c18b
Improve format and clean up code
fendor May 6, 2019
c967931
Silence warnings
fendor Jun 4, 2019
d89ca8d
Remove unused flag to Cabal
fendor Jun 4, 2019
6f0cb5b
In the tests, do not record
fendor Jun 4, 2019
feefced
Communicate with Cabal over temp files
fendor Jun 9, 2019
b62af6c
Add tests for new-show-build-info
fendor Jun 9, 2019
3dcc14a
Remove unit.json that is a temporary file
fendor Jun 9, 2019
a1b4f6a
Fix test for unit-file
fendor Jun 9, 2019
114bb5a
Add fake repository for complex project
fendor Jun 9, 2019
b3b7ab1
Add exact test for library component
fendor Jun 9, 2019
70359c6
Add check for component modules
fendor Jun 9, 2019
7014bb7
Prepare test component tests
fendor Jun 9, 2019
1bd70df
Manually escape special json characters
fendor Jun 13, 2019
e213925
Remove/adapt inaccurate test cases
fendor Jun 13, 2019
e1b3875
More explicit check for ghc flags
fendor Jun 13, 2019
4562c8d
Remove accidentally commited build-info files
fendor Jun 13, 2019
53aa595
Pattern match on compiler flavour
fendor Jun 13, 2019
928e92f
Modify test files
fendor Jun 18, 2019
c0c1ac2
Add file ending for Cabal ShowBuildInfo
fendor Jun 18, 2019
0c18343
Use hspec package instead of test-framework in test case
fendor Jun 19, 2019
041e370
Fail if Cabal version is too old
fendor Jun 21, 2019
83bf662
Rename spec.cabal to hspec.cabal in test-suite
fendor Jun 21, 2019
141d706
Prefer (++) over (<>) for string concatenation
fendor Jun 21, 2019
decab60
Remove redundant import
fendor Jun 23, 2019
0d06867
Use case of to be exhaustive in pattern matching
fendor Jun 23, 2019
457d8a5
Use qualified target names
fendor Jun 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cabal/Cabal.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ library
Distribution.Simple.Program.Types
Distribution.Simple.Register
Distribution.Simple.Setup
Distribution.Simple.ShowBuildInfo
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the functionality needs to be in Distribution.Simple, can't it be implemented solely in cabal-install. There should be a rationale why it cannot be, if there are legitimate reasons.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the functionality needs to be in Distribution.Simple, can't it be implemented solely in cabal-install. There should be a rationale why it cannot be, if there are legitimate reasons.

There is, according to @hvr and @DanielG, and this pr is obsolete since the Cabal part has already been merged.
For discussions on cabal-install part, see: #6241

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good.

Sad we need support in Cabal though, as we have to worry about build-type: Custom stuff.

Distribution.Simple.SrcDist
Distribution.Simple.Test
Distribution.Simple.Test.ExeV10
Expand Down Expand Up @@ -534,6 +535,7 @@ library
Distribution.Simple.GHC.EnvironmentParser
Distribution.Simple.GHC.Internal
Distribution.Simple.GHC.ImplInfo
Distribution.Simple.Utils.Json
Paths_Cabal

if flag(bundled-binary-generic)
Expand Down
28 changes: 28 additions & 0 deletions Cabal/Distribution/Simple.hs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ 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
Expand Down Expand Up @@ -264,6 +265,33 @@ buildAction hooks flags args = do
(return lbi { withPrograms = progs })
hooks flags' { buildArgs = args } args

showBuildInfoAction :: UserHooks -> ShowBuildInfoFlags -> Args -> IO ()
showBuildInfoAction hooks (ShowBuildInfoFlags flags fileOutput) args = do
distPref <- findDistPrefOrDefault (buildDistPref flags)
let verbosity = fromFlag $ buildVerbosity flags
lbi <- getBuildConfig hooks verbosity distPref
let flags' = flags { buildDistPref = toFlag distPref
, buildCabalFilePath = maybeToFlag (cabalFilePath lbi)
}

progs <- reconfigurePrograms verbosity
(buildProgramPaths flags')
(buildProgramArgs flags')
(withPrograms lbi)

pbi <- preBuild hooks args flags'
let lbi' = lbi { withPrograms = progs }
pkg_descr0 = localPkgDescr lbi'
pkg_descr = updatePackageDescription pbi pkg_descr0
-- TODO: Somehow don't ignore build hook?
buildInfoString <- showBuildInfo pkg_descr lbi' flags

case fileOutput of
Nothing -> putStr buildInfoString
Just fp -> writeFile fp buildInfoString

postBuild hooks args flags' pkg_descr lbi'

replAction :: UserHooks -> ReplFlags -> Args -> IO ()
replAction hooks flags args = do
distPref <- findDistPrefOrDefault (replDistPref flags)
Expand Down
16 changes: 15 additions & 1 deletion Cabal/Distribution/Simple/Build.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
--

module Distribution.Simple.Build (
build, repl,
build, showBuildInfo, repl,
hvr marked this conversation as resolved.
Show resolved Hide resolved
startInterpreter,

initialBuildSteps,
Expand Down Expand Up @@ -69,11 +69,13 @@ import Distribution.Simple.PreProcess
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Program.Types
import Distribution.Simple.Program.Db
import Distribution.Simple.ShowBuildInfo
import Distribution.Simple.BuildPaths
import Distribution.Simple.Configure
import Distribution.Simple.Register
import Distribution.Simple.Test.LibV09
import Distribution.Simple.Utils
import Distribution.Simple.Utils.Json

import Distribution.System
import Distribution.Pretty
Expand Down Expand Up @@ -128,6 +130,18 @@ build pkg_descr lbi flags suffixes = do
verbosity = fromFlag (buildVerbosity flags)


showBuildInfo :: PackageDescription -- ^ Mostly information from the .cabal file
-> LocalBuildInfo -- ^ Configuration information
-> BuildFlags -- ^ Flags that the user passed to build
-> IO String
showBuildInfo pkg_descr lbi flags = do
let verbosity = fromFlag (buildVerbosity flags)
targets <- readTargetInfos verbosity pkg_descr lbi (buildArgs flags)
let targetsToBuild = neededTargetsInBuildOrder' pkg_descr lbi (map nodeKey targets)
doc = mkBuildInfo pkg_descr lbi flags targetsToBuild
return $ renderJson doc ""


repl :: PackageDescription -- ^ Mostly information from the .cabal file
-> LocalBuildInfo -- ^ Configuration information
-> ReplFlags -- ^ Flags that the user passed to build
Expand Down
76 changes: 76 additions & 0 deletions Cabal/Distribution/Simple/Setup.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module Distribution.Simple.Setup (
HaddockFlags(..), emptyHaddockFlags, defaultHaddockFlags, haddockCommand,
HscolourFlags(..), emptyHscolourFlags, defaultHscolourFlags, hscolourCommand,
BuildFlags(..), emptyBuildFlags, defaultBuildFlags, buildCommand,
ShowBuildInfoFlags(..), defaultShowBuildFlags, showBuildInfoCommand,
ReplFlags(..), defaultReplFlags, replCommand,
CleanFlags(..), emptyCleanFlags, defaultCleanFlags, cleanCommand,
RegisterFlags(..), emptyRegisterFlags, defaultRegisterFlags, registerCommand,
Expand Down Expand Up @@ -2205,6 +2206,81 @@ 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
} deriving Show

defaultShowBuildFlags :: ShowBuildInfoFlags
defaultShowBuildFlags =
ShowBuildInfoFlags
{ buildInfoBuildFlags = defaultBuildFlags
, buildInfoOutputFile = Nothing
}

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 (\pf flags -> flags { buildInfoOutputFile = pf })
(reqArg' "FILE" Just (maybe [] pure))
]

}

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
-- ------------------------------------------------------------
Expand Down
153 changes: 153 additions & 0 deletions Cabal/Distribution/Simple/ShowBuildInfo.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
-- |
-- 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.
--
--
-- This format is intended for consumption by external tooling and should
-- therefore be rather stable. Moreover, this allows tooling users to avoid
-- linking against Cabal. This is an important advantage as direct API usage
-- tends to be rather fragile in the presence of user-initiated upgrades of
-- Cabal.
--
-- Below is an example of the output this module produces,
--
-- @
-- { "cabal-version": "1.23.0.0",
-- "compiler": {
-- "flavor": "GHC",
-- "compiler-id": "ghc-7.10.2",
-- "path": "/usr/bin/ghc",
-- },
-- "components": [
-- { "type": "lib",
-- "name": "lib:Cabal",
-- "compiler-args":
-- ["-O", "-XHaskell98", "-Wall",
-- "-package-id", "parallel-3.2.0.6-b79c38c5c25fff77f3ea7271851879eb"]
-- "modules": ["Project.ModA", "Project.ModB", "Paths_project"],
-- "src-files": [],
-- "src-dirs": ["src"]
-- }
-- ]
-- }
-- @
--
-- The @cabal-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.
--
-- The @components@ property gives a list of the Cabal 'Component's defined by
-- the package. Each has,
--
-- * @type@: the type of the component (one of @lib@, @exe@,
-- @test@, @bench@, or @flib@)
-- * @name@: a string serving to uniquely identify the component within the
-- package.
-- * @compiler-args@: the command-line arguments Cabal would pass to the
-- compiler to compile the component
-- * @modules@: the modules belonging to the component
-- * @src-dirs@: a list of directories where the modules might be found
-- * @src-files@: any other Haskell sources needed by the component
--
-- Note: At the moment this is only supported when using the GHC compiler.
--

module Distribution.Simple.ShowBuildInfo (mkBuildInfo) where

import qualified Distribution.Simple.GHC as GHC
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.Program
import Distribution.Simple.Setup
import Distribution.Simple.Utils (cabalVersion)
import Distribution.Simple.Utils.Json
import Distribution.Types.TargetInfo
import Distribution.Text
import Distribution.Pretty

-- | Construct a JSON document describing the build information for a package
mkBuildInfo :: PackageDescription -- ^ Mostly information from the .cabal file
-> LocalBuildInfo -- ^ Configuration information
-> BuildFlags -- ^ Flags that the user passed to build
-> [TargetInfo]
-> Json
mkBuildInfo pkg_descr lbi _flags targetsToBuild = info
where
componentsToBuild = map (\target -> (componentLocalName $ targetCLBI target,targetCLBI target)) targetsToBuild
(.=) :: String -> Json -> (String, Json)
k .= v = (k, v)

info = JsonObject
[ "cabal-version" .= JsonString (display cabalVersion)
, "compiler" .= mkCompilerInfo
, "components" .= JsonArray (map mkComponentInfo componentsToBuild)
]

mkCompilerInfo = JsonObject
[ "flavour" .= JsonString (prettyShow $ compilerFlavor $ compiler lbi)
, "compiler-id" .= JsonString (showCompilerId $ compiler lbi)
, "path" .= path
]
where
path = maybe JsonNull (JsonString . programPath)
$ (flavorToProgram . compilerFlavor $ compiler lbi)
>>= flip lookupProgram (withPrograms lbi)

flavorToProgram :: CompilerFlavor -> Maybe Program
flavorToProgram GHC = Just ghcProgram
flavorToProgram GHCJS = Just ghcjsProgram
flavorToProgram UHC = Just uhcProgram
flavorToProgram JHC = Just jhcProgram
flavorToProgram _ = Nothing

mkComponentInfo (name, clbi) = JsonObject
[ "type" .= JsonString compType
, "name" .= JsonString (prettyShow name)
, "unit-id" .= JsonString (prettyShow $ componentUnitId clbi)
, "compiler-args" .= JsonArray (map JsonString $ getCompilerArgs bi lbi clbi)
, "modules" .= JsonArray (map (JsonString . display) modules)
, "src-files" .= JsonArray (map JsonString sourceFiles)
, "src-dirs" .= JsonArray (map JsonString $ hsSourceDirs bi)
]
where
bi = componentBuildInfo comp
Just comp = lookupComponent pkg_descr name
compType = case comp of
CLib _ -> "lib"
CExe _ -> "exe"
CTest _ -> "test"
CBench _ -> "bench"
CFLib _ -> "flib"
modules = case comp of
CLib lib -> explicitLibModules lib
CExe exe -> exeModules exe
_ -> []
sourceFiles = case comp of
CLib _ -> []
CExe exe -> [modulePath exe]
_ -> []

-- | Get the command-line arguments that would be passed
-- to the compiler to build the given component.
getCompilerArgs :: BuildInfo
-> LocalBuildInfo
-> ComponentLocalBuildInfo
-> [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
where
-- This is absolutely awful
ghc = GHC.renderGhcOptions (compiler lbi) (hostPlatform lbi) baseOpts
where
baseOpts = GHC.componentGhcOptions normal lbi bi clbi (buildDir lbi)
46 changes: 46 additions & 0 deletions Cabal/Distribution/Simple/Utils/Json.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- | Utility json lib for Cabal
-- TODO: Remove it again.
module Distribution.Simple.Utils.Json
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file should be removed, is that still true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, definitely! We already have a JSON library (which is more or less a derivative of http://hackage.haskell.org/package/microaeson-0.1.0.0/docs/Data-Aeson-Micro.html) inside exe:cabal.

By moving show-build-info into exe:cabal we'd kill two birds with one stone...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point I don't care whether we address this as part of this PR, or at a later stage; so feel free to consider this issue of the JSON library duplication punted to a follow-up PR

It may be worth leaving a TODO source-comment at the top of this module

( Json(..)
, renderJson
) where

data Json = JsonArray [Json]
| JsonBool !Bool
| JsonNull
| JsonNumber !Int
| JsonObject [(String, Json)]
| JsonString !String

renderJson :: Json -> ShowS
renderJson (JsonArray objs) =
surround "[" "]" $ intercalate "," $ map renderJson objs
renderJson (JsonBool True) = showString "true"
renderJson (JsonBool False) = showString "false"
renderJson JsonNull = showString "null"
renderJson (JsonNumber n) = shows n
renderJson (JsonObject attrs) =
surround "{" "}" $ intercalate "," $ map render attrs
where
render (k,v) = (surround "\"" "\"" $ showString' k) . showString ":" . renderJson v
renderJson (JsonString s) = surround "\"" "\"" $ showString' s

surround :: String -> String -> ShowS -> ShowS
surround begin end middle = showString begin . middle . showString end

showString' :: String -> ShowS
showString' xs = showStringWorker xs
where
showStringWorker :: String -> ShowS
showStringWorker ('\"':as) = showString "\\\"" . showStringWorker as
showStringWorker ('\\':as) = showString "\\\\" . showStringWorker as
showStringWorker ('\'':as) = showString "\\\'" . showStringWorker as
showStringWorker (x:as) = showString [x] . showStringWorker as
showStringWorker [] = showString ""

intercalate :: String -> [ShowS] -> ShowS
intercalate sep = go
where
go [] = id
go [x] = x
go (x:xs) = x . showString' sep . go xs
Loading