clash-lib-1.10.0: src/Clash/Driver/BrokenGhcs.hs
{-|
Copyright : (C) 2024, Martijn Bastiaan
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
Utilities to detect and report GHC / operating system combinations that are
known to be buggy.
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE QuasiQuotes #-}
module Clash.Driver.BrokenGhcs where
import Data.Maybe (listToMaybe)
import Data.Version (Version(Version, versionBranch))
import GHC.Platform (OS(..))
import System.Info (fullCompilerVersion)
import qualified Clash.Util.Interpolate as I
import qualified System.Info
-- | Current OS. Currently only recognizes Linux, Windows, and macOS.
os :: OS
os = case System.Info.os of
"darwin" -> OSDarwin
"linux" -> OSLinux
"mingw32" -> OSMinGW32
_ -> OSUnknown
-- | What OS GHC is broken on (or all)
data BrokenOn = All | SomeOs OS
data GhcVersion = Ghc
{ major0 :: Int
, major1 :: Int
, patch :: Int
}
deriving (Eq, Ord)
data GhcRange = GhcRange
{ from :: GhcVersion
-- ^ Start of range, inclusive
, to :: GhcVersion
-- ^ End of range, exclusive
}
-- | Check if a 'GhcVersion' is within a 'GhcRange'
ghcInRange :: GhcVersion -> GhcRange -> Bool
ghcInRange ghc GhcRange{from, to} = from <= ghc && ghc < to
-- | Construct a range of all GHC versions matching a major version
ghcMajor :: Int -> Int -> GhcRange
ghcMajor major0 major1 = GhcRange
{ from=Ghc major0 major1 0
, to=Ghc major0 (major1 + 1) 0
}
data Why = Why
{ what :: String
-- ^ What is broken
, solution :: String
-- ^ What can be done to work around or solve the issue
, issue :: String
-- ^ Link to issue
, brokenOn :: [(BrokenOn, GhcRange)]
-- ^ What operation systems are affected
}
-- | Get current GHC version expressed as a triple. It probably does something
-- non-sensible on unreleased GHCs.
ghcVersion :: GhcVersion
ghcVersion = Ghc{major0, major1, patch}
where
(major0, major1, patch) =
case fullCompilerVersion of
Version{versionBranch} ->
case versionBranch of
[] -> (0, 0, 1)
[a] -> (a, 0, 1)
[a, b] -> (a, b, 1)
[a, b, c] -> (a, b, c)
(a:b:c:_) -> (a, b, c)
-- | Pretty print 'Why' into an error message
whyPp :: Why -> String
whyPp Why{what, solution, issue}= [I.i|
Clash has known issues on #{major0}.#{major1}.#{patch} on your current
OS. While not completely preventing the compiler from working, we recommend
switching to another GHC version. Symptoms:
#{what}
Consider the following work around or solution:
#{solution}
More information can be found at:
#{issue}
If you want to ignore this message, pass the following flag to Clash:
-fclash-ignore-broken-ghcs
Alternatively, you can set the environment variable CLASH_IGNORE_BROKEN_GHCS
to 'True'.
|]
where
Ghc{major0, major1, patch} = ghcVersion
-- | Which GHCs are broken and why
brokenGhcs :: [Why]
brokenGhcs = [brokenClashCores, brokenTypeErrors, slowStarts]
where
brokenClashCores = Why
{ what = "GHC is known to fail compilation of libraries used by the Clash compiler test suite"
, solution = "Upgrade to GHC 9.4 or downgrade to GHC 8.10"
, issue = "<no link>"
, brokenOn = [(SomeOs OSMinGW32, ghcMajor 9 0)]
}
brokenTypeErrors = Why
{ what = "Clash type error messages are indecipherable"
, solution = "Upgrade to GHC 9.4 or downgrade to GHC 9.0"
, issue = "<no link>"
, brokenOn = [(All, ghcMajor 9 2)]
}
slowStarts = Why
{ what = "Clash starts really slowly from GHC 9.4.8 up to and including 9.6.2"
, solution = "Upgrade to GHC 9.6.3 or newer, or downgrade to GHC 9.4.7"
, issue = "https://github.com/clash-lang/clash-compiler/issues/2710"
, brokenOn = [(All, GhcRange{from=Ghc 9 4 8, to=Ghc 9 6 3})]
}
-- | Given a 'BrokenOn', determine whether current OS matches
matchOs :: BrokenOn -> Bool
matchOs All = True
matchOs (SomeOs brokenOs) = os == brokenOs
-- | Given a 'BrokenOn' and 'GhcVersion', determine whether it matches current OS and GHC
matchBroken :: (BrokenOn, GhcRange) -> Bool
matchBroken (brokenOs, brokenRange) = matchOs brokenOs && ghcInRange ghcVersion brokenRange
-- | Get first reason for GHC/OS being broken, if any
broken :: Maybe Why
broken = listToMaybe [why | why <- brokenGhcs, any matchBroken (brokenOn why)]
-- | Throw an error if current OS / GHC version is known to be buggy
assertWorking :: IO ()
assertWorking = case broken of
Nothing -> pure ()
Just why -> error (whyPp why)