[Git][ghc/ghc][wip/boot-lib-testing] 2 commits: check-submodules: initial commit

Ben Gamari (@bgamari) gitlab at gitlab.haskell.org
Sat Dec 21 01:09:04 UTC 2024



Ben Gamari pushed to branch wip/boot-lib-testing at Glasgow Haskell Compiler / GHC


Commits:
663143d3 by Ben Gamari at 2024-12-20T20:08:53-05:00
check-submodules: initial commit

- - - - -
ba3deaf9 by Ben Gamari at 2024-12-20T20:08:53-05:00
gitlab-ci: Add boot library linting steps

- - - - -


15 changed files:

- .gitlab-ci.yml
- + utils/check-submodules/LICENSE
- + utils/check-submodules/README.mkd
- + utils/check-submodules/app/Main.hs
- + utils/check-submodules/check-submodules.cabal
- + utils/check-submodules/flake.lock
- + utils/check-submodules/flake.nix
- + utils/check-submodules/hie.yaml
- + utils/check-submodules/src/CheckTags.hs
- + utils/check-submodules/src/CheckVersions.hs
- + utils/check-submodules/src/Git.hs
- + utils/check-submodules/src/Hackage.hs
- + utils/check-submodules/src/Package.hs
- + utils/check-submodules/src/Packages.hs
- + utils/check-submodules/src/Pretty.hs


Changes:

=====================================
.gitlab-ci.yml
=====================================
@@ -205,6 +205,23 @@ not-interruptible:
     - if: $NIGHTLY
       when: always
 
+.nix:
+  image: nixos/nix:2.25.2
+  before_script:
+    - echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
+    # Note [Nix-in-Docker]
+    # ~~~~~~~~~~~~~~~~~~~~
+    # The nixos/nix default config is max-jobs=1 and cores=$(logical
+    # cores num) which doesn't play nice with our $CPUS convention. We
+    # fix it before invoking any nix build to avoid oversubscribing
+    # while allowing a reasonable degree of parallelism.
+    # FIXME: Disabling build-users-group=nixbld is a workaround for a Nix-in-Docker issue. See
+    # https://gitlab.haskell.org/ghc/head.hackage/-/issues/38#note_560487 for
+    # discussion.
+    - echo "cores = $CPUS" >> /etc/nix/nix.conf
+    - echo "max-jobs = $CPUS" >> /etc/nix/nix.conf
+    - nix run nixpkgs#gnused -- -i -e 's/ nixbld//' /etc/nix/nix.conf
+
 
 ############################################################
 # Validate jobs
@@ -255,6 +272,24 @@ typecheck-testsuite:
     - mypy testsuite/driver/runtests.py
   dependencies: []
 
+lint-boot-tags:
+  extends: [.lint, .nix]
+  script:
+    - nix run ./utils/check-submodules# -- check-tags
+  rules:
+    - if: $RELEASE_JOB
+      allow_failure: false
+    - allow_failure: true
+
+lint-boot-versions:
+  extends: [.lint, .nix]
+  script:
+    - nix run ./utils/check-submodules# -- check-versions
+  rules:
+    - if: $RELEASE_JOB
+      allow_failure: false
+    - allow_failure: true
+
 # We allow the submodule checker to fail when run on merge requests (to
 # accommodate, e.g., haddock changes not yet upstream) but not on `master` or
 # Marge jobs.
@@ -292,26 +327,11 @@ lint-author:
     - *drafts-can-fail-lint
 
 lint-ci-config:
-  image: nixos/nix:2.25.2
-  extends: .lint
+  extends: [.lint, .nix]
   # We don't need history/submodules in this job
   variables:
     GIT_DEPTH: 1
     GIT_SUBMODULE_STRATEGY: none
