stack-3.7.1: src/Stack/Path.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module : Stack.Path
Description : Types and functions related to Stack's @path@ command.
License : BSD-3-Clause
Types and functions related to Stack's @path@ command.
-}
module Stack.Path
( EnvConfigPathInfo
, UseHaddocks
, path
, pathsFromRunner
, pathsFromConfig
, pathsFromEnvConfig
) where
import Control.Exception ( throw )
import Data.List ( intercalate )
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Path ( (</>), parent )
import Path.Extra ( toFilePathNoTrailingSep )
import RIO.Process ( HasProcessContext (..), exeSearchPathL )
import Stack.Config ( determineStackRootAndOwnership )
import Stack.Constants
( docDirSuffix, stackGlobalConfigOptionName
, stackRootOptionName
)
import Stack.Constants.Config ( distRelativeDir )
import Stack.GhcPkg as GhcPkg
import Stack.Prelude hiding ( pi )
import Stack.Runners
( ShouldReexec (..), withConfig, withDefaultEnvConfig )
import Stack.Types.BuildConfig
( BuildConfig (..), HasBuildConfig (..), configFileL )
import Stack.Types.BuildOptsMonoid ( buildOptsMonoidHaddockL )
import Stack.Types.CompilerPaths
( CompilerPaths (..), HasCompiler (..), getCompilerPath )
import Stack.Types.Config
( Config (..), HasConfig (..), userGlobalConfigFileL )
import Stack.Types.Config.Exception ( ConfigPrettyException (..) )
import Stack.Types.EnvConfig
( EnvConfig, HasEnvConfig (..), bindirCompilerTools
, hpcReportDir, installationRootDeps, installationRootLocal
, packageDatabaseDeps, packageDatabaseExtra
, packageDatabaseLocal
)
import qualified Stack.Types.EnvConfig as EnvConfig
import Stack.Types.GHCVariant ( HasGHCVariant (..) )
import Stack.Types.GlobalOpts
( GlobalOpts (..), globalOptsBuildOptsMonoidL )
import Stack.Types.Platform ( HasPlatform (..) )
import Stack.Types.Runner ( HasRunner (..), Runner, globalOptsL )
import qualified System.FilePath as FP
-- | Print out useful path information in a human-readable format (and support
-- others later).
path :: [Text] -> RIO Runner ()
path keys = do
let -- filter the chosen paths in flags (keys), or show all of them if no
-- specific paths chosen.
filterKeys (_, key, _) = null keys || elem key keys
goodPathsFromRunner = null keys || elem stackRootOptionName' keys
goodPathsFromConfig = filter filterKeys pathsFromConfig
goodPathsFromEnvConfig = filter filterKeys pathsFromEnvConfig
toKeyPath (_, key, p) = (key, p)
goodPathsFromConfig' = map toKeyPath goodPathsFromConfig
singlePath = (if goodPathsFromRunner then 1 else 0) +
length goodPathsFromConfig + length goodPathsFromEnvConfig == 1
toEither (_, k, UseHaddocks a) = Left (k, a)
toEither (_, k, WithoutHaddocks a) = Right (k, a)
(with, without) = partitionEithers $ map toEither goodPathsFromEnvConfig
when goodPathsFromRunner $ printKeysWithRunner singlePath
unless (null goodPathsFromConfig') $
runHaddockWithConfig $ printKeysWithConfig goodPathsFromConfig' singlePath
unless (null without) $
runHaddockWithEnvConfig False $ printKeysWithEnvConfig without singlePath
unless (null with) $
runHaddockWithEnvConfig True $ printKeysWithEnvConfig with singlePath
printKeysWithRunner ::
Bool
-> RIO Runner ()
printKeysWithRunner single = do
clArgs <- view $ globalOptsL . to (.configMonoid)
liftIO $ do
(_, stackRoot, _) <- determineStackRootAndOwnership clArgs
let prefix = if single then "" else stackRootOptionName' <> ": "
T.putStrLn $ prefix <> T.pack (toFilePathNoTrailingSep stackRoot)
printKeysWithConfig ::
HasConfig env
=> [(Text, Config -> Text)]
-> Bool
-> RIO env ()
printKeysWithConfig extractors single =
view configL >>= printKeys extractors single
printKeysWithEnvConfig ::
HasEnvConfig env
=> [(Text, EnvConfigPathInfo -> Text)]
-> Bool
-> RIO env ()
printKeysWithEnvConfig extractors single =
fillEnvConfigPathInfo >>= printKeys extractors single
printKeys ::
[(Text, info -> Text)]
-> Bool
-> info
-> RIO env ()
printKeys extractors single info = do
liftIO $ forM_ extractors $ \(key, extractPath) -> do
let prefix = if single then "" else key <> ": "
T.putStrLn $ prefix <> extractPath info
runHaddockWithEnvConfig :: Bool -> RIO EnvConfig () -> RIO Runner ()
runHaddockWithEnvConfig x action = runHaddock x (withDefaultEnvConfig action)
runHaddockWithConfig :: RIO Config () -> RIO Runner ()
runHaddockWithConfig = runHaddock False
runHaddock :: Bool -> RIO Config () -> RIO Runner ()
runHaddock x action = local modifyConfig $ withConfig YesReexec action
where
modifyConfig = set
(globalOptsL . globalOptsBuildOptsMonoidL . buildOptsMonoidHaddockL)
(Just x)
fillEnvConfigPathInfo :: HasEnvConfig env => RIO env EnvConfigPathInfo
fillEnvConfigPathInfo = do
-- We must use a BuildConfig from an EnvConfig to ensure that it contains the
-- full environment info including GHC paths etc.
buildConfig <- view $ envConfigL . buildConfigL
-- This is the modified 'bin-path',
-- including the local GHC or MSYS if not configured to operate on
-- global GHC.
-- It was set up in 'withBuildConfigAndLock -> withBuildConfigExt -> setupEnv'.
-- So it's not the *minimal* override path.
snapDb <- packageDatabaseDeps
localDb <- packageDatabaseLocal
extraDbs <- packageDatabaseExtra
globalDb <- view $ compilerPathsL . to (.globalDB)
snapRoot <- installationRootDeps
localRoot <- installationRootLocal
toolsDir <- bindirCompilerTools
hoogleRoot <- EnvConfig.hoogleRoot
distDir <- distRelativeDir
hpcDir <- hpcReportDir
compiler <- getCompilerPath
pure EnvConfigPathInfo
{ buildConfig
, snapDb
, localDb
, globalDb
, snapRoot
, localRoot
, toolsDir
, hoogleRoot
, distDir
, hpcDir
, extraDbs
, compiler
}
-- | Type representing information needed to generate an appropriate string for
-- paths of interest to a user which require an 'EnvConfig'.
data EnvConfigPathInfo = EnvConfigPathInfo
{ buildConfig :: !BuildConfig
, snapDb :: !(Path Abs Dir)
, localDb :: !(Path Abs Dir)
, globalDb :: !(Path Abs Dir)
, snapRoot :: !(Path Abs Dir)
, localRoot :: !(Path Abs Dir)
, toolsDir :: !(Path Abs Dir)
, hoogleRoot :: !(Path Abs Dir)
, distDir :: Path Rel Dir
, hpcDir :: !(Path Abs Dir)
, extraDbs :: ![Path Abs Dir]
, compiler :: !(Path Abs File)
}
instance HasPlatform EnvConfigPathInfo where
platformL = configL . platformL
{-# INLINE platformL #-}
platformVariantL = configL . platformVariantL
{-# INLINE platformVariantL #-}
instance HasLogFunc EnvConfigPathInfo where
logFuncL = configL . logFuncL
instance HasRunner EnvConfigPathInfo where
runnerL = configL . runnerL
instance HasStylesUpdate EnvConfigPathInfo where
stylesUpdateL = runnerL . stylesUpdateL
instance HasTerm EnvConfigPathInfo where
useColorL = runnerL . useColorL
termWidthL = runnerL . termWidthL
instance HasGHCVariant EnvConfigPathInfo where
ghcVariantL = configL . ghcVariantL
{-# INLINE ghcVariantL #-}
instance HasConfig EnvConfigPathInfo where
configL = buildConfigL . lens (.config) (\x y -> x { config = y })
{-# INLINE configL #-}
instance HasPantryConfig EnvConfigPathInfo where
pantryConfigL = configL . pantryConfigL
instance HasProcessContext EnvConfigPathInfo where
processContextL = configL . processContextL
instance HasBuildConfig EnvConfigPathInfo where
buildConfigL =
lens (.buildConfig) (\x y -> x { buildConfig = y }) . buildConfigL
-- | Type representing whether or not building Haddocks is required.
data UseHaddocks a
= UseHaddocks a
-- ^ Building Haddocks is required.
| WithoutHaddocks a
-- ^ Building Haddocks is not required.
-- | The paths of interest to a user which do require a t'Config' or
-- 'EnvConfig'. The first tuple string is used for a description that the
-- optparse flag uses, and the second string as a machine-readable key and also
-- for @--foo@ flags. The user can choose a specific path to list like
-- @--stack-root@. But really it's mainly for the documentation aspect.
pathsFromRunner :: (String, Text)
pathsFromRunner = ("Global Stack root directory", stackRootOptionName')
-- | The paths of interest to a user which do require an 'EnvConfig'. The first
-- tuple string is used for a description that the optparse flag uses, and the
-- second string as a machine-readable key and also for @--foo@ flags. The user
-- can choose a specific path to list like @--stack-root@. But really it's
-- mainly for the documentation aspect.
--
-- When printing output we generate t'Config' and pass it to the function
-- to generate an appropriate string. Trailing slashes are removed, see #506.
pathsFromConfig :: [(String, Text, Config -> Text)]
pathsFromConfig =
[ ( "User-specific global configuration file"
, T.pack stackGlobalConfigOptionName
, view (userGlobalConfigFileL . to toFilePath . to T.pack)
)
, ( "Install location for GHC and other core tools (see 'stack ls tools' command)"
, "programs"
, view (configL . to (.localPrograms) . to toFilePathNoTrailingSep . to T.pack)
)
, ( "Directory where Stack installs executables (e.g. ~/.local/bin (Unix-like OSs) or %APPDATA%\\local\\bin (Windows))"
, "local-bin"
, view $ configL . to (.localBin) . to toFilePathNoTrailingSep . to T.pack
)
]
-- | The paths of interest to a user which require a 'EnvConfig'. The first
-- tuple string is used for a description that the optparse flag uses, and the
-- second string as a machine-readable key and also for @--foo@ flags. The user
-- can choose a specific path to list like @--project-root@. But really it's
-- mainly for the documentation aspect.
--
-- When printing output we generate t'EnvConfigPathInfo' and pass it to the
-- function to generate an appropriate string. Trailing slashes are removed, see
-- #506.
pathsFromEnvConfig :: [(String, Text, UseHaddocks (EnvConfigPathInfo -> Text))]
pathsFromEnvConfig =
[ ( "Project root (derived from the project-level configuration file; \
\stack.yaml, by default)"
, "project-root"
, WithoutHaddocks $ view (configFileL . to toProjectConfigFileRootPath)
)
, ( "Project-level configuration file (stack.yaml, by default)"
, "config-location"
, WithoutHaddocks $ view (configFileL . to toProjectConfigFilePath)
)
, ( "PATH environment variable"
, "bin-path"
, WithoutHaddocks $
T.pack . intercalate [FP.searchPathSeparator] . view exeSearchPathL
)
, ( "Compiler binary (e.g. ghc)"
, "compiler-exe"
, WithoutHaddocks $ T.pack . toFilePath . (.compiler)
)
, ( "Directory containing the compiler binary (e.g. ghc)"
, "compiler-bin"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . parent . (.compiler)
)
, ( "Directory containing binaries specific to a particular compiler"
, "compiler-tools-bin"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.toolsDir)
)
, ( "Extra include directories"
, "extra-include-dirs"
, WithoutHaddocks $
T.intercalate ", " . map T.pack . (.extraIncludeDirs) . view configL
)
, ( "Extra library directories"
, "extra-library-dirs"
, WithoutHaddocks $
T.intercalate ", " . map T.pack . (.extraLibDirs) . view configL
)
, ( "Snapshot package database"
, "snapshot-pkg-db"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.snapDb)
)
, ( "Local project package database"
, "local-pkg-db"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.localDb)
)
, ( "Global package database"
, "global-pkg-db"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.globalDb)
)
, ( "GHC_PACKAGE_PATH environment variable"
, "ghc-package-path"
, WithoutHaddocks $
\pi -> mkGhcPackagePath
True
pi.localDb
pi.snapDb
pi.extraDbs
pi.globalDb
)
, ( "Snapshot installation root"
, "snapshot-install-root"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.snapRoot)
)
, ( "Local project installation root"
, "local-install-root"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.localRoot)
)
, ( "Snapshot documentation root"
, "snapshot-doc-root"
, UseHaddocks $
\pi -> T.pack (toFilePathNoTrailingSep (pi.snapRoot </> docDirSuffix))
)
, ( "Local project documentation root"
, "local-doc-root"
, UseHaddocks $
\pi -> T.pack (toFilePathNoTrailingSep (pi.localRoot </> docDirSuffix))
)
, ( "Local project documentation root"
, "local-hoogle-root"
, UseHaddocks $ T.pack . toFilePathNoTrailingSep . (.hoogleRoot)
)
, ( "Dist work directory, relative to package directory"
, "dist-dir"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.distDir)
)
, ( "Where HPC reports and tix files are stored"
, "local-hpc-root"
, WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . (.hpcDir)
)
]
where
toProjectConfigFileRootPath :: Either (Path Abs File) (Path Abs File) -> Text
toProjectConfigFileRootPath (Left _) =
throw $ PrettyException ConfigFileNotProjectLevelBug
toProjectConfigFileRootPath (Right projectConfigFile) =
T.pack $ toFilePathNoTrailingSep $ parent projectConfigFile
toProjectConfigFilePath :: Either (Path Abs File) (Path Abs File) -> Text
toProjectConfigFilePath (Left _) =
throw $ PrettyException ConfigFileNotProjectLevelBug
toProjectConfigFilePath (Right projectConfigFile) =
T.pack $ toFilePath projectConfigFile
-- | 'Text' equivalent of 'stackRootOptionName'.
stackRootOptionName' :: Text
stackRootOptionName' = T.pack stackRootOptionName