diff options
Diffstat (limited to 'compiler/main/SysTools/BaseDir.hs')
-rw-r--r-- | compiler/main/SysTools/BaseDir.hs | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/compiler/main/SysTools/BaseDir.hs b/compiler/main/SysTools/BaseDir.hs new file mode 100644 index 0000000000..f858c8ffad --- /dev/null +++ b/compiler/main/SysTools/BaseDir.hs @@ -0,0 +1,281 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE ScopedTypeVariables #-} + +{- +----------------------------------------------------------------------------- +-- +-- (c) The University of Glasgow 2001-2017 +-- +-- Finding the compiler's base directory. +-- +----------------------------------------------------------------------------- +-} + +module SysTools.BaseDir + ( expandTopDir, expandToolDir + , findTopDir, findToolDir + ) where + +#include "HsVersions.h" + +import GhcPrelude + +import Panic + +import System.Environment (lookupEnv) +import System.FilePath +import Data.List + +-- POSIX +#if defined(darwin_HOST_OS) || defined(linux_HOST_OS) +import System.Environment (getExecutablePath) +#endif + +-- Windows +#if defined(mingw32_HOST_OS) +# if MIN_VERSION_Win32(2,5,0) +# if !MIN_VERSION_base(4,11,0) +import qualified System.Win32.Types as Win32 +# endif +# else +import qualified System.Win32.Info as Win32 +# endif +# if MIN_VERSION_base(4,11,0) +import System.Environment (getExecutablePath) +import System.Directory (doesDirectoryExist) +# else +import Data.Char +import Exception +import Foreign +import Foreign.C.String +import System.Directory +import System.Win32.Types (DWORD, LPTSTR, HANDLE) +import System.Win32.Types (failIfNull, failIf, iNVALID_HANDLE_VALUE) +import System.Win32.File (createFile,closeHandle, gENERIC_READ, fILE_SHARE_READ, oPEN_EXISTING, fILE_ATTRIBUTE_NORMAL, fILE_FLAG_BACKUP_SEMANTICS ) +import System.Win32.DLL (loadLibrary, getProcAddress) +# endif +#endif + +#if defined(mingw32_HOST_OS) +# if defined(i386_HOST_ARCH) +# define WINDOWS_CCONV stdcall +# elif defined(x86_64_HOST_ARCH) +# define WINDOWS_CCONV ccall +# else +# error Unknown mingw32 arch +# endif +#endif + +{- +Note [topdir: How GHC finds its files] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +GHC needs various support files (library packages, RTS etc), plus +various auxiliary programs (cp, gcc, etc). It starts by finding topdir, +the root of GHC's support files + +On Unix: + - ghc always has a shell wrapper that passes a -B<dir> option + +On Windows: + - ghc never has a shell wrapper. + - we can find the location of the ghc binary, which is + $topdir/<foo>/<something>.exe + where <something> may be "ghc", "ghc-stage2", or similar + - we strip off the "<foo>/<something>.exe" to leave $topdir. + +from topdir we can find package.conf, ghc-asm, etc. + + +Note [tooldir: How GHC finds mingw and perl on Windows] + +GHC has some custom logic on Windows for finding the mingw +toolchain and perl. Depending on whether GHC is built +with the make build system or Hadrian, and on whether we're +running a bindist, we might find the mingw toolchain and perl +either under $topdir/../{mingw, perl}/ or +$topdir/../../{mingw, perl}/. + +-} + +-- | Expand occurrences of the @$topdir@ interpolation in a string. +expandTopDir :: FilePath -> String -> String +expandTopDir = expandPathVar "topdir" + +-- | Expand occurrences of the @$tooldir@ interpolation in a string +-- on Windows, leave the string untouched otherwise. +expandToolDir :: Maybe FilePath -> String -> String +#if defined(mingw32_HOST_OS) +expandToolDir (Just tool_dir) s = expandPathVar "tooldir" tool_dir s +expandToolDir Nothing _ = panic "Could not determine $tooldir" +#else +expandToolDir _ s = s +#endif + +-- | @expandPathVar var value str@ +-- +-- replaces occurences of variable @$var@ with @value@ in str. +expandPathVar :: String -> FilePath -> String -> String +expandPathVar var value str + | Just str' <- stripPrefix ('$':var) str + , null str' || isPathSeparator (head str') + = value ++ expandPathVar var value str' +expandPathVar var value (x:xs) = x : expandPathVar var value xs +expandPathVar _ _ [] = [] + +-- | Returns a Unix-format path pointing to TopDir. +findTopDir :: Maybe String -- Maybe TopDir path (without the '-B' prefix). + -> IO String -- TopDir (in Unix format '/' separated) +findTopDir (Just minusb) = return (normalise minusb) +findTopDir Nothing + = do -- The _GHC_TOP_DIR environment variable can be used to specify + -- the top dir when the -B argument is not specified. It is not + -- intended for use by users, it was added specifically for the + -- purpose of running GHC within GHCi. + maybe_env_top_dir <- lookupEnv "_GHC_TOP_DIR" + case maybe_env_top_dir of + Just env_top_dir -> return env_top_dir + Nothing -> do + -- Get directory of executable + maybe_exec_dir <- getBaseDir + case maybe_exec_dir of + -- "Just" on Windows, "Nothing" on unix + Nothing -> throwGhcExceptionIO $ + InstallationError "missing -B<dir> option" + Just dir -> return dir + +getBaseDir :: IO (Maybe String) + +#if defined(mingw32_HOST_OS) + +-- locate the "base dir" when given the path +-- to the real ghc executable (as opposed to symlink) +-- that is running this function. +rootDir :: FilePath -> FilePath +rootDir = takeDirectory . takeDirectory . normalise + +#if MIN_VERSION_base(4,11,0) +getBaseDir = Just . (\p -> p </> "lib") . rootDir <$> getExecutablePath +#else +-- Assuming we are running ghc, accessed by path $(stuff)/<foo>/ghc.exe, +-- return the path $(stuff)/lib. +getBaseDir = try_size 2048 -- plenty, PATH_MAX is 512 under Win32. + where + try_size size = allocaArray (fromIntegral size) $ \buf -> do + ret <- c_GetModuleFileName nullPtr buf size + case ret of + 0 -> return Nothing + _ | ret < size -> do + path <- peekCWString buf + real <- getFinalPath path -- try to resolve symlinks paths + let libdir = (buildLibDir . sanitize . maybe path id) real + exists <- doesDirectoryExist libdir + if exists + then return $ Just libdir + else fail path + | otherwise -> try_size (size * 2) + + -- getFinalPath returns paths in full raw form. + -- Unfortunately GHC isn't set up to handle these + -- So if the call succeeded, we need to drop the + -- \\?\ prefix. + sanitize s = if "\\\\?\\" `isPrefixOf` s + then drop 4 s + else s + + buildLibDir :: FilePath -> FilePath + buildLibDir s = + (takeDirectory . takeDirectory . normalise $ s) </> "lib" + + fail s = panic ("can't decompose ghc.exe path: " ++ show s) + +foreign import WINDOWS_CCONV unsafe "windows.h GetModuleFileNameW" + c_GetModuleFileName :: Ptr () -> CWString -> Word32 -> IO Word32 + +-- Attempt to resolve symlinks in order to find the actual location GHC +-- is located at. See Trac #11759. +getFinalPath :: FilePath -> IO (Maybe FilePath) +getFinalPath name = do + dllHwnd <- failIfNull "LoadLibrary" $ loadLibrary "kernel32.dll" + -- Note: The API GetFinalPathNameByHandleW is only available starting from Windows Vista. + -- This means that we can't bind directly to it since it may be missing. + -- Instead try to find it's address at runtime and if we don't succeed consider the + -- function failed. + addr_m <- (fmap Just $ failIfNull "getProcAddress" $ getProcAddress dllHwnd "GetFinalPathNameByHandleW") + `catch` (\(_ :: SomeException) -> return Nothing) + case addr_m of + Nothing -> return Nothing + Just addr -> do handle <- failIf (==iNVALID_HANDLE_VALUE) "CreateFile" + $ createFile name + gENERIC_READ + fILE_SHARE_READ + Nothing + oPEN_EXISTING + (fILE_ATTRIBUTE_NORMAL .|. fILE_FLAG_BACKUP_SEMANTICS) + Nothing + let fnPtr = makeGetFinalPathNameByHandle $ castPtrToFunPtr addr + -- First try to resolve the path to get the actual path + -- of any symlinks or other file system redirections that + -- may be in place. However this function can fail, and in + -- the event it does fail, we need to try using the + -- original path and see if we can decompose that. + -- If the call fails Win32.try will raise an exception + -- that needs to be caught. See #14159 + path <- (Win32.try "GetFinalPathName" + (\buf len -> fnPtr handle buf len 0) 512 + `finally` closeHandle handle) + `catch` + (\(_ :: IOException) -> return name) + return $ Just path + +type GetFinalPath = HANDLE -> LPTSTR -> DWORD -> DWORD -> IO DWORD + +foreign import WINDOWS_CCONV unsafe "dynamic" + makeGetFinalPathNameByHandle :: FunPtr GetFinalPath -> GetFinalPath +#endif +#elif defined(darwin_HOST_OS) || defined(linux_HOST_OS) +-- on unix, this is a bit more confusing. +-- The layout right now is something like +-- +-- /bin/ghc-X.Y.Z <- wrapper script (1) +-- /bin/ghc <- symlink to wrapper script (2) +-- /lib/ghc-X.Y.Z/bin/ghc <- ghc executable (3) +-- /lib/ghc-X.Y.Z <- $topdir (4) +-- +-- As such, we first need to find the absolute location to the +-- binary. +-- +-- getExecutablePath will return (3). One takeDirectory will +-- give use /lib/ghc-X.Y.Z/bin, and another will give us (4). +-- +-- This of course only works due to the current layout. If +-- the layout is changed, such that we have ghc-X.Y.Z/{bin,lib} +-- this would need to be changed accordingly. +-- +getBaseDir = Just . (\p -> p </> "lib") . takeDirectory . takeDirectory <$> getExecutablePath +#else +getBaseDir = return Nothing +#endif + +-- See Note [tooldir: How GHC finds mingw and perl on Windows] +-- Returns @Nothing@ when not on Windows. +-- When called on Windows, it either throws an error when the +-- tooldir can't be located, or returns @Just tooldirpath@. +findToolDir + :: FilePath -- ^ topdir + -> IO (Maybe FilePath) +#if defined(mingw32_HOST_OS) +findToolDir top_dir = go 0 (top_dir </> "..") + where maxDepth = 3 + go :: Int -> FilePath -> IO (Maybe FilePath) + go k path + | k == maxDepth = throwGhcExceptionIO $ + InstallationError "could not detect mingw toolchain" + | otherwise = do + oneLevel <- doesDirectoryExist (path </> "mingw") + if oneLevel + then return (Just path) + else go (k+1) (path </> "..") +#else +findToolDir _ = return Nothing +#endif |