-  before_script:
-    - echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
-    # Note [Nix-in-Docker]
-    # ~~~~~~~~~~~~~~~~~~~~
-    # The nixos/nix default config is max-jobs=1 and cores=$(logical
-    # cores num) which doesn't play nice with our $CPUS convention. We
-    # fix it before invoking any nix build to avoid oversubscribing
-    # while allowing a reasonable degree of parallelism.
-    # FIXME: Disabling build-users-group=nixbld is a workaround for a Nix-in-Docker issue. See
-    # https://gitlab.haskell.org/ghc/head.hackage/-/issues/38#note_560487 for
-    # discussion.
-    - echo "cores = $CPUS" >> /etc/nix/nix.conf
-    - echo "max-jobs = $CPUS" >> /etc/nix/nix.conf
-    - nix run nixpkgs#gnused -- -i -e 's/ nixbld//' /etc/nix/nix.conf
   script:
     - nix run .gitlab/generate-ci#generate-jobs
     # 1 if .gitlab/generate_jobs changed the output of the generated config


=====================================
utils/check-submodules/LICENSE
=====================================
@@ -0,0 +1,30 @@
+Copyright (c) 2024, Ben Gamari
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Ben Gamari nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


=====================================
utils/check-submodules/README.mkd
=====================================
@@ -0,0 +1,16 @@
+# check-submodules
+
+This is a utilities used in GHC CI to verify the consistency and
+up-to-date-ness of GHC's boot library dependencies. Specifically
+we verify that:
+
+ * the referenced commits are released on Hackage
+ * that the Hackage version has not been deprecated
+ * that there is not a more recent version in the same major series
+
+## Usage
+
+In the GHC tree:
+```bash
+nix run ./utils/check-submodules#
+```


=====================================
utils/check-submodules/app/Main.hs
=====================================
@@ -0,0 +1,15 @@
+module Main (main) where
+
+import CheckVersions
+import CheckTags
+import System.Environment (getArgs)
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    ["check-versions"] -> checkVersions
+    ["check-tags"] -> checkTags
+    ["summarize"] -> summarize
+    ["email"] -> maintainerEmails >>= putStrLn
+    _ -> fail "invalid mode"


=====================================
utils/check-submodules/check-submodules.cabal
=====================================
@@ -0,0 +1,50 @@
+cabal-version:      3.0
+name:               check-submodules
+version:            0.1.0.0
+-- synopsis:
+-- description:
+homepage:           https://gitlab.haskell.org/ghc/ghc
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Ben Gamari
+maintainer:         ben at smart-cactus.org
+copyright:          (c) 2024 Ben Gamari
+category:           Development
+build-type:         Simple
+-- extra-source-files:
+
+common warnings
+    ghc-options: -Wall
+
+executable check-submodules
+    import:           warnings
+    main-is:          Main.hs
+    build-depends:    base,
+                      check-submodules
+    hs-source-dirs:   app
+    default-language: Haskell2010
+
+library
+    import:           warnings
+    exposed-modules:  Git,
+                      Hackage,
+                      CheckVersions,
+                      CheckTags,
+                      Packages,
+                      Package,
+                      Pretty
+    build-depends:    base,
+                      wreq,
+                      aeson,
+                      bytestring,
+                      text,
+                      transformers,
+                      prettyprinter,
+                      prettyprinter-ansi-terminal,
+                      filepath,
+                      microlens,
+                      containers,
+                      typed-process,
+                      Cabal
+    hs-source-dirs:   src
+    default-language: Haskell2010


=====================================
utils/check-submodules/flake.lock
=====================================
@@ -0,0 +1,58 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1734083684,
+        "narHash": "sha256-5fNndbndxSx5d+C/D0p/VF32xDiJCJzyOqorOYW4JEo=",
+        "path": "/nix/store/0xbni69flk8380w0apw4h640n37wn1i9-source",
+        "rev": "314e12ba369ccdb9b352a4db26ff419f7c49fa84",
+        "type": "path"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "type": "indirect"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}


=====================================
utils/check-submodules/flake.nix
=====================================
@@ -0,0 +1,26 @@
+{
+  description = "GHC boot library linting";
+
+  inputs.flake-utils.url = "github:numtide/flake-utils";
+
+  outputs = { self, nixpkgs, flake-utils }:
+    flake-utils.lib.eachDefaultSystem (system:
+      let pkgs = nixpkgs.legacyPackages.${system}; in
+      {
+        packages = rec {
+          check-submodules = pkgs.haskellPackages.callCabal2nix "generate-ci" ./. {};
+          default = check-submodules;
+        };
+
+        devShells.default = self.packages.${system}.default.env;
+
+        apps = rec {
+          check-submodules = flake-utils.lib.mkApp {
+            drv = self.packages.${system}.check-submodules;
+          };
+          default = check-submodules;
+        };
+      }
+    );
+}
+


