-- |
--
-- This modules provides functions for working with both the legacy
-- "build-tools" field, and its replacement, "build-tool-depends". Prefer using
-- the functions contained to access those fields directly.
module Distribution.Simple.BuildToolDepends where

import Distribution.Compat.Prelude
import Prelude ()

import qualified Data.Map as Map

import Distribution.Package
import Distribution.PackageDescription

-- | Same as 'desugarBuildTool', but requires atomic information (package
-- name, executable names) instead of a whole 'PackageDescription'.
desugarBuildToolSimple
  :: PackageName
  -> [UnqualComponentName]
  -> LegacyExeDependency
  -> Maybe ExeDependency
desugarBuildToolSimple :: PackageName
-> [UnqualComponentName]
-> LegacyExeDependency
-> Maybe ExeDependency
desugarBuildToolSimple PackageName
pname [UnqualComponentName]
exeNames (LegacyExeDependency [Char]
name VersionRange
reqVer)
  | Bool
foundLocal = ExeDependency -> Maybe ExeDependency
forall a. a -> Maybe a
Just (ExeDependency -> Maybe ExeDependency)
-> ExeDependency -> Maybe ExeDependency
forall a b. (a -> b) -> a -> b
$ PackageName -> UnqualComponentName -> VersionRange -> ExeDependency
ExeDependency PackageName
pname UnqualComponentName
toolName VersionRange
reqVer
  | Bool
otherwise = [Char] -> Map [Char] ExeDependency -> Maybe ExeDependency
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup [Char]
name Map [Char] ExeDependency
allowMap
  where
    toolName :: UnqualComponentName
toolName = [Char] -> UnqualComponentName
mkUnqualComponentName [Char]
name
    foundLocal :: Bool
foundLocal = UnqualComponentName
toolName UnqualComponentName -> [UnqualComponentName] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [UnqualComponentName]
exeNames
    allowlist :: [[Char]]
allowlist =
      [ [Char]
"hscolour"
      , [Char]
"haddock"
      , [Char]
"happy"
      , [Char]
"alex"
      , [Char]
"hsc2hs"
      , [Char]
"c2hs"
      , [Char]
"cpphs"
      , [Char]
"hspec-discover"
      ]
    allowMap :: Map [Char] ExeDependency
allowMap = [([Char], ExeDependency)] -> Map [Char] ExeDependency
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([([Char], ExeDependency)] -> Map [Char] ExeDependency)
-> [([Char], ExeDependency)] -> Map [Char] ExeDependency
forall a b. (a -> b) -> a -> b
$ (([Char] -> ([Char], ExeDependency))
 -> [[Char]] -> [([Char], ExeDependency)])
-> [[Char]]
-> ([Char] -> ([Char], ExeDependency))
-> [([Char], ExeDependency)]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ([Char] -> ([Char], ExeDependency))
-> [[Char]] -> [([Char], ExeDependency)]
forall a b. (a -> b) -> [a] -> [b]
map [[Char]]
allowlist (([Char] -> ([Char], ExeDependency)) -> [([Char], ExeDependency)])
-> ([Char] -> ([Char], ExeDependency)) -> [([Char], ExeDependency)]
forall a b. (a -> b) -> a -> b
$ \[Char]
n ->
      ([Char]
n, PackageName -> UnqualComponentName -> VersionRange -> ExeDependency
ExeDependency ([Char] -> PackageName
mkPackageName [Char]
n) ([Char] -> UnqualComponentName
mkUnqualComponentName [Char]
n) VersionRange
reqVer)

-- | Desugar a "build-tools" entry into a proper executable dependency if
-- possible.
--
-- An entry can be so desugared in two cases:
--
-- 1. The name in build-tools matches a locally defined executable.  The
--    executable dependency produced is on that exe in the current package.
--
-- 2. The name in build-tools matches a hard-coded set of known tools.  For now,
--    the executable dependency produced is one an executable in a package of
--    the same, but the hard-coding could just as well be per-key.
--
-- The first cases matches first.
desugarBuildTool
  :: PackageDescription
  -> LegacyExeDependency
  -> Maybe ExeDependency
