content: -----------------------------------------------------------------------------
  -- |
  -- Module      :  Distribution.Simple.Program.Types
  -- Copyright   :  Duncan Coutts 2013
  --
  -- Maintainer  :  cabal-devel@haskell.org
  -- Portability :  portable
  --
  -- A somewhat extended notion of the normal program search path concept.
  --
  -- Usually when finding executables we just want to look in the usual places
  -- using the OS's usual method for doing so. In Haskell the normal OS-specific
  -- method is captured by 'findExecutable'. On all common OSs that makes use of
  -- a @PATH@ environment variable, (though on Windows it is not just the @PATH@).
  --
  -- However it is sometimes useful to be able to look in additional locations
  -- without having to change the process-global @PATH@ environment variable.
  -- So we need an extension of the usual 'findExecutable' that can look in
  -- additional locations, either before, after or instead of the normal OS
  -- locations.
  --
  module Distribution.Simple.Program.Find (
      -- * Program search path
      ProgramSearchPath,
      ProgramSearchPathEntry(..),
      defaultProgramSearchPath,
      findProgramOnSearchPath,
      programSearchPathAsPATHVar,
    ) where
  
  import Distribution.Verbosity
           ( Verbosity )
  import Distribution.Simple.Utils
           ( debug )
  import Distribution.System
           ( OS(..), buildOS )
  import System.Directory
           ( findExecutable, doesFileExist, Permissions(..), getPermissions )
  import Distribution.Compat.Environment
           ( getEnvironment )
  import System.FilePath
           ( (</>), (<.>), splitSearchPath, searchPathSeparator )
  import Data.List
           ( intercalate )
  
  
  -- | A search path to use when locating executables. This is analogous
  -- to the unix @$PATH@ or win32 @%PATH%@ but with the ability to use
  -- the system default method for finding executables ('findExecutable' which
  -- on unix is simply looking on the @$PATH@ but on win32 is a bit more
  -- complicated).
  --
  -- The default to use is @[ProgSearchPathDefault]@ but you can add extra dirs
  -- either before, after or instead of the default, e.g. here we add an extra
  -- dir to search after the usual ones.
  --
  -- > ['ProgramSearchPathDefault', 'ProgramSearchPathDir' dir]
  --
  type ProgramSearchPath = [ProgramSearchPathEntry]
  data ProgramSearchPathEntry =
           ProgramSearchPathDir FilePath  -- ^ A specific dir
         | ProgramSearchPathDefault       -- ^ The system default
  
  defaultProgramSearchPath :: ProgramSearchPath
  defaultProgramSearchPath = [ProgramSearchPathDefault]
  
  findProgramOnSearchPath :: Verbosity -> ProgramSearchPath
                          -> FilePath -> IO (Maybe FilePath)
  findProgramOnSearchPath verbosity searchpath prog = do
      debug verbosity $ "Searching for " ++ prog ++ " in path."
      res <- tryPathElems searchpath
      case res of
        Nothing   -> debug verbosity ("Cannot find " ++ prog ++ " on the path")
        Just path -> debug verbosity ("Found " ++ prog ++ " at "++ path)
      return res
    where
      tryPathElems []       = return Nothing
      tryPathElems (pe:pes) = do
        res <- tryPathElem pe
        case res of
          Nothing -> tryPathElems pes
          Just _  -> return res
  
      tryPathElem (ProgramSearchPathDir dir) =
          findFirstExe [ dir </> prog <.> ext | ext <- extensions ]
        where
          -- Possible improvement: on Windows, read the list of extensions from
          -- the PATHEXT environment variable. By default PATHEXT is ".com; .exe;
          -- .bat; .cmd".
          extensions = case buildOS of
                         Windows -> ["", "exe"]
                         _       -> [""]
  
      tryPathElem ProgramSearchPathDefault =
        findExecutable prog
  
      findFirstExe []     = return Nothing
      findFirstExe (f:fs) = do
        exists <- doesFileExist f
        if exists
          then do perms <- getPermissions f
                  if executable perms
                    then return (Just f)
                    else findFirstExe fs
          else findFirstExe fs
  
  
  -- | Interpret a 'ProgramSearchPath' to construct a new @$PATH@ env var.
  -- Note that this is close but not perfect because on Windows the search
  -- algorithm looks at more than just the @%PATH%@.
  programSearchPathAsPATHVar :: ProgramSearchPath -> IO String
  programSearchPathAsPATHVar searchpath = do
      ess <- mapM getEntries searchpath
      return (intercalate [searchPathSeparator] (concat ess))
    where
      getEntries (ProgramSearchPathDir dir) = return [dir]
      getEntries ProgramSearchPathDefault   = do
        env <- getEnvironment
        return (maybe [] splitSearchPath (lookup "PATH" env))
  