=====================================
utils/check-submodules/hie.yaml
=====================================
@@ -0,0 +1,2 @@
+cradle:
+  cabal:


=====================================
utils/check-submodules/src/CheckTags.hs
=====================================
@@ -0,0 +1,64 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+
+module CheckTags
+    ( checkTags
+    ) where
+
+import Data.List (isPrefixOf, isSuffixOf)
+import Git qualified
+import Package (Package(..))
+import Packages (packages)
+import Pretty
+
+findReleaseTag :: Git.GitRepo -> Package -> IO (Maybe Git.Tag)
+findReleaseTag repo pkg = do
+    allTags <- Git.reachableTags repo "HEAD"
+    case filter (\tag -> pkgIsReleaseTag pkg tag || isGhcTag tag) allTags of
+      [] -> return Nothing
+      tag:_ -> return (Just tag)
+
+isGhcTag :: Git.Tag -> Bool
+isGhcTag tag = "-ghc" `isSuffixOf` tag
+
+checkTag :: Git.GitRepo -> Package -> IO (Maybe Doc)
+checkTag repo pkg = do
+    mb_tag <- findReleaseTag repo pkg
+    case mb_tag of
+      Nothing -> return $ Just "No release tags found"
+      Just tag -> checkChanges repo tag
+
+-- | Check whether the tag only deviates from HEAD in trivial ways.
+checkChanges :: Git.GitRepo -> Git.Ref -> IO (Maybe Doc)
+checkChanges repo tag = do
+    files <- Git.changedFiles repo tag "HEAD"
+    case filter (not . okayChange) files of
+      [] -> return Nothing
+      badFiles  -> do
+          described <- Git.describeRef repo "HEAD"
+          let msg = vsep
+                [ "Tag" <+> ppCommit (pretty tag) <+> "differs from" <+> ppCommit (pretty described) <+> "in:"
+                , bulletList fileList
+                ]
+              maxFiles = 5
+              fileList
+                | length badFiles > maxFiles = take maxFiles (map pretty badFiles) ++ ["... and" <+> pretty (length badFiles - maxFiles) <> " other files"]
+                | otherwise = map pretty badFiles
+          return $ Just msg
+
+okayChange :: FilePath -> Bool
+okayChange path
+  | "." `isPrefixOf` path = True
+  | ".gitignore" `isSuffixOf` path = True
+  | otherwise = False
+
+checkTags :: IO ()
+checkTags = do
+    let ghcRepo = Git.GitRepo "."
+    errs <- mapM (\pkg -> (pkg,) <$> checkTag (Git.submoduleIn ghcRepo (pkgPath pkg)) pkg) packages
+    putDoc $ bulletList
+      [ severityIcon Error <+> ppPackage pkg <> ":" <+> err
+      | (pkg, Just err) <- errs
+      ]
+


