content: {-# 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
    , createPackageEnvironmentFile
    , tryLoadSandboxPackageEnvironmentFile
    , readPackageEnvironmentFile
    , showPackageEnvironment
    , showPackageEnvironmentWithComments
    , setPackageDB
    , sandboxPackageDBPath
    , loadUserConfig
  
    , basePackageEnvironment
    , initialPackageEnvironment
    , commentPackageEnvironment
    , sandboxPackageEnvironmentFile
    , userPackageEnvironmentFile
    ) where
  
  import Distribution.Client.Config      ( SavedConfig(..), commentSavedConfig
                                         , loadConfig, configFieldDescriptions
                                         , haddockFlagsFields
                                         , installDirsFields, withProgramsFields
                                         , withProgramOptionsFields
                                         , defaultCompiler )
  import Distribution.Client.ParseUtils  ( parseFields, ppFields, ppSection )
  import Distribution.Client.Setup       ( GlobalFlags(..), ConfigExFlags(..)
                                         , InstallFlags(..)
                                         , defaultSandboxLocation )
  import Distribution.Client.Targets     ( userConstraintPackageName )
  import Distribution.Utils.NubList      ( toNubList )
  import Distribution.Simple.Compiler    ( Compiler, PackageDB(..)
                                         , compilerFlavor, showCompilerIdWithAbi )
  import Distribution.Simple.InstallDirs ( InstallDirs(..), PathTemplate
                                         , defaultInstallDirs, combineInstallDirs
                                         , fromPathTemplate, toPathTemplate )
  import Distribution.Simple.Setup       ( Flag(..)
                                         , ConfigFlags(..), HaddockFlags(..)
                                         , fromFlagOrDefault, toFlag, flagToMaybe )
  import Distribution.Simple.Utils       ( die', info, notice, warn, debug )
  import Distribution.Solver.Types.ConstraintSource
  import Distribution.ParseUtils         ( FieldDescr(..), ParseResult(..)
                                         , commaListField, commaNewLineListField
                                         , liftField, lineNo, locatedErrorMsg
                                         , parseFilePathQ, readFields
                                         , showPWarning, simpleField
                                         , syntaxError, warning )
  import Distribution.System             ( Platform )
  import Distribution.Verbosity          ( Verbosity, normal )
  import Control.Monad                   ( foldM, liftM2, when, unless )
  import Data.List                       ( partition, sortBy )
  import Data.Maybe                      ( isJust )
  import Data.Ord                        ( comparing )
  import Distribution.Compat.Exception   ( catchIO )
  import Distribution.Compat.Semigroup
  import System.Directory                ( doesDirectoryExist, doesFileExist
                                         , renameFile )
  import System.FilePath                 ( (<.>), (</>), takeDirectory )
  import System.IO.Error                 ( isDoesNotExistError )
  import Text.PrettyPrint                ( ($+$) )
  
  import qualified Text.PrettyPrint          as Disp
  import qualified Distribution.Compat.ReadP as Parse
  import qualified Distribution.ParseUtils   as ParseUtils ( Field(..) )
  import qualified Distribution.Text         as Text
  import GHC.Generics ( Generic )
  
  
  --
  -- * 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 {
    -- The 'inherit' feature is not used ATM, but could be useful in the future
    -- for constructing nested sandboxes (see discussion in #1196).
    pkgEnvInherit       :: Flag FilePath,
    pkgEnvSavedConfig   :: SavedConfig
  } deriving Generic
  
  instance Monoid PackageEnvironment where
    mempty = gmempty
    mappend = (<>)
  
  instance Semigroup PackageEnvironment where
    (<>) = gmappend
  
  -- | The automatically-created package environment file that should not be
  -- touched by the user.
  sandboxPackageEnvironmentFile :: FilePath
  sandboxPackageEnvironmentFile = "cabal.sandbox.config"
  
  -- | 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 =
    SandboxPackageEnvironment   -- ^ './cabal.sandbox.config'
    | UserPackageEnvironment    -- ^ './cabal.config'
    | AmbientPackageEnvironment -- ^ '~/.etlas/config'
  
  -- | Is there a 'cabal.sandbox.config' or 'cabal.config' in this
  -- directory?
  classifyPackageEnvironment :: FilePath -> Flag FilePath -> Flag Bool
                                -> IO PackageEnvironmentType
  classifyPackageEnvironment pkgEnvDir sandboxConfigFileFlag ignoreSandboxFlag =
    do isSandbox <- liftM2 (||) (return forceSandboxConfig)
                    (configExists sandboxPackageEnvironmentFile)
       isUser    <- configExists userPackageEnvironmentFile
       return (classify isSandbox isUser)
    where
      configExists fname   = doesFileExist (pkgEnvDir </> fname)
      ignoreSandbox        = fromFlagOrDefault False ignoreSandboxFlag
      forceSandboxConfig   = isJust . flagToMaybe $ sandboxConfigFileFlag
  
      classify :: Bool -> Bool -> PackageEnvironmentType
      classify True _
        | not ignoreSandbox = SandboxPackageEnvironment
      classify _    True    = UserPackageEnvironment
      classify _    False   = AmbientPackageEnvironment
  
  -- | Defaults common to 'initialPackageEnvironment' and
  -- 'commentPackageEnvironment'.
  commonPackageEnvironmentConfig :: FilePath -> SavedConfig
  commonPackageEnvironmentConfig sandboxDir =
    mempty {
      savedConfigureFlags = mempty {
         -- TODO: Currently, we follow cabal-dev and set 'user-install: False' in
         -- the config file. In the future we may want to distinguish between
         -- global, sandbox and user install types.
         configUserInstall = toFlag False,
         configInstallDirs = installDirs
         },
      savedUserInstallDirs   = installDirs,
      savedGlobalInstallDirs = installDirs,
      savedGlobalFlags = mempty {
        globalLogsDir = toFlag $ sandboxDir </> "logs",
        -- Is this right? cabal-dev uses the global world file.
        globalWorldFile = toFlag $ sandboxDir </> "world"
        }
      }
    where
      installDirs = sandboxInstallDirs sandboxDir
  
  -- | 'commonPackageEnvironmentConfig' wrapped inside a 'PackageEnvironment'.
  commonPackageEnvironment :: FilePath -> PackageEnvironment
  commonPackageEnvironment sandboxDir = mempty {
    pkgEnvSavedConfig = commonPackageEnvironmentConfig sandboxDir
    }
  
  -- | Given a path to a sandbox, return the corresponding InstallDirs record.
  sandboxInstallDirs :: FilePath -> InstallDirs (Flag PathTemplate)
  sandboxInstallDirs sandboxDir = mempty {
    prefix = toFlag (toPathTemplate sandboxDir)
    }
  
  -- | These are the absolute basic defaults, the fields that must be
  -- initialised. When we load the package environment from the file we layer the
  -- loaded values over these ones.
  basePackageEnvironment :: PackageEnvironment
  basePackageEnvironment =
      mempty {
        pkgEnvSavedConfig = mempty {
           savedConfigureFlags = mempty {
              configHcFlavor    = toFlag defaultCompiler,
              configVerbosity   = toFlag normal
              }
           }
        }
  
  -- | Initial configuration that we write out to the package environment file if
  -- it does not exist. When the package environment gets loaded this
  -- configuration gets layered on top of 'basePackageEnvironment'.
  initialPackageEnvironment :: FilePath -> Compiler -> Platform
                               -> IO PackageEnvironment
  initialPackageEnvironment sandboxDir compiler platform = do
    defInstallDirs <- defaultInstallDirs (compilerFlavor compiler)
                      {- userInstall= -} False {- _hasLibs= -} False
    let initialConfig = commonPackageEnvironmentConfig sandboxDir
        installDirs   = combineInstallDirs (\d f -> Flag $ fromFlagOrDefault d f)
                        defInstallDirs (savedUserInstallDirs initialConfig)
    return $ mempty {
      pkgEnvSavedConfig = initialConfig {
         savedUserInstallDirs   = installDirs,
         savedGlobalInstallDirs = installDirs,
         savedGlobalFlags = (savedGlobalFlags initialConfig) {
            globalLocalRepos = toNubList [sandboxDir </> "packages"]
            },
         savedConfigureFlags = setPackageDB sandboxDir compiler platform
                               (savedConfigureFlags initialConfig),
         savedInstallFlags = (savedInstallFlags initialConfig) {
           installSummaryFile = toNubList [toPathTemplate (sandboxDir </>
                                                 "logs" </> "build.log")]
           }
         }
      }
  
  -- | Return the path to the sandbox package database.
  sandboxPackageDBPath :: FilePath -> Compiler -> Platform -> String
  sandboxPackageDBPath sandboxDir compiler platform =
      sandboxDir
           </> (Text.display platform ++ "-"
               ++ showCompilerIdWithAbi compiler
               ++ "-packages.conf.d")
  -- The path in sandboxPackageDBPath should be kept in sync with the
  -- path in the bootstrap.sh which is used to bootstrap etlas
  -- into a sandbox.
  
  -- | Use the package DB location specific for this compiler.
  setPackageDB :: FilePath -> Compiler -> Platform -> ConfigFlags -> ConfigFlags
  setPackageDB sandboxDir compiler platform configFlags =
    configFlags {
      configPackageDBs = [Just (SpecificPackageDB $ sandboxPackageDBPath
                                                        sandboxDir
                                                        compiler
                                                        platform)]
      }
  
  -- | Almost the same as 'savedConf `mappend` pkgEnv', but some settings are
  -- overridden instead of mappend'ed.
  overrideSandboxSettings :: PackageEnvironment -> PackageEnvironment ->
                             PackageEnvironment
  overrideSandboxSettings pkgEnv0 pkgEnv =
    pkgEnv {
      pkgEnvSavedConfig = mappendedConf {
           savedConfigureFlags = (savedConfigureFlags mappendedConf) {
            configPackageDBs = configPackageDBs pkgEnvConfigureFlags
            }
         , savedInstallFlags = (savedInstallFlags mappendedConf) {
            installSummaryFile = installSummaryFile pkgEnvInstallFlags
            }
         },
      pkgEnvInherit = pkgEnvInherit pkgEnv0
      }
    where
      pkgEnvConf           = pkgEnvSavedConfig pkgEnv
      mappendedConf        = (pkgEnvSavedConfig pkgEnv0) `mappend` pkgEnvConf
      pkgEnvConfigureFlags = savedConfigureFlags pkgEnvConf
      pkgEnvInstallFlags   = savedInstallFlags pkgEnvConf
  
  -- | Default values that get used if no value is given. Used here to include in
  -- comments when we write out the initial package environment.
  commentPackageEnvironment :: FilePath -> IO PackageEnvironment
  commentPackageEnvironment sandboxDir = do
    commentConf  <- commentSavedConfig
    let baseConf =  commonPackageEnvironmentConfig sandboxDir
    return $ mempty {
      pkgEnvSavedConfig = commentConf `mappend` baseConf
      }
  
  -- | If this package environment inherits from some other package environment,
  -- return that package environment; otherwise return mempty.
  inheritedPackageEnvironment :: Verbosity -> PackageEnvironment
                                 -> Flag Bool
                                 -> IO PackageEnvironment
  inheritedPackageEnvironment verbosity pkgEnv sendMetricsFlag = do
    case (pkgEnvInherit pkgEnv) of
      NoFlag                -> return mempty
      confPathFlag@(Flag _) -> do
        conf <- loadConfig verbosity confPathFlag sendMetricsFlag
        return $ mempty { pkgEnvSavedConfig = conf }
  
  -- | 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
        when (not $ 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
  
  -- | Common error handling code used by 'tryLoadSandboxPackageEnvironment' and
  -- 'updatePackageEnvironment'.
  handleParseResult :: Verbosity -> FilePath
                       -> Maybe (ParseResult PackageEnvironment)
                       -> IO PackageEnvironment
  handleParseResult verbosity path minp =
    case minp of
      Nothing -> die' verbosity $
        "The package environment file '" ++ path ++ "' doesn't exist"
      Just (ParseOk warns parseResult) -> do
        when (not $ null warns) $ warn verbosity $
          unlines (map (showPWarning path) warns)
        return parseResult
      Just (ParseFailed err) -> do
        let (line, msg) = locatedErrorMsg err
        die' verbosity $ "Error parsing package environment file " ++ path
          ++ maybe "" (\n -> ":" ++ show n) line ++ ":\n" ++ msg
  
  -- | Try to load the given package environment file, exiting with error if it
  -- doesn't exist. Also returns the path to the sandbox directory. The path
  -- parameter should refer to an existing file.
  tryLoadSandboxPackageEnvironmentFile :: Verbosity -> FilePath -> (Flag FilePath)
                                          -> Flag Bool
                                          -> IO (FilePath, PackageEnvironment)
  tryLoadSandboxPackageEnvironmentFile verbosity pkgEnvFile configFileFlag sendMetricsFlag = do
    let pkgEnvDir = takeDirectory pkgEnvFile
    minp   <- readPackageEnvironmentFile
              (ConstraintSourceSandboxConfig pkgEnvFile) mempty pkgEnvFile
    pkgEnv <- handleParseResult verbosity pkgEnvFile minp
  
    -- Get the saved sandbox directory.
    -- TODO: Use substPathTemplate with
    -- compilerTemplateEnv ++ platformTemplateEnv ++ abiTemplateEnv.
    let sandboxDir = fromFlagOrDefault defaultSandboxLocation
                     . fmap fromPathTemplate . prefix . savedUserInstallDirs
                     . pkgEnvSavedConfig $ pkgEnv
  
    -- Do some sanity checks
    dirExists            <- doesDirectoryExist sandboxDir
    -- TODO: Also check for an initialised package DB?
    unless dirExists $
      die' verbosity ("No sandbox exists at " ++ sandboxDir)
    info verbosity $ "Using a sandbox located at " ++ sandboxDir
  
    let base   = basePackageEnvironment
    let common = commonPackageEnvironment sandboxDir
    user      <- userPackageEnvironment verbosity pkgEnvDir Nothing --TODO
    inherited <- inheritedPackageEnvironment verbosity user sendMetricsFlag
  
    -- Layer the package environment settings over settings from ~/.etlas/config.
    cabalConfig <- fmap unsetSymlinkBinDir $ loadConfig verbosity configFileFlag sendMetricsFlag
    return (sandboxDir,
            updateInstallDirs $
            (base `mappend` (toPkgEnv cabalConfig) `mappend`
             common `mappend` inherited `mappend` user)
            `overrideSandboxSettings` pkgEnv)
      where
        toPkgEnv config = mempty { pkgEnvSavedConfig = config }
  
        updateInstallDirs pkgEnv =
          let config         = pkgEnvSavedConfig    pkgEnv
              configureFlags = savedConfigureFlags  config
              installDirs    = savedUserInstallDirs config
          in pkgEnv {
            pkgEnvSavedConfig = config {
               savedConfigureFlags = configureFlags {
                  configInstallDirs = installDirs
                  }
               }
            }
  
        -- We don't want to inherit the value of 'symlink-bindir' from
        -- '~/.etlas/config'. See #1514.
        unsetSymlinkBinDir config =
          let installFlags = savedInstallFlags config
          in config {
            savedInstallFlags = installFlags {
               installSymlinkBinDir = NoFlag
               }
            }
  
  -- | Create a new package environment file, replacing the existing one if it
  -- exists. Note that the path parameters should point to existing directories.
  createPackageEnvironmentFile :: Verbosity -> FilePath -> FilePath
                                  -> Compiler
                                  -> Platform
                                  -> IO ()
  createPackageEnvironmentFile verbosity sandboxDir pkgEnvFile compiler platform = do
    notice verbosity $ "Writing a default package environment file to " ++ pkgEnvFile
    initialPkgEnv <- initialPackageEnvironment sandboxDir compiler platform
    writePackageEnvironmentFile pkgEnvFile initialPkgEnv
  
  -- | Descriptions of all fields in the package environment file.
  pkgEnvFieldDescrs :: ConstraintSource -> [FieldDescr PackageEnvironment]
  pkgEnvFieldDescrs src = [
    simpleField "inherit"
      (fromFlagOrDefault Disp.empty . fmap Disp.text) (optional parseFilePathQ)
      pkgEnvInherit (\v pkgEnv -> pkgEnv { pkgEnvInherit = v })
  
    , commaNewLineListField "constraints"
      (Text.disp . fst) ((\pc -> (pc, src)) `fmap` Text.parse)
      (sortConstraints . configExConstraints
       . savedConfigureExFlags . pkgEnvSavedConfig)
      (\v pkgEnv -> updateConfigureExFlags pkgEnv
                    (\flags -> flags { configExConstraints = v }))
  
    , commaListField "preferences"
      Text.disp Text.parse
      (configPreferences . savedConfigureExFlags . pkgEnvSavedConfig)
      (\v pkgEnv -> updateConfigureExFlags pkgEnv
                    (\flags -> flags { configPreferences = v }))
    ]
    ++ map toPkgEnv configFieldDescriptions'
    where
      optional = Parse.option mempty . fmap toFlag
  
      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) (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 -> String
                             -> 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])])
  
  -- | Write out the package environment file.
  writePackageEnvironmentFile :: FilePath -> PackageEnvironment -> IO ()
  writePackageEnvironmentFile path pkgEnv = do
    let tmpPath = (path <.> "tmp")
    writeFile tmpPath $ explanation ++ pkgEnvStr ++ "\n"
    renameFile tmpPath path
    where
      pkgEnvStr = showPackageEnvironment pkgEnv
      explanation = unlines
        ["-- This is a Etlas package environment file."
        ,"-- THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY."
        ,"-- Please create a 'cabal.config' file in the same directory"
        ,"-- if you want to change the default settings for this sandbox."
        ,"",""
        ]
  
  -- | 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 ~/.etlas/config).
  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
  