PageRenderTime 30ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 1ms

/cabal-install/Distribution/Client/BuildReports/Anonymous.hs

https://gitlab.com/kranium/cabal
Haskell | 315 lines | 230 code | 41 blank | 44 comment | 4 complexity | af35580eade0d254066ff1324ab8a525 MD5 | raw file
  1. -----------------------------------------------------------------------------
  2. -- |
  3. -- Module : Distribution.Client.Reporting
  4. -- Copyright : (c) David Waern 2008
  5. -- License : BSD-like
  6. --
  7. -- Maintainer : david.waern@gmail.com
  8. -- Stability : experimental
  9. -- Portability : portable
  10. --
  11. -- Anonymous build report data structure, printing and parsing
  12. --
  13. -----------------------------------------------------------------------------
  14. module Distribution.Client.BuildReports.Anonymous (
  15. BuildReport(..),
  16. InstallOutcome(..),
  17. Outcome(..),
  18. -- * Constructing and writing reports
  19. new,
  20. -- * parsing and pretty printing
  21. parse,
  22. parseList,
  23. show,
  24. -- showList,
  25. ) where
  26. import Distribution.Client.Types
  27. ( ConfiguredPackage(..) )
  28. import qualified Distribution.Client.Types as BR
  29. ( BuildResult, BuildFailure(..), BuildSuccess(..)
  30. , DocsResult(..), TestsResult(..) )
  31. import Distribution.Client.Utils
  32. ( mergeBy, MergeResult(..) )
  33. import qualified Paths_cabal_install (version)
  34. import Distribution.Package
  35. ( PackageIdentifier(..), PackageName(..), Package(packageId) )
  36. import Distribution.PackageDescription
  37. ( FlagName(..), FlagAssignment )
  38. --import Distribution.Version
  39. -- ( Version )
  40. import Distribution.System
  41. ( OS, Arch )
  42. import Distribution.Compiler
  43. ( CompilerId )
  44. import qualified Distribution.Text as Text
  45. ( Text(disp, parse) )
  46. import Distribution.ParseUtils
  47. ( FieldDescr(..), ParseResult(..), Field(..)
  48. , simpleField, listField, ppFields, readFields
  49. , syntaxError, locatedErrorMsg )
  50. import Distribution.Simple.Utils
  51. ( comparing )
  52. import qualified Distribution.Compat.ReadP as Parse
  53. ( ReadP, pfail, munch1, skipSpaces )
  54. import qualified Text.PrettyPrint as Disp
  55. ( Doc, render, char, text )
  56. import Text.PrettyPrint
  57. ( (<+>), (<>) )
  58. import Data.List
  59. ( unfoldr, sortBy )
  60. import Data.Char as Char
  61. ( isAlpha, isAlphaNum )
  62. import Prelude hiding (show)
  63. data BuildReport
  64. = BuildReport {
  65. -- | The package this build report is about
  66. package :: PackageIdentifier,
  67. -- | The OS and Arch the package was built on
  68. os :: OS,
  69. arch :: Arch,
  70. -- | The Haskell compiler (and hopefully version) used
  71. compiler :: CompilerId,
  72. -- | The uploading client, ie cabal-install-x.y.z
  73. client :: PackageIdentifier,
  74. -- | Which configurations flags we used
  75. flagAssignment :: FlagAssignment,
  76. -- | Which dependent packages we were using exactly
  77. dependencies :: [PackageIdentifier],
  78. -- | Did installing work ok?
  79. installOutcome :: InstallOutcome,
  80. -- Which version of the Cabal library was used to compile the Setup.hs
  81. -- cabalVersion :: Version,
  82. -- Which build tools we were using (with versions)
  83. -- tools :: [PackageIdentifier],
  84. -- | Configure outcome, did configure work ok?
  85. docsOutcome :: Outcome,
  86. -- | Configure outcome, did configure work ok?
  87. testsOutcome :: Outcome
  88. }
  89. data InstallOutcome
  90. = DependencyFailed PackageIdentifier
  91. | DownloadFailed
  92. | UnpackFailed
  93. | SetupFailed
  94. | ConfigureFailed
  95. | BuildFailed
  96. | TestsFailed
  97. | InstallFailed
  98. | InstallOk
  99. deriving Eq
  100. data Outcome = NotTried | Failed | Ok
  101. deriving Eq
  102. new :: OS -> Arch -> CompilerId -- -> Version
  103. -> ConfiguredPackage -> BR.BuildResult
  104. -> BuildReport
  105. new os' arch' comp (ConfiguredPackage pkg flags _ deps) result =
  106. BuildReport {
  107. package = packageId pkg,
  108. os = os',
  109. arch = arch',
  110. compiler = comp,
  111. client = cabalInstallID,
  112. flagAssignment = flags,
  113. dependencies = deps,
  114. installOutcome = convertInstallOutcome,
  115. -- cabalVersion = undefined
  116. docsOutcome = convertDocsOutcome,
  117. testsOutcome = convertTestsOutcome
  118. }
  119. where
  120. convertInstallOutcome = case result of
  121. Left (BR.DependentFailed p) -> DependencyFailed p
  122. Left (BR.DownloadFailed _) -> DownloadFailed
  123. Left (BR.UnpackFailed _) -> UnpackFailed
  124. Left (BR.ConfigureFailed _) -> ConfigureFailed
  125. Left (BR.BuildFailed _) -> BuildFailed
  126. Left (BR.TestsFailed _) -> TestsFailed
  127. Left (BR.InstallFailed _) -> InstallFailed
  128. Right (BR.BuildOk _ _) -> InstallOk
  129. convertDocsOutcome = case result of
  130. Left _ -> NotTried
  131. Right (BR.BuildOk BR.DocsNotTried _) -> NotTried
  132. Right (BR.BuildOk BR.DocsFailed _) -> Failed
  133. Right (BR.BuildOk BR.DocsOk _) -> Ok
  134. convertTestsOutcome = case result of
  135. Left (BR.TestsFailed _) -> Failed
  136. Left _ -> NotTried
  137. Right (BR.BuildOk _ BR.TestsNotTried) -> NotTried
  138. Right (BR.BuildOk _ BR.TestsOk) -> Ok
  139. cabalInstallID :: PackageIdentifier
  140. cabalInstallID =
  141. PackageIdentifier (PackageName "cabal-install") Paths_cabal_install.version
  142. -- ------------------------------------------------------------
  143. -- * External format
  144. -- ------------------------------------------------------------
  145. initialBuildReport :: BuildReport
  146. initialBuildReport = BuildReport {
  147. package = requiredField "package",
  148. os = requiredField "os",
  149. arch = requiredField "arch",
  150. compiler = requiredField "compiler",
  151. client = requiredField "client",
  152. flagAssignment = [],
  153. dependencies = [],
  154. installOutcome = requiredField "install-outcome",
  155. -- cabalVersion = Nothing,
  156. -- tools = [],
  157. docsOutcome = NotTried,
  158. testsOutcome = NotTried
  159. }
  160. where
  161. requiredField fname = error ("required field: " ++ fname)
  162. -- -----------------------------------------------------------------------------
  163. -- Parsing
  164. parse :: String -> Either String BuildReport
  165. parse s = case parseFields s of
  166. ParseFailed perror -> Left msg where (_, msg) = locatedErrorMsg perror
  167. ParseOk _ report -> Right report
  168. --FIXME: this does not allow for optional or repeated fields
  169. parseFields :: String -> ParseResult BuildReport
  170. parseFields input = do
  171. fields <- mapM extractField =<< readFields input
  172. let merged = mergeBy (\desc (_,name,_) -> compare (fieldName desc) name)
  173. sortedFieldDescrs
  174. (sortBy (comparing (\(_,name,_) -> name)) fields)
  175. checkMerged initialBuildReport merged
  176. where
  177. extractField :: Field -> ParseResult (Int, String, String)
  178. extractField (F line name value) = return (line, name, value)
  179. extractField (Section line _ _ _) = syntaxError line "Unrecognized stanza"
  180. extractField (IfBlock line _ _ _) = syntaxError line "Unrecognized stanza"
  181. checkMerged report [] = return report
  182. checkMerged report (merged:remaining) = case merged of
  183. InBoth fieldDescr (line, _name, value) -> do
  184. report' <- fieldSet fieldDescr line value report
  185. checkMerged report' remaining
  186. OnlyInRight (line, name, _) ->
  187. syntaxError line ("Unrecognized field " ++ name)
  188. OnlyInLeft fieldDescr ->
  189. fail ("Missing field " ++ fieldName fieldDescr)
  190. parseList :: String -> [BuildReport]
  191. parseList str =
  192. [ report | Right report <- map parse (split str) ]
  193. where
  194. split :: String -> [String]
  195. split = filter (not . null) . unfoldr chunk . lines
  196. chunk [] = Nothing
  197. chunk ls = case break null ls of
  198. (r, rs) -> Just (unlines r, dropWhile null rs)
  199. -- -----------------------------------------------------------------------------
  200. -- Pretty-printing
  201. show :: BuildReport -> String
  202. show = Disp.render . ppFields fieldDescrs
  203. -- -----------------------------------------------------------------------------
  204. -- Description of the fields, for parsing/printing
  205. fieldDescrs :: [FieldDescr BuildReport]
  206. fieldDescrs =
  207. [ simpleField "package" Text.disp Text.parse
  208. package (\v r -> r { package = v })
  209. , simpleField "os" Text.disp Text.parse
  210. os (\v r -> r { os = v })
  211. , simpleField "arch" Text.disp Text.parse
  212. arch (\v r -> r { arch = v })
  213. , simpleField "compiler" Text.disp Text.parse
  214. compiler (\v r -> r { compiler = v })
  215. , simpleField "client" Text.disp Text.parse
  216. client (\v r -> r { client = v })
  217. , listField "flags" dispFlag parseFlag
  218. flagAssignment (\v r -> r { flagAssignment = v })
  219. , listField "dependencies" Text.disp Text.parse
  220. dependencies (\v r -> r { dependencies = v })
  221. , simpleField "install-outcome" Text.disp Text.parse
  222. installOutcome (\v r -> r { installOutcome = v })
  223. , simpleField "docs-outcome" Text.disp Text.parse
  224. docsOutcome (\v r -> r { docsOutcome = v })
  225. , simpleField "tests-outcome" Text.disp Text.parse
  226. testsOutcome (\v r -> r { testsOutcome = v })
  227. ]
  228. sortedFieldDescrs :: [FieldDescr BuildReport]
  229. sortedFieldDescrs = sortBy (comparing fieldName) fieldDescrs
  230. dispFlag :: (FlagName, Bool) -> Disp.Doc
  231. dispFlag (FlagName name, True) = Disp.text name
  232. dispFlag (FlagName name, False) = Disp.char '-' <> Disp.text name
  233. parseFlag :: Parse.ReadP r (FlagName, Bool)
  234. parseFlag = do
  235. name <- Parse.munch1 (\c -> Char.isAlphaNum c || c == '_' || c == '-')
  236. case name of
  237. ('-':flag) -> return (FlagName flag, False)
  238. flag -> return (FlagName flag, True)
  239. instance Text.Text InstallOutcome where
  240. disp (DependencyFailed pkgid) = Disp.text "DependencyFailed" <+> Text.disp pkgid
  241. disp DownloadFailed = Disp.text "DownloadFailed"
  242. disp UnpackFailed = Disp.text "UnpackFailed"
  243. disp SetupFailed = Disp.text "SetupFailed"
  244. disp ConfigureFailed = Disp.text "ConfigureFailed"
  245. disp BuildFailed = Disp.text "BuildFailed"
  246. disp TestsFailed = Disp.text "TestsFailed"
  247. disp InstallFailed = Disp.text "InstallFailed"
  248. disp InstallOk = Disp.text "InstallOk"
  249. parse = do
  250. name <- Parse.munch1 Char.isAlphaNum
  251. case name of
  252. "DependencyFailed" -> do Parse.skipSpaces
  253. pkgid <- Text.parse
  254. return (DependencyFailed pkgid)
  255. "DownloadFailed" -> return DownloadFailed
  256. "UnpackFailed" -> return UnpackFailed
  257. "SetupFailed" -> return SetupFailed
  258. "ConfigureFailed" -> return ConfigureFailed
  259. "BuildFailed" -> return BuildFailed
  260. "TestsFailed" -> return TestsFailed
  261. "InstallFailed" -> return InstallFailed
  262. "InstallOk" -> return InstallOk
  263. _ -> Parse.pfail
  264. instance Text.Text Outcome where
  265. disp NotTried = Disp.text "NotTried"
  266. disp Failed = Disp.text "Failed"
  267. disp Ok = Disp.text "Ok"
  268. parse = do
  269. name <- Parse.munch1 Char.isAlpha
  270. case name of
  271. "NotTried" -> return NotTried
  272. "Failed" -> return Failed
  273. "Ok" -> return Ok
  274. _ -> Parse.pfail