=====================================
utils/check-submodules/src/CheckVersions.hs
=====================================
@@ -0,0 +1,82 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+
+module CheckVersions
+    ( checkVersions
+    , summarize
+    , maintainerEmails
+    ) where
+
+import Control.Monad (forM_)
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Writer
+import Data.Function (on)
+import Data.List (intercalate, sort, nubBy)
+import Data.Map.Strict qualified as M
+import Data.Text qualified as T
+import Data.Version
+import Distribution.Types.PackageName qualified as C
+import System.Exit
+
+import Hackage (getVersions, PackageState (..))
+import Pretty
+import Package
+import Packages
+
+isPvpCompatible :: Version -> Version -> Bool
+isPvpCompatible a b =
+    take 2 (versionBranch a) == take 2 (versionBranch b)
+
+updateVersion :: M.Map Version PackageState -> Version -> Maybe Version
+updateVersion available v
+  | [] <- compatible = Nothing
+  | otherwise        = Just $ maximum compatible
+  where
+    compatible =
+      [ v'
+      | (v', Normal) <- M.assocs available -- non-deprecated versions available via Hackage...
+      , v' > v                             -- that are newer than the submodule...
+      , v' `isPvpCompatible` v             -- and are compatible with the submodule
+      ]
+
+checkPackage :: Package -> WriterT [(Severity, Doc)] IO ()
+checkPackage pkg = do
+    v <- liftIO $ getPackageVersion pkg
+    available <- liftIO $ getVersions (pkgName pkg)
+
+    case M.lookup v available of
+        Nothing         -> tellMsg Error $ "Version" <+> ppVersion v <+> "is not on Hackage"
+        Just Deprecated -> tellMsg Error $ "Version" <+> ppVersion v <+> "has been deprecated"
+        Just Normal     -> return ()
+
+    case updateVersion available v of
+        Nothing -> return ()
+        Just v' -> tellMsg Warning $ "Shipping with" <+> ppVersion v <+> "but newer version" <+> ppVersion v' <+> "is available"
+
+tellMsg :: Severity -> Doc -> WriterT [(Severity, Doc)] IO ()
+tellMsg sev msg = tell [(sev, msg)]
+
+summarizeSubmodules :: [Package] -> IO ()
+summarizeSubmodules pkgs = forM_ pkgs $ \pkg -> do
+    v <- getPackageVersion pkg
+    putStrLn $ "    " <> C.unPackageName (pkgName pkg) <> " " <> showVersion v <> " @ " <> pkgPath pkg
+
+maintainerEmails :: IO String
+maintainerEmails = do
+    maintainers <- concat <$> mapM getPackageMaintainers packages
+    return $ intercalate ", " $ map (T.unpack . contactRecipient) $ nubBy ((==) `on` contactEmail) $ sort maintainers
+
+summarize :: IO ()
+summarize =
+    summarizeSubmodules packages
+
+checkVersions :: IO ()
+checkVersions = do
+    errs <- mapM (\pkg -> map (pkg, ) <$> execWriterT (checkPackage pkg)) packages
+    putDoc $ bulletList
+      [ severityIcon sev <+> ppPackage pkg <> ":" <+> err
+      | (pkg, (sev, err)) <- concat errs
+      ]
+    exitWith $ if null errs then ExitSuccess else ExitFailure 1
+


=====================================
utils/check-submodules/src/Git.hs
=====================================
@@ -0,0 +1,52 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+
+module Git
+    ( GitRepo(..)
+    , submoduleIn
+
+    , Ref
+    , describeRef
+    , submoduleCommit
+    , Tag
+    , reachableTags
+    , changedFiles
+    ) where
+
+import System.Process.Typed
+import Data.ByteString.Lazy.Char8 qualified as BSL
+import System.FilePath ((</>))
+
+newtype GitRepo = GitRepo { gitRepoPath :: FilePath }
+
+submoduleIn :: GitRepo -> FilePath -> GitRepo
+submoduleIn (GitRepo path) submod =
+    GitRepo $ path </> submod
+
+type Ref = String
+type Tag = String
+
+runGit :: GitRepo -> [String] -> IO BSL.ByteString
+runGit (GitRepo path) args = do
+    readProcessStdout_ $ setWorkingDir path (proc "git" args)
+
+describeRef :: GitRepo -> Ref -> IO String
+describeRef repo ref =
+    head . lines . BSL.unpack <$> runGit repo ["describe", "--always", ref]
+
+-- | Get the commit of the given submodule.
+submoduleCommit :: GitRepo -> FilePath -> IO Ref
+submoduleCommit repo submodule = do
+    out <- runGit repo ["submodule", "status", submodule]
+    case BSL.words $ BSL.drop 1 out of
+      commit:_ -> return $ BSL.unpack commit
+      _ -> fail "Unrecognized output from `git submodule status`"
+
+-- | Get the most recent tags reacheable from the given commit.
+reachableTags :: GitRepo -> Ref -> IO [Tag]
+reachableTags repo ref =
+    reverse . map BSL.unpack . BSL.lines <$> runGit repo ["tag", "--sort=taggerdate", "--merged", ref]
+
+changedFiles :: GitRepo -> Ref -> Ref -> IO [FilePath]
+changedFiles repo a b = do
+    map BSL.unpack . BSL.lines <$> runGit repo ["diff", "--name-only", a, b]
+


=====================================
utils/check-submodules/src/Hackage.hs
=====================================
@@ -0,0 +1,32 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Hackage
+    ( PackageState(..)
+    , getVersions
+    ) where
+
+import qualified Data.Map.Strict as M
+import Lens.Micro
+import Network.Wreq
+import Distribution.Types.PackageName
+import qualified Data.Aeson as JSON
+import Data.Version
+
+data PackageState = Normal | Deprecated
+    deriving (Show)
+
+instance JSON.FromJSON PackageState where
+    parseJSON = JSON.withText "package state" $ \case
+        "normal" -> pure Normal
+        "deprecated" -> pure Deprecated
+        _ -> fail "unknown PackageState"
+
+getVersions :: PackageName -> IO (M.Map Version PackageState)
+getVersions pn = do
+    r <- asJSON =<< getWith opts url
+    maybe (fail "getVersions: failed") pure (r ^? responseBody)
+  where
+    opts = defaults & header "Accept" .~ ["application/json"]
+    url = "https://hackage.haskell.org/package/" <> unPackageName pn
+


=====================================
utils/check-submodules/src/Package.hs
=====================================
@@ -0,0 +1,62 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Package
+  ( Contact(..)
+  , parseContact
+  , contactRecipient
+
+  , Package(..)
+  , getPackageVersion
+  , getPackageMaintainers
+  ) where
+
+import Data.ByteString qualified as BS
+import Data.Text qualified as T
+import Data.Version
+import Distribution.PackageDescription.Parsec qualified as C
+import Distribution.Types.GenericPackageDescription qualified as C
+import Distribution.Types.PackageDescription qualified as C
+import Distribution.Types.PackageId qualified as C
+import Distribution.Types.PackageName (PackageName)
+import Distribution.Types.PackageName qualified as C
+import Distribution.Types.Version qualified as C
+import Distribution.Utils.ShortText qualified as C
+import System.FilePath
+
+data Contact = Contact { contactName, contactEmail :: T.Text }
+    deriving (Eq, Ord, Show)
+
+parseContact :: T.Text -> Contact
+parseContact t
+  | '<' `T.elem` t =
+    let (name,email) = T.break (== '<') t
+     in Contact (T.strip name) (T.strip $ T.takeWhile (/= '>') $ T.drop 1 email)
+  | otherwise = Contact "" t
+
+contactRecipient :: Contact -> T.Text
+contactRecipient (Contact name email)
+  | T.null name = email
+  | otherwise = name <> " <" <> email <> ">"
+
+data Package = Package { pkgName :: PackageName
+                       , pkgPath :: FilePath
+                       , pkgIsReleaseTag :: String -> Bool
+                       }
+
+getPackageDescription :: Package -> IO C.PackageDescription
+getPackageDescription pkg = do
+    Just gpd <- C.parseGenericPackageDescriptionMaybe <$> BS.readFile (pkgPath pkg </> C.unPackageName (pkgName pkg) <.> "cabal")
+    return $ C.packageDescription gpd
+
+getPackageMaintainers :: Package -> IO [Contact]
+getPackageMaintainers pkg =
+    map (parseContact . T.strip . T.filter (/= '\n')) . T.splitOn ","
+    . T.pack . C.fromShortText . C.maintainer
+    <$> getPackageDescription pkg
+
+getPackageVersion :: Package -> IO Version
+getPackageVersion pkg =
+    Data.Version.makeVersion . C.versionNumbers . C.pkgVersion . C.package
+    <$> getPackageDescription pkg
+


=====================================
utils/check-submodules/src/Packages.hs
=====================================
@@ -0,0 +1,54 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Packages (packages) where
+
+import Package
+import Data.Char (isDigit)
+import qualified Distribution.Types.PackageName as C
+import Data.List
+
+packages :: [Package]
+packages =
+    [ stdPackage "file-io" "libraries/file-io"
+    , stdPackage "hsc2hs" "utils/hsc2hs"
+    , Package "Cabal" "libraries/Cabal/Cabal"  (isPrefixTag "Cabal-")
+    , Package "Cabal-syntax" "libraries/Cabal/Cabal-syntax" (isPrefixTag "Cabal-syntax-")
+    , stdPackage "bytestring" "libraries/bytestring"
+    , stdPackage "binary" "libraries/binary"
+    , stdPackage "array" "libraries/array"
+    , stdPackage "containers" "libraries/containers/containers"
+    , stdPackage "deepseq" "libraries/deepseq"
+    , stdPackage "directory" "libraries/directory"
+    , stdPackage "filepath" "libraries/filepath"
+    , stdPackage "haskeline" "libraries/haskeline"
+    , stdPackage "hpc" "libraries/hpc"
+    , stdPackage "mtl" "libraries/mtl"
+    , stdPackage "parsec" "libraries/parsec"
+    , stdPackage "pretty" "libraries/pretty"
+    , stdPackage "process" "libraries/process"
+    , stdPackage "terminfo" "libraries/terminfo"
+    , stdPackage "text" "libraries/text"
+    , stdPackage "time" "libraries/time"
+    , stdPackage "unix" "libraries/unix"
+    , stdPackage "exceptions" "libraries/exceptions"
+    , stdPackage "semaphore-compat" "libraries/semaphore-compat"
+    , stdPackage "stm" "libraries/stm"
+    , stdPackage "Win32" "libraries/Win32"
+    , stdPackage "xhtml" "libraries/xhtml"
+    ]
+
+stdPackage :: C.PackageName -> FilePath -> Package
+stdPackage name path = Package name path stdIsReleaseTag
+
+looksLikeVersion :: String -> Bool
+looksLikeVersion =
+    all (\c -> isDigit c || c == '.')
+
+isPrefixTag :: String -> String -> Bool
+isPrefixTag prefix tag
+  | Just rest <- prefix `stripPrefix` tag = looksLikeVersion rest
+  | otherwise = False
+
+stdIsReleaseTag :: String -> Bool
+stdIsReleaseTag tag =
+    isPrefixTag "v" tag || isPrefixTag "" tag


=====================================
utils/check-submodules/src/Pretty.hs
=====================================
@@ -0,0 +1,57 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Pretty
+    ( module Prettyprinter
+    , Doc
+    , mkMsg
+    , Severity(..)
+    , severityIcon
+    , bulletList
+    , ppCommit
+    , ppPackage
+    , ppVersion
+    , ppHeading
+    , putDoc
+    ) where
+
+import Data.Version
+import Package
+import Prettyprinter hiding (Doc)
+import Prettyprinter qualified as PP
+import Prettyprinter.Render.Terminal
+import Distribution.Types.PackageName qualified as C
+
+type Doc = PP.Doc AnsiStyle
+
+ppPackage :: Package -> Doc
+ppPackage =
+    annotate (color Green) . pretty . C.unPackageName . pkgName
+
+ppVersion :: Version -> Doc
+ppVersion v =
+    annotate (color Blue) $ pretty $ showVersion v
+
+ppCommit :: Doc -> Doc
+ppCommit =
+    annotate (color Blue)
+
+ppHeading :: Doc -> Doc
+ppHeading =
+    annotate bold . ("#" <+>)
+
+bullet :: Doc
+bullet = "‣"
+
+bulletList :: [Doc] -> Doc
+bulletList xs = vcat [ " " <> bullet <+> align x | x <- xs ]
+
+data Severity = Info | Warning | Error
+
+severityIcon :: Severity -> Doc
+severityIcon Info    = annotate (color Blue) "ℹ" -- "🔵"
+severityIcon Warning = "🟡"
+severityIcon Error   = annotate (color Red) "✗" -- "🔴"
+
+mkMsg :: Severity -> Doc -> Doc
+mkMsg s msg = severityIcon s <+> msg



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/7c34906b6c07639d52447f9141a698df656f4e56...ba3deaf9a3f576c33e4dd118da962c87f0b1ced1

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/7c34906b6c07639d52447f9141a698df656f4e56...ba3deaf9a3f576c33e4dd118da962c87f0b1ced1
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20241220/cad6f414/attachment-0001.html>


More information about the ghc-commits mailing list