Commit 5e4377b3 authored by John Ericson's avatar John Ericson Committed by GitHub
Browse files

Merge pull request #4104 from Ericson2314/tool-depends

[WIP] implement tool-depends
parents 6f0399ed a6cee38d
......@@ -159,6 +159,7 @@ library
Distribution.Simple.Build.PathsModule
Distribution.Simple.BuildPaths
Distribution.Simple.BuildTarget
Distribution.Simple.BuildToolDepends
Distribution.Simple.CCompiler
Distribution.Simple.Command
Distribution.Simple.Compiler
......@@ -210,6 +211,7 @@ library
Distribution.Types.BuildType
Distribution.Types.ComponentInclude
Distribution.Types.Dependency
Distribution.Types.ExeDependency
Distribution.Types.LegacyExeDependency
Distribution.Types.PkgconfigDependency
Distribution.Types.Executable
......
......@@ -9,10 +9,11 @@ module Distribution.Backpack.ComponentsGraph (
import Distribution.Package
import Distribution.PackageDescription as PD hiding (Flag)
import Distribution.Simple.BuildToolDepends
import Distribution.Simple.LocalBuildInfo
import Distribution.Types.ComponentRequestedSpec
import Distribution.Types.Dependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.ExeDependency
import Distribution.Types.UnqualComponentName
import Distribution.Simple.Utils
import Distribution.Compat.Graph (Node(..))
......@@ -55,9 +56,8 @@ toComponentsGraph enabled pkg_descr =
-- The dependencies for the given component
componentDeps component =
[ CExeName toolname
| LegacyExeDependency name _ <- buildTools bi
, let toolname = mkUnqualComponentName name
, toolname `elem` map exeName (executables pkg_descr) ]
| (ExeDependency _ toolname _)
<- getAllInternalToolDependencies pkg_descr bi ]
++ [ if pkgname == packageName pkg_descr
then CLibName
......
......@@ -19,13 +19,14 @@ import Distribution.Compat.Prelude hiding ((<>))
import Distribution.Backpack.Id
import Distribution.Types.Dependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.ExeDependency
import Distribution.Types.IncludeRenaming
import Distribution.Types.Mixin
import Distribution.Types.UnqualComponentName
import Distribution.Types.ComponentInclude
import Distribution.Package
import Distribution.PackageDescription as PD hiding (Flag)
import Distribution.Simple.BuildToolDepends
import Distribution.Simple.Setup as Setup
import Distribution.Simple.LocalBuildInfo
import Distribution.Version
......@@ -169,8 +170,9 @@ toConfiguredComponent pkg_descr this_cid
| otherwise
= Map.toList external_lib_map
exe_deps = [ cid
| LegacyExeDependency name _ <- buildTools bi
, Just cid <- [ Map.lookup (mkUnqualComponentName name) exe_map ] ]
| (ExeDependency _ toolName _)
<- getAllInternalToolDependencies pkg_descr bi
, Just cid <- [ Map.lookup toolName exe_map ] ]
-- | Also computes the 'ComponentId', and sets cc_public if necessary.
-- This is Cabal-only; cabal-install won't use this.
......
......@@ -58,8 +58,9 @@ data LinkedComponent
lc_pkgid :: PackageId,
-- | Corresponds to 'cc_component'.
lc_component :: Component,
-- | Local @build-tools@ dependencies on executables from the
-- same executable. Corresponds to 'cc_internal_build_tools'.
-- | Local @build-tools@ and @build-tool-depends@ dependencies on
-- executables from the same package. Corresponds to
-- 'cc_internal_build_tools'.
lc_internal_build_tools :: [OpenUnitId],
-- | Is this the public library of a package? Corresponds to
-- 'cc_public'.
......
......@@ -42,9 +42,11 @@ import Distribution.Compiler
import Distribution.System
import Distribution.License
import Distribution.Simple.BuildPaths (autogenPathsModuleName)
import Distribution.Simple.BuildToolDepends
import Distribution.Simple.CCompiler
import Distribution.Types.ComponentRequestedSpec
import Distribution.Types.Dependency
import Distribution.Types.ExeDependency
import Distribution.Types.UnqualComponentName
import Distribution.Types.CondTree
import Distribution.Simple.Utils hiding (findPackageDesc, notice)
......@@ -527,13 +529,42 @@ checkFields pkg =
++ "for example 'tested-with: GHC==6.10.4, GHC==6.12.3' and not "
++ "'tested-with: GHC==6.10.4 && ==6.12.3'."
, check (not (null buildDependsRangeOnInternalLibrary)) $
, check (not (null depInternalLibraryWithExtraVersion)) $
PackageBuildWarning $
"The package has a version range for a dependency on an "
"The package has an extraneous version range for a dependency on an "
++ "internal library: "
++ commaSep (map display buildDependsRangeOnInternalLibrary)
++ ". This version range has no semantic meaning and can be "
++ "removed."
++ commaSep (map display depInternalLibraryWithExtraVersion)
++ ". This version range includes the current package but isn't needed "
++ "as the current package's library will always be used."
, check (not (null depInternalLibraryWithImpossibleVersion)) $
PackageBuildImpossible $
"The package has an impossible version range for a dependency on an "
++ "internal library: "
++ commaSep (map display depInternalLibraryWithImpossibleVersion)
++ ". This version range does not include the current package, and must "
++ "be removed as the current package's library will always be used."
, check (not (null depInternalExecutableWithExtraVersion)) $
PackageBuildWarning $
"The package has an extraneous version range for a dependency on an "
++ "internal executable: "
++ commaSep (map display depInternalExecutableWithExtraVersion)
++ ". This version range includes the current package but isn't needed "
++ "as the current package's executable will always be used."
, check (not (null depInternalExecutableWithImpossibleVersion)) $
PackageBuildImpossible $
"The package has an impossible version range for a dependency on an "
++ "internal executable: "
++ commaSep (map display depInternalExecutableWithImpossibleVersion)
++ ". This version range does not include the current package, and must "
++ "be removed as the current package's executable will always be used."
, check (not (null depMissingInternalExecutable)) $
PackageBuildImpossible $
"The package depends on a missing internal executable: "
++ commaSep (map display depInternalExecutableWithImpossibleVersion)
]
where
unknownCompilers = [ name | (OtherCompiler name, _) <- testedWith pkg ]
......@@ -559,14 +590,54 @@ checkFields pkg =
internalLibraries =
map (maybe (packageName pkg) (unqualComponentNameToPackageName) . libName)
(allLibraries pkg)
buildDependsRangeOnInternalLibrary =
internalExecutables = map exeName $ executables pkg
internalLibDeps =
[ dep
| bi <- allBuildInfo pkg
, dep@(Dependency name versionRange) <- targetBuildDepends bi
, not (isAnyVersion versionRange)
, dep@(Dependency name _) <- targetBuildDepends bi
, name `elem` internalLibraries
]
internalExeDeps =
[ dep
| bi <- allBuildInfo pkg
, dep <- getAllInternalToolDependencies pkg bi
]
depInternalLibraryWithExtraVersion =
[ dep
| dep@(Dependency _ versionRange) <- internalLibDeps
, not $ isAnyVersion versionRange
, packageVersion pkg `withinRange` versionRange
]
depInternalLibraryWithImpossibleVersion =
[ dep
| dep@(Dependency _ versionRange) <- internalLibDeps
, not $ packageVersion pkg `withinRange` versionRange
]
depInternalExecutableWithExtraVersion =
[ dep
| dep@(ExeDependency _ _ versionRange) <- internalExeDeps
, not $ isAnyVersion versionRange
, packageVersion pkg `withinRange` versionRange
]
depInternalExecutableWithImpossibleVersion =
[ dep
| dep@(ExeDependency _ _ versionRange) <- internalExeDeps
, not $ packageVersion pkg `withinRange` versionRange
]
depMissingInternalExecutable =
[ dep
| dep@(ExeDependency _ eName _) <- internalExeDeps
, not $ eName `elem` internalExecutables
]
checkLicense :: PackageDescription -> [PackageCheck]
checkLicense pkg =
......
......@@ -418,6 +418,9 @@ binfoFieldDescrs =
, commaListField "build-tools"
disp parse
buildTools (\xs binfo -> binfo{buildTools=xs})
, commaListField "build-tool-depends"
disp parse
toolDepends (\xs binfo -> binfo{toolDepends=xs})
, commaListFieldWithSep vcat "build-depends"
disp parse
targetBuildDepends (\xs binfo -> binfo{targetBuildDepends=xs})
......
......@@ -423,6 +423,9 @@ binfoFieldDescrs =
, commaListField "build-tools"
disp parsec
buildTools (\xs binfo -> binfo{buildTools=xs})
, commaListField "build-tool-depends"
disp parsec
toolDepends (\xs binfo -> binfo{toolDepends=xs})
, commaListFieldWithSep vcat "build-depends"
disp parsec
targetBuildDepends (\xs binfo -> binfo{targetBuildDepends=xs})
......
......@@ -42,6 +42,7 @@ import Distribution.Types.BenchmarkType
(BenchmarkType (..))
import Distribution.Types.BuildType (BuildType (..))
import Distribution.Types.Dependency (Dependency (..))
import Distribution.Types.ExeDependency (ExeDependency (..))
import Distribution.Types.LegacyExeDependency (LegacyExeDependency (..))
import Distribution.Types.PkgconfigDependency (PkgconfigDependency (..))
import Distribution.Types.GenericPackageDescription (FlagName, mkFlagName)
......@@ -136,6 +137,14 @@ instance Parsec Dependency where
ver <- parsec <|> pure anyVersion
return (Dependency name ver)
instance Parsec ExeDependency where
parsec = do
name <- lexemeParsec
_ <- P.char ':'
exe <- lexemeParsec
ver <- parsec <|> pure anyVersion
return (ExeDependency name exe ver)
instance Parsec LegacyExeDependency where
parsec = do
name <- parsecMaybeQuoted nameP
......
......@@ -30,7 +30,7 @@ import Prelude ()
import Distribution.Compat.Prelude
import Distribution.Types.Dependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.ExeDependency
import Distribution.Types.LocalBuildInfo
import Distribution.Types.TargetInfo
import Distribution.Types.ComponentRequestedSpec
......@@ -59,6 +59,7 @@ import qualified Distribution.ModuleName as ModuleName
import Distribution.Simple.Setup
import Distribution.Simple.BuildTarget
import Distribution.Simple.BuildToolDepends
import Distribution.Simple.PreProcess
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Program.Types
......@@ -77,7 +78,6 @@ import Distribution.Compat.Graph (IsNode(..))
import Control.Monad
import qualified Data.Set as Set
import Data.List ( intersect )
import System.FilePath ( (</>), (<.>), takeDirectory )
import System.Directory ( getCurrentDirectory )
......@@ -536,14 +536,10 @@ addInternalBuildTools pkg lbi bi progs =
foldr updateProgram progs internalBuildTools
where
internalBuildTools =
[ simpleConfiguredProgram toolName (FoundOnSystem toolLocation)
| toolName <- toolNames
, let toolLocation = buildDir lbi </> toolName </> toolName <.> exeExtension ]
toolNames = intersect buildToolNames internalExeNames
internalExeNames = map (unUnqualComponentName . exeName) (executables pkg)
buildToolNames = map buildToolName (buildTools bi)
where
buildToolName (LegacyExeDependency pname _) = pname
[ simpleConfiguredProgram toolName' (FoundOnSystem toolLocation)
| ExeDependency _ toolName _ <- getAllInternalToolDependencies pkg bi
, let toolName' = unUnqualComponentName toolName
, let toolLocation = buildDir lbi </> toolName' </> toolName' <.> exeExtension ]
-- TODO: build separate libs in separate dirs so that we can build
......
-- |
--
-- 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 Data.Maybe
import qualified Data.Map as Map
import Distribution.Package
import Distribution.PackageDescription
import Distribution.Types.ExeDependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.UnqualComponentName
-- | Desugar a "build-tools" entry into proper a executable dependency if
-- possible.
--
-- An entry can be so desguared 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 pkg led =
if foundLocal
then Just $ ExeDependency (packageName pkg) toolName reqVer
else Map.lookup name whiteMap
where
LegacyExeDependency name reqVer = led
toolName = mkUnqualComponentName name
foundLocal = toolName `elem` map exeName (executables pkg)
whitelist = [ "hscolour", "haddock", "happy", "alex", "hsc2hs", "c2hs"
, "cpphs", "greencard"]
whiteMap = Map.fromList $ flip map whitelist $ \n ->
(n, ExeDependency (mkPackageName n) (mkUnqualComponentName n) reqVer)
-- | 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
-- `toolDepends` field directly.
getAllToolDependencies :: PackageDescription
-> BuildInfo
-> [ExeDependency]
getAllToolDependencies pkg bi =
toolDepends bi ++ mapMaybe (desugarBuildTool pkg) (buildTools 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 pkg (ExeDependency n _ _) = n == packageName 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
-> [ExeDependency]
getAllInternalToolDependencies pkg bi =
filter (isInternal pkg) $ getAllToolDependencies pkg bi
......@@ -75,11 +75,13 @@ import Distribution.Types.PackageDescription as PD
import Distribution.PackageDescription.PrettyPrint
import Distribution.PackageDescription.Configuration
import Distribution.PackageDescription.Check hiding (doesFileExist)
import Distribution.Simple.BuildToolDepends
import Distribution.Simple.Program
import Distribution.Simple.Setup as Setup
import Distribution.Simple.BuildTarget
import Distribution.Simple.LocalBuildInfo
import Distribution.Types.Dependency
import Distribution.Types.ExeDependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.PkgconfigDependency
import Distribution.Types.LocalBuildInfo
......@@ -573,17 +575,11 @@ configure (pkg_descr0', pbi) cfg = do
--
-- TODO: Factor this into a helper package.
let requiredBuildTools =
[ buildTool
| let exeNames = map (unUnqualComponentName . exeName) (executables pkg_descr)
, bi <- enabledBuildInfos pkg_descr enabled
, buildTool@(LegacyExeDependency toolPName reqVer)
<- buildTools bi
, let isInternal =
toolPName `elem` exeNames
-- we assume all internal build-tools are
-- versioned with the package:
&& packageVersion pkg_descr `withinRange` reqVer
, not isInternal ]
[ LegacyExeDependency (unUnqualComponentName eName) versionRange
| bi <- enabledBuildInfos pkg_descr enabled
, buildTool@(ExeDependency _ eName versionRange)
<- getAllToolDependencies pkg_descr bi
, not $ isInternal pkg_descr buildTool ]
programDb' <-
configureAllKnownPrograms (lessVerbose verbosity) programDb
......
......@@ -19,6 +19,7 @@ import Distribution.Compat.Prelude
import Distribution.Types.Mixin
import Distribution.Types.Dependency
import Distribution.Types.ExeDependency
import Distribution.Types.LegacyExeDependency
import Distribution.Types.PkgconfigDependency
......@@ -29,7 +30,23 @@ import Language.Haskell.Extension
-- Consider refactoring into executable and library versions.
data BuildInfo = BuildInfo {
buildable :: Bool, -- ^ component is buildable here
buildTools :: [LegacyExeDependency], -- ^ tools needed to build this bit
-- | Tools needed to build this bit.
--
-- This is a legacy field that "build-tool-depends" larely supersedes.
--
-- Unless use are very sure what you are doing, use the functions in
-- `Distribution.Simple.BuildToolDepends` rather than accessing this
-- field directly.
buildTools :: [LegacyExeDependency],
-- | Haskell tools needed to build this bit
--
-- This field is better than "build-tools" because it allows one to
-- precisely specify an executable in a package.
--
-- Unless use are very sure what you are doing, use the functions in
-- `Distribution.Simple.BuildToolDepends` rather than accessing this
-- field directly.
toolDepends :: [ExeDependency],
cppOptions :: [String], -- ^ options for pre-processing Haskell code
ccOptions :: [String], -- ^ options for C compiler
ldOptions :: [String], -- ^ options for linker
......@@ -71,6 +88,7 @@ instance Monoid BuildInfo where
mempty = BuildInfo {
buildable = True,
buildTools = [],
toolDepends = [],
cppOptions = [],
ccOptions = [],
ldOptions = [],
......@@ -106,6 +124,7 @@ instance Semigroup BuildInfo where
a <> b = BuildInfo {
buildable = buildable a && buildable b,
buildTools = combine buildTools,
toolDepends = combine toolDepends,
cppOptions = combine cppOptions,
ccOptions = combine ccOptions,
ldOptions = combine ldOptions,
......@@ -176,4 +195,3 @@ lookupHcOptions :: (BuildInfo -> [(CompilerFlavor,[String])])
lookupHcOptions f hc bi = [ opt | (hc',opts) <- f bi
, hc' == hc
, opt <- opts ]
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
module Distribution.Types.ExeDependency
( ExeDependency(..)
, qualifiedExeName
) where
import Prelude ()
import Distribution.Compat.Prelude
import Distribution.Package
import Distribution.Types.ComponentName
import Distribution.Types.UnqualComponentName
import Distribution.Version ( VersionRange, anyVersion )
import qualified Distribution.Compat.ReadP as Parse
import Distribution.Compat.ReadP
import Distribution.Text
import Text.PrettyPrint ((<+>), text)
-- | Describes a dependency on an executable from a package
--
data ExeDependency = ExeDependency
PackageName
UnqualComponentName -- name of executable component of package
VersionRange
deriving (Generic, Read, Show, Eq, Typeable, Data)
instance Binary ExeDependency
instance NFData ExeDependency where rnf = genericRnf
instance Text ExeDependency where
disp (ExeDependency name exe ver) =
(disp name <<>> text ":" <<>> disp exe) <+> disp ver
parse = do name <- parse
_ <- Parse.char ':'
exe <- parse
Parse.skipSpaces
ver <- parse <++ return anyVersion
Parse.skipSpaces
return (ExeDependency name exe ver)
qualifiedExeName :: ExeDependency -> ComponentName
qualifiedExeName (ExeDependency _ ucn _) = CExeName ucn
......@@ -1703,14 +1703,39 @@ system-dependent values for these fields.
Deprecated in favor of :pkg-field:`default-extensions`.
.. pkg-field:: build-tool-depends: package:executable list
A list of Haskell programs needed to build this component. Each is specified
by the package containing the executable and the name of the executable
itself, separated by a colon. It is fine for the package to be the current
one, in which case this is termed an *internal*, rather than *external*
executable dependency.
External dependencies can (and should) contain a version bound like
conventional :pkg-field:`build-depends` dependenices. Internal deps should
not contain a version bound, as they will be always resolved within the same
configuration of the package in the build plan. Specifically, version bounds
that include the package's version will be warned for being extraneous, and
version bounds that exclude the package's version will raise and error for
being impossible to follow.
Cabal can make sure that specified programs are built and on the ``PATH``
before building the component in question. It will always do so for internal
dependencies, and also do so for external dependencies when using Nix-style
local builds.
.. pkg-field:: build-tools: program list
:deprecated:
A list of programs, possibly annotated with versions, needed to
build this package, e.g. ``c2hs >= 0.15, cpphs``. If no version
constraint is specified, any version is assumed to be acceptable.
:pkg-field:`build-tools` can refer to locally defined executables, in which
case Cabal will make sure that executable is built first and add it
to the PATH upon invocations to the compiler.
Deprecated in favor of :pkg-field:`build-tool-depends`.
Confusingly, programs in the list either refer to other executables in the
same package, or one of a hard-coded set of build tools. In the case of the
former, the entry can be sugared into a :pkg-field:`build-tool-depends`
entry by prefixing with ``$pkg:``. In the case of the latter, It is
desugared by looking up the package and executable name in a hard-coded
table. Refer to the documentation for :pkg-field:`build-tool-depends` to
understand the desugared fields meaning.
.. pkg-field:: buildable: boolean
......
......@@ -495,8 +495,8 @@ This has the following effects:
(As mentioned previously, you *must* specify internal dependencies as
well.)
- Internal ``build-tools`` dependencies are expected to be in the
``PATH`` upon subsequent invocations of ``setup``.
- Internal ``build-tool-depends`` and ``build-tools`` dependencies are expected
to be in the ``PATH`` upon subsequent invocations of ``setup``.
Full details can be found in the `Componentized Cabal
proposal <https://github.com/ezyang/ghc-proposals/blob/master/proposals/0000-componentized-cabal.rst>`__.
......
......@@ -12,8 +12,9 @@ import Prelude hiding (pi)
import Distribution.Compiler
import Distribution.InstalledPackageInfo as IPI
import Distribution.Package -- from Cabal
import Distribution.Simple.BuildToolDepends -- from Cabal
import Distribution.Types.Dependency -- from Cabal
import Distribution.Types.LegacyExeDependency -- from Cabal
import Distribution.Types.ExeDependency -- from Cabal
import Distribution.Types.PkgconfigDependency -- from Cabal
import Distribution.Types.UnqualComponentName -- from Cabal
import Distribution.Types.CondTree -- from Cabal
......@@ -132,7 +133,7 @@ convGPD os arch cinfo strfl sexes pi
conv :: Mon.Monoid a => Component -> (a -> BuildInfo) ->
CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN
conv comp getInfo = convCondTree os arch cinfo pi fds comp getInfo ipns sexes .
conv comp getInfo = convCondTree pkg os arch cinfo pi fds comp getInfo ipns sexes .
PDC.addBuildableCondition getInfo
flagged_deps
......@@ -179,47 +180,33 @@ filterIPNs ipns (Dependency pn _) fd
-- | Convert condition trees to flagged dependencies. Mutually
-- recursive with 'convBranch'. See 'convBranch' for an explanation
-- of all arguments preceeding the input 'CondTree'.
convCondTree :: OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->
convCondTree :: PackageDescription -> OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->
Component ->
(a -> BuildInfo) ->
IPNs ->
SolveExecutables ->
CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN
convCondTree os arch cinfo pi@(PI pn _) fds comp getInfo ipns sexes@(SolveExecutables sexes') (CondNode info ds branches) =
convCondTree pkg os arch cinfo pi@(PI pn _) fds comp getInfo ipns sexes@(SolveExecutables sexes') (CondNode info ds branches) =