-----------------------------------------------------------------------------

-- |
-- Module      :  Distribution.Simple.Program.Builtin
-- Copyright   :  Isaac Jones 2006, Duncan Coutts 2007-2009
--
-- Maintainer  :  cabal-devel@haskell.org
-- Portability :  portable
--
-- The module defines all the known built-in 'Program's.
--
-- Where possible we try to find their version numbers.
module Distribution.Simple.Program.Builtin
  ( -- * The collection of unconfigured and configured programs
    builtinPrograms

    -- * Programs that Cabal knows about
  , ghcProgram
  , ghcPkgProgram
  , runghcProgram
  , ghcjsProgram
  , ghcjsPkgProgram
  , hmakeProgram
  , jhcProgram
  , haskellSuiteProgram
  , haskellSuitePkgProgram
  , uhcProgram
  , gccProgram
  , arProgram
  , stripProgram
  , happyProgram
  , alexProgram
  , hsc2hsProgram
  , c2hsProgram
  , cpphsProgram
  , hscolourProgram
  , doctestProgram
  , haddockProgram
  , greencardProgram
  , ldProgram
  , tarProgram
  , cppProgram
  , pkgConfigProgram
  , hpcProgram
  ) where

import Distribution.Compat.Prelude
import Prelude ()

import Distribution.Simple.Program.Find
import Distribution.Simple.Program.GHC
import Distribution.Simple.Program.Internal
import Distribution.Simple.Program.Run
import Distribution.Simple.Program.Types
import Distribution.Simple.Utils
import Distribution.Verbosity
import Distribution.Version

import qualified Data.Map as Map

-- ------------------------------------------------------------

-- * Known programs

-- ------------------------------------------------------------

-- | The default list of programs.
-- These programs are typically used internally to Cabal.
builtinPrograms :: [Program]
builtinPrograms :: [Program]
builtinPrograms =
  [ -- compilers and related progs
    Program
ghcProgram
  , Program
runghcProgram
  , Program
ghcPkgProgram
  , Program
ghcjsProgram
  , Program
ghcjsPkgProgram
  , Program
haskellSuiteProgram
  , Program
haskellSuitePkgProgram
  , Program
hmakeProgram
  , Program
jhcProgram
  , Program
uhcProgram
  , Program
hpcProgram
  , -- preprocessors
    Program
hscolourProgram
  , Program
doctestProgram
  , Program
haddockProgram
  , Program
happyProgram
  , Program
alexProgram
  , Program
hsc2hsProgram
  , Program
c2hsProgram
  , Program
cpphsProgram
  , Program
greencardProgram
  , -- platform toolchain
    Program
gccProgram
  , Program
arProgram
  , Program
stripProgram
  , Program
ldProgram
  , Program
tarProgram
  , -- configuration tools
    Program
pkgConfigProgram
  ]

ghcProgram :: Program
ghcProgram :: Program
ghcProgram =
  (String -> Program
simpleProgram String
"ghc")
    { programFindVersion = findProgramVersion "--numeric-version" id
    , -- Workaround for https://gitlab.haskell.org/ghc/ghc/-/issues/8825
      -- (spurious warning on non-english locales)
      programPostConf = \Verbosity
_verbosity ConfiguredProgram
ghcProg ->
        do
          let ghcProg' :: ConfiguredProgram
ghcProg' =
                ConfiguredProgram
ghcProg
                  { programOverrideEnv =
                      ("LANGUAGE", Just "en")
                        : programOverrideEnv ghcProg
                  }
              -- Only the 7.8 branch seems to be affected. Fixed in 7.8.4.
              affectedVersionRange :: VersionRange
affectedVersionRange =
                VersionRange -> VersionRange -> VersionRange
intersectVersionRanges
                  (Version -> VersionRange
laterVersion (Version -> VersionRange) -> Version -> VersionRange
forall a b. (a -> b) -> a -> b
$ [Int] -> Version
mkVersion [Int
7, Int
8, Int
0])
                  (Version -> VersionRange
earlierVersion (Version -> VersionRange) -> Version -> VersionRange
forall a b. (a -> b) -> a -> b
$ [Int] -> Version
mkVersion [Int
7, Int
8, Int
4])
          ConfiguredProgram -> IO ConfiguredProgram
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (ConfiguredProgram -> IO ConfiguredProgram)
-> ConfiguredProgram -> IO ConfiguredProgram
forall a b. (a -> b) -> a -> b
$
            ConfiguredProgram
-> (Version -> ConfiguredProgram)
-> Maybe Version
-> ConfiguredProgram
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
              ConfiguredProgram
ghcProg
              ( \Version
v ->
                  if Version -> VersionRange -> Bool
withinRange Version
v VersionRange
affectedVersionRange
                    then ConfiguredProgram
ghcProg'
                    else ConfiguredProgram
ghcProg
              )
              (ConfiguredProgram -> Maybe Version
programVersion ConfiguredProgram
ghcProg)
    , programNormaliseArgs = normaliseGhcArgs
    }