desugarBuildTool :: PackageDescription -> LegacyExeDependency -> Maybe ExeDependency
desugarBuildTool PackageDescription
pkg LegacyExeDependency
led =
  PackageName
-> [UnqualComponentName]
-> LegacyExeDependency
-> Maybe ExeDependency
desugarBuildToolSimple
    (PackageDescription -> PackageName
forall pkg. Package pkg => pkg -> PackageName
packageName PackageDescription
pkg)
    ((Executable -> UnqualComponentName)
-> [Executable] -> [UnqualComponentName]
forall a b. (a -> b) -> [a] -> [b]
map Executable -> UnqualComponentName
exeName ([Executable] -> [UnqualComponentName])
-> [Executable] -> [UnqualComponentName]
forall a b. (a -> b) -> a -> b
$ PackageDescription -> [Executable]
executables PackageDescription
pkg)
    LegacyExeDependency
led

-- | Get everything from "build-tool-depends", along with entries from
-- "build-tools" that we know how to desugar.
--
-- This should almost always be used instead of just accessing the
-- `buildToolDepends` field directly.
getAllToolDependencies
  :: PackageDescription
  -> BuildInfo
  -> [ExeDependency]
getAllToolDependencies :: PackageDescription -> BuildInfo -> [ExeDependency]
getAllToolDependencies PackageDescription
pkg BuildInfo
bi =
  BuildInfo -> [ExeDependency]
buildToolDepends BuildInfo
bi [ExeDependency] -> [ExeDependency] -> [ExeDependency]
forall a. [a] -> [a] -> [a]
++ (LegacyExeDependency -> Maybe ExeDependency)
-> [LegacyExeDependency] -> [ExeDependency]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (PackageDescription -> LegacyExeDependency -> Maybe ExeDependency
desugarBuildTool PackageDescription
pkg) (BuildInfo -> [LegacyExeDependency]
buildTools BuildInfo
bi)

-- | Does the given executable dependency map to this current package?
--
-- This is a tiny function, but used in a number of places.
--
-- This function is only sound to call on `BuildInfo`s from the given package
-- description. This is because it just filters the package names of each
-- dependency, and does not check whether version bounds in fact exclude the
-- current package, or the referenced components in fact exist in the current
-- package.
--
-- This is OK because when a package is loaded, it is checked (in
-- `Distribution.Package.Check`) that dependencies matching internal components
-- do indeed have version bounds accepting the current package, and any
-- depended-on component in the current package actually exists. In fact this
-- check is performed by gathering the internal tool dependencies of each
-- component of the package according to this module, and ensuring those
-- properties on each so-gathered dependency.
--
-- version bounds and components of the package are unchecked. This is because
-- we sanitize exe deps so that the matching name implies these other
-- conditions.
isInternal :: PackageDescription -> ExeDependency -> Bool
isInternal :: PackageDescription -> ExeDependency -> Bool
isInternal PackageDescription
pkg (ExeDependency PackageName
n UnqualComponentName
_ VersionRange
_) = PackageName
n PackageName -> PackageName -> Bool
forall a. Eq a => a -> a -> Bool
== PackageDescription -> PackageName
forall pkg. Package pkg => pkg -> PackageName
packageName PackageDescription
pkg

-- | Get internal "build-tool-depends", along with internal "build-tools"
--
-- This is a tiny function, but used in a number of places. The same
-- restrictions that apply to `isInternal` also apply to this function.
getAllInternalToolDependencies
  :: PackageDescription
  -> BuildInfo
  -> [UnqualComponentName]
getAllInternalToolDependencies :: PackageDescription -> BuildInfo -> [UnqualComponentName]
getAllInternalToolDependencies PackageDescription
pkg BuildInfo
bi =
  [ UnqualComponentName
toolname
  | dep :: ExeDependency
dep@(ExeDependency PackageName
_ UnqualComponentName
toolname VersionRange
_) <- PackageDescription -> BuildInfo -> [ExeDependency]
getAllToolDependencies PackageDescription
pkg BuildInfo
bi
  , PackageDescription -> ExeDependency -> Bool
isInternal PackageDescription
pkg ExeDependency
dep
  ]