{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DisambiguateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -- | All utility functions for Components in Stack (library, internal library, -- foreign library, executable, tests, benchmarks). In particular, this module -- gathers all the Cabal-to-Stack component translations, which previously -- occurred in the "Stack.Package" module. See "Stack.Types.Component" for more -- details about the design choices. module Stack.Component ( isComponentBuildable , stackLibraryFromCabal , stackExecutableFromCabal , stackForeignLibraryFromCabal , stackBenchmarkFromCabal , stackTestFromCabal , foldOnNameAndBuildInfo , stackUnqualToQual , componentDependencyMap , fromCabalName ) where import qualified Data.Map as Map import qualified Data.Set as Set import Data.Text ( pack ) import Distribution.PackageDescription ( Benchmark (..), Executable, ForeignLib, Library (..) , TestSuite (..) ) import Distribution.Types.BuildInfo ( BuildInfo ) import Distribution.Package ( mkPackageName ) import qualified Distribution.PackageDescription as Cabal import GHC.Records ( HasField ) import Stack.Prelude import Stack.Types.Component ( HasBuildInfo, StackBenchmark (..), StackBuildInfo (..) , StackExecutable (..), StackForeignLibrary (..) , StackLibrary (..), StackTestSuite (..) , StackUnqualCompName (..) ) import Stack.Types.ComponentUtils ( fromCabalName ) import Stack.Types.Dependency ( cabalExeToStackDep, cabalToStackDep ) import Stack.Types.NamedComponent ( NamedComponent ) stackUnqualToQual :: (Text -> NamedComponent) -> StackUnqualCompName -> NamedComponent stackUnqualToQual c (StackUnqualCompName n) = c n foldOnNameAndBuildInfo :: ( HasField "buildInfo" a StackBuildInfo , HasField "name" a StackUnqualCompName , Foldable c ) => c a -> (StackUnqualCompName -> StackBuildInfo -> t -> t) -> t -> t foldOnNameAndBuildInfo initialCollection accumulator input = foldr' iterator input initialCollection where iterator comp = accumulator comp.name comp.buildInfo stackLibraryFromCabal :: Library -> StackLibrary stackLibraryFromCabal cabalLib = StackLibrary { name = case cabalLib.libName of LMainLibName -> StackUnqualCompName mempty LSubLibName v -> fromCabalName v , buildInfo = stackBuildInfoFromCabal cabalLib.libBuildInfo , exposedModules = cabalLib.exposedModules } stackExecutableFromCabal :: Executable -> StackExecutable stackExecutableFromCabal cabalExecutable = StackExecutable { name = fromCabalName cabalExecutable.exeName , buildInfo = stackBuildInfoFromCabal cabalExecutable.buildInfo , modulePath = cabalExecutable.modulePath } stackForeignLibraryFromCabal :: ForeignLib -> StackForeignLibrary stackForeignLibraryFromCabal cabalForeignLib = StackForeignLibrary { name = fromCabalName cabalForeignLib.foreignLibName , buildInfo=stackBuildInfoFromCabal cabalForeignLib.foreignLibBuildInfo } stackBenchmarkFromCabal :: Benchmark -> StackBenchmark stackBenchmarkFromCabal cabalBenchmark = StackBenchmark { name = fromCabalName cabalBenchmark.benchmarkName , interface = cabalBenchmark.benchmarkInterface , buildInfo = stackBuildInfoFromCabal cabalBenchmark.benchmarkBuildInfo } stackTestFromCabal :: TestSuite -> StackTestSuite stackTestFromCabal cabalTest = StackTestSuite { name = fromCabalName cabalTest.testName , interface = cabalTest.testInterface , buildInfo = stackBuildInfoFromCabal cabalTest.testBuildInfo } isComponentBuildable :: HasBuildInfo component => component -> Bool isComponentBuildable componentRec = componentRec.buildInfo.buildable stackBuildInfoFromCabal :: BuildInfo -> StackBuildInfo stackBuildInfoFromCabal buildInfoV = gatherComponentToolsAndDepsFromCabal buildInfoV.buildTools buildInfoV.buildToolDepends buildInfoV.targetBuildDepends StackBuildInfo { buildable = buildInfoV.buildable , otherModules = buildInfoV.otherModules , jsSources = buildInfoV.jsSources , hsSourceDirs = buildInfoV.hsSourceDirs , cSources = buildInfoV.cSources , dependency = mempty , unknownTools = mempty , cppOptions = buildInfoV.cppOptions , targetBuildDepends = buildInfoV.targetBuildDepends , options = buildInfoV.options , allLanguages = Cabal.allLanguages buildInfoV , usedExtensions = Cabal.usedExtensions buildInfoV , includeDirs = buildInfoV.includeDirs , extraLibs = buildInfoV.extraLibs , extraLibDirs = buildInfoV.extraLibDirs , frameworks = buildInfoV.frameworks } -- | Iterate on all three dependency list given, and transform and sort them -- between 'sbiUnknownTools' and legitimate 'DepValue' sbiDependency. Bear in -- mind that this only gathers the component level dependencies. gatherComponentToolsAndDepsFromCabal :: [Cabal.LegacyExeDependency] -- ^ Legacy build tools dependency from -- 'Distribution.Types.BuildInfo.buildTools'. -> [Cabal.ExeDependency] -- ^ Build tools dependency from -- `Distribution.Types.BuildInfo.buildToolDepends'. -> [Cabal.Dependency] -- ^ Cabal-syntax defines -- 'Distribution.Types.BuildInfo.targetBuildDepends'. These are the -- simplest dependencies for a component extracted from the Cabal file such -- as: -- @ -- build-depends: -- foo ^>= 1.2.3.4, -- bar ^>= 1 -- @ -> StackBuildInfo -> StackBuildInfo gatherComponentToolsAndDepsFromCabal legacyBuildTools buildTools targetDeps = gatherTargetDependency . gatherToolsDependency . gatherUnknownTools where gatherUnknownTools sbi = foldl' processLegacyExeDepency sbi legacyBuildTools gatherToolsDependency sbi = foldl' processExeDependency sbi buildTools gatherTargetDependency sbi = foldl' processDependency sbi targetDeps -- This is similar to Cabal's -- 'Distribution.Simple.BuildToolDepends.desugarBuildTool', however it uses -- our own hard-coded map which drops tools shipped with GHC (like hsc2hs), -- and includes some tools from Stackage. processLegacyExeDepency sbi (Cabal.LegacyExeDependency exeName range) = case isKnownLegacyExe exeName of Just pName -> processExeDependency sbi (Cabal.ExeDependency pName (Cabal.mkUnqualComponentName exeName) range) Nothing -> sbi { unknownTools = Set.insert (pack exeName) sbi.unknownTools } processExeDependency sbi exeDep@(Cabal.ExeDependency pName _ _) | isPreInstalledPackages pName = sbi | otherwise = sbi { dependency = Map.insert pName (cabalExeToStackDep exeDep) sbi.dependency } processDependency sbi dep@(Cabal.Dependency pName _ _) = sbi { dependency = Map.insert pName (cabalToStackDep dep) sbi.dependency } componentDependencyMap :: (HasField "buildInfo" r1 r2, HasField "dependency" r2 a) => r1 -> a componentDependencyMap component = component.buildInfo.dependency -- | A hard-coded map for tool dependencies. If a dependency is within this map -- it's considered "known" (the exe will be found at the execution stage). The -- corresponding Cabal function is -- 'Distribution.Simple.BuildToolDepends.desugarBuildTool'. isKnownLegacyExe :: String -> Maybe PackageName isKnownLegacyExe input = case input of "alex" -> justPck "alex" "happy" -> justPck "happy" "cpphs" -> justPck "cpphs" "greencard" -> justPck "greencard" "c2hs" -> justPck "c2hs" "hscolour" -> justPck "hscolour" "hspec-discover" -> justPck "hspec-discover" "hsx2hs" -> justPck "hsx2hs" "gtk2hsC2hs" -> justPck "gtk2hs-buildtools" "gtk2hsHookGenerator" -> justPck "gtk2hs-buildtools" "gtk2hsTypeGen" -> justPck "gtk2hs-buildtools" _ -> Nothing where justPck = Just . mkPackageName -- | Executable-only packages which come pre-installed with GHC and do not need -- to be built. Without this exception, we would either end up unnecessarily -- rebuilding these packages, or failing because the packages do not appear in -- the Stackage snapshot. isPreInstalledPackages :: PackageName -> Bool isPreInstalledPackages input = case input of "hsc2hs" -> True "haddock" -> True _ -> False