runghcProgram :: Program
runghcProgram :: Program
runghcProgram =
  (String -> Program
simpleProgram String
"runghc")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        case String -> [String]
words String
str of
          -- "runghc 7.10.3"
          (String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

ghcPkgProgram :: Program
ghcPkgProgram :: Program
ghcPkgProgram =
  (String -> Program
simpleProgram String
"ghc-pkg")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "ghc-pkg --version" gives a string like
        -- "GHC package manager version 6.4.1"
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
_ : String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

ghcjsProgram :: Program
ghcjsProgram :: Program
ghcjsProgram =
  (String -> Program
simpleProgram String
"ghcjs")
    { programFindVersion = findProgramVersion "--numeric-ghcjs-version" id
    }

-- note: version is the version number of the GHC version that ghcjs-pkg was built with
ghcjsPkgProgram :: Program
ghcjsPkgProgram :: Program
ghcjsPkgProgram =
  (String -> Program
simpleProgram String
"ghcjs-pkg")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "ghcjs-pkg --version" gives a string like
        -- "GHCJS package manager version 6.4.1"
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
_ : String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

hmakeProgram :: Program
hmakeProgram :: Program
hmakeProgram =
  (String -> Program
simpleProgram String
"hmake")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "hmake --version" gives a string line
        -- "/usr/local/bin/hmake: 3.13 (2006-11-01)"
        case String -> [String]
words String
str of
          (String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

jhcProgram :: Program
jhcProgram :: Program
jhcProgram =
  (String -> Program
simpleProgram String
"jhc")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- invoking "jhc --version" gives a string like
        -- "jhc 0.3.20080208 (wubgipkamcep-2)
        -- compiled by ghc-6.8 on a x86_64 running linux"
        case String -> [String]
words String
str of
          (String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

uhcProgram :: Program
uhcProgram :: Program
uhcProgram =
  (String -> Program
simpleProgram String
"uhc")
    { programFindVersion = findProgramVersion "--version-dotted" id
    }

hpcProgram :: Program
hpcProgram :: Program
hpcProgram =
  (String -> Program
simpleProgram String
"hpc")
    { programFindVersion = findProgramVersion "version" $ \String
str ->
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

-- This represents a haskell-suite compiler. Of course, the compiler
-- itself probably is not called "haskell-suite", so this is not a real
-- program. (But we don't know statically the name of the actual compiler,
-- so this is the best we can do.)
--
-- Having this Program value serves two purposes:
--
-- 1. We can accept options for the compiler in the form of
--
--   --haskell-suite-option(s)=...
--
-- 2. We can find a program later using this static id (with
-- requireProgram).
--
-- The path to the real compiler is found and recorded in the ProgramDb
-- during the configure phase.
haskellSuiteProgram :: Program
haskellSuiteProgram :: Program
haskellSuiteProgram =
  (String -> Program
simpleProgram String
"haskell-suite")
    { -- pretend that the program exists, otherwise it won't be in the
      -- "configured" state
      programFindLocation = \Verbosity
_verbosity ProgramSearchPath
_searchPath ->
        Maybe (String, [String]) -> IO (Maybe (String, [String]))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe (String, [String]) -> IO (Maybe (String, [String])))
-> Maybe (String, [String]) -> IO (Maybe (String, [String]))
forall a b. (a -> b) -> a -> b
$ (String, [String]) -> Maybe (String, [String])
forall a. a -> Maybe a
Just (String
"haskell-suite-dummy-location", [])
    }

-- This represent a haskell-suite package manager. See the comments for
-- haskellSuiteProgram.
haskellSuitePkgProgram :: Program
haskellSuitePkgProgram :: Program
haskellSuitePkgProgram =
  (String -> Program
simpleProgram String
"haskell-suite-pkg")
    { programFindLocation = \Verbosity
_verbosity ProgramSearchPath
_searchPath ->
        Maybe (String, [String]) -> IO (Maybe (String, [String]))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe (String, [String]) -> IO (Maybe (String, [String])))
-> Maybe (String, [String]) -> IO (Maybe (String, [String]))
forall a b. (a -> b) -> a -> b
$ (String, [String]) -> Maybe (String, [String])
forall a. a -> Maybe a
Just (String
"haskell-suite-pkg-dummy-location", [])
    }

happyProgram :: Program
happyProgram :: Program
happyProgram =
  (String -> Program
simpleProgram String
"happy")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "happy --version" gives a string like
        -- "Happy Version 1.16 Copyright (c) ...."
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

alexProgram :: Program
alexProgram :: Program
alexProgram =
  (String -> Program
simpleProgram String
"alex")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "alex --version" gives a string like
        -- "Alex version 2.1.0, (c) 2003 Chris Dornan and Simon Marlow"
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
ver : [String]
_) -> (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (\Char
x -> Char -> Bool
isDigit Char
x Bool -> Bool -> Bool
|| Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') String
ver
          [String]
_ -> String
""
    }

gccProgram :: Program
gccProgram :: Program
gccProgram =
  (String -> Program
simpleProgram String
"gcc")
    { programFindVersion = findProgramVersion "-dumpversion" id
    }

arProgram :: Program
arProgram :: Program
arProgram = String -> Program
simpleProgram String
"ar"

stripProgram :: Program
stripProgram :: Program
stripProgram =
  (String -> Program
simpleProgram String
"strip")
    { programFindVersion = \Verbosity
verbosity ->
        String
-> (String -> String) -> Verbosity -> String -> IO (Maybe Version)
findProgramVersion String
"--version" String -> String
stripExtractVersion (Verbosity -> Verbosity
lessVerbose Verbosity
verbosity)
    }

hsc2hsProgram :: Program
hsc2hsProgram :: Program
hsc2hsProgram =
  (String -> Program
simpleProgram String
"hsc2hs")
    { programFindVersion =
        findProgramVersion "--version" $ \String
str ->
          -- Invoking "hsc2hs --version" gives a string like "hsc2hs version 0.66"
          case String -> [String]
words String
str of
            (String
_ : String
_ : String
ver : [String]
_) -> String
ver
            [String]
_ -> String
""
    }

c2hsProgram :: Program
c2hsProgram :: Program
c2hsProgram =
  (String -> Program
simpleProgram String
"c2hs")
    { programFindVersion = findProgramVersion "--numeric-version" id
    }

cpphsProgram :: Program
cpphsProgram :: Program
cpphsProgram =
  (String -> Program
simpleProgram String
"cpphs")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "cpphs --version" gives a string like "cpphs 1.3"
        case String -> [String]
words String
str of
          (String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

hscolourProgram :: Program
hscolourProgram :: Program
hscolourProgram =
  (String -> Program
simpleProgram String
"hscolour")
    { programFindLocation = \Verbosity
v ProgramSearchPath
p -> Verbosity
-> ProgramSearchPath -> String -> IO (Maybe (String, [String]))
findProgramOnSearchPath Verbosity
v ProgramSearchPath
p String
"HsColour"
    , programFindVersion = findProgramVersion "-version" $ \String
str ->
        -- Invoking "HsColour -version" gives a string like "HsColour 1.7"
        case String -> [String]
words String
str of
          (String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

-- TODO: Ensure that doctest is built against the same GHC as the one
--       that's being used.  Same for haddock.  @phadej pointed this out.
doctestProgram :: Program
doctestProgram :: Program
doctestProgram =
  (String -> Program
simpleProgram String
"doctest")
    { programFindLocation = \Verbosity
v ProgramSearchPath
p -> Verbosity
-> ProgramSearchPath -> String -> IO (Maybe (String, [String]))
findProgramOnSearchPath Verbosity
v ProgramSearchPath
p String
"doctest"
    , programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- "doctest version 0.11.2"
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
ver : [String]
_) -> String
ver
          [String]
_ -> String
""
    }

haddockProgram :: Program
haddockProgram :: Program
haddockProgram =
  (String -> Program
simpleProgram String
"haddock")
    { programFindVersion = findProgramVersion "--version" $ \String
str ->
        -- Invoking "haddock --version" gives a string like
        -- "Haddock version 0.8, (c) Simon Marlow 2006"
        case String -> [String]
words String
str of
          (String
_ : String
_ : String
ver : [String]
_) -> (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (Char
'.' Char -> String -> String
forall a. a -> [a] -> [a]
: [Char
'0' .. Char
'9'])) String
ver
          [String]
_ -> String
""
    , programNormaliseArgs = \Maybe Version
_ PackageDescription
_ [String]
args -> [String]
args
    }

greencardProgram :: Program
greencardProgram :: Program
greencardProgram = String -> Program
simpleProgram String
"greencard"

ldProgram :: Program
ldProgram :: Program
ldProgram =
  (String -> Program
simpleProgram String
"ld")
    { programPostConf = \Verbosity
verbosity ConfiguredProgram
ldProg -> do
        -- The `lld` linker cannot create merge (relocatable) objects so we
        -- want to detect this.
        -- If the linker does support relocatable objects, we want to use that
        -- to create partially pre-linked objects for GHCi, so we get much
        -- faster loading as we do not have to do the separate loading and
        -- in-memory linking the static linker in GHC does, but can offload
        -- parts of this process to a pre-linking step.
        -- However this requires the linker to support this features. Not all
        -- linkers do, and notably as of this writing `lld` which is a popular
        -- choice for windows linking does not support this feature. However
        -- if using binutils ld or another linker that supports --relocatable,
        -- we should still be good to generate pre-linked objects.
        ldHelpOutput <-
          Verbosity -> ProgramInvocation -> IO String
getProgramInvocationOutput
            Verbosity
verbosity
            (ConfiguredProgram -> [String] -> ProgramInvocation
programInvocation ConfiguredProgram
ldProg [String
"--help"])
            -- In case the linker does not support '--help'. Eg the LLVM linker,
            -- `lld` only accepts `-help`.
            IO String -> (IOException -> IO String) -> IO String
forall a. IO a -> (IOException -> IO a) -> IO a
`catchIO` (\IOException
_ -> String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return String
"")
        let k = String
"Supports relocatable output"
            -- Standard GNU `ld` uses `--relocatable` while `ld.gold` uses
            -- `-relocatable` (single `-`).
            v
              | String
"-relocatable" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
ldHelpOutput = String
"YES"
              -- ld64 on macOS has this lovely response for "--help"
              --
              --   ld64: For information on command line options please use 'man ld'.
              --
              -- it does however support -r, if you read the manpage
              -- (e.g. https://www.manpagez.com/man/1/ld64/)
              | String
"ld64:" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
ldHelpOutput = String
"YES"
              | Bool
otherwise = String
"NO"

            m = String -> String -> Map String String -> Map String String
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
k String
v (ConfiguredProgram -> Map String String
programProperties ConfiguredProgram
ldProg)
        return $ ldProg{programProperties = m}
    }

tarProgram :: Program
tarProgram :: Program
tarProgram =
  (String -> Program
simpleProgram String
"tar")
    { -- See #1901. Some versions of 'tar' (OpenBSD, NetBSD, ...) don't support the
      -- '--format' option.
      programPostConf = \Verbosity
verbosity ConfiguredProgram
tarProg -> do
        tarHelpOutput <-
          Verbosity -> ProgramInvocation -> IO String
getProgramInvocationOutput
            Verbosity
verbosity
            (ConfiguredProgram -> [String] -> ProgramInvocation
programInvocation ConfiguredProgram
tarProg [String
"--help"])
            -- Some versions of tar don't support '--help'.
            IO String -> (IOException -> IO String) -> IO String
forall a. IO a -> (IOException -> IO a) -> IO a
`catchIO` (\IOException
_ -> String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return String
"")
        let k = String
"Supports --format"
            v = if (String
"--format" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
tarHelpOutput) then String
"YES" else String
"NO"
            m = String -> String -> Map String String -> Map String String
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
k String
v (ConfiguredProgram -> Map String String
programProperties ConfiguredProgram
tarProg)
        return $ tarProg{programProperties = m}
    }

cppProgram :: Program
cppProgram :: Program
cppProgram = String -> Program
simpleProgram String
"cpp"

pkgConfigProgram :: Program
pkgConfigProgram :: Program
pkgConfigProgram =
  (String -> Program
simpleProgram String
"pkg-config")
    { programFindVersion = findProgramVersion "--version" id
    , programPostConf = \Verbosity
_ ConfiguredProgram
pkgConfProg ->
        let programOverrideEnv' :: [(String, Maybe String)]
programOverrideEnv' =
              ConfiguredProgram -> [(String, Maybe String)]
programOverrideEnv ConfiguredProgram
pkgConfProg
                [(String, Maybe String)]
-> [(String, Maybe String)] -> [(String, Maybe String)]
forall a. [a] -> [a] -> [a]
++ [ (String
"PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", String -> Maybe String
forall a. a -> Maybe a
Just String
"1")
                   , (String
"PKG_CONFIG_ALLOW_SYSTEM_LIBS", String -> Maybe String
forall a. a -> Maybe a
Just String
"1")
                   ]
         in ConfiguredProgram -> IO ConfiguredProgram
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ConfiguredProgram -> IO ConfiguredProgram)
-> ConfiguredProgram -> IO ConfiguredProgram
forall a b. (a -> b) -> a -> b
$ ConfiguredProgram
pkgConfProg{programOverrideEnv = programOverrideEnv'}
    }