1#! /usr/bin/env nix-shell
2#! nix-shell -p "haskellPackages.ghcWithPackages (p: [p.aeson p.req])"
3#! nix-shell -p hydra-unstable
4#! nix-shell -i runhaskell
5
6{-
7
8The purpose of this script is
9
101) download the state of the nixpkgs/haskell-updates job from hydra (with get-report)
112) print a summary of the state suitable for pasting into a github comment (with ping-maintainers)
123) print a list of broken packages suitable for pasting into configuration-hackage2nix.yaml
13
14Because step 1) is quite expensive and takes roughly ~5 minutes the result is cached in a json file in XDG_CACHE.
15
16-}
17{-# LANGUAGE BlockArguments #-}
18{-# LANGUAGE DeriveAnyClass #-}
19{-# LANGUAGE DeriveGeneric #-}
20{-# LANGUAGE DerivingStrategies #-}
21{-# LANGUAGE DuplicateRecordFields #-}
22{-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE MultiWayIf #-}
24{-# LANGUAGE NamedFieldPuns #-}
25{-# LANGUAGE OverloadedStrings #-}
26{-# LANGUAGE ScopedTypeVariables #-}
27{-# LANGUAGE TupleSections #-}
28{-# OPTIONS_GHC -Wall #-}
29{-# LANGUAGE ViewPatterns #-}
30{-# LANGUAGE TupleSections #-}
31
32import Control.Monad (forM_, (<=<))
33import Control.Monad.Trans (MonadIO (liftIO))
34import Data.Aeson (
35 FromJSON,
36 ToJSON,
37 decodeFileStrict',
38 eitherDecodeStrict',
39 encodeFile,
40 )
41import Data.Foldable (Foldable (toList), foldl')
42import Data.List.NonEmpty (NonEmpty, nonEmpty)
43import qualified Data.List.NonEmpty as NonEmpty
44import Data.Map.Strict (Map)
45import qualified Data.Map.Strict as Map
46import Data.Maybe (fromMaybe, mapMaybe, isNothing)
47import Data.Monoid (Sum (Sum, getSum))
48import Data.Sequence (Seq)
49import qualified Data.Sequence as Seq
50import Data.Set (Set)
51import qualified Data.Set as Set
52import Data.Text (Text)
53import qualified Data.Text as Text
54import Data.Text.Encoding (encodeUtf8)
55import Data.Time (defaultTimeLocale, formatTime, getCurrentTime)
56import Data.Time.Clock (UTCTime)
57import GHC.Generics (Generic)
58import Network.HTTP.Req (
59 GET (GET),
60 NoReqBody (NoReqBody),
61 defaultHttpConfig,
62 header,
63 https,
64 jsonResponse,
65 req,
66 responseBody,
67 responseTimeout,
68 runReq,
69 (/:),
70 )
71import System.Directory (XdgDirectory (XdgCache), getXdgDirectory)
72import System.Environment (getArgs)
73import System.Process (readProcess)
74import Prelude hiding (id)
75import Data.List (sortOn)
76import Control.Concurrent.Async (concurrently)
77import Control.Exception (evaluate)
78import qualified Data.IntMap.Strict as IntMap
79import qualified Data.IntSet as IntSet
80import Data.Bifunctor (second)
81
82newtype JobsetEvals = JobsetEvals
83 { evals :: Seq Eval
84 }
85 deriving (Generic, ToJSON, FromJSON, Show)
86
87newtype Nixpkgs = Nixpkgs {revision :: Text}
88 deriving (Generic, ToJSON, FromJSON, Show)
89
90newtype JobsetEvalInputs = JobsetEvalInputs {nixpkgs :: Nixpkgs}
91 deriving (Generic, ToJSON, FromJSON, Show)
92
93data Eval = Eval
94 { id :: Int
95 , jobsetevalinputs :: JobsetEvalInputs
96 }
97 deriving (Generic, ToJSON, FromJSON, Show)
98
99data Build = Build
100 { job :: Text
101 , buildstatus :: Maybe Int
102 , finished :: Int
103 , id :: Int
104 , nixname :: Text
105 , system :: Text
106 , jobsetevals :: Seq Int
107 }
108 deriving (Generic, ToJSON, FromJSON, Show)
109
110main :: IO ()
111main = do
112 args <- getArgs
113 case args of
114 ["get-report"] -> getBuildReports
115 ["ping-maintainers"] -> printMaintainerPing
116 ["mark-broken-list"] -> printMarkBrokenList
117 _ -> putStrLn "Usage: get-report | ping-maintainers | mark-broken-list"
118
119reportFileName :: IO FilePath
120reportFileName = getXdgDirectory XdgCache "haskell-updates-build-report.json"
121
122showT :: Show a => a -> Text
123showT = Text.pack . show
124
125getBuildReports :: IO ()
126getBuildReports = runReq defaultHttpConfig do
127 evalMay <- Seq.lookup 0 . evals <$> myReq (https "hydra.nixos.org" /: "jobset" /: "nixpkgs" /: "haskell-updates" /: "evals") mempty
128 eval@Eval{id} <- maybe (liftIO $ fail "No Evalution found") pure evalMay
129 liftIO . putStrLn $ "Fetching evaluation " <> show id <> " from Hydra. This might take a few minutes..."
130 buildReports :: Seq Build <- myReq (https "hydra.nixos.org" /: "eval" /: showT id /: "builds") (responseTimeout 600000000)
131 liftIO do
132 fileName <- reportFileName
133 putStrLn $ "Finished fetching all builds from Hydra, saving report as " <> fileName
134 now <- getCurrentTime
135 encodeFile fileName (eval, now, buildReports)
136 where
137 myReq query option = responseBody <$> req GET query NoReqBody jsonResponse (header "User-Agent" "hydra-report.hs/v1 (nixpkgs;maintainers/scripts/haskell)" <> option)
138
139hydraEvalCommand :: FilePath
140hydraEvalCommand = "hydra-eval-jobs"
141
142hydraEvalParams :: [String]
143hydraEvalParams = ["-I", ".", "pkgs/top-level/release-haskell.nix"]
144
145nixExprCommand :: FilePath
146nixExprCommand = "nix-instantiate"
147
148nixExprParams :: [String]
149nixExprParams = ["--eval", "--strict", "--json"]
150
151-- | This newtype is used to parse a Hydra job output from @hydra-eval-jobs@.
152-- The only field we are interested in is @maintainers@, which is why this
153-- is just a newtype.
154--
155-- Note that there are occasionally jobs that don't have a maintainers
156-- field, which is why this has to be @Maybe Text@.
157newtype Maintainers = Maintainers { maintainers :: Maybe Text }
158 deriving stock (Generic, Show)
159 deriving anyclass (FromJSON, ToJSON)
160
161-- | This is a 'Map' from Hydra job name to maintainer email addresses.
162--
163-- It has values similar to the following:
164--
165-- @@
166-- fromList
167-- [ ("arion.aarch64-linux", Maintainers (Just "robert@example.com"))
168-- , ("bench.x86_64-linux", Maintainers (Just ""))
169-- , ("conduit.x86_64-linux", Maintainers (Just "snoy@man.com, web@ber.com"))
170-- , ("lens.x86_64-darwin", Maintainers (Just "ek@category.com"))
171-- ]
172-- @@
173--
174-- Note that Hydra jobs without maintainers will have an empty string for the
175-- maintainer list.
176type HydraJobs = Map Text Maintainers
177
178-- | Map of email addresses to GitHub handles.
179-- This is built from the file @../../maintainer-list.nix@.
180--
181-- It has values similar to the following:
182--
183-- @@
184-- fromList
185-- [ ("robert@example.com", "rob22")
186-- , ("ek@category.com", "edkm")
187-- ]
188-- @@
189type EmailToGitHubHandles = Map Text Text
190
191-- | Map of Hydra jobs to maintainer GitHub handles.
192--
193-- It has values similar to the following:
194--
195-- @@
196-- fromList
197-- [ ("arion.aarch64-linux", ["rob22"])
198-- , ("conduit.x86_64-darwin", ["snoyb", "webber"])
199-- ]
200-- @@
201type MaintainerMap = Map Text (NonEmpty Text)
202
203-- | Information about a package which lists its dependencies and whether the
204-- package is marked broken.
205data DepInfo = DepInfo {
206 deps :: Set Text,
207 broken :: Bool
208}
209 deriving stock (Generic, Show)
210 deriving anyclass (FromJSON, ToJSON)
211
212-- | Map from package names to their DepInfo. This is the data we get out of a
213-- nix call.
214type DependencyMap = Map Text DepInfo
215
216-- | Map from package names to its broken state, number of reverse dependencies (fst) and
217-- unbroken reverse dependencies (snd).
218type ReverseDependencyMap = Map Text (Int, Int)
219
220-- | Calculate the (unbroken) reverse dependencies of a package by transitively
221-- going through all packages if it’s a dependency of them.
222calculateReverseDependencies :: DependencyMap -> ReverseDependencyMap
223calculateReverseDependencies depMap = Map.fromDistinctAscList $ zip keys (zip (rdepMap False) (rdepMap True))
224 where
225 -- This code tries to efficiently invert the dependency map and calculate
226 -- it’s transitive closure by internally identifying every pkg with it’s index
227 -- in the package list and then using memoization.
228 keys = Map.keys depMap
229 pkgToIndexMap = Map.fromDistinctAscList (zip keys [0..])
230 intDeps = zip [0..] $ (\DepInfo{broken,deps} -> (broken,mapMaybe (`Map.lookup` pkgToIndexMap) $ Set.toList deps)) <$> Map.elems depMap
231 rdepMap onlyUnbroken = IntSet.size <$> resultList
232 where
233 resultList = go <$> [0..]
234 oneStepMap = IntMap.fromListWith IntSet.union $ (\(key,(_,deps)) -> (,IntSet.singleton key) <$> deps) <=< filter (\(_, (broken,_)) -> not (broken && onlyUnbroken)) $ intDeps
235 go pkg = IntSet.unions (oneStep:((resultList !!) <$> IntSet.toList oneStep))
236 where oneStep = IntMap.findWithDefault mempty pkg oneStepMap
237
238-- | Generate a mapping of Hydra job names to maintainer GitHub handles. Calls
239-- hydra-eval-jobs and the nix script ./maintainer-handles.nix.
240getMaintainerMap :: IO MaintainerMap
241getMaintainerMap = do
242 hydraJobs :: HydraJobs <-
243 readJSONProcess hydraEvalCommand hydraEvalParams "Failed to decode hydra-eval-jobs output: "
244 handlesMap :: EmailToGitHubHandles <-
245 readJSONProcess nixExprCommand ("maintainers/scripts/haskell/maintainer-handles.nix":nixExprParams) "Failed to decode nix output for lookup of github handles: "
246 pure $ Map.mapMaybe (splitMaintainersToGitHubHandles handlesMap) hydraJobs
247 where
248 -- Split a comma-spearated string of Maintainers into a NonEmpty list of
249 -- GitHub handles.
250 splitMaintainersToGitHubHandles
251 :: EmailToGitHubHandles -> Maintainers -> Maybe (NonEmpty Text)
252 splitMaintainersToGitHubHandles handlesMap (Maintainers maint) =
253 nonEmpty . mapMaybe (`Map.lookup` handlesMap) . Text.splitOn ", " $ fromMaybe "" maint
254
255-- | Get the a map of all dependencies of every package by calling the nix
256-- script ./dependencies.nix.
257getDependencyMap :: IO DependencyMap
258getDependencyMap =
259 readJSONProcess nixExprCommand ("maintainers/scripts/haskell/dependencies.nix":nixExprParams) "Failed to decode nix output for lookup of dependencies: "
260
261-- | Run a process that produces JSON on stdout and and decode the JSON to a
262-- data type.
263--
264-- If the JSON-decoding fails, throw the JSON-decoding error.
265readJSONProcess
266 :: FromJSON a
267 => FilePath -- ^ Filename of executable.
268 -> [String] -- ^ Arguments
269 -> String -- ^ String to prefix to JSON-decode error.
270 -> IO a
271readJSONProcess exe args err = do
272 output <- readProcess exe args ""
273 let eitherDecodedOutput = eitherDecodeStrict' . encodeUtf8 . Text.pack $ output
274 case eitherDecodedOutput of
275 Left decodeErr -> error $ err <> decodeErr <> "\nRaw: '" <> take 1000 output <> "'"
276 Right decodedOutput -> pure decodedOutput
277
278-- BuildStates are sorted by subjective importance/concerningness
279data BuildState
280 = Failed
281 | DependencyFailed
282 | OutputLimitExceeded
283 | Unknown (Maybe Int)
284 | TimedOut
285 | Canceled
286 | HydraFailure
287 | Unfinished
288 | Success
289 deriving stock (Show, Eq, Ord)
290
291icon :: BuildState -> Text
292icon = \case
293 Failed -> ":x:"
294 DependencyFailed -> ":heavy_exclamation_mark:"
295 OutputLimitExceeded -> ":warning:"
296 Unknown x -> "unknown code " <> showT x
297 TimedOut -> ":hourglass::no_entry_sign:"
298 Canceled -> ":no_entry_sign:"
299 Unfinished -> ":hourglass_flowing_sand:"
300 HydraFailure -> ":construction:"
301 Success -> ":heavy_check_mark:"
302
303platformIcon :: Platform -> Text
304platformIcon (Platform x) = case x of
305 "x86_64-linux" -> ":penguin:"
306 "aarch64-linux" -> ":iphone:"
307 "x86_64-darwin" -> ":apple:"
308 _ -> x
309
310data BuildResult = BuildResult {state :: BuildState, id :: Int} deriving (Show, Eq, Ord)
311newtype Platform = Platform {platform :: Text} deriving (Show, Eq, Ord)
312newtype Table row col a = Table (Map (row, col) a)
313data SummaryEntry = SummaryEntry {
314 summaryBuilds :: Table Text Platform BuildResult,
315 summaryMaintainers :: Set Text,
316 summaryReverseDeps :: Int,
317 summaryUnbrokenReverseDeps :: Int
318}
319type StatusSummary = Map Text SummaryEntry
320
321instance (Ord row, Ord col, Semigroup a) => Semigroup (Table row col a) where
322 Table l <> Table r = Table (Map.unionWith (<>) l r)
323instance (Ord row, Ord col, Semigroup a) => Monoid (Table row col a) where
324 mempty = Table Map.empty
325instance Functor (Table row col) where
326 fmap f (Table a) = Table (fmap f a)
327instance Foldable (Table row col) where
328 foldMap f (Table a) = foldMap f a
329
330buildSummary :: MaintainerMap -> ReverseDependencyMap -> Seq Build -> StatusSummary
331buildSummary maintainerMap reverseDependencyMap = foldl (Map.unionWith unionSummary) Map.empty . fmap toSummary
332 where
333 unionSummary (SummaryEntry (Table lb) lm lr lu) (SummaryEntry (Table rb) rm rr ru) = SummaryEntry (Table $ Map.union lb rb) (lm <> rm) (max lr rr) (max lu ru)
334 toSummary Build{finished, buildstatus, job, id, system} = Map.singleton name (SummaryEntry (Table (Map.singleton (set, Platform system) (BuildResult state id))) maintainers reverseDeps unbrokenReverseDeps)
335 where
336 state :: BuildState
337 state = case (finished, buildstatus) of
338 (0, _) -> Unfinished
339 (_, Just 0) -> Success
340 (_, Just 1) -> Failed
341 (_, Just 2) -> DependencyFailed
342 (_, Just 3) -> HydraFailure
343 (_, Just 4) -> Canceled
344 (_, Just 7) -> TimedOut
345 (_, Just 11) -> OutputLimitExceeded
346 (_, i) -> Unknown i
347 packageName = fromMaybe job (Text.stripSuffix ("." <> system) job)
348 splitted = nonEmpty $ Text.splitOn "." packageName
349 name = maybe packageName NonEmpty.last splitted
350 set = maybe "" (Text.intercalate "." . NonEmpty.init) splitted
351 maintainers = maybe mempty (Set.fromList . toList) (Map.lookup job maintainerMap)
352 (reverseDeps, unbrokenReverseDeps) = Map.findWithDefault (0,0) name reverseDependencyMap
353
354readBuildReports :: IO (Eval, UTCTime, Seq Build)
355readBuildReports = do
356 file <- reportFileName
357 fromMaybe (error $ "Could not decode " <> file) <$> decodeFileStrict' file
358
359sep :: Text
360sep = " | "
361joinTable :: [Text] -> Text
362joinTable t = sep <> Text.intercalate sep t <> sep
363
364type NumSummary = Table Platform BuildState Int
365
366printTable :: (Ord rows, Ord cols) => Text -> (rows -> Text) -> (cols -> Text) -> (entries -> Text) -> Table rows cols entries -> [Text]
367printTable name showR showC showE (Table mapping) = joinTable <$> (name : map showC cols) : replicate (length cols + sepsInName + 1) "---" : map printRow rows
368 where
369 sepsInName = Text.count "|" name
370 printRow row = showR row : map (\col -> maybe "" showE (Map.lookup (row, col) mapping)) cols
371 rows = toList $ Set.fromList (fst <$> Map.keys mapping)
372 cols = toList $ Set.fromList (snd <$> Map.keys mapping)
373
374printJob :: Int -> Text -> (Table Text Platform BuildResult, Text) -> [Text]
375printJob evalId name (Table mapping, maintainers) =
376 if length sets <= 1
377 then map printSingleRow sets
378 else ["- [ ] " <> makeJobSearchLink "" name <> " " <> maintainers] <> map printRow sets
379 where
380 printRow set = " - " <> printState set <> " " <> makeJobSearchLink set (if Text.null set then "toplevel" else set)
381 printSingleRow set = "- [ ] " <> printState set <> " " <> makeJobSearchLink set (makePkgName set) <> " " <> maintainers
382 makePkgName set = (if Text.null set then "" else set <> ".") <> name
383 printState set = Text.intercalate " " $ map (\pf -> maybe "" (label pf) $ Map.lookup (set, pf) mapping) platforms
384 makeJobSearchLink set linkLabel= makeSearchLink evalId linkLabel (makePkgName set)
385 sets = toList $ Set.fromList (fst <$> Map.keys mapping)
386 platforms = toList $ Set.fromList (snd <$> Map.keys mapping)
387 label pf (BuildResult s i) = "[[" <> platformIcon pf <> icon s <> "]](https://hydra.nixos.org/build/" <> showT i <> ")"
388
389makeSearchLink :: Int -> Text -> Text -> Text
390makeSearchLink evalId linkLabel query = "[" <> linkLabel <> "](" <> "https://hydra.nixos.org/eval/" <> showT evalId <> "?filter=" <> query <> ")"
391
392statusToNumSummary :: StatusSummary -> NumSummary
393statusToNumSummary = fmap getSum . foldMap (fmap Sum . jobTotals)
394
395jobTotals :: SummaryEntry -> Table Platform BuildState Int
396jobTotals (summaryBuilds -> Table mapping) = getSum <$> Table (Map.foldMapWithKey (\(_, platform) (BuildResult buildstate _) -> Map.singleton (platform, buildstate) (Sum 1)) mapping)
397
398details :: Text -> [Text] -> [Text]
399details summary content = ["<details><summary>" <> summary <> " </summary>", ""] <> content <> ["</details>", ""]
400
401printBuildSummary :: Eval -> UTCTime -> StatusSummary -> [(Text, Int)] -> Text
402printBuildSummary
403 Eval{id, jobsetevalinputs = JobsetEvalInputs{nixpkgs = Nixpkgs{revision}}}
404 fetchTime
405 summary
406 topBrokenRdeps =
407 Text.unlines $
408 headline <> [""] <> tldr <> ((" * "<>) <$> (errors <> warnings)) <> [""]
409 <> totals
410 <> optionalList "#### Maintained packages with build failure" (maintainedList fails)
411 <> optionalList "#### Maintained packages with failed dependency" (maintainedList failedDeps)
412 <> optionalList "#### Maintained packages with unknown error" (maintainedList unknownErr)
413 <> optionalHideableList "#### Unmaintained packages with build failure" (unmaintainedList fails)
414 <> optionalHideableList "#### Unmaintained packages with failed dependency" (unmaintainedList failedDeps)
415 <> optionalHideableList "#### Unmaintained packages with unknown error" (unmaintainedList unknownErr)
416 <> optionalHideableList "#### Top 50 broken packages, sorted by number of reverse dependencies" (brokenLine <$> topBrokenRdeps)
417 <> ["","*:arrow_heading_up:: The number of packages that depend (directly or indirectly) on this package (if any). If two numbers are shown the first (lower) number considers only packages which currently have enabled hydra jobs, i.e. are not marked broken. The second (higher) number considers all packages.*",""]
418 <> footer
419 where
420 footer = ["*Report generated with [maintainers/scripts/haskell/hydra-report.hs](https://github.com/NixOS/nixpkgs/blob/haskell-updates/maintainers/scripts/haskell/hydra-report.sh)*"]
421 totals =
422 [ "#### Build summary"
423 , ""
424 ]
425 <> printTable "Platform" (\x -> makeSearchLink id (platform x <> " " <> platformIcon x) ("." <> platform x)) (\x -> showT x <> " " <> icon x) showT numSummary
426 headline =
427 [ "### [haskell-updates build report from hydra](https://hydra.nixos.org/jobset/nixpkgs/haskell-updates)"
428 , "*evaluation ["
429 <> showT id
430 <> "](https://hydra.nixos.org/eval/"
431 <> showT id
432 <> ") of nixpkgs commit ["
433 <> Text.take 7 revision
434 <> "](https://github.com/NixOS/nixpkgs/commits/"
435 <> revision
436 <> ") as of "
437 <> Text.pack (formatTime defaultTimeLocale "%Y-%m-%d %H:%M UTC" fetchTime)
438 <> "*"
439 ]
440 brokenLine (name, rdeps) = "[" <> name <> "](https://packdeps.haskellers.com/reverse/" <> name <> ") :arrow_heading_up: " <> Text.pack (show rdeps) <> " "
441 numSummary = statusToNumSummary summary
442 jobsByState predicate = Map.filter (predicate . worstState) summary
443 worstState = foldl' min Success . fmap state . summaryBuilds
444 fails = jobsByState (== Failed)
445 failedDeps = jobsByState (== DependencyFailed)
446 unknownErr = jobsByState (\x -> x > DependencyFailed && x < TimedOut)
447 withMaintainer = Map.mapMaybe (\e -> (summaryBuilds e,) <$> nonEmpty (Set.toList (summaryMaintainers e)))
448 withoutMaintainer = Map.mapMaybe (\e -> if Set.null (summaryMaintainers e) then Just e else Nothing)
449 optionalList heading list = if null list then mempty else [heading] <> list
450 optionalHideableList heading list = if null list then mempty else [heading] <> details (showT (length list) <> " job(s)") list
451 maintainedList = showMaintainedBuild <=< Map.toList . withMaintainer
452 unmaintainedList = showBuild <=< sortOn (\(snd -> x) -> (negate (summaryUnbrokenReverseDeps x), negate (summaryReverseDeps x))) . Map.toList . withoutMaintainer
453 showBuild (name, entry) = printJob id name (summaryBuilds entry, Text.pack (if summaryReverseDeps entry > 0 then " :arrow_heading_up: " <> show (summaryUnbrokenReverseDeps entry) <>" | "<> show (summaryReverseDeps entry) else ""))
454 showMaintainedBuild (name, (table, maintainers)) = printJob id name (table, Text.intercalate " " (fmap ("@" <>) (toList maintainers)))
455 tldr = case (errors, warnings) of
456 ([],[]) -> [":green_circle: **Ready to merge**"]
457 ([],_) -> [":yellow_circle: **Potential issues**"]
458 _ -> [":red_circle: **Branch not mergeable**"]
459 warnings =
460 if' (Unfinished > maybe Success worstState maintainedJob) "`maintained` jobset failed." <>
461 if' (Unfinished == maybe Success worstState mergeableJob) "`mergeable` jobset is not finished." <>
462 if' (Unfinished == maybe Success worstState maintainedJob) "`maintained` jobset is not finished."
463 errors =
464 if' (isNothing mergeableJob) "No `mergeable` job found." <>
465 if' (isNothing maintainedJob) "No `maintained` job found." <>
466 if' (Unfinished > maybe Success worstState mergeableJob) "`mergeable` jobset failed." <>
467 if' (outstandingJobs (Platform "x86_64-linux") > 100) "Too many outstanding jobs on x86_64-linux." <>
468 if' (outstandingJobs (Platform "aarch64-linux") > 100) "Too many outstanding jobs on aarch64-linux."
469 if' p e = if p then [e] else mempty
470 outstandingJobs platform | Table m <- numSummary = Map.findWithDefault 0 (platform, Unfinished) m
471 maintainedJob = Map.lookup "maintained" summary
472 mergeableJob = Map.lookup "mergeable" summary
473
474printMaintainerPing :: IO ()
475printMaintainerPing = do
476 (maintainerMap, (reverseDependencyMap, topBrokenRdeps)) <- concurrently getMaintainerMap do
477 depMap <- getDependencyMap
478 rdepMap <- evaluate . calculateReverseDependencies $ depMap
479 let tops = take 50 . sortOn (negate . snd) . fmap (second fst) . filter (\x -> maybe False broken $ Map.lookup (fst x) depMap) . Map.toList $ rdepMap
480 pure (rdepMap, tops)
481 (eval, fetchTime, buildReport) <- readBuildReports
482 putStrLn (Text.unpack (printBuildSummary eval fetchTime (buildSummary maintainerMap reverseDependencyMap buildReport) topBrokenRdeps))
483
484printMarkBrokenList :: IO ()
485printMarkBrokenList = do
486 (_, _, buildReport) <- readBuildReports
487 forM_ buildReport \Build{buildstatus, job} ->
488 case (buildstatus, Text.splitOn "." job) of
489 (Just 1, ["haskellPackages", name, "x86_64-linux"]) -> putStrLn $ " - " <> Text.unpack name
490 _ -> pure ()