content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE RankNTypes #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Distribution.Simple.Program.Find
  -- 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,
      getSystemSearchPath,
    ) where
  
  import Prelude ()
  import Distribution.Compat.Prelude
  
  import Distribution.Verbosity
  import Distribution.Simple.Utils
  import Distribution.System
  import Distribution.Compat.Environment
  
  import qualified System.Directory as Directory
           ( findExecutable )
  import System.FilePath as FilePath
           ( (</>), (<.>), splitSearchPath, searchPathSeparator, getSearchPath
           , takeDirectory )
  #if defined(mingw32_HOST_OS)
  import qualified System.Win32 as Win32
  #endif
  
  -- | 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
    deriving (Eq, Generic)
  
  instance Binary ProgramSearchPathEntry
  
  defaultProgramSearchPath :: ProgramSearchPath
  defaultProgramSearchPath = [ProgramSearchPathDefault]
  
  findProgramOnSearchPath :: Verbosity -> ProgramSearchPath
                          -> FilePath -> IO (Maybe (FilePath, [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 :: [[FilePath]] -> [ProgramSearchPathEntry]
                   -> IO (Maybe (FilePath, [FilePath]))
      tryPathElems _     []       = return Nothing
      tryPathElems tried (pe:pes) = do
        res <- tryPathElem pe
        case res of
          (Nothing,      notfoundat) -> tryPathElems (notfoundat : tried) pes
          (Just foundat, notfoundat) -> return (Just (foundat, alltried))
            where
              alltried = concat (reverse (notfoundat : tried))
  
      tryPathElem :: ProgramSearchPathEntry -> NoCallStackIO (Maybe FilePath, [FilePath])
      tryPathElem (ProgramSearchPathDir dir) =
          findFirstExe [ dir </> prog <.> ext | ext <- exeExtensions ]
  
      -- On windows, getSystemSearchPath is not guaranteed 100% correct so we
      -- use findExecutable and then approximate the not-found-at locations.
      tryPathElem ProgramSearchPathDefault | buildOS == Windows = do
        mExe    <- findExecutable prog
        syspath <- getSystemSearchPath
        case mExe of
          Nothing ->
            let notfoundat = [ dir </> prog | dir <- syspath ] in
            return (Nothing, notfoundat)
  
          Just foundat -> do
            let founddir   = takeDirectory foundat
                notfoundat = [ dir </> prog
                             | dir <- takeWhile (/= founddir) syspath ]
            return (Just foundat, notfoundat)
  
      -- On other OSs we can just do the simple thing
      tryPathElem ProgramSearchPathDefault = do
        dirs <- getSystemSearchPath
        findFirstExe [ dir </> prog <.> ext | dir <- dirs, ext <- exeExtensions ]
  
      findFirstExe :: [FilePath] -> NoCallStackIO (Maybe FilePath, [FilePath])
      findFirstExe = go []
        where
          go fs' []     = return (Nothing, reverse fs')
          go fs' (f:fs) = do
            isExe <- doesExecutableExist f
            if isExe
              then return (Just f, reverse fs')
              else go (f:fs') 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 -> NoCallStackIO String
  programSearchPathAsPATHVar searchpath = do
      ess <- traverse getEntries searchpath
      return (intercalate [searchPathSeparator] (concat ess))
    where
      getEntries (ProgramSearchPathDir dir) = return [dir]
      getEntries ProgramSearchPathDefault   = do
        env <- getEnvironment
        return (maybe [] splitSearchPath (lookup "PATH" env))
  
  -- | Get the system search path. On Unix systems this is just the @$PATH@ env
  -- var, but on windows it's a bit more complicated.
  --
  getSystemSearchPath :: NoCallStackIO [FilePath]
  getSystemSearchPath = fmap nub $ do
  #if defined(mingw32_HOST_OS)
      processdir <- takeDirectory `fmap` Win32.getModuleFileName Win32.nullHANDLE
      currentdir <- Win32.getCurrentDirectory
      systemdir  <- Win32.getSystemDirectory
      windowsdir <- Win32.getWindowsDirectory
      pathdirs   <- FilePath.getSearchPath
      let path = processdir : currentdir
               : systemdir  : windowsdir
               : pathdirs
      return path
  #else
      FilePath.getSearchPath
  #endif
  
  #ifdef MIN_VERSION_directory
  #if MIN_VERSION_directory(1,2,1)
  #define HAVE_directory_121
  #endif
  #endif
  
  findExecutable :: FilePath -> NoCallStackIO (Maybe FilePath)
  #ifdef HAVE_directory_121
  findExecutable = Directory.findExecutable
  #else
  findExecutable prog = do
        -- With directory < 1.2.1 'findExecutable' doesn't check that the path
        -- really refers to an executable.
        mExe <- Directory.findExecutable prog
        case mExe of
          Just exe -> do
            exeExists <- doesExecutableExist exe
            if exeExists
              then return mExe
              else return Nothing
          _     -> return mExe
  #endif
  
  