cabal-install-3.16.0.0: src/Distribution/Client/Sandbox/PackageEnvironment.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Client.Sandbox.PackageEnvironment
-- Maintainer : cabal-devel@haskell.org
-- Portability : portable
--
-- Utilities for working with the package environment file. Patterned after
-- Distribution.Client.Config.
module Distribution.Client.Sandbox.PackageEnvironment
( PackageEnvironment (..)
, PackageEnvironmentType (..)
, classifyPackageEnvironment
, readPackageEnvironmentFile
, showPackageEnvironment
, showPackageEnvironmentWithComments
, loadUserConfig
, userPackageEnvironmentFile
) where
import Distribution.Client.Compat.Prelude
import Prelude ()
import Distribution.Client.Config
( SavedConfig (..)
, configFieldDescriptions
, haddockFlagsFields
, installDirsFields
, withProgramOptionsFields
, withProgramsFields
)
import Distribution.Client.ParseUtils (parseFields, ppFields, ppSection)
import Distribution.Client.Setup
( ConfigExFlags (..)
)
import Distribution.Client.Targets (userConstraintPackageName)
import Distribution.Deprecated.ParseUtils
( FieldDescr (..)
, ParseResult (..)
, commaListFieldParsec
, commaNewLineListFieldParsec
, liftField
, lineNo
, locatedErrorMsg
, readFields
, showPWarning
, syntaxError
, warning
)
import Distribution.Simple.InstallDirs (InstallDirs (..), PathTemplate)
import Distribution.Simple.Setup
( ConfigFlags (..)
, Flag
, HaddockFlags (..)
)
import Distribution.Simple.Utils (debug, warn)
import Distribution.Solver.Types.ConstraintSource
import System.Directory (doesFileExist)
import System.FilePath ((</>))
import System.IO.Error (isDoesNotExistError)
import Text.PrettyPrint (($+$))
import qualified Data.ByteString as BS
import qualified Distribution.Deprecated.ParseUtils as ParseUtils (Field (..))
import qualified Text.PrettyPrint as Disp
--
-- * Configuration saved in the package environment file
--
-- TODO: would be nice to remove duplication between
-- D.C.Sandbox.PackageEnvironment and D.C.Config.
data PackageEnvironment = PackageEnvironment
{ pkgEnvSavedConfig :: SavedConfig
}
deriving (Generic)
instance Monoid PackageEnvironment where
mempty = gmempty
mappend = (<>)
instance Semigroup PackageEnvironment where
(<>) = gmappend
-- | Optional package environment file that can be used to customize the default
-- settings. Created by the user.
userPackageEnvironmentFile :: FilePath
userPackageEnvironmentFile = "cabal.config"
-- | Type of the current package environment.
data PackageEnvironmentType
= -- | './cabal.config'
UserPackageEnvironment
| -- | '~/.config/cabal/config'
AmbientPackageEnvironment
-- | Is there a 'cabal.config' in this directory?
classifyPackageEnvironment :: FilePath -> IO PackageEnvironmentType
classifyPackageEnvironment pkgEnvDir = do
isUser <- configExists userPackageEnvironmentFile
return (classify isUser)
where
configExists fname = doesFileExist (pkgEnvDir </> fname)
classify :: Bool -> PackageEnvironmentType
classify True = UserPackageEnvironment
classify False = AmbientPackageEnvironment
-- | Load the user package environment if it exists (the optional "cabal.config"
-- file). If it does not exist locally, attempt to load an optional global one.
userPackageEnvironment
:: Verbosity
-> FilePath
-> Maybe FilePath
-> IO PackageEnvironment
userPackageEnvironment verbosity pkgEnvDir globalConfigLocation = do
let path = pkgEnvDir </> userPackageEnvironmentFile
minp <-
readPackageEnvironmentFile
(ConstraintSourceUserConfig path)
mempty
path
case (minp, globalConfigLocation) of
(Just parseRes, _) -> processConfigParse path parseRes
(_, Just globalLoc) -> do
minp' <-
readPackageEnvironmentFile
(ConstraintSourceUserConfig globalLoc)
mempty
globalLoc
maybe
( warn verbosity ("no constraints file found at " ++ globalLoc)
>> return mempty
)
(processConfigParse globalLoc)
minp'
_ -> do
debug verbosity ("no user package environment file found at " ++ pkgEnvDir)
return mempty
where
processConfigParse path (ParseOk warns parseResult) = do
unless (null warns) $
warn verbosity $
unlines (map (showPWarning path) warns)
return parseResult
processConfigParse path (ParseFailed err) = do
let (line, msg) = locatedErrorMsg err
warn verbosity $
"Error parsing package environment file "
++ path
++ maybe "" (\n -> ":" ++ show n) line
++ ":\n"
++ msg
return mempty
-- | Same as @userPackageEnvironmentFile@, but returns a SavedConfig.
loadUserConfig :: Verbosity -> FilePath -> Maybe FilePath -> IO SavedConfig
loadUserConfig verbosity pkgEnvDir globalConfigLocation =
fmap pkgEnvSavedConfig $
userPackageEnvironment verbosity pkgEnvDir globalConfigLocation
-- | Descriptions of all fields in the package environment file.
pkgEnvFieldDescrs :: ConstraintSource -> [FieldDescr PackageEnvironment]
pkgEnvFieldDescrs src =
[ commaNewLineListFieldParsec
"constraints"
(pretty . fst)
((\pc -> (pc, src)) `fmap` parsec)
( sortConstraints
. configExConstraints
. savedConfigureExFlags
. pkgEnvSavedConfig
)
( \v pkgEnv ->
updateConfigureExFlags
pkgEnv
(\flags -> flags{configExConstraints = v})
)
, commaListFieldParsec
"preferences"
pretty
parsec
(configPreferences . savedConfigureExFlags . pkgEnvSavedConfig)
( \v pkgEnv ->
updateConfigureExFlags
pkgEnv
(\flags -> flags{configPreferences = v})
)
]
++ map toPkgEnv configFieldDescriptions'
where
configFieldDescriptions' :: [FieldDescr SavedConfig]
configFieldDescriptions' =
filter
(\(FieldDescr name _ _) -> name /= "preference" && name /= "constraint")
(configFieldDescriptions src)
toPkgEnv :: FieldDescr SavedConfig -> FieldDescr PackageEnvironment
toPkgEnv fieldDescr =
liftField
pkgEnvSavedConfig
(\savedConfig pkgEnv -> pkgEnv{pkgEnvSavedConfig = savedConfig})
fieldDescr
updateConfigureExFlags
:: PackageEnvironment
-> (ConfigExFlags -> ConfigExFlags)
-> PackageEnvironment
updateConfigureExFlags pkgEnv f =
pkgEnv
{ pkgEnvSavedConfig =
(pkgEnvSavedConfig pkgEnv)
{ savedConfigureExFlags =
f . savedConfigureExFlags . pkgEnvSavedConfig $
pkgEnv
}
}
sortConstraints = sortBy (comparing $ userConstraintPackageName . fst)
-- | Read the package environment file.
readPackageEnvironmentFile
:: ConstraintSource
-> PackageEnvironment
-> FilePath
-> IO (Maybe (ParseResult PackageEnvironment))
readPackageEnvironmentFile src initial file =
handleNotExists $
fmap (Just . parsePackageEnvironment src initial) (BS.readFile file)
where
handleNotExists action = catchIO action $ \ioe ->
if isDoesNotExistError ioe
then return Nothing
else ioError ioe
-- | Parse the package environment file.
parsePackageEnvironment
:: ConstraintSource
-> PackageEnvironment
-> BS.ByteString
-> ParseResult PackageEnvironment
parsePackageEnvironment src initial str = do
fields <- readFields str
let (knownSections, others) = partition isKnownSection fields
pkgEnv <- parse others
let config = pkgEnvSavedConfig pkgEnv
installDirs0 = savedUserInstallDirs config
(haddockFlags, installDirs, paths, args) <-
foldM
parseSections
(savedHaddockFlags config, installDirs0, [], [])
knownSections
return
pkgEnv
{ pkgEnvSavedConfig =
config
{ savedConfigureFlags =
(savedConfigureFlags config)
{ configProgramPaths = paths
, configProgramArgs = args
}
, savedHaddockFlags = haddockFlags
, savedUserInstallDirs = installDirs
, savedGlobalInstallDirs = installDirs
}
}
where
isKnownSection :: ParseUtils.Field -> Bool
isKnownSection (ParseUtils.Section _ "haddock" _ _) = True
isKnownSection (ParseUtils.Section _ "install-dirs" _ _) = True
isKnownSection (ParseUtils.Section _ "program-locations" _ _) = True
isKnownSection (ParseUtils.Section _ "program-default-options" _ _) = True
isKnownSection _ = False
parse :: [ParseUtils.Field] -> ParseResult PackageEnvironment
parse = parseFields (pkgEnvFieldDescrs src) initial
parseSections
:: SectionsAccum
-> ParseUtils.Field
-> ParseResult SectionsAccum
parseSections
accum@(h, d, p, a)
(ParseUtils.Section _ "haddock" name fs)
| name == "" = do
h' <- parseFields haddockFlagsFields h fs
return (h', d, p, a)
| otherwise = do
warning "The 'haddock' section should be unnamed"
return accum
parseSections
(h, d, p, a)
(ParseUtils.Section line "install-dirs" name fs)
| name == "" = do
d' <- parseFields installDirsFields d fs
return (h, d', p, a)
| otherwise =
syntaxError line $
"Named 'install-dirs' section: '"
++ name
++ "'. Note that named 'install-dirs' sections are not allowed in the '"
++ userPackageEnvironmentFile
++ "' file."
parseSections
accum@(h, d, p, a)
(ParseUtils.Section _ "program-locations" name fs)
| name == "" = do
p' <- parseFields withProgramsFields p fs
return (h, d, p', a)
| otherwise = do
warning "The 'program-locations' section should be unnamed"
return accum
parseSections
accum@(h, d, p, a)
(ParseUtils.Section _ "program-default-options" name fs)
| name == "" = do
a' <- parseFields withProgramOptionsFields a fs
return (h, d, p, a')
| otherwise = do
warning "The 'program-default-options' section should be unnamed"
return accum
parseSections accum f = do
warning $ "Unrecognized stanza on line " ++ show (lineNo f)
return accum
-- | Accumulator type for 'parseSections'.
type SectionsAccum =
( HaddockFlags
, InstallDirs (Flag PathTemplate)
, [(String, FilePath)]
, [(String, [String])]
)
-- | Pretty-print the package environment.
showPackageEnvironment :: PackageEnvironment -> String
showPackageEnvironment pkgEnv = showPackageEnvironmentWithComments Nothing pkgEnv
-- | Pretty-print the package environment with default values for empty fields
-- commented out (just like the default Cabal config file).
showPackageEnvironmentWithComments
:: (Maybe PackageEnvironment)
-> PackageEnvironment
-> String
showPackageEnvironmentWithComments mdefPkgEnv pkgEnv =
Disp.render $
ppFields
(pkgEnvFieldDescrs ConstraintSourceUnknown)
mdefPkgEnv
pkgEnv
$+$ Disp.text ""
$+$ ppSection
"install-dirs"
""
installDirsFields
(fmap installDirsSection mdefPkgEnv)
(installDirsSection pkgEnv)
where
installDirsSection = savedUserInstallDirs . pkgEnvSavedConfig