hackport-0.4.7: cabal/Cabal/Distribution/Simple/Program/Find.hs
-----------------------------------------------------------------------------
-- |
-- 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, doesExecutableExist )
import Distribution.System
( OS(..), buildOS )
import System.Directory
( findExecutable )
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"]
Ghcjs -> ["", "exe"]
_ -> [""]
tryPathElem ProgramSearchPathDefault = do
-- 'findExecutable' doesn't check that the path really refers to an
-- executable on Windows (at least with GHC < 7.8). See
-- https://ghc.haskell.org/trac/ghc/ticket/2184
mExe <- findExecutable prog
case mExe of
Just exe -> do
exeExists <- doesExecutableExist exe
if exeExists
then return mExe
else return Nothing
_ -> return mExe
findFirstExe [] = return Nothing
findFirstExe (f:fs) = do
isExe <- doesExecutableExist f
if isExe
then return (Just f)
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))