Distribution/System.hs

    {-# LANGUAGE DeriveDataTypeable #-}
    {-# LANGUAGE DeriveGeneric #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Distribution.System
    -- Copyright   :  Duncan Coutts 2007-2008
    --
    -- Maintainer  :  cabal-devel@haskell.org
    -- Portability :  portable
    --
    -- Cabal often needs to do slightly different things on specific platforms. You
    -- probably know about the 'System.Info.os' however using that is very
    -- inconvenient because it is a string and different Haskell implementations
    -- do not agree on using the same strings for the same platforms! (In
    -- particular see the controversy over \"windows\" vs \"ming32\"). So to make it
    -- more consistent and easy to use we have an 'OS' enumeration.
    --
    module Distribution.System (
      -- * Operating System
      OS(..),
      buildOS,

      -- * Machine Architecture
      Arch(..),
      buildArch,

      -- * Platform is a pair of arch and OS
      Platform(..),
      buildPlatform,
      platformFromTriple,

      -- * Internal
      knownOSs,
      knownArches
      ) where

    import qualified System.Info (os, arch)
    import qualified Data.Char as Char (toLower, isAlphaNum, isAlpha)

    import Distribution.Compat.Binary
    import Distribution.Text
    import qualified Distribution.Compat.ReadP as Parse

    import Control.Monad (liftM2)
    import Data.Data (Data)
    import Data.Typeable (Typeable)
    import Data.Maybe (fromMaybe, listToMaybe)
    import GHC.Generics (Generic)
    import qualified Text.PrettyPrint as Disp
    import Text.PrettyPrint ((<>))

    -- | How strict to be when classifying strings into the 'OS' and 'Arch' enums.
    --
    -- The reason we have multiple ways to do the classification is because there
    -- are two situations where we need to do it.
    --
    -- For parsing OS and arch names in .cabal files we really want everyone to be
    -- referring to the same or or arch by the same name. Variety is not a virtue
    -- in this case. We don't mind about case though.
    --
    -- For the System.Info.os\/arch different Haskell implementations use different
    -- names for the same or\/arch. Also they tend to distinguish versions of an
    -- OS\/arch which we just don't care about.
    --
    -- The 'Compat' classification allows us to recognise aliases that are already
    -- in common use but it allows us to distinguish them from the canonical name
    -- which enables us to warn about such deprecated aliases.
    --
    data ClassificationStrictness = Permissive | Compat | Strict

    -- ------------------------------------------------------------
    -- * Operating System
    -- ------------------------------------------------------------

    data OS = Linux | Windows | OSX        -- tier 1 desktop OSs
            | FreeBSD | OpenBSD | NetBSD   -- other free Unix OSs
            | DragonFly
            | Solaris | AIX | HPUX | IRIX  -- ageing Unix OSs
            | HaLVM                        -- bare metal / VMs / hypervisors
            | Hurd                         -- GNU's microkernel
            | IOS  | Android               -- mobile OSs
            | Ghcjs
            | OtherOS String
      deriving (Eq, Generic, Ord, Show, Read, Typeable, Data)

    instance Binary OS

    knownOSs :: [OS]
    knownOSs = [Linux, Windows, OSX
               ,FreeBSD, OpenBSD, NetBSD, DragonFly
               ,Solaris, AIX, HPUX, IRIX
               ,HaLVM
               ,Hurd
               ,IOS, Android
               ,Ghcjs]

    osAliases :: ClassificationStrictness -> OS -> [String]
    osAliases Permissive Windows = ["mingw32", "win32", "cygwin32"]
    osAliases Compat     Windows = ["mingw32", "win32"]
    osAliases _          OSX     = ["darwin"]
    osAliases _          Hurd    = ["gnu"]
    osAliases Permissive FreeBSD = ["kfreebsdgnu"]
    osAliases Compat     FreeBSD = ["kfreebsdgnu"]
    osAliases Permissive Solaris = ["solaris2"]
    osAliases Compat     Solaris = ["solaris2"]
    osAliases _          _       = []

    instance Text OS where
      disp (OtherOS name) = Disp.text name
      disp other          = Disp.text (lowercase (show other))

      parse = fmap (classifyOS Compat) ident

    classifyOS :: ClassificationStrictness -> String -> OS
    classifyOS strictness s =
      fromMaybe (OtherOS s) $ lookup (lowercase s) osMap
      where
        osMap = [ (name, os)
                | os <- knownOSs
                , name <- display os : osAliases strictness os ]

    buildOS :: OS
    buildOS = classifyOS Permissive System.Info.os

    -- ------------------------------------------------------------
    -- * Machine Architecture
    -- ------------------------------------------------------------

    data Arch = I386  | X86_64 | PPC | PPC64 | Sparc
              | Arm   | Mips   | SH
              | IA64  | S390
              | Alpha | Hppa   | Rs6000
              | M68k  | Vax
              | JavaScript
              | OtherArch String
      deriving (Eq, Generic, Ord, Show, Read, Typeable, Data)

    instance Binary Arch

    knownArches :: [Arch]
    knownArches = [I386, X86_64, PPC, PPC64, Sparc
                  ,Arm, Mips, SH
                  ,IA64, S390
                  ,Alpha, Hppa, Rs6000
                  ,M68k, Vax
                  ,JavaScript]

    archAliases :: ClassificationStrictness -> Arch -> [String]
    archAliases Strict _     = []
    archAliases Compat _     = []
    archAliases _      PPC   = ["powerpc"]
    archAliases _      PPC64 = ["powerpc64"]
    archAliases _      Sparc = ["sparc64", "sun4"]
    archAliases _      Mips  = ["mipsel", "mipseb"]
    archAliases _      Arm   = ["armeb", "armel"]
    archAliases _      _     = []

    instance Text Arch where
      disp (OtherArch name) = Disp.text name
      disp other            = Disp.text (lowercase (show other))

      parse = fmap (classifyArch Strict) ident

    classifyArch :: ClassificationStrictness -> String -> Arch
    classifyArch strictness s =
      fromMaybe (OtherArch s) $ lookup (lowercase s) archMap
      where
        archMap = [ (name, arch)
                  | arch <- knownArches
                  , name <- display arch : archAliases strictness arch ]

    buildArch :: Arch
    buildArch = classifyArch Permissive System.Info.arch

    -- ------------------------------------------------------------
    -- * Platform
    -- ------------------------------------------------------------

    data Platform = Platform Arch OS
      deriving (Eq, Generic, Ord, Show, Read, Typeable, Data)

    instance Binary Platform

    instance Text Platform where
      disp (Platform arch os) = disp arch <> Disp.char '-' <> disp os
      -- TODO: there are ambigious platforms like: `arch-word-os`
      -- which could be parsed as
      --   * Platform "arch-word" "os"
      --   * Platform "arch" "word-os"
      -- We could support that preferring variants 'OtherOS' or 'OtherArch'
      --
      -- For now we split into arch and os parts on the first dash.
      parse = do
        arch <- parseDashlessArch
        _ <- Parse.char '-'
        os   <- parse
        return (Platform arch os)
          where
            parseDashlessArch :: Parse.ReadP r Arch
            parseDashlessArch = fmap (classifyArch Strict) dashlessIdent

    -- | The platform Cabal was compiled on. In most cases,
    -- @LocalBuildInfo.hostPlatform@ should be used instead (the platform we're
    -- targeting).
    buildPlatform :: Platform
    buildPlatform = Platform buildArch buildOS

    -- Utils:

    ident :: Parse.ReadP r String
    ident = liftM2 (:) first rest
      where first = Parse.satisfy Char.isAlpha
            rest = Parse.munch (\c -> Char.isAlphaNum c || c == '_' || c == '-')

    dashlessIdent :: Parse.ReadP r String
    dashlessIdent = liftM2 (:) first rest
      where first = Parse.satisfy Char.isAlpha
            rest = Parse.munch (\c -> Char.isAlphaNum c || c == '_')

    lowercase :: String -> String
    lowercase = map Char.toLower

    platformFromTriple :: String -> Maybe Platform
    platformFromTriple triple =
      fmap fst (listToMaybe $ Parse.readP_to_S parseTriple triple)
      where parseWord = Parse.munch1 (\c -> Char.isAlphaNum c || c == '_')
            parseTriple = do
              arch <- fmap (classifyArch Permissive) parseWord
              _ <- Parse.char '-'
              _ <- parseWord -- Skip vendor
              _ <- Parse.char '-'
              os <- fmap (classifyOS Permissive) ident -- OS may have hyphens, like
                                                   -- 'nto-qnx'
              return $ Platform arch os