diff --git a/.gitignore b/.gitignore index 01dcdc009e..95dec1e4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ stan.html # macOS-related .DS_Store +# VS Code workspace settings +.vscode + # VS Code Counter (Visual Studio Code Extension)-related .VSCodeCounter diff --git a/.hlint.yaml b/.hlint.yaml index 02f1a89d42..2a571b0be4 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -57,13 +57,21 @@ # the GHC2021 set. Other extensions can be added, if need be. - name: - NoImplicitPrelude + - ConstraintKinds - DataKinds - DefaultSignatures + - DeriveDataTypeable + - DeriveGeneric - DerivingStrategies + - DisambiguateRecordFields + - DuplicateRecordFields + - FlexibleContexts - GADTs + - GeneralizedNewtypeDeriving - LambdaCase - MultiWayIf - OverloadedLists + - OverloadedRecordDot - OverloadedStrings - QuasiQuotes - RecordWildCards diff --git a/.stan.toml b/.stan.toml index 4511240028..99909c5b33 100644 --- a/.stan.toml +++ b/.stan.toml @@ -52,7 +52,7 @@ # Anti-pattern: Data.ByteString.Char8.pack [[ignore]] - id = "OBS-STAN-0203-fki0nd-1111:21" + id = "OBS-STAN-0203-fki0nd-1119:21" # ✦ Description: Usage of 'pack' function that doesn't handle Unicode characters # ✦ Category: #AntiPattern # ✦ File: src\Stack\Build\Execute.hs @@ -63,7 +63,7 @@ # Anti-pattern: Data.ByteString.Char8.pack [[ignore]] - id = "OBS-STAN-0203-fki0nd-2656:3" + id = "OBS-STAN-0203-fki0nd-2661:3" # ✦ Description: Usage of 'pack' function that doesn't handle Unicode characters # ✦ Category: #AntiPattern # ✦ File: src\Stack\Build\Execute.hs diff --git a/src/Stack/Build.hs b/src/Stack/Build.hs index 674d65e4bd..dc7338f50f 100644 --- a/src/Stack/Build.hs +++ b/src/Stack/Build.hs @@ -30,7 +30,7 @@ import Stack.Build.Installed ( getInstalled, toInstallMap ) import Stack.Build.Source ( localDependencies, projectLocalPackages ) import Stack.Build.Target ( NeedTargets (..) ) import Stack.FileWatch ( fileWatch, fileWatchPoll ) -import Stack.Package ( resolvePackage ) +import Stack.Package ( buildableExes, resolvePackage ) import Stack.Prelude hiding ( loadPackage ) import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig ) import Stack.Setup ( withNewLocalBuildTargets ) @@ -297,7 +297,7 @@ warnIfExecutablesWithSameNameCouldBeOverwritten locals plan = do collect [ (exe, packageName pkg) | pkg <- map lpPackage locals - , exe <- Set.toList (packageExes pkg) + , exe <- Set.toList (buildableExes pkg) ] collect :: Ord k => [(k, v)] -> Map k (NonEmpty v) collect = Map.mapMaybe nonEmpty . Map.fromDistinctAscList . groupSort diff --git a/src/Stack/Build/ConstructPlan.hs b/src/Stack/Build/ConstructPlan.hs index 7abd5c50c3..7d8281e2aa 100644 --- a/src/Stack/Build/ConstructPlan.hs +++ b/src/Stack/Build/ConstructPlan.hs @@ -28,7 +28,10 @@ import Stack.Build.Cache ( tryGetFlagCache ) import Stack.Build.Haddock ( shouldHaddockDeps ) import Stack.Build.Source ( loadLocalPackage ) import Stack.Constants ( compilerOptionsCabalFlag ) -import Stack.Package ( applyForceCustomBuild ) +import Stack.Package + ( applyForceCustomBuild, buildableExes + , hasBuildableMainLibrary, packageUnknownTools + ) import Stack.Prelude hiding ( loadPackage ) import Stack.SourceMap ( getPLIVersion, mkProjectPackage ) import Stack.Types.Build @@ -45,6 +48,7 @@ import Stack.Types.BuildConfig ( BuildConfig (..), HasBuildConfig (..), stackYamlL ) import Stack.Types.BuildOpts ( BuildOpts (..), BuildOptsCLI (..), BuildSubset (..) ) +import Stack.Types.CompCollection ( collectionMember ) import Stack.Types.Compiler ( WhichCompiler (..) ) import Stack.Types.CompilerPaths ( CompilerPaths (..), HasCompiler (..) ) @@ -67,8 +71,8 @@ import Stack.Types.NamedComponent ( exeComponents, renderComponent ) import Stack.Types.Package ( ExeName (..), InstallLocation (..), Installed (..) , InstalledMap, LocalPackage (..), Package (..) - , PackageLibraries (..), PackageSource (..), installedVersion - , packageIdentifier, psVersion, runMemoizedWith + , PackageSource (..), installedVersion, packageIdentifier + , psVersion, runMemoizedWith ) import Stack.Types.ParentMap ( ParentMap ) import Stack.Types.Platform ( HasPlatform (..) ) @@ -799,7 +803,7 @@ tellExecutablesPackage loc p = do tell mempty { wInstall = Map.fromList $ - map (, loc) $ Set.toList $ filterComps myComps $ packageExes p + map (, loc) $ Set.toList $ filterComps myComps $ buildableExes p } where filterComps myComps x @@ -1162,10 +1166,7 @@ addPackageDeps package = do -- make sure we consider sub-libraries as libraries too packageHasLibrary :: Package -> Bool packageHasLibrary p = - not (Set.null (packageSubLibraries p)) || - case packageLibraries p of - HasLibraries _ -> True - NoLibraries -> False + hasBuildableMainLibrary p || not (null (packageSubLibraries p)) checkDirtiness :: PackageSource @@ -1334,8 +1335,8 @@ checkAndWarnForUnknownTools p = do -- Check whether the tool is on the PATH or a package executable before -- warning about it. warnings <- - fmap catMaybes $ forM unknownTools $ \name@(ExeName toolName) -> - runMaybeT $ notOnPath toolName *> notPackageExe toolName *> warn name + fmap catMaybes $ forM unknownTools $ \toolName -> + runMaybeT $ notOnPath toolName *> notPackageExe toolName *> warn toolName tell mempty { wWarnings = (map toolWarningText warnings ++) } pure () where @@ -1349,8 +1350,9 @@ checkAndWarnForUnknownTools p = do skipIf $ isRight eFound -- From Cabal 1.12, build-tools can specify another executable in the same -- package. - notPackageExe toolName = MaybeT $ skipIf $ toolName `Set.member` packageExes p - warn name = MaybeT . pure . Just $ ToolWarning name (packageName p) + notPackageExe toolName = + MaybeT $ skipIf $ collectionMember toolName (packageExecutables p) + warn name = MaybeT . pure . Just $ ToolWarning (ExeName name) (packageName p) skipIf p' = pure $ if p' then Nothing else Just () -- | Warn about tools in the snapshot definition. States the tool name diff --git a/src/Stack/Build/Execute.hs b/src/Stack/Build/Execute.hs index 167a39e1fa..c9099db537 100644 --- a/src/Stack/Build/Execute.hs +++ b/src/Stack/Build/Execute.hs @@ -64,6 +64,7 @@ import Distribution.Types.UnqualComponentName ( mkUnqualComponentName ) import Distribution.Verbosity ( showForCabal ) import Distribution.Version ( mkVersion ) +import GHC.Records ( getField ) import Path ( PathException, (), addExtension, filename , isProperPrefixOf, parent, parseRelDir, parseRelFile @@ -124,7 +125,10 @@ import Stack.Coverage , generateHpcUnifiedReport, updateTixFile ) import Stack.GhcPkg ( ghcPkg, unregisterGhcPkgIds ) -import Stack.Package ( buildLogPath ) +import Stack.Package + ( buildLogPath, buildableExes, buildableSubLibs + , hasBuildableMainLibrary, mainLibraryHasExposedModules + ) import Stack.PackageDump ( conduitDumpPackage, ghcPkgDescribe ) import Stack.Prelude import Stack.Types.ApplyGhcOptions ( ApplyGhcOptions (..) ) @@ -144,6 +148,10 @@ import Stack.Types.BuildOpts , CabalVerbosity (..), HaddockOpts (..) , ProgressBarFormat (..), TestOpts (..) ) +import Stack.Types.CompCollection + ( collectionKeyValueList, collectionLookup + , getBuildableListText + ) import Stack.Types.Compiler ( ActualCompiler (..), WhichCompiler (..) , compilerVersionString, getGhcVersion, whichCompilerL @@ -152,6 +160,7 @@ import Stack.Types.CompilerPaths ( CompilerPaths (..), GhcPkgExe (..), HasCompiler (..) , cabalVersionL, cpWhich, getCompilerPath, getGhcPkgExe ) +import qualified Stack.Types.Component as Component import Stack.Types.Config ( Config (..), HasConfig (..), buildOptsL, stackRootL ) import Stack.Types.ConfigureOpts @@ -175,9 +184,8 @@ import Stack.Types.NamedComponent ) import Stack.Types.Package ( InstallLocation (..), Installed (..), InstalledMap - , LocalPackage (..), Package (..), PackageLibraries (..) - , installedPackageIdentifier, packageIdentifier - , runMemoizedWith + , LocalPackage (..), Package (..), installedPackageIdentifier + , packageIdentifier, runMemoizedWith ) import Stack.Types.PackageFile ( PackageWarning (..) ) import Stack.Types.Platform ( HasPlatform (..) ) @@ -1713,7 +1721,7 @@ singleBuild && not isFinalBuild -- Works around haddock failing on bytestring-builder since it has no -- modules when bytestring is new enough. - && packageHasExposedModules package + && mainLibraryHasExposedModules package -- Special help for the curator tool to avoid haddocks that are known -- to fail && maybe True (Set.notMember pname . curatorSkipHaddock) mcurator @@ -1748,11 +1756,8 @@ singleBuild (hasLib, hasSubLib, hasExe) = case taskType of TTLocalMutable lp -> let package = lpPackage lp - hasLibrary = - case packageLibraries package of - NoLibraries -> False - HasLibraries _ -> True - hasSubLibraries = not . Set.null $ packageSubLibraries package + hasLibrary = hasBuildableMainLibrary package + hasSubLibraries = not . null $ packageSubLibraries package hasExecutables = not . Set.null $ exesToBuild executableBuildStatuses lp in (hasLibrary, hasSubLibraries, hasExecutables) @@ -1797,9 +1802,9 @@ singleBuild -- However, we must unregister any such library in the new snapshot, in case -- it was built with different flags. let - subLibNames = Set.toList $ case taskType of - TTLocalMutable lp -> packageSubLibraries $ lpPackage lp - TTRemotePackage _ p _ -> packageSubLibraries p + subLibNames = Set.toList $ buildableSubLibs $ case taskType of + TTLocalMutable lp -> lpPackage lp + TTRemotePackage _ p _ -> p toMungedPackageId :: Text -> MungedPackageId toMungedPackageId subLib = let subLibName = LSubLibName $ mkUnqualComponentName $ T.unpack subLib @@ -2038,13 +2043,9 @@ singleBuild cabal0 keep KeepTHLoading $ "haddock" : args - let hasLibrary = - case packageLibraries package of - NoLibraries -> False - HasLibraries _ -> True - packageHasComponentSet f = not $ Set.null $ f package - hasSubLibraries = packageHasComponentSet packageSubLibraries - hasExecutables = packageHasComponentSet packageExes + let hasLibrary = hasBuildableMainLibrary package + hasSubLibraries = not $ null $ packageSubLibraries package + hasExecutables = not $ null $ packageExecutables package shouldCopy = not isFinalBuild && (hasLibrary || hasSubLibraries || hasExecutables) @@ -2093,10 +2094,10 @@ singleBuild let ident = PackageIdentifier (packageName package) (packageVersion package) -- only pure the sub-libraries to cache them if we also cache the main -- library (that is, if it exists) - (mpkgid, subLibsPkgIds) <- case packageLibraries package of - HasLibraries _ -> do + (mpkgid, subLibsPkgIds) <- if hasBuildableMainLibrary package + then do subLibsPkgIds <- fmap catMaybes $ - forM (Set.toList $ packageSubLibraries package) $ \subLib -> do + forM (getBuildableListText $ packageSubLibraries package) $ \subLib -> do let subLibName = MungedPackageName (packageName package) (LSubLibName $ mkUnqualComponentName $ T.unpack subLib) @@ -2112,10 +2113,10 @@ singleBuild case mpkgid of Nothing -> throwM $ Couldn'tFindPkgId $ packageName package Just pkgid -> pure (Library ident pkgid Nothing, subLibsPkgIds) - NoLibraries -> do + else do markExeInstalled (taskLocation task) pkgId -- TODO unify somehow -- with writeFlagCache? - pure (Executable ident, []) -- don't pure sub-libraries in this case + pure (Executable ident, []) -- don't pure sublibs in this case case taskType of TTRemotePackage Immutable _ loc -> @@ -2126,7 +2127,7 @@ singleBuild (configCacheHaddock cache) mpkgid subLibsPkgIds - (packageExes package) + (buildableExes package) _ -> pure () case taskType of @@ -2174,7 +2175,7 @@ getExecutableBuildStatuses package pkgDir = do platform <- view platformL fmap Map.fromList - (mapM (checkExeStatus platform distDir) (Set.toList (packageExes package))) + (mapM (checkExeStatus platform distDir) (Set.toList (buildableExes package))) -- | Check whether the given executable is defined in the given dist directory. checkExeStatus :: @@ -2276,7 +2277,9 @@ singleTest topts testsToRun ac ee task installedMap = do let suitesToRun = [ testSuitePair - | testSuitePair <- Map.toList $ packageTests package + | testSuitePair <- + (fmap . fmap) (getField @"interface") <$> + collectionKeyValueList $ packageTestSuites package , let testName = fst testSuitePair , testName `elem` testsToRun ] @@ -2485,9 +2488,11 @@ singleTest topts testsToRun ac ee task installedMap = do when needHpc $ do let testsToRun' = map f testsToRun f tName = - case Map.lookup tName (packageTests package) of - Just C.TestSuiteLibV09{} -> tName <> "Stub" - _ -> tName + case getField @"interface" <$> mComponent of + Just C.TestSuiteLibV09{} -> tName <> "Stub" + _ -> tName + where + mComponent = collectionLookup tName (packageTestSuites package) generateHpcReport pkgDir package testsToRun' bs <- liftIO $ @@ -2696,17 +2701,17 @@ primaryComponentOptions :: primaryComponentOptions executableBuildStatuses lp = -- TODO: get this information from target parsing instead, which will allow -- users to turn off library building if desired - ( case packageLibraries package of - NoLibraries -> [] - HasLibraries names -> map - T.unpack - ( T.append "lib:" (T.pack (packageNameString (packageName package))) - : map (T.append "flib:") (Set.toList names) - ) + ( if hasBuildableMainLibrary package + then map T.unpack + $ T.append "lib:" (T.pack (packageNameString (packageName package))) + : map + (T.append "flib:") + (getBuildableListText (packageForeignLibraries package)) + else [] ) ++ map (T.unpack . T.append "lib:") - (Set.toList $ packageSubLibraries package) + (getBuildableListText $ packageSubLibraries package) ++ map (T.unpack . T.append "exe:") (Set.toList $ exesToBuild executableBuildStatuses lp) @@ -2729,7 +2734,7 @@ exesToBuild :: Map Text ExecutableBuildStatus -> LocalPackage -> Set Text exesToBuild executableBuildStatuses lp = if cabalIsSatisfied executableBuildStatuses && lpWanted lp then exeComponents (lpComponents lp) - else packageExes (lpPackage lp) + else buildableExes (lpPackage lp) -- | Do the current executables satisfy Cabal's bugged out requirements? cabalIsSatisfied :: Map k ExecutableBuildStatus -> Bool diff --git a/src/Stack/Build/Source.hs b/src/Stack/Build/Source.hs index 4dd1e45646..0a33756fb8 100644 --- a/src/Stack/Build/Source.hs +++ b/src/Stack/Build/Source.hs @@ -24,7 +24,11 @@ import qualified Distribution.PackageDescription as C import qualified Pantry.SHA256 as SHA256 import Stack.Build.Cache ( tryGetBuildCache ) import Stack.Build.Haddock ( shouldHaddockDeps ) -import Stack.Package ( resolvePackage ) +import Stack.Package + ( buildableBenchmarks, buildableExes, buildableTestSuites + , hasBuildableMainLibrary, resolvePackage + ) +import Stack.PackageFile ( getPackageFile ) import Stack.Prelude import Stack.SourceMap ( DumpedGlobalPackage, checkFlagsUsedThrowing @@ -52,10 +56,11 @@ import Stack.Types.NamedComponent ( NamedComponent (..), isCSubLib, splitComponents ) import Stack.Types.Package ( FileCacheInfo (..), LocalPackage (..), Package (..) - , PackageConfig (..), PackageLibraries (..) - , dotCabalGetPath, memoizeRefWith, runMemoizedWith + , PackageConfig (..), dotCabalGetPath, memoizeRefWith + , runMemoizedWith ) -import Stack.Types.PackageFile ( PackageWarning, getPackageFiles ) +import Stack.Types.PackageFile + ( PackageComponentFile (..), PackageWarning ) import Stack.Types.Platform ( HasPlatform (..) ) import Stack.Types.SourceMap ( CommonPackage (..), DepPackage (..), ProjectPackage (..) @@ -312,17 +317,17 @@ loadLocalPackage pp = do let (_s, e, t, b) = splitComponents $ Set.toList comps in (e, t, b) Just (TargetAll _packageType) -> - ( packageExes pkg + ( buildableExes pkg , if boptsTests bopts && maybe True (Set.notMember name . curatorSkipTest) mcurator - then Map.keysSet (packageTests pkg) + then buildableTestSuites pkg else Set.empty , if boptsBenchmarks bopts && maybe True (Set.notMember name . curatorSkipBenchmark) mcurator - then packageBenchmarks pkg + then buildableBenchmarks pkg else Set.empty ) Nothing -> mempty @@ -334,13 +339,9 @@ loadLocalPackage pp = do -- individual executables or library") is resolved, 'hasLibrary' is only -- relevant if the library is part of the target spec. Just _ -> - let hasLibrary = - case packageLibraries pkg of - NoLibraries -> False - HasLibraries _ -> True - in hasLibrary - || not (Set.null nonLibComponents) - || not (Set.null $ packageSubLibraries pkg) + hasBuildableMainLibrary pkg + || not (Set.null nonLibComponents) + || not (null $ packageSubLibraries pkg) filterSkippedComponents = Set.filter (not . (`elem` boptsSkipComponents bopts)) @@ -421,9 +422,9 @@ loadLocalPackage pp = do -- through component parsing, but the components aren't present, then they -- must not be buildable. , lpUnbuildable = toComponents - (exes `Set.difference` packageExes pkg) - (tests `Set.difference` Map.keysSet (packageTests pkg)) - (benches `Set.difference` packageBenchmarks pkg) + (exes `Set.difference` buildableExes pkg) + (tests `Set.difference` buildableTestSuites pkg) + (benches `Set.difference` buildableBenchmarks pkg) } -- | Compare the current filesystem state to the cached information, and @@ -499,8 +500,8 @@ getPackageFilesForTargets :: -> Set NamedComponent -> RIO env (Map NamedComponent (Set (Path Abs File)), [PackageWarning]) getPackageFilesForTargets pkg cabalFP nonLibComponents = do - (components',compFiles,otherFiles,warnings) <- - getPackageFiles (packageFiles pkg) cabalFP + PackageComponentFile components' compFiles otherFiles warnings <- + getPackageFile pkg cabalFP let necessaryComponents = Set.insert CLib $ Set.filter isCSubLib (M.keysSet components') components = necessaryComponents `Set.union` nonLibComponents diff --git a/src/Stack/Component.hs b/src/Stack/Component.hs new file mode 100644 index 0000000000..d1666549b2 --- /dev/null +++ b/src/Stack/Component.hs @@ -0,0 +1,216 @@ +{-# 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 + ) where + +import Data.Foldable ( foldr' ) +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.Types.UnqualComponentName ( UnqualComponentName ) +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 (..), StackTest (..), StackUnqualCompName (..) + ) +import Stack.Types.Dependency ( cabalExeToStackDep, cabalToStackDep ) +import Stack.Types.NamedComponent ( NamedComponent ) + +fromCabalName :: UnqualComponentName -> StackUnqualCompName +fromCabalName unqualName = + StackUnqualCompName $ pack . Cabal.unUnqualComponentName $ unqualName + +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 -> StackTest +stackTestFromCabal cabalTest = StackTest + { name = fromCabalName cabalTest.testName + , interface = cabalTest.testInterface + , buildInfo = stackBuildInfoFromCabal cabalTest.testBuildInfo + } + +isComponentBuildable :: HasBuildInfo component => component -> Bool +isComponentBuildable componentRec = componentRec.buildInfo.sbiBuildable + +stackBuildInfoFromCabal :: BuildInfo -> StackBuildInfo +stackBuildInfoFromCabal buildInfoV = gatherComponentToolsAndDepsFromCabal + buildInfoV.buildTools + buildInfoV.buildToolDepends + buildInfoV.targetBuildDepends + StackBuildInfo + { sbiBuildable = buildInfoV.buildable + , sbiOtherModules = buildInfoV.otherModules + , jsSources = buildInfoV.jsSources + , hsSourceDirs = buildInfoV.hsSourceDirs + , cSources = buildInfoV.cSources + , sbiDependency = mempty + , sbiUnknownTools = 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 + -- [buildTools](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html#t:buildTools). + -> [Cabal.ExeDependency] + -- ^ Build tools dependency + -- [buildToolDepends](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html#t:buildToolDepends) + -> [Cabal.Dependency] + -- ^ The Cabal defined + -- [targetBuildDepends](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html#t: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 + -- [desugarBuildTool](https://hackage.haskell.org/package/Cabal/docs/src/Distribution.Simple.BuildToolDepends.html#desugarBuildTool) + -- from Cabal, 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 + {sbiUnknownTools = Set.insert (pack exeName) $ sbiUnknownTools sbi} + processExeDependency sbi exeDep@(Cabal.ExeDependency pName _ _) + | isPreInstalledPackages pName = sbi + | otherwise = sbi + { sbiDependency = + Map.insert pName (cabalExeToStackDep exeDep) $ sbiDependency sbi + } + processDependency sbi dep@(Cabal.Dependency pName _ _) = sbi + { sbiDependency = + Map.insert pName (cabalToStackDep dep) $ sbiDependency sbi + } + +-- | 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). +-- [It also exists in Cabal](https://hackage.haskell.org/package/Cabal/docs/src/Distribution.Simple.BuildToolDepends.html#local-6989586621679259154) +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-iscover" -> 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 diff --git a/src/Stack/ComponentFile.hs b/src/Stack/ComponentFile.hs index 896ff119f0..0f0df76ba9 100644 --- a/src/Stack/ComponentFile.hs +++ b/src/Stack/ComponentFile.hs @@ -1,5 +1,6 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} -- | A module which exports all component-level file-gathering logic. It also @@ -7,15 +8,16 @@ module Stack.ComponentFile ( resolveOrWarn - , libraryFiles - , executableFiles - , testFiles - , benchmarkFiles , componentOutputDir , componentBuildDir , packageAutogenDir , buildDir , componentAutogenDir + , ComponentFile (..) + , stackLibraryFiles + , stackExecutableFiles + , stackTestFiles + , stackBenchmarkFiles ) where import Control.Exception ( throw ) @@ -27,13 +29,12 @@ import qualified Data.Text as T import Distribution.ModuleName ( ModuleName ) import qualified Distribution.ModuleName as Cabal import Distribution.PackageDescription - ( Benchmark (..), BenchmarkInterface (..), BuildInfo (..) - , Executable (..), Library (..), TestSuite (..) - , TestSuiteInterface (..) - ) + ( BenchmarkInterface (..), TestSuiteInterface (..) ) import Distribution.Text ( display ) -import Distribution.Utils.Path ( getSymbolicPath ) +import Distribution.Utils.Path + ( PackageDir, SourceDir, SymbolicPath, getSymbolicPath ) import Distribution.Version ( mkVersion ) +import GHC.Records ( HasField ) import qualified HiFileParser as Iface import Path ( (), filename, isProperPrefixOf, parent, parseRelDir @@ -50,6 +51,11 @@ import Stack.Constants , relDirAutogen, relDirBuild, relDirGlobalAutogen ) import Stack.Prelude hiding ( Display (..) ) +import Stack.Types.Component + ( StackBenchmark (..), StackBuildInfo (..) + , StackExecutable (..), StackLibrary (..), StackTest (..) + , sbiOtherModules, unqualCompToText + ) import Stack.Types.Config ( Config (..), HasConfig (..), prettyStackDevL ) import Stack.Types.NamedComponent ( NamedComponent (..) ) @@ -61,83 +67,82 @@ import Stack.Types.PackageFile import qualified System.Directory as D ( doesFileExist ) import qualified System.FilePath as FilePath +data ComponentFile = ComponentFile + { moduleFileMap :: !(Map ModuleName (Path Abs File)) + , otherFile :: ![DotCabalPath] + , packageWarning :: ![PackageWarning] + } + -- | Get all files referenced by the benchmark. -benchmarkFiles :: - NamedComponent - -> Benchmark - -> RIO - GetPackageFileContext - (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning]) -benchmarkFiles component bench = - resolveComponentFiles component build names +stackBenchmarkFiles :: + StackBenchmark + -> RIO GetPackageFileContext (NamedComponent, ComponentFile) +stackBenchmarkFiles bench = + resolveComponentFiles (CBench $ unqualCompToText bench.name) build names where names = bnames <> exposed exposed = - case benchmarkInterface bench of + case bench.interface of BenchmarkExeV10 _ fp -> [DotCabalMain fp] BenchmarkUnsupported _ -> [] - bnames = map DotCabalModule (otherModules build) - build = benchmarkBuildInfo bench + bnames = map DotCabalModule build.sbiOtherModules + build = bench.buildInfo -- | Get all files referenced by the test. -testFiles :: - NamedComponent - -> TestSuite - -> RIO - GetPackageFileContext - (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning]) -testFiles component test = - resolveComponentFiles component build names +stackTestFiles :: + StackTest + -> RIO GetPackageFileContext (NamedComponent, ComponentFile) +stackTestFiles test = + resolveComponentFiles (CTest $ unqualCompToText test.name) build names where names = bnames <> exposed exposed = - case testInterface test of + case test.interface of TestSuiteExeV10 _ fp -> [DotCabalMain fp] TestSuiteLibV09 _ mn -> [DotCabalModule mn] TestSuiteUnsupported _ -> [] - bnames = map DotCabalModule (otherModules build) - build = testBuildInfo test + bnames = map DotCabalModule build.sbiOtherModules + build = test.buildInfo -- | Get all files referenced by the executable. -executableFiles :: - NamedComponent - -> Executable - -> RIO - GetPackageFileContext - (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning]) -executableFiles component exe = - resolveComponentFiles component build names +stackExecutableFiles :: + StackExecutable + -> RIO GetPackageFileContext (NamedComponent, ComponentFile) +stackExecutableFiles exe = + resolveComponentFiles (CExe $ unqualCompToText exe.name) build names where - build = buildInfo exe + build = exe.buildInfo names = - map DotCabalModule (otherModules build) ++ - [DotCabalMain (modulePath exe)] + map DotCabalModule build.sbiOtherModules ++ [DotCabalMain exe.modulePath] --- | Get all files referenced by the library. -libraryFiles :: - NamedComponent - -> Library - -> RIO - GetPackageFileContext - (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning]) -libraryFiles component lib = - resolveComponentFiles component build names +-- | Get all files referenced by the library. Handle all libraries (CLib and +-- SubLib), based on empty name or not. +stackLibraryFiles :: + StackLibrary + -> RIO GetPackageFileContext (NamedComponent, ComponentFile) +stackLibraryFiles lib = + resolveComponentFiles componentName build names where - build = libBuildInfo lib + componentRawName = unqualCompToText lib.name + componentName + | componentRawName == mempty = CLib + | otherwise = CSubLib componentRawName + build = lib.buildInfo names = bnames ++ exposed - exposed = map DotCabalModule (exposedModules lib) - bnames = map DotCabalModule (otherModules build) + exposed = map DotCabalModule lib.exposedModules + bnames = map DotCabalModule build.sbiOtherModules -- | Get all files referenced by the component. resolveComponentFiles :: - NamedComponent - -> BuildInfo + ( CAndJsSources rec + , HasField "hsSourceDirs" rec [SymbolicPath PackageDir SourceDir] + ) + => NamedComponent + -> rec -> [DotCabalDescriptor] - -> RIO - GetPackageFileContext - (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning]) + -> RIO GetPackageFileContext (NamedComponent, ComponentFile) resolveComponentFiles component build names = do - dirs <- mapMaybeM (resolveDirOrWarn . getSymbolicPath) (hsSourceDirs build) + dirs <- mapMaybeM (resolveDirOrWarn . getSymbolicPath) build.hsSourceDirs dir <- asks (parent . ctxFile) agdirs <- autogenDirs (modules,files,warnings) <- @@ -146,7 +151,7 @@ resolveComponentFiles component build names = do ((if null dirs then [dir] else dirs) ++ agdirs) names cfiles <- buildOtherSources build - pure (modules, files <> cfiles, warnings) + pure (component, ComponentFile modules (files <> cfiles) warnings) where autogenDirs = do cabalVer <- asks ctxCabalVer @@ -155,10 +160,9 @@ resolveComponentFiles component build names = do pkgDir = maybeToList $ packageAutogenDir cabalVer distDir filterM doesDirExist $ compDir : pkgDir --- | Try to resolve the list of base names in the given directory by --- looking for unique instances of base names applied with the given --- extensions, plus find any of their module and TemplateHaskell --- dependencies. +-- | Try to resolve the list of base names in the given directory by looking for +-- unique instances of base names applied with the given extensions, plus find +-- any of their module and TemplateHaskell dependencies. resolveFilesAndDeps :: NamedComponent -- ^ Package component name -> [Path Abs Dir] -- ^ Directories to look in. @@ -444,8 +448,14 @@ logPossibilities dirs mn = do ) dirs +type CAndJsSources rec = + (HasField "cSources" rec [FilePath], HasField "jsSources" rec [FilePath]) + -- | Get all C sources and extra source files in a build. -buildOtherSources :: BuildInfo -> RIO GetPackageFileContext [DotCabalPath] +buildOtherSources :: + CAndJsSources rec + => rec + -> RIO GetPackageFileContext [DotCabalPath] buildOtherSources build = do cwd <- liftIO getCurrentDir dir <- asks (parent . ctxFile) @@ -458,19 +468,17 @@ buildOtherSources build = do warnMissingFile "File" cwd fp file pure Nothing Just p -> pure $ Just (toCabalPath p) - csources <- resolveDirFiles (cSources build) DotCabalCFilePath - jsources <- resolveDirFiles (targetJsSources build) DotCabalFilePath + csources <- resolveDirFiles build.cSources DotCabalCFilePath + jsources <- resolveDirFiles build.jsSources DotCabalFilePath pure (csources <> jsources) --- | Get the target's JS sources. -targetJsSources :: BuildInfo -> [FilePath] -targetJsSources = jsSources - -- | Resolve file as a child of a specified directory, symlinks -- don't get followed. resolveDirFile :: (MonadIO m, MonadThrow m) - => Path Abs Dir -> FilePath.FilePath -> m (Maybe (Path Abs File)) + => Path Abs Dir + -> FilePath.FilePath + -> m (Maybe (Path Abs File)) resolveDirFile x y = do -- The standard canonicalizePath does not work for this case p <- parseCollapsedAbsFile (toFilePath x FilePath. y) @@ -542,7 +550,8 @@ buildDir distDir = distDir relDirBuild -- component names. componentNameToDir :: Text -> Path Rel Dir componentNameToDir name = - fromMaybe (throw ComponentNotParsedBug) (parseRelDir (T.unpack name)) + fromMaybe (throw $ ComponentNotParsedBug sName) (parseRelDir sName) + where sName = T.unpack name -- | See 'Distribution.Simple.LocalBuildInfo.componentBuildDir' componentBuildDir :: Version -> NamedComponent -> Path Abs Dir -> Path Abs Dir diff --git a/src/Stack/Coverage.hs b/src/Stack/Coverage.hs index 8f01572c99..f051538f9f 100644 --- a/src/Stack/Coverage.hs +++ b/src/Stack/Coverage.hs @@ -40,19 +40,20 @@ import Stack.Constants , relFileHpcIndexHtml, relFileIndexHtml ) import Stack.Constants.Config ( distDirFromDir, hpcRelativeDir ) +import Stack.Package ( hasBuildableMainLibrary ) import Stack.Prelude import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig ) import Stack.Types.BuildConfig ( BuildConfig (..), HasBuildConfig (..) ) import Stack.Types.Compiler ( getGhcVersion ) +import Stack.Types.CompCollection ( getBuildableSetText ) import Stack.Types.BuildOpts ( BuildOptsCLI (..), defaultBuildOptsCLI ) import Stack.Types.EnvConfig ( EnvConfig (..), HasEnvConfig (..), actualCompilerVersionL , hpcReportDir ) import Stack.Types.NamedComponent ( NamedComponent (..) ) -import Stack.Types.Package - ( Package (..), PackageLibraries (..), packageIdentifier ) +import Stack.Types.Package ( Package (..), packageIdentifier ) import Stack.Types.Runner ( Runner ) import Stack.Types.SourceMap ( PackageType (..), SMTargets (..), SMWanted (..) @@ -181,16 +182,13 @@ generateHpcReport pkgDir package tests = do let pkgId = packageIdentifierString (packageIdentifier package) pkgName' = packageNameString $ packageName package ghcVersion = getGhcVersion compilerVersion - hasLibrary = - case packageLibraries package of - NoLibraries -> False - HasLibraries _ -> True + hasLibrary = hasBuildableMainLibrary package subLibs = packageSubLibraries package eincludeName <- -- Pre-7.8 uses plain PKG-version in tix files. if ghcVersion < mkVersion [7, 10] then pure $ Right $ Just [pkgId] -- We don't expect to find a package key if there is no library. - else if not hasLibrary && Set.null subLibs then pure $ Right Nothing + else if not hasLibrary && null subLibs then pure $ Right Nothing -- Look in the inplace DB for the package key. -- See https://github.com/commercialhaskell/stack/issues/1181#issuecomment-148968986 else do @@ -201,7 +199,7 @@ generateHpcReport pkgDir package tests = do findPackageFieldForBuiltPackage pkgDir (packageIdentifier package) - subLibs + (getBuildableSetText subLibs) hpcNameField case eincludeName of Left err -> do @@ -248,104 +246,113 @@ generateHpcReportInternal :: -> [String] -> [String] -> RIO env (Maybe (Path Abs File)) -generateHpcReportInternal tixSrc reportDir report reportHtml extraMarkupArgs extraReportArgs = do - -- If a .tix file exists, move it to the HPC output directory and generate a - -- report for it. - tixFileExists <- doesFileExist tixSrc - if not tixFileExists - then do - prettyError $ - "[S-4634]" - <> line - <> flow "Didn't find" - <> style File ".tix" - <> "for" - <> report - <> flow "- expected to find it at" - <> pretty tixSrc <> "." - pure Nothing - else (`catch` \(err :: ProcessException) -> do - logError $ displayShow err - generateHpcErrorReport reportDir $ display $ sanitize $ - displayException err - pure Nothing) $ - (`onException` - prettyError - ( "[S-8215]" - <> line - <> flow "Error occurred while producing" - <> report <> "." - )) $ do - -- Directories for .mix files. - hpcRelDir <- hpcRelativeDir - -- Compute arguments used for both "hpc markup" and "hpc report". - pkgDirs <- view $ buildConfigL.to (map ppRoot . Map.elems . smwProject . bcSMWanted) - let args = - -- Use index files from all packages (allows cross-package coverage results). - concatMap (\x -> ["--srcdir", toFilePathNoTrailingSep x]) pkgDirs ++ - -- Look for index files in the correct dir (relative to each pkgdir). - ["--hpcdir", toFilePathNoTrailingSep hpcRelDir, "--reset-hpcdirs"] - prettyInfoL - [ "Generating" - , report <> "." - ] - -- Strip @\r@ characters because Windows. - outputLines <- map (L8.filter (/= '\r')) . L8.lines . fst <$> - proc "hpc" - ( "report" - : toFilePath tixSrc - : (args ++ extraReportArgs) - ) - readProcess_ - if all ("(0/0)" `L8.isSuffixOf`) outputLines +generateHpcReportInternal + tixSrc + reportDir + report + reportHtml + extraMarkupArgs + extraReportArgs + = do + -- If a .tix file exists, move it to the HPC output directory and generate + -- a report for it. + tixFileExists <- doesFileExist tixSrc + if not tixFileExists then do - let msgHtml = - "Error: [S-6829]\n\ - \The " - <> display reportHtml - <> " did not consider any code. One possible cause of this is \ - \if your test-suite builds the library code (see Stack \ - \\ - \issue #1008\ - \\ - \). It may also indicate a bug in Stack or the hpc program. \ - \Please report this issue if you think your coverage report \ - \should have meaningful results." prettyError $ - "[S-6829]" + "[S-4634]" <> line - <> fillSep - [ "The" - , report - , flow "did not consider any code. One possible cause of this \ - \is if your test-suite builds the library code (see \ - \Stack issue #1008). It may also indicate a bug in \ - \Stack or the hpc program. Please report this issue if \ - \you think your coverage report should have meaningful \ - \results." - ] - generateHpcErrorReport reportDir msgHtml + <> flow "Didn't find" + <> style File ".tix" + <> "for" + <> report + <> flow "- expected to find it at" + <> pretty tixSrc <> "." pure Nothing - else do - let reportPath = reportDir relFileHpcIndexHtml - -- Print the summary report to the standard output stream. - putUtf8Builder =<< displayWithColor - ( fillSep - [ "Summary" - , report <> ":" - ] - <> line - ) - forM_ outputLines putStrLn - -- Generate the HTML markup. - void $ proc "hpc" - ( "markup" + else (`catch` \(err :: ProcessException) -> do + logError $ displayShow err + generateHpcErrorReport reportDir $ display $ sanitize $ + displayException err + pure Nothing) $ + (`onException` + prettyError + ( "[S-8215]" + <> line + <> flow "Error occurred while producing" + <> report <> "." + )) $ do + -- Directories for .mix files. + hpcRelDir <- hpcRelativeDir + -- Compute arguments used for both "hpc markup" and "hpc report". + pkgDirs <- view $ buildConfigL.to + (map ppRoot . Map.elems . smwProject . bcSMWanted) + let args = + -- Use index files from all packages (allows cross-package + -- coverage results). + concatMap (\x -> ["--srcdir", toFilePathNoTrailingSep x]) pkgDirs ++ + -- Look for index files in the correct dir (relative to each pkgdir). + ["--hpcdir", toFilePathNoTrailingSep hpcRelDir, "--reset-hpcdirs"] + prettyInfoL + [ "Generating" + , report <> "." + ] + -- Strip @\r@ characters because Windows. + outputLines <- map (L8.filter (/= '\r')) . L8.lines . fst <$> + proc "hpc" + ( "report" : toFilePath tixSrc - : ("--destdir=" ++ toFilePathNoTrailingSep reportDir) - : (args ++ extraMarkupArgs) + : (args ++ extraReportArgs) ) readProcess_ - pure (Just reportPath) + if all ("(0/0)" `L8.isSuffixOf`) outputLines + then do + let msgHtml = + "Error: [S-6829]\n\ + \The " + <> display reportHtml + <> " did not consider any code. One possible cause of this is \ + \if your test-suite builds the library code (see Stack \ + \\ + \issue #1008\ + \\ + \). It may also indicate a bug in Stack or the hpc program. \ + \Please report this issue if you think your coverage report \ + \should have meaningful results." + prettyError $ + "[S-6829]" + <> line + <> fillSep + [ "The" + , report + , flow "did not consider any code. One possible cause of this \ + \is if your test-suite builds the library code (see \ + \Stack issue #1008). It may also indicate a bug in \ + \Stack or the hpc program. Please report this issue if \ + \you think your coverage report should have meaningful \ + \results." + ] + generateHpcErrorReport reportDir msgHtml + pure Nothing + else do + let reportPath = reportDir relFileHpcIndexHtml + -- Print the summary report to the standard output stream. + putUtf8Builder =<< displayWithColor + ( fillSep + [ "Summary" + , report <> ":" + ] + <> line + ) + forM_ outputLines putStrLn + -- Generate the HTML markup. + void $ proc "hpc" + ( "markup" + : toFilePath tixSrc + : ("--destdir=" ++ toFilePathNoTrailingSep reportDir) + : (args ++ extraMarkupArgs) + ) + readProcess_ + pure (Just reportPath) generateHpcReportForTargets :: HasEnvConfig env => HpcReportOpts -> [Text] -> [Text] -> RIO env () @@ -415,11 +422,12 @@ generateHpcUnifiedReport = do outputDir <- hpcReportDir ensureDir outputDir (dirs, _) <- listDir outputDir - tixFiles0 <- fmap (concat . concat) $ forM (filter (("combined" /=) . dirnameString) dirs) $ \dir -> do - (dirs', _) <- listDir dir - forM dirs' $ \dir' -> do - (_, files) <- listDir dir' - pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) + tixFiles0 <- + fmap (concat . concat) $ forM (filter (("combined" /=) . dirnameString) dirs) $ \dir -> do + (dirs', _) <- listDir dir + forM dirs' $ \dir' -> do + (_, files) <- listDir dir' + pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) extraTixFiles <- findExtraTixFiles let tixFiles = tixFiles0 ++ extraTixFiles reportDir = outputDir relDirCombined relDirAll @@ -463,7 +471,8 @@ generateUnionReport report reportHtml reportDir tixFiles = do ] <> line <> bulletedList (map fromString errs :: [StyleDoc]) - tixDest <- (reportDir ) <$> parseRelFile (dirnameString reportDir ++ ".tix") + tixDest <- + (reportDir ) <$> parseRelFile (dirnameString reportDir ++ ".tix") ensureDir (parent tixDest) liftIO $ writeTix (toFilePath tixDest) tix generateHpcReportInternal tixDest reportDir report reportHtml [] [] @@ -488,10 +497,11 @@ readTixOrLog path = do ] pure mtix --- | Module names which contain '/' have a package name, and so they weren't built into the --- executable. +-- | Module names which contain '/' have a package name, and so they weren't +-- built into the executable. removeExeModules :: Tix -> Tix -removeExeModules (Tix ms) = Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms) +removeExeModules (Tix ms) = + Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms) unionTixes :: [Tix] -> ([String], Tix) unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs)) @@ -500,7 +510,8 @@ unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs)) toMap (Tix ms) = Map.fromList (map (\x@(TixModule k _ _ _) -> (k, Right x)) ms) merge (Right (TixModule k hash1 len1 tix1)) (Right (TixModule _ hash2 len2 tix2)) - | hash1 == hash2 && len1 == len2 = Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2)) + | hash1 == hash2 && len1 == len2 = + Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2)) merge _ _ = Left () generateHpcMarkupIndex :: HasEnvConfig env => RIO env () diff --git a/src/Stack/Ghci.hs b/src/Stack/Ghci.hs index 5de6cb05c4..ceaad19b7d 100644 --- a/src/Stack/Ghci.hs +++ b/src/Stack/Ghci.hs @@ -48,9 +48,12 @@ import Stack.Ghci.Script , scriptToLazyByteString ) import Stack.Package - ( PackageDescriptionPair (..), packageFromPackageDescription + ( PackageDescriptionPair (..), buildableExes + , buildableForeignLibs, hasBuildableMainLibrary + , getPackageOpts, packageFromPackageDescription , readDotBuildinfo, resolvePackageDescription ) +import Stack.PackageFile ( getPackageFile ) import Stack.Prelude import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig ) import Stack.Types.Build.Exception @@ -61,6 +64,7 @@ import Stack.Types.BuildOpts ( ApplyCLIFlag, BenchmarkOpts (..), BuildOpts (..) , BuildOptsCLI (..), TestOpts (..), defaultBuildOptsCLI ) +import Stack.Types.CompCollection ( getBuildableListText ) import Stack.Types.CompilerPaths ( CompilerPaths (..), HasCompiler (..) ) import Stack.Types.Config ( Config (..), HasConfig (..), buildOptsL ) @@ -74,10 +78,9 @@ import Stack.Types.NamedComponent import Stack.Types.Package ( BuildInfoOpts (..), InstallMap, InstalledMap , LocalPackage (..), Package (..), PackageConfig (..) - , PackageLibraries (..), dotCabalCFilePath, dotCabalGetPath - , dotCabalMainPath, getPackageOpts + , dotCabalCFilePath, dotCabalGetPath, dotCabalMainPath ) -import Stack.Types.PackageFile ( getPackageFiles ) +import Stack.Types.PackageFile ( PackageComponentFile (..) ) import Stack.Types.Platform ( HasPlatform (..) ) import Stack.Types.Runner ( HasRunner, Runner ) import Stack.Types.SourceMap @@ -345,7 +348,7 @@ findFileTargets :: -> RIO env (Map PackageName Target, Map PackageName [Path Abs File], [Path Abs File]) findFileTargets locals fileTargets = do filePackages <- forM locals $ \lp -> do - (_,compFiles,_,_) <- getPackageFiles (packageFiles (lpPackage lp)) (lpCabalFile lp) + PackageComponentFile _ compFiles _ _ <- getPackageFile (lpPackage lp) (lpCabalFile lp) pure (lp, M.map (map dotCabalGetPath) compFiles) let foundFileTargetComponents :: [(Path Abs File, [(PackageName, NamedComponent)])] foundFileTargetComponents = @@ -929,7 +932,8 @@ makeGhciPkgInfo installMap installedMap locals addPkgs mfileTargets pkgDesc = do cabalfp = ghciDescCabalFp pkgDesc target = ghciDescTarget pkgDesc name = packageName pkg - (mods,files,opts) <- getPackageOpts (packageOpts pkg) installMap installedMap locals addPkgs cabalfp + (mods, files, opts) <- + getPackageOpts pkg installMap installedMap locals addPkgs cabalfp let filteredOpts = filterWanted opts filterWanted = M.filterWithKey (\k _ -> k `S.member` allWanted) allWanted = wantedPackageComponents bopts target pkg @@ -957,17 +961,20 @@ makeGhciPkgInfo installMap installedMap locals addPkgs mfileTargets pkgDesc = do wantedPackageComponents :: BuildOpts -> Target -> Package -> Set NamedComponent wantedPackageComponents _ (TargetComps cs) _ = cs wantedPackageComponents bopts (TargetAll PTProject) pkg = S.fromList $ - ( case packageLibraries pkg of - NoLibraries -> [] - HasLibraries names -> CLib : map CSubLib (S.toList names) - ) - <> map CExe (S.toList (packageExes pkg)) - <> map CSubLib (S.toList $ packageSubLibraries pkg) - <> (if boptsTests bopts then map CTest (M.keys (packageTests pkg)) else []) - <> ( if boptsBenchmarks bopts - then map CBench (S.toList (packageBenchmarks pkg)) + ( if hasBuildableMainLibrary pkg + then CLib : map CSubLib buildableForeignLibs' else [] ) + <> map CExe buildableExes' + <> map CSubLib buildableSubLibs + <> (if boptsTests bopts then map CTest buildableTestSuites else []) + <> (if boptsBenchmarks bopts then map CBench buildableBenchmarks else []) + where + buildableForeignLibs' = S.toList $ buildableForeignLibs pkg + buildableSubLibs = getBuildableListText $ packageSubLibraries pkg + buildableExes' = S.toList $ buildableExes pkg + buildableTestSuites = getBuildableListText $ packageTestSuites pkg + buildableBenchmarks = getBuildableListText $ packageBenchmarks pkg wantedPackageComponents _ _ _ = S.empty checkForIssues :: HasTerm env => [GhciPkgInfo] -> RIO env () diff --git a/src/Stack/Package.hs b/src/Stack/Package.hs index a6763e0ee4..06a3332dfd 100644 --- a/src/Stack/Package.hs +++ b/src/Stack/Package.hs @@ -1,7 +1,7 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} -- | Dealing with Cabal. @@ -11,15 +11,24 @@ module Stack.Package , packageFromPackageDescription , Package (..) , PackageDescriptionPair (..) - , GetPackageOpts (..) , PackageConfig (..) , buildLogPath , PackageException (..) , resolvePackageDescription , packageDependencies , applyForceCustomBuild + , hasBuildableMainLibrary + , mainLibraryHasExposedModules + , packageUnknownTools + , buildableForeignLibs + , buildableSubLibs + , buildableExes + , buildableTestSuites + , buildableBenchmarks + , getPackageOpts ) where +import Data.Foldable ( Foldable (..) ) import Data.List ( unzip ) import qualified Data.Map.Strict as M import qualified Data.Set as S @@ -28,6 +37,7 @@ import Distribution.CabalSpecVersion ( cabalSpecToVersionDigits ) import qualified Distribution.Compat.NonEmptySet as NES import Distribution.Compiler ( CompilerFlavor (..), PerCompilerFlavor (..) ) +import Distribution.ModuleName ( ModuleName ) import Distribution.Package ( mkPackageName ) import Distribution.PackageDescription ( Benchmark (..), BuildInfo (..), BuildType (..) @@ -35,9 +45,9 @@ import Distribution.PackageDescription , Dependency (..), Executable (..), ForeignLib (..) , GenericPackageDescription (..), HookedBuildInfo , Library (..), PackageDescription (..), PackageFlag (..) - , SetupBuildInfo (..), TestSuite (..), allLanguages - , allLibraries, buildType, depPkgName, depVerRange - , libraryNameString, maybeToLibraryName, usedExtensions + , SetupBuildInfo (..), TestSuite (..), allLibraries + , buildType, depPkgName, depVerRange, libraryNameString + , maybeToLibraryName ) import Distribution.Pretty ( prettyShow ) import Distribution.Simple.PackageDescription ( readHookedBuildInfo ) @@ -51,39 +61,53 @@ import Distribution.Utils.Path ( getSymbolicPath ) import Distribution.Verbosity ( silent ) import Distribution.Version ( anyVersion, mkVersion, orLaterVersion ) +import GHC.Records ( getField ) import Path ( (), parent, parseAbsDir, parseRelDir, parseRelFile , stripProperPrefix ) import Path.Extra ( concatAndCollapseAbsDir, toFilePathNoTrailingSep ) -import Stack.Constants (relFileCabalMacrosH, relDirLogs) -import Stack.Constants.Config ( distDirFromDir ) -import Stack.Prelude hiding ( Display (..) ) +import Stack.Component + ( foldOnNameAndBuildInfo, isComponentBuildable + , stackBenchmarkFromCabal, stackExecutableFromCabal + , stackForeignLibraryFromCabal, stackLibraryFromCabal + , stackTestFromCabal, stackUnqualToQual + ) import Stack.ComponentFile ( buildDir, componentAutogenDir, componentBuildDir , componentOutputDir, packageAutogenDir ) +import Stack.Constants (relFileCabalMacrosH, relDirLogs) +import Stack.Constants.Config ( distDirFromDir ) +import Stack.PackageFile ( getPackageFile, stackPackageFileFromCabal ) +import Stack.Prelude hiding ( Display (..) ) import Stack.Types.BuildConfig ( HasBuildConfig (..), getProjectWorkDir ) +import Stack.Types.CompCollection + ( CompCollection, foldAndMakeCollection + , getBuildableSetText + ) import Stack.Types.Compiler ( ActualCompiler (..) ) import Stack.Types.CompilerPaths ( cabalVersionL ) +import Stack.Types.Component ( HasBuildInfo ) +import qualified Stack.Types.Component as Component import Stack.Types.Config ( Config (..), HasConfig (..) ) +import Stack.Types.Dependency ( DepType (..), DepValue (..) ) import Stack.Types.EnvConfig ( HasEnvConfig ) import Stack.Types.GhcPkgId ( ghcPkgIdString ) import Stack.Types.NamedComponent ( NamedComponent (..), subLibComponents ) import Stack.Types.Package - ( BuildInfoOpts (..), ExeName (..), GetPackageOpts (..) + ( BioInput (..), BuildInfoOpts (..), ExeName (..) , InstallMap, Installed (..), InstalledMap, Package (..) , PackageConfig (..), PackageException (..) - , PackageLibraries (..), dotCabalCFilePath, packageIdentifier + , dotCabalCFilePath, packageIdentifier ) +import Stack.Types.PackageFile + ( DotCabalPath, PackageComponentFile (..) ) import Stack.Types.Version ( VersionRange, intersectVersionRanges, withinRange ) import System.FilePath ( replaceExtension ) -import Stack.Types.Dependency ( DepValue (..), DepType (..) ) -import Stack.Types.PackageFile ( DotCabalPath , GetPackageFiles (..) ) -import Stack.PackageFile ( getPackageFile ) -- | Read @.buildinfo@ ancillary files produced by some Setup.hs hooks. -- The file includes Cabal file syntax to be merged into the package description @@ -108,75 +132,38 @@ packageFromPackageDescription :: -> [PackageFlag] -> PackageDescriptionPair -> Package -packageFromPackageDescription packageConfig pkgFlags (PackageDescriptionPair pkgNoMod pkg) = - Package - { packageName = name - , packageVersion = pkgVersion pkgId - , packageLicense = licenseRaw pkg - , packageDeps = deps - , packageFiles = pkgFiles - , packageUnknownTools = unknownTools - , packageGhcOptions = packageConfigGhcOptions packageConfig - , packageCabalConfigOpts = packageConfigCabalConfigOpts packageConfig - , packageFlags = packageConfigFlags packageConfig - , packageDefaultFlags = M.fromList - [(flagName flag, flagDefault flag) | flag <- pkgFlags] - , packageAllDeps = M.keysSet deps - , packageSubLibDeps = subLibDeps - , packageLibraries = - let mlib = do - lib <- library pkg - guard $ buildable $ libBuildInfo lib - Just lib - in - case mlib of - Nothing -> NoLibraries - Just _ -> HasLibraries foreignLibNames - , packageSubLibraries = subLibNames - , packageTests = M.fromList - [ (T.pack (Cabal.unUnqualComponentName $ testName t), testInterface t) - | t <- testSuites pkgNoMod - , buildable (testBuildInfo t) - ] - , packageBenchmarks = S.fromList - [ T.pack (Cabal.unUnqualComponentName $ benchmarkName b) - | b <- benchmarks pkgNoMod - , buildable (benchmarkBuildInfo b) - ] - -- Same comment about buildable applies here too. - , packageExes = S.fromList - [ T.pack (Cabal.unUnqualComponentName $ exeName biBuildInfo) - | biBuildInfo <- executables pkg - , buildable (buildInfo biBuildInfo) - ] - -- This is an action used to collect info needed for "stack ghci". - -- This info isn't usually needed, so computation of it is deferred. - , packageOpts = GetPackageOpts $ - \installMap installedMap omitPkgs addPkgs cabalfp -> do - (componentsModules,componentFiles, _, _) <- getPackageFiles pkgFiles cabalfp - let subLibs = - S.toList $ subLibComponents $ M.keysSet componentsModules - excludedSubLibs <- mapM (parsePackageNameThrowing . T.unpack) subLibs - mungedSubLibs <- mapM - (parsePackageNameThrowing . T.unpack . toInternalPackageMungedName) - subLibs - componentsOpts <- generatePkgDescOpts - installMap - installedMap - (excludedSubLibs ++ omitPkgs) - (mungedSubLibs ++ addPkgs) - cabalfp - pkg - componentFiles - pure (componentsModules, componentFiles, componentsOpts) - , packageHasExposedModules = maybe - False - (not . null . exposedModules) - (library pkg) - , packageBuildType = buildType pkg - , packageSetupDeps = msetupDeps - , packageCabalSpec = specVersion pkg - } +packageFromPackageDescription + packageConfig + pkgFlags + (PackageDescriptionPair pkgNoMod pkg) + = Package + { packageName = name + , packageVersion = pkgVersion pkgId + , packageLicense = licenseRaw pkg + , packageDeps = deps + , packageGhcOptions = packageConfigGhcOptions packageConfig + , packageCabalConfigOpts = packageConfigCabalConfigOpts packageConfig + , packageFlags = packageConfigFlags packageConfig + , packageDefaultFlags = M.fromList + [(flagName flag, flagDefault flag) | flag <- pkgFlags] + , packageLibrary = stackLibraryFromCabal <$> library pkg + , packageSubLibraries = + foldAndMakeCollection stackLibraryFromCabal $ subLibraries pkg + , packageForeignLibraries = + foldAndMakeCollection stackForeignLibraryFromCabal $ foreignLibs pkg + , packageTestSuites = + foldAndMakeCollection stackTestFromCabal $ testSuites pkgNoMod + , packageBenchmarks = + foldAndMakeCollection stackBenchmarkFromCabal $ benchmarks pkgNoMod + , packageExecutables = + foldAndMakeCollection stackExecutableFromCabal $ executables pkg + , packageAllDeps = M.keysSet deps + , packageSubLibDeps = subLibDeps + , packageBuildType = buildType pkg + , packageSetupDeps = msetupDeps + , packageCabalSpec = specVersion pkg + , packageFile = stackPackageFileFromCabal pkg + } where extraLibNames = S.union subLibNames foreignLibNames @@ -195,18 +182,13 @@ packageFromPackageDescription packageConfig pkgFlags (PackageDescriptionPair pkg $ filter (buildable . foreignLibBuildInfo) $ foreignLibs pkg - toInternalPackageMungedName - = T.pack . prettyShow . MungedPackageName (pkgName pkgId) - . maybeToLibraryName . Just . Cabal.mkUnqualComponentName . T.unpack - -- Gets all of the modules, files, build files, and data files that constitute -- the package. This is primarily used for dirtiness checking during build, as -- well as use by "stack ghci" - pkgFiles = GetPackageFiles $ getPackageFile pkg pkgId = package pkg name = pkgName pkgId - (unknownTools, knownTools) = packageDescTools pkg + (_unknownTools, knownTools) = packageDescTools pkg deps = M.filterWithKey (const . not . isMe) (M.unionsWith (<>) [ asLibrary <$> packageDependencies pkg @@ -240,6 +222,59 @@ packageFromPackageDescription packageConfig pkgFlags (PackageDescriptionPair pkg isMe name' = name' == name || fromString (packageNameString name') `S.member` extraLibNames +toInternalPackageMungedName :: Package -> Text -> Text +toInternalPackageMungedName pkg = + T.pack + . prettyShow + . MungedPackageName (packageName pkg) + . maybeToLibraryName + . Just + . Cabal.mkUnqualComponentName + . T.unpack + +-- | This is an action used to collect info needed for "stack ghci". This info +-- isn't usually needed, so computation of it is deferred. +getPackageOpts :: + (HasEnvConfig env, MonadReader env m, MonadThrow m, MonadUnliftIO m ) + => Package + -> InstallMap + -> InstalledMap + -> [PackageName] + -> [PackageName] + -> Path Abs File + -> m ( Map NamedComponent (Map ModuleName (Path Abs File)) + , Map NamedComponent [DotCabalPath] + , Map NamedComponent BuildInfoOpts + ) +getPackageOpts + stackPackage + installMap + installedMap + omitPkgs + addPkgs + cabalfp + = do + PackageComponentFile !componentsModules componentFiles _ _ <- + getPackageFile stackPackage cabalfp + let subLibs = + S.toList $ subLibComponents $ M.keysSet componentsModules + excludedSubLibs <- mapM (parsePackageNameThrowing . T.unpack) subLibs + mungedSubLibs <- mapM + ( parsePackageNameThrowing + . T.unpack + . toInternalPackageMungedName stackPackage + ) + subLibs + componentsOpts <- generatePkgDescOpts + installMap + installedMap + (excludedSubLibs ++ omitPkgs) + (mungedSubLibs ++ addPkgs) + cabalfp + stackPackage + componentFiles + pure (componentsModules, componentFiles, componentsOpts) + -- | Generate GHC options for the package's components, and a list of options -- which apply generally to the package, not one specific component. generatePkgDescOpts :: @@ -251,16 +286,22 @@ generatePkgDescOpts :: -> [PackageName] -- ^ Packages to add to the "-package" flags -> Path Abs File - -> PackageDescription + -> Package -> Map NamedComponent [DotCabalPath] -> m (Map NamedComponent BuildInfoOpts) -generatePkgDescOpts installMap installedMap omitPkgs addPkgs cabalfp pkg componentPaths = do - config <- view configL - cabalVer <- view cabalVersionL - distDir <- distDirFromDir cabalDir - let generate namedComponent binfo = - ( namedComponent - , generateBuildInfoOpts BioInput +generatePkgDescOpts + installMap + installedMap + omitPkgs + addPkgs + cabalfp + pkg + componentPaths + = do + config <- view configL + cabalVer <- view cabalVersionL + distDir <- distDirFromDir cabalDir + let generate namedComponent binfo = generateBuildInfoOpts BioInput { biInstallMap = installMap , biInstalledMap = installedMap , biCabalDir = cabalDir @@ -275,73 +316,29 @@ generatePkgDescOpts installMap installedMap omitPkgs addPkgs cabalfp pkg compone , biComponentName = namedComponent , biCabalVersion = cabalVer } - ) - pure - ( M.fromList - ( concat - [ maybe - [] - (pure . generate CLib . libBuildInfo) - (library pkg) - , mapMaybe - (\subLib -> do - let maybeLib = - CSubLib . T.pack . Cabal.unUnqualComponentName <$> - (libraryNameString . libName) subLib - flip generate (libBuildInfo subLib) <$> maybeLib - ) - (subLibraries pkg) - , fmap - (\exe -> - generate - (CExe (T.pack (Cabal.unUnqualComponentName (exeName exe)))) - (buildInfo exe) - ) - (executables pkg) - , fmap - (\bench -> - generate - (CBench - (T.pack (Cabal.unUnqualComponentName (benchmarkName bench))) - ) - (benchmarkBuildInfo bench) - ) - (benchmarks pkg) - , fmap - (\test -> - generate - (CTest (T.pack (Cabal.unUnqualComponentName (testName test)))) - (testBuildInfo test) - ) - (testSuites pkg) - ] - ) - ) + let insertInMap name compVal = M.insert name (generate name compVal) + let translatedInsertInMap constructor name = + insertInMap (stackUnqualToQual constructor name) + let makeBuildInfoOpts selector constructor = + foldOnNameAndBuildInfo + (selector pkg) + (translatedInsertInMap constructor) + let aggregateAllBuildInfoOpts = + makeBuildInfoOpts packageLibrary (const CLib) + . makeBuildInfoOpts packageSubLibraries CSubLib + . makeBuildInfoOpts packageExecutables CExe + . makeBuildInfoOpts packageBenchmarks CBench + . makeBuildInfoOpts packageTestSuites CTest + pure $ aggregateAllBuildInfoOpts mempty where cabalDir = parent cabalfp --- | Input to 'generateBuildInfoOpts' -data BioInput = BioInput - { biInstallMap :: !InstallMap - , biInstalledMap :: !InstalledMap - , biCabalDir :: !(Path Abs Dir) - , biDistDir :: !(Path Abs Dir) - , biOmitPackages :: ![PackageName] - , biAddPackages :: ![PackageName] - , biBuildInfo :: !BuildInfo - , biDotCabalPaths :: ![DotCabalPath] - , biConfigLibDirs :: ![FilePath] - , biConfigIncludeDirs :: ![FilePath] - , biComponentName :: !NamedComponent - , biCabalVersion :: !Version - } - -- | Generate GHC options for the target. Since Cabal also figures out these -- options, currently this is only used for invoking GHCI (via stack ghci). generateBuildInfoOpts :: BioInput -> BuildInfoOpts generateBuildInfoOpts BioInput {..} = BuildInfoOpts - { bioOpts = ghcOpts ++ fmap ("-optP" <>) (cppOptions biBuildInfo) + { bioOpts = ghcOpts ++ fmap ("-optP" <>) (Component.cppOptions biBuildInfo) -- NOTE for future changes: Due to this use of nubOrd (and other uses -- downstream), these generated options must not rely on multiple -- argument sequences. For example, ["--main-is", "Foo.hs", "--main- @@ -376,50 +373,54 @@ generateBuildInfoOpts BioInput {..} = pkgs = biAddPackages ++ [ name - | Dependency name _ _ <- targetBuildDepends biBuildInfo - -- TODO: cabal 3 introduced multiple public libraries in a single dependency + | Dependency name _ _ <- Component.targetBuildDepends biBuildInfo + -- TODO: Cabal 3.0 introduced multiple public libraries in a single + -- dependency , name `notElem` biOmitPackages ] - PerCompilerFlavor ghcOpts _ = options biBuildInfo + PerCompilerFlavor ghcOpts _ = Component.options biBuildInfo extOpts = - map (("-X" ++) . display) (allLanguages biBuildInfo) - <> map (("-X" ++) . display) (usedExtensions biBuildInfo) + map (("-X" ++) . display) (Component.allLanguages biBuildInfo) + <> map (("-X" ++) . display) (Component.usedExtensions biBuildInfo) srcOpts = map (("-i" <>) . toFilePathNoTrailingSep) (concat [ [ componentBuildDir biCabalVersion biComponentName biDistDir ] , [ biCabalDir - | null (hsSourceDirs biBuildInfo) + | null (Component.hsSourceDirs biBuildInfo) ] - , mapMaybe (toIncludeDir . getSymbolicPath) (hsSourceDirs biBuildInfo) + , mapMaybe + (toIncludeDir . getSymbolicPath) + (Component.hsSourceDirs biBuildInfo) , [ componentAutogen ] , maybeToList (packageAutogenDir biCabalVersion biDistDir) , [ componentOutputDir biComponentName biDistDir ] ]) ++ [ "-stubdir=" ++ toFilePathNoTrailingSep (buildDir biDistDir) ] - componentAutogen = componentAutogenDir biCabalVersion biComponentName biDistDir + componentAutogen = + componentAutogenDir biCabalVersion biComponentName biDistDir toIncludeDir "." = Just biCabalDir toIncludeDir relDir = concatAndCollapseAbsDir biCabalDir relDir includeOpts = map ("-I" <>) (biConfigIncludeDirs <> pkgIncludeOpts) pkgIncludeOpts = [ toFilePathNoTrailingSep absDir - | dir <- includeDirs biBuildInfo + | dir <- Component.includeDirs biBuildInfo , absDir <- handleDir dir ] libOpts = - map ("-l" <>) (extraLibs biBuildInfo) <> + map ("-l" <>) (Component.extraLibs biBuildInfo) <> map ("-L" <>) (biConfigLibDirs <> pkgLibDirs) pkgLibDirs = [ toFilePathNoTrailingSep absDir - | dir <- extraLibDirs biBuildInfo + | dir <- Component.extraLibDirs biBuildInfo , absDir <- handleDir dir ] handleDir dir = case (parseAbsDir dir, parseRelDir dir) of (Just ab, _ ) -> [ab] (_ , Just rel) -> [biCabalDir rel] (Nothing, Nothing ) -> [] - fworks = map ("-framework=" <>) (frameworks biBuildInfo) + fworks = map ("-framework=" <>) (Component.frameworks biBuildInfo) -- | Make the .o path from the .c file path for a component. Example: -- @@ -595,19 +596,28 @@ resolvePackageDescription go modBuildable = desc { library = fmap (resolveConditions rc updateLibDeps) mlib , subLibraries = map - (\(n, v) -> (resolveConditions rc updateLibDeps v){libName=LSubLibName n}) + ( \(n, v) -> + (resolveConditions rc updateLibDeps v){libName = LSubLibName n} + ) subLibs , foreignLibs = map - (\(n, v) -> (resolveConditions rc updateForeignLibDeps v){foreignLibName=n}) + ( \(n, v) -> + (resolveConditions rc updateForeignLibDeps v){foreignLibName = n} + ) foreignLibs' , executables = map - (\(n, v) -> (resolveConditions rc updateExeDeps v){exeName=n}) + ( \(n, v) -> (resolveConditions rc updateExeDeps v){exeName = n} ) exes , testSuites = map - (\(n, v) -> (resolveConditions rc (updateTestDeps modBuildable) v){testName=n}) + ( \(n, v) -> + (resolveConditions rc (updateTestDeps modBuildable) v){testName = n} + ) tests , benchmarks = map - (\(n, v) -> (resolveConditions rc (updateBenchmarkDeps modBuildable) v){benchmarkName=n}) + ( \(n, v) -> + (resolveConditions rc (updateBenchmarkDeps modBuildable) v) + {benchmarkName = n} + ) benches } @@ -802,3 +812,52 @@ applyForceCustomBuild cabalVersion package forceCustomBuild = packageBuildType package == Simple && not (cabalVersion `withinRange` cabalVersionRange) + +-- | Check if the package has a main library that is buildable. +hasBuildableMainLibrary :: Package -> Bool +hasBuildableMainLibrary package = + maybe False isComponentBuildable $ packageLibrary package + +-- | Check if the main library has any exposed modules. +-- +-- This should become irrelevant at some point since there's nothing inherently +-- wrong or different with packages exposing only modules in internal libraries +-- (for instance). +mainLibraryHasExposedModules :: Package -> Bool +mainLibraryHasExposedModules package = + maybe False (not . null . Component.exposedModules) $ packageLibrary package + +-- | Aggregate all unknown tools from all components. Mostly meant for +-- build tools specified in the legacy manner (build-tools:) that failed the +-- hard-coded lookup. See 'Stack.Types.Component.sbiUnknownTools' for more +-- information. +packageUnknownTools :: Package -> Set Text +packageUnknownTools pkg = lib (bench <> tests <> flib <> sublib <> exe) + where + lib setT = case packageLibrary pkg of + Just libV -> addUnknownTools libV setT + Nothing -> setT + bench = gatherUnknownTools $ packageBenchmarks pkg + tests = gatherUnknownTools $ packageTestSuites pkg + flib = gatherUnknownTools $ packageForeignLibraries pkg + sublib = gatherUnknownTools $ packageSubLibraries pkg + exe = gatherUnknownTools $ packageExecutables pkg + addUnknownTools :: HasBuildInfo x => x -> Set Text -> Set Text + addUnknownTools = (<>) . Component.sbiUnknownTools . getField @"buildInfo" + gatherUnknownTools :: HasBuildInfo x => CompCollection x -> Set Text + gatherUnknownTools = foldr' addUnknownTools mempty + +buildableForeignLibs :: Package -> Set Text +buildableForeignLibs pkg = getBuildableSetText (packageForeignLibraries pkg) + +buildableSubLibs :: Package -> Set Text +buildableSubLibs pkg = getBuildableSetText (packageSubLibraries pkg) + +buildableExes :: Package -> Set Text +buildableExes pkg = getBuildableSetText (packageExecutables pkg) + +buildableTestSuites :: Package -> Set Text +buildableTestSuites pkg = getBuildableSetText (packageTestSuites pkg) + +buildableBenchmarks :: Package -> Set Text +buildableBenchmarks pkg = getBuildableSetText (packageBenchmarks pkg) diff --git a/src/Stack/PackageFile.hs b/src/Stack/PackageFile.hs index 8ac13bdac7..a7234967be 100644 --- a/src/Stack/PackageFile.hs +++ b/src/Stack/PackageFile.hs @@ -4,28 +4,21 @@ -- | A module which exports all package-level file-gathering logic. module Stack.PackageFile ( getPackageFile - , packageDescModulesAndFiles + , stackPackageFileFromCabal ) where +import Data.Foldable ( Foldable (..) ) import qualified Data.Map.Strict as M import qualified Data.Set as S -import qualified Data.Text as T import Distribution.CabalSpecVersion ( CabalSpecVersion ) -import Distribution.ModuleName ( ModuleName ) -import Distribution.PackageDescription - ( BuildType (..), PackageDescription, benchmarkName - , benchmarks, buildType, dataDir, dataFiles, exeName - , executables, extraSrcFiles, libName, library - , libraryNameString, specVersion, subLibraries, testName - , testSuites ) +import qualified Distribution.PackageDescription as Cabal import Distribution.Simple.Glob ( matchDirFileGlob ) -import qualified Distribution.Types.UnqualComponentName as Cabal import Path ( parent, () ) import Path.Extra ( forgivingResolveFile, rejectMissingFile ) import Path.IO ( doesFileExist ) import Stack.ComponentFile - ( benchmarkFiles, executableFiles, libraryFiles - , resolveOrWarn, testFiles + ( ComponentFile (..), resolveOrWarn, stackBenchmarkFiles + , stackExecutableFiles, stackLibraryFiles ) import Stack.Constants ( relFileHpackPackageConfig, relFileSetupHs, relFileSetupLhs @@ -34,11 +27,12 @@ import Stack.Constants.Config ( distDirFromDir ) import Stack.Prelude import Stack.Types.BuildConfig ( HasBuildConfig (..) ) import Stack.Types.CompilerPaths ( cabalVersionL ) -import Stack.Types.EnvConfig ( HasEnvConfig ) +import Stack.Types.EnvConfig ( HasEnvConfig (..) ) import Stack.Types.NamedComponent ( NamedComponent (..) ) +import Stack.Types.Package ( Package(..) ) import Stack.Types.PackageFile - ( DotCabalPath (..), GetPackageFileContext (..) - , PackageWarning (..) + ( GetPackageFileContext (..), PackageComponentFile (..) + , StackPackageFile (..) ) import qualified System.FilePath as FilePath import System.IO.Error ( isUserError ) @@ -53,70 +47,33 @@ resolveFileOrWarn = resolveOrWarn "File" f -- | Get all files referenced by the package. packageDescModulesAndFiles :: - PackageDescription + Package -> RIO GetPackageFileContext - ( Map NamedComponent (Map ModuleName (Path Abs File)) - , Map NamedComponent [DotCabalPath] - , Set (Path Abs File) - , [PackageWarning] - ) + PackageComponentFile packageDescModulesAndFiles pkg = do - (libraryMods, libDotCabalFiles, libWarnings) <- - maybe - (pure (M.empty, M.empty, [])) - (asModuleAndFileMap libComponent libraryFiles) - (library pkg) - (subLibrariesMods, subLibDotCabalFiles, subLibWarnings) <- - fmap - foldTuples - ( mapM - (asModuleAndFileMap subLibComponent libraryFiles) - (subLibraries pkg) - ) - (executableMods, exeDotCabalFiles, exeWarnings) <- - fmap - foldTuples - ( mapM - (asModuleAndFileMap exeComponent executableFiles) - (executables pkg) - ) - (testMods, testDotCabalFiles, testWarnings) <- - fmap - foldTuples - (mapM (asModuleAndFileMap testComponent testFiles) (testSuites pkg)) - (benchModules, benchDotCabalPaths, benchWarnings) <- - fmap - foldTuples - ( mapM - (asModuleAndFileMap benchComponent benchmarkFiles) - (benchmarks pkg) - ) - dfiles <- resolveGlobFiles - (specVersion pkg) - ( extraSrcFiles pkg - ++ map (dataDir pkg FilePath.) (dataFiles pkg) - ) - let modules = libraryMods <> subLibrariesMods <> executableMods <> testMods <> - benchModules - files = libDotCabalFiles <> subLibDotCabalFiles <> exeDotCabalFiles <> - testDotCabalFiles <> benchDotCabalPaths - warnings = libWarnings <> subLibWarnings <> exeWarnings <> testWarnings <> - benchWarnings - pure (modules, files, dfiles, warnings) - where - libComponent = const CLib - subLibComponent = - CSubLib . T.pack . maybe - "" Cabal.unUnqualComponentName . libraryNameString . libName - exeComponent = CExe . T.pack . Cabal.unUnqualComponentName . exeName - testComponent = CTest . T.pack . Cabal.unUnqualComponentName . testName - benchComponent = CBench . T.pack . Cabal.unUnqualComponentName . benchmarkName - asModuleAndFileMap label f lib = do - (a, b, c) <- f (label lib) lib - pure (M.singleton (label lib) a, M.singleton (label lib) b, c) - foldTuples = foldl' (<>) (M.empty, M.empty, []) + packageExtraFile <- resolveGlobFilesFromStackPackageFile + (packageCabalSpec pkg) (packageFile pkg) + let initialValue = mempty{packageExtraFile=packageExtraFile} + let accumulator f comp st = (insertComponentFile <$> st) <*> f comp + let gatherCompFileCollection createCompFileFn getCompFn res = + foldr' (accumulator createCompFileFn) res (getCompFn pkg) + gatherCompFileCollection stackLibraryFiles packageLibrary + . gatherCompFileCollection stackLibraryFiles packageSubLibraries + . gatherCompFileCollection stackExecutableFiles packageExecutables + . gatherCompFileCollection stackBenchmarkFiles packageBenchmarks + $ pure initialValue +resolveGlobFilesFromStackPackageFile :: + CabalSpecVersion + -> StackPackageFile + -> RIO GetPackageFileContext (Set (Path Abs File)) +resolveGlobFilesFromStackPackageFile + csvV + (StackPackageFile extraSrcFilesV dataDirV dataFilesV) + = resolveGlobFiles + csvV + (extraSrcFilesV ++ map (dataDirV FilePath.) dataFilesV) -- | Resolve globbing of files (e.g. data files) to absolute paths. resolveGlobFiles :: @@ -124,8 +81,7 @@ resolveGlobFiles :: -> [String] -> RIO GetPackageFileContext (Set (Path Abs File)) resolveGlobFiles cabalFileVersion = - fmap (S.fromList . catMaybes . concat) . - mapM resolve + fmap (S.fromList . catMaybes . concat) . mapM resolve where resolve name = if '*' `elem` name @@ -156,44 +112,59 @@ resolveGlobFiles cabalFileVersion = -- well as use by "stack ghci" getPackageFile :: ( HasEnvConfig s, MonadReader s m, MonadThrow m, MonadUnliftIO m ) - => PackageDescription + => Package -> Path Abs File - -> m ( Map NamedComponent (Map ModuleName (Path Abs File)) - , Map NamedComponent [DotCabalPath] - , Set (Path Abs File) - , [PackageWarning] - ) + -> m PackageComponentFile getPackageFile pkg cabalfp = debugBracket ("getPackageFiles" <+> pretty cabalfp) $ do let pkgDir = parent cabalfp distDir <- distDirFromDir pkgDir bc <- view buildConfigL cabalVer <- view cabalVersionL - (componentModules, componentFiles, dataFiles', warnings) <- + packageComponentFile <- runRIO (GetPackageFileContext cabalfp distDir bc cabalVer) (packageDescModulesAndFiles pkg) setupFiles <- - if buildType pkg == Custom - then do - let setupHsPath = pkgDir relFileSetupHs - setupLhsPath = pkgDir relFileSetupLhs - setupHsExists <- doesFileExist setupHsPath - if setupHsExists - then pure (S.singleton setupHsPath) - else do - setupLhsExists <- doesFileExist setupLhsPath - if setupLhsExists - then pure (S.singleton setupLhsPath) - else pure S.empty - else pure S.empty - buildFiles <- fmap (S.insert cabalfp . S.union setupFiles) $ do + if packageBuildType pkg == Cabal.Custom + then do + let setupHsPath = pkgDir relFileSetupHs + setupLhsPath = pkgDir relFileSetupLhs + setupHsExists <- doesFileExist setupHsPath + if setupHsExists + then pure (S.singleton setupHsPath) + else do + setupLhsExists <- doesFileExist setupLhsPath + if setupLhsExists + then pure (S.singleton setupLhsPath) + else pure S.empty + else pure S.empty + moreBuildFiles <- fmap (S.insert cabalfp . S.union setupFiles) $ do let hpackPath = pkgDir relFileHpackPackageConfig hpackExists <- doesFileExist hpackPath pure $ if hpackExists then S.singleton hpackPath else S.empty - pure - ( componentModules - , componentFiles - , buildFiles <> dataFiles' - , warnings - ) + pure packageComponentFile + { packageExtraFile = + moreBuildFiles <> packageExtraFile packageComponentFile + } + +stackPackageFileFromCabal :: Cabal.PackageDescription -> StackPackageFile +stackPackageFileFromCabal cabalPkg = + StackPackageFile + (Cabal.extraSrcFiles cabalPkg) + (Cabal.dataDir cabalPkg) + (Cabal.dataFiles cabalPkg) + +insertComponentFile :: + PackageComponentFile + -> (NamedComponent, ComponentFile) + -> PackageComponentFile +insertComponentFile packageCompFile (name, compFile) = + PackageComponentFile nCompFile nDotCollec packageExtraFile nWarnings + where + (ComponentFile moduleFileMap dotCabalFileList warningsCollec) = compFile + (PackageComponentFile modules files packageExtraFile warnings) = + packageCompFile + nCompFile = M.insert name moduleFileMap modules + nDotCollec = M.insert name dotCabalFileList files + nWarnings = warningsCollec ++ warnings diff --git a/src/Stack/Types/CompCollection.hs b/src/Stack/Types/CompCollection.hs new file mode 100644 index 0000000000..d55c63940b --- /dev/null +++ b/src/Stack/Types/CompCollection.hs @@ -0,0 +1,159 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | A package has collections of the types exported by "Stack.Types.Component". +-- In Cabal, the components are grouped as a list in a @PackageDescription@; +-- this is Stack's counterpart. See for instance the +-- [foreign libraries field](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-PackageDescription.html#t:GenericPackageDescription) +-- in Cabal. Cabal removes all the unbuildable components very early (at the +-- cost of slightly worse error messages) and it historically used a list for +-- components. For , as well as set listed for backward compatbility at least, +-- hence this structure. +module Stack.Types.CompCollection + ( CompCollection + , getBuildableSet + , getBuildableSetText + , getBuildableListText + , getBuildableListAs + , foldAndMakeCollection + , hasBuildableComponent + , collectionLookup + , collectionKeyValueList + , collectionMember + ) where + +import qualified Data.HashMap.Strict as HM +import qualified Data.Set as Set +import Data.Foldable ( Foldable (..) ) +import Stack.Prelude +import Stack.Types.Component + ( HasBuildInfo, HasName, StackBuildInfo (..) + , StackUnqualCompName (..), unqualCompToText + ) + +-- | This is a collection discriminating buildable components and non-buildable +-- ones. +data CompCollection component = CompCollection + { buildableOnes :: {-# UNPACK #-} !(InnerCollection component) + , unbuildableOnes :: Set StackUnqualCompName + -- ^ Note that the field is lazy beacause it should only serve when users + -- explicitely require unbuildable components to be built (and this field + -- ensure intelligible error messages). + } + deriving (Show) + +instance Semigroup (CompCollection component) where + a <> b = CompCollection + { buildableOnes = buildableOnes a <> buildableOnes b + , unbuildableOnes = unbuildableOnes a <> unbuildableOnes b + } + +instance Monoid (CompCollection component) where + mempty = CompCollection + { buildableOnes = mempty + , unbuildableOnes = mempty + } + +-- | This is a naive collection which trades memory consumption for speed, the +-- name set is strictly redundant but it takes O(n) to compute it from the map's +-- keys, hence the redundancy. The assumption is 'asNameSet' is always equal to +-- @keySet . asNameMap@, but faster because cached. But this assumption is not +-- checked anywhere, it's simply built into the 'addComponent' function, which +-- should always be used rather than record's fields for inserting new elements. +data InnerCollection component = InnerCollection + { asNameMap :: !(HashMap StackUnqualCompName component) + , asNameSet :: !(Set StackUnqualCompName) + } + deriving (Show) + +instance Semigroup (InnerCollection component) where + a <> b = InnerCollection + { asNameMap = asNameMap a <> asNameMap b + , asNameSet = asNameSet a <> asNameSet b + } + +instance Monoid (InnerCollection component) where + mempty = InnerCollection + { asNameMap = mempty + , asNameSet = mempty + } + +instance Foldable CompCollection where + foldMap fn collection = foldMap fn (asNameMap $ buildableOnes collection) + foldr' fn c collection = HM.foldr' fn c (asNameMap $ buildableOnes collection) + null = HM.null . asNameMap . buildableOnes + +-- | +-- +-- >>> :set -XOverloadedStrings +-- >>> import Stack.Types.Component (StackUnqualCompName(StackUnqualCompName)) +-- >>> data TestComp = TestComp { name :: StackUnqualCompName } deriving (Show) +-- >>> let ok = TestComp (StackUnqualCompName "some-comp-name") +-- >>> addComponent ok mempty +-- InnerCollection {asNameMap = fromList [(StackUnqualCompName "some-comp-name",TestComp {name = StackUnqualCompName "some-comp-name"})], asNameSet = fromList [StackUnqualCompName "some-comp-name"]} +addComponent :: + HasName component + => component + -> InnerCollection component + -> InnerCollection component +addComponent componentV collection = + let nameV = componentV.name + in collection + { asNameMap=HM.insert nameV componentV (asNameMap collection) + , asNameSet=Set.insert nameV (asNameSet collection) + } + +-- | Iterates on a collection of @compA@ and map each element to @compB@ while +-- building the corresponding @CompCollection@ out of it. +foldAndMakeCollection :: + (HasBuildInfo compB, HasName compB, Foldable sourceCollection) + => (compA -> compB) + -> sourceCollection compA + -> CompCollection compB +foldAndMakeCollection mapFn = foldl' compIterator mempty + where + compIterator existingCollection component = + compCreator existingCollection (mapFn component) + compCreator existingCollection component + | component.buildInfo.sbiBuildable = existingCollection + { buildableOnes = + addComponent component (buildableOnes existingCollection) + } + | otherwise = existingCollection + { unbuildableOnes = + Set.insert component.name (unbuildableOnes existingCollection) + } + +-- | Get the name set of buildable components from this collection. +getBuildableSet :: CompCollection component -> Set StackUnqualCompName +getBuildableSet = asNameSet . buildableOnes + +getBuildableSetText :: CompCollection component -> Set Text +getBuildableSetText = Set.mapMonotonic unqualCompToText . getBuildableSet + +getBuildableListText :: CompCollection component -> [Text] +getBuildableListText = getBuildableListAs unqualCompToText + +getBuildableListAs :: + (StackUnqualCompName -> something) + -> CompCollection component + -> [something] +getBuildableListAs fn = Set.foldr' (\v l -> fn v:l) [] . getBuildableSet + +hasBuildableComponent :: CompCollection component -> Bool +hasBuildableComponent = not . null . getBuildableSet + +collectionLookup :: Text -> CompCollection component -> Maybe component +collectionLookup needle haystack = + HM.lookup (StackUnqualCompName needle) (asNameMap $ buildableOnes haystack) + +collectionKeyValueList :: CompCollection component -> [(Text, component)] +collectionKeyValueList haystack = + (\(StackUnqualCompName k, !v) -> (k, v)) + <$> HM.toList (asNameMap $ buildableOnes haystack) + +collectionMember :: Text -> CompCollection component -> Bool +collectionMember needle haystack = isJust $ collectionLookup needle haystack diff --git a/src/Stack/Types/Component.hs b/src/Stack/Types/Component.hs new file mode 100644 index 0000000000..dcb447ba7d --- /dev/null +++ b/src/Stack/Types/Component.hs @@ -0,0 +1,169 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | All component-related types in Stack (library, internal library, foreign +-- library, executable, tests and benchmarks). The chosen design replicates many +-- of Cabal existing things but in simplified and sometimes more typed versions. +-- It's a work in progress to bring Stack to a more componentized design, and +-- closer to Cabal. +module Stack.Types.Component + ( HasName + , HasBuildInfo + , StackBenchmark (..) + , StackBuildInfo (..) + , StackExecutable (..) + , StackForeignLibrary (..) + , StackLibrary (..) + , StackTest (..) + , StackUnqualCompName (..) + , unqualCompToText + ) where + +import Distribution.Compiler ( PerCompilerFlavor ) +import Distribution.ModuleName ( ModuleName ) +import Distribution.PackageDescription + ( BenchmarkInterface, Dependency, TestSuiteInterface ) +import Distribution.Simple ( Extension, Language ) +import Distribution.Utils.Path ( PackageDir, SourceDir, SymbolicPath ) +import GHC.Records ( HasField ) +import Stack.Prelude +import Stack.Types.Dependency ( DepValue ) + +type HasName component = HasField "name" component StackUnqualCompName + +type HasBuildInfo component = HasField "buildInfo" component StackBuildInfo + +-- | A main or sub library. We do not keep the +-- [Cabal ADT name distinction](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-LibraryName.html#t:LibraryName) +-- because in Cabal 3.0 it's +-- [likely](https://github.com/haskell/cabal/issues/8567) that the main/sub +-- distinction doesn't make sense anymore. Besides, the missing name from main +-- lib can simply be encoded as an empty string for backward compatibility +-- without loosing info. Through this simplification we get a clean name +-- interface for all components (they all have a potentially mempty name of the +-- same type). +-- +-- The Cabal equivalent is +-- [Library](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Library.html). +data StackLibrary = StackLibrary + { name :: StackUnqualCompName + , buildInfo :: !StackBuildInfo + , exposedModules :: [ModuleName] + -- |^ This is only used for gathering the files related to this component. + } + deriving (Show, Typeable) + +-- Stack foreign libraries. +-- +-- The Cabal equivalent is +-- [ForeignLib](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Foreign-Libraries.html). +data StackForeignLibrary = StackForeignLibrary + { name :: StackUnqualCompName + , buildInfo :: !StackBuildInfo + } + deriving (Show, Typeable) + +-- Stack executable. +-- +-- The Cabal equivalent is +-- [Executable](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Executable.html). +data StackExecutable = StackExecutable + { name :: StackUnqualCompName + , buildInfo :: !StackBuildInfo + , modulePath :: FilePath + } + deriving (Show, Typeable) + +-- Stack test. +-- +-- The Cabal equivalent is +-- [TestSuite](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-TestSuite.html). +data StackTest = StackTest + { name :: StackUnqualCompName + , buildInfo :: !StackBuildInfo + , interface :: !TestSuiteInterface + } + deriving (Show, Typeable) + +-- Stack benchmark. +-- +-- The Cabal equivalent is +-- [Benchmark](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Benchmark.html). +data StackBenchmark = StackBenchmark + { name :: StackUnqualCompName + , buildInfo :: StackBuildInfo + , interface :: BenchmarkInterface + -- ^ This is only used for gathering the files related to this component. + } + deriving (Show, Typeable) + +-- | Name of an executable. +newtype ExeName = ExeName Text + deriving (Data, Eq, Hashable, IsString, Generic, NFData, Ord, Show, Typeable) + +-- | The name of an unqualified component (that is, it can be an executable, a +-- library, anything). The Cabal equivalent is +-- [UnqualComponentName](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-UnqualComponentName.html#t:UnqualComponentName). +-- Ideally, we'd want to use the Cabal type behind this newtype and not Text to +-- avoid unnecessary work, but for now this is too much refactoring (also there +-- is no Hashable instance for UnqualComponentName yet). +newtype StackUnqualCompName = StackUnqualCompName Text + deriving (Data, Eq, Hashable, IsString, Generic, NFData, Ord, Show, Typeable) + +unqualCompToText :: StackUnqualCompName -> Text +unqualCompToText (StackUnqualCompName v) = v + +-- | This is the Stack equivalent of Cabal's +-- [BuildInfo](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html). +-- We don't use the Cabal version because Cabal provides a list of dependencies +-- (and we need a Map), and we only need a tiny subset of all the Cabal-provided +-- info. It's also the decomposition of @Package@ based information in prior +-- versions of Stack, to enable component based builds and backpack. The file +-- gathering related fields are lazy because not always needed. +data StackBuildInfo = StackBuildInfo + { sbiBuildable :: !Bool + -- ^ From BuildInfo in Cabal. + , sbiDependency :: !(Map PackageName DepValue) + -- ^ From targetBuildDepends in BuildInfo in Cabal, and known legacy + -- specified build tools (buildTool). + , sbiUnknownTools :: Set Text + -- ^ From buildTool in Cabal, we only keep the legacy build tool depends + -- that we know (from a hardcoded list). We only use the deduplication + -- aspect of the Set here, as this field is only used for error reporting in + -- the end. This is kept lazy because it's an error reporting field only. + , sbiOtherModules :: [ModuleName] + -- ^ Only used in file gathering. See usage in "Stack.ComponentFile" module. + , jsSources :: [FilePath] + -- ^ Only used in file gathering. See usage in "Stack.ComponentFile" module. + , hsSourceDirs :: [SymbolicPath PackageDir SourceDir] + -- ^ Only used in file & opts gathering. See usage in "Stack.ComponentFile" + -- module for fle gathering. + , cSources :: [FilePath] + -- ^ Only used in file gathering. See usage in "Stack.ComponentFile" module. + , cppOptions :: [String] + -- ^ Only used in opts gathering. See usage in "Stack.Package" module. + , targetBuildDepends :: [Dependency] + -- ^ Only used in opts gathering. + , options :: PerCompilerFlavor [String] + -- ^ Only used in opts gathering. + , allLanguages :: [Language] + -- ^ Only used in opts gathering. + , usedExtensions :: [Extension] + -- ^ Only used in opts gathering. + , includeDirs :: [FilePath] + -- ^ Only used in opts gathering. + , extraLibs :: [String] + -- ^ Only used in opts gathering. + , extraLibDirs :: [String] + -- ^ Only used in opts gathering. + , frameworks :: [String] + -- ^ Only used in opts gathering. + } + deriving (Show) diff --git a/src/Stack/Types/Dependency.hs b/src/Stack/Types/Dependency.hs index 892ab2911f..3985b8871e 100644 --- a/src/Stack/Types/Dependency.hs +++ b/src/Stack/Types/Dependency.hs @@ -3,13 +3,15 @@ module Stack.Types.Dependency ( DepValue (..) , DepType (..) + , cabalToStackDep + , cabalExeToStackDep ) where +import qualified Distribution.PackageDescription as Cabal import Distribution.Types.VersionRange ( VersionRange ) import Stack.Prelude import Stack.Types.Version ( intersectVersionRanges ) - -- | The value for a map from dependency name. This contains both the version -- range and the type of dependency, and provides a semigroup instance. data DepValue = DepValue @@ -32,3 +34,10 @@ data DepType instance Semigroup DepType where AsLibrary <> _ = AsLibrary AsBuildTool <> x = x + +cabalToStackDep :: Cabal.Dependency -> DepValue +cabalToStackDep (Cabal.Dependency _ verRange _libNameSet) = + DepValue{dvVersionRange = verRange, dvType = AsLibrary} +cabalExeToStackDep :: Cabal.ExeDependency -> DepValue +cabalExeToStackDep (Cabal.ExeDependency _ _name verRange) = + DepValue{dvVersionRange = verRange, dvType = AsBuildTool} diff --git a/src/Stack/Types/Package.hs b/src/Stack/Types/Package.hs index c2aa3a19d0..058ab582a2 100644 --- a/src/Stack/Types/Package.hs +++ b/src/Stack/Types/Package.hs @@ -1,12 +1,12 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} module Stack.Types.Package - ( BuildInfoOpts (..) + ( BioInput (..) + , BuildInfoOpts (..) , ExeName (..) , FileCacheInfo (..) - , GetPackageOpts (..) , InstallLocation (..) , InstallMap , Installed (..) @@ -19,7 +19,6 @@ module Stack.Types.Package , PackageDatabase (..) , PackageDbVariety (..) , PackageException (..) - , PackageLibraries (..) , PackageSource (..) , dotCabalCFilePath , dotCabalGetPath @@ -49,19 +48,23 @@ import Distribution.Parsec ( PError (..), PWarning (..), showPos ) import qualified Distribution.SPDX.License as SPDX import Distribution.License ( License ) import Distribution.ModuleName ( ModuleName ) -import Distribution.PackageDescription - ( TestSuiteInterface, BuildType ) +import Distribution.PackageDescription ( BuildType ) import Distribution.System ( Platform (..) ) import qualified RIO.Text as T import Stack.Prelude +import Stack.Types.CompCollection ( CompCollection ) import Stack.Types.Compiler ( ActualCompiler ) +import Stack.Types.Component + ( StackBenchmark, StackBuildInfo, StackExecutable + , StackForeignLibrary, StackLibrary, StackTest + ) import Stack.Types.Dependency ( DepValue ) import Stack.Types.EnvConfig ( EnvConfig, HasEnvConfig (..) ) import Stack.Types.GhcPkgId ( GhcPkgId ) import Stack.Types.NamedComponent ( NamedComponent ) import Stack.Types.PackageFile - ( GetPackageFiles (..), DotCabalDescriptor (..) - , DotCabalPath (..) + ( DotCabalDescriptor (..), DotCabalPath (..) + , StackPackageFile ) import Stack.Types.SourceMap ( CommonPackage, FromSnapshot ) import Stack.Types.Version ( VersionRange ) @@ -77,7 +80,7 @@ data PackageException | MismatchedCabalIdentifier !PackageIdentifierRevision !PackageIdentifier | CabalFileNameParseFail FilePath | CabalFileNameInvalidPackageName FilePath - | ComponentNotParsedBug + | ComponentNotParsedBug String deriving (Show, Typeable) instance Exception PackageException where @@ -132,16 +135,12 @@ instance Exception PackageException where \extension, the following is invalid: " , fp ] - displayException ComponentNotParsedBug = bugReport "[S-4623]" - "Component names should always parse as directory names." - --- | Libraries in a package. Since Cabal 2.0, sub-libraries are a thing. -data PackageLibraries - = NoLibraries - | HasLibraries !(Set Text) - -- ^ the foreign library names, sub-libraries get built automatically - -- without explicit component name passing - deriving (Show, Typeable) + displayException (ComponentNotParsedBug name) = bugReport "[S-4623]" + ( "Component names should always parse as directory names. The component \ + \name without a directory is '" + <> name + <> "'." + ) -- | Name of an executable. newtype ExeName @@ -156,13 +155,9 @@ data Package = Package -- ^ Version of the package , packageLicense :: !(Either SPDX.License License) -- ^ The license the package was released under. - , packageFiles :: !GetPackageFiles - -- ^ Get all files of the package. , packageDeps :: !(Map PackageName DepValue) - -- ^ Packages that the package depends on, both as libraries and build tools. - , packageUnknownTools :: !(Set ExeName) - -- ^ Build tools specified in the legacy manner (build-tools:) that failed - -- the hard-coded lookup. + -- ^ Packages that the package depends on, both as libraries and build + -- tools. , packageAllDeps :: !(Set PackageName) -- ^ Original dependencies (not sieved). , packageSubLibDeps :: !(Map MungedPackageName DepValue) @@ -175,26 +170,27 @@ data Package = Package -- ^ Flags used on package. , packageDefaultFlags :: !(Map FlagName Bool) -- ^ Defaults for unspecified flags. - , packageLibraries :: !PackageLibraries - -- ^ does the package have a buildable library stanza? - , packageSubLibraries :: !(Set Text) - -- ^ Names of sub-libraries - , packageTests :: !(Map Text TestSuiteInterface) - -- ^ names and interfaces of test suites - , packageBenchmarks :: !(Set Text) - -- ^ names of benchmarks - , packageExes :: !(Set Text) - -- ^ names of executables - , packageOpts :: !GetPackageOpts - -- ^ Args to pass to GHC. - , packageHasExposedModules :: !Bool - -- ^ Does the package have exposed modules? + , packageLibrary :: !(Maybe StackLibrary) + -- ^ Does the package have a buildable main library stanza? + , packageSubLibraries :: !(CompCollection StackLibrary) + -- ^ The sub-libraries of the package. + , packageForeignLibraries :: !(CompCollection StackForeignLibrary) + -- ^ The foreign libraries of the package. + , packageTestSuites :: !(CompCollection StackTest) + -- ^ The test suites of the package. + , packageBenchmarks :: !(CompCollection StackBenchmark) + -- ^ The benchmarks of the package. + , packageExecutables :: !(CompCollection StackExecutable) + -- ^ The executables of the package. , packageBuildType :: !BuildType -- ^ Package build-type. , packageSetupDeps :: !(Maybe (Map PackageName VersionRange)) -- ^ If present: custom-setup dependencies , packageCabalSpec :: !CabalSpecVersion -- ^ Cabal spec range + , packageFile :: StackPackageFile + -- ^ The cabal sourced files related to the package at the package level + -- The components may have file information in their own types } deriving (Show, Typeable) @@ -209,25 +205,6 @@ packageDefinedFlags = M.keysSet . packageDefaultFlags -- or mutable) and package versions. type InstallMap = Map PackageName (InstallLocation, Version) --- | Files that the package depends on, relative to package directory. --- Argument is the location of the Cabal file -newtype GetPackageOpts = GetPackageOpts - { getPackageOpts :: forall env. HasEnvConfig env - => InstallMap - -> InstalledMap - -> [PackageName] - -> [PackageName] - -> Path Abs File - -> RIO env - ( Map NamedComponent (Map ModuleName (Path Abs File)) - , Map NamedComponent [DotCabalPath] - , Map NamedComponent BuildInfoOpts - ) - } - -instance Show GetPackageOpts where - show _ = "" - -- | GHC options based on cabal information and ghc-options. data BuildInfoOpts = BuildInfoOpts { bioOpts :: [String] @@ -498,3 +475,19 @@ installedVersion :: Installed -> Version installedVersion i = let PackageIdentifier _ version = installedPackageIdentifier i in version + +-- | Type representing inputs to 'Stack.Package.generateBuildInfoOpts'. +data BioInput = BioInput + { biInstallMap :: !InstallMap + , biInstalledMap :: !InstalledMap + , biCabalDir :: !(Path Abs Dir) + , biDistDir :: !(Path Abs Dir) + , biOmitPackages :: ![PackageName] + , biAddPackages :: ![PackageName] + , biBuildInfo :: !StackBuildInfo + , biDotCabalPaths :: ![DotCabalPath] + , biConfigLibDirs :: ![FilePath] + , biConfigIncludeDirs :: ![FilePath] + , biComponentName :: !NamedComponent + , biCabalVersion :: !Version + } diff --git a/src/Stack/Types/PackageFile.hs b/src/Stack/Types/PackageFile.hs index e42a6fce8e..64b6a1c3ea 100644 --- a/src/Stack/Types/PackageFile.hs +++ b/src/Stack/Types/PackageFile.hs @@ -9,8 +9,9 @@ module Stack.Types.PackageFile ( GetPackageFileContext (..) , DotCabalPath (..) , DotCabalDescriptor (..) - , GetPackageFiles (..) , PackageWarning (..) + , StackPackageFile (..) + , PackageComponentFile (..) ) where import Distribution.ModuleName ( ModuleName ) @@ -19,7 +20,6 @@ import Stack.Prelude import Stack.Types.BuildConfig ( BuildConfig (..), HasBuildConfig (..) ) import Stack.Types.Config ( HasConfig (..) ) -import Stack.Types.EnvConfig ( HasEnvConfig ) import Stack.Types.GHCVariant ( HasGHCVariant (..) ) import Stack.Types.NamedComponent ( NamedComponent ) import Stack.Types.Platform ( HasPlatform (..) ) @@ -91,21 +91,6 @@ data DotCabalDescriptor | DotCabalCFile !FilePath deriving (Eq, Ord, Show) --- | Files that the package depends on, relative to package directory. --- Argument is the location of the Cabal file -newtype GetPackageFiles = GetPackageFiles - { getPackageFiles :: forall env. HasEnvConfig env - => Path Abs File - -> RIO env - ( Map NamedComponent (Map ModuleName (Path Abs File)) - , Map NamedComponent [DotCabalPath] - , Set (Path Abs File) - , [PackageWarning] - ) - } -instance Show GetPackageFiles where - show _ = "" - -- | Warning generated when reading a package data PackageWarning = UnlistedModulesWarning NamedComponent [ModuleName] @@ -116,3 +101,27 @@ data PackageWarning | MissingModulesWarning (Path Abs File) (Maybe String) [ModuleName] -- ^ Modules not found in file system, which are listed in Cabal file -} + +-- | This is the information from Cabal we need at the package level to track +-- files. +data StackPackageFile = StackPackageFile + { extraSrcFiles :: [FilePath] + , dataDir :: FilePath + , dataFiles :: [FilePath] + } + deriving (Show, Typeable) + +-- | Files that the package depends on, relative to package directory. +data PackageComponentFile = PackageComponentFile + { modulePathMap :: Map NamedComponent (Map ModuleName (Path Abs File)) + , cabalFileMap :: !(Map NamedComponent [DotCabalPath]) + , packageExtraFile :: Set (Path Abs File) + , warnings :: [PackageWarning] + } + +instance Semigroup PackageComponentFile where + PackageComponentFile x1 x2 x3 x4 <> PackageComponentFile y1 y2 y3 y4 = + PackageComponentFile (x1 <> y1) (x2 <> y2) (x3 <> y3) (x4 <> y4) + +instance Monoid PackageComponentFile where + mempty = PackageComponentFile mempty mempty mempty mempty diff --git a/stack.cabal b/stack.cabal index fde4aadaff..9fae0428e8 100644 --- a/stack.cabal +++ b/stack.cabal @@ -326,7 +326,10 @@ library Paths_stack other-modules: GHC.Utils.GhcPkg.Main.Compat + Stack.Component Stack.Config.ConfigureScript + Stack.Types.CompCollection + Stack.Types.Component Stack.Types.FileDigestCache autogen-modules: Build_stack