2Module : Util.DynamicLinker
3Description : Platform-specific dynamic linking support. Add new platforms to this file through conditional compilation.
6Maintainer : The Idris Community.
8{-# LANGUAGE CPP, ExistentialQuantification, ScopedTypeVariables #-}
9module Util.DynamicLinker ( ForeignFun(..)
17import Foreign.Ptr (FunPtr, Ptr(), castPtrToFunPtr, nullFunPtr, nullPtr)
18import System.Directory
19#ifndef mingw32_HOST_OS
20import Control.Exception (IOException, throwIO, try)
21import Data.Array (Array, bounds, inRange, (!))
22import Data.Functor ((<$>))
23import Data.Maybe (catMaybes)
24import System.FilePath.Posix ((</>))
25import System.Posix.DynamicLinker
28import qualified Control.Exception as Exception (IOException, catch)
29import System.FilePath.Windows ((</>))
30import System.Win32.DLL
31import System.Win32.Types
35hostDynamicLibExt :: String
36#if defined(linux_HOST_OS) || defined(freebsd_HOST_OS) \
37 || defined(dragonfly_HOST_OS) || defined(openbsd_HOST_OS) \
38 || defined(netbsd_HOST_OS)
39hostDynamicLibExt = "so"
40#elif defined(darwin_HOST_OS)
41hostDynamicLibExt = "dylib"
42#elif defined(mingw32_HOST_OS)
43hostDynamicLibExt = "dll"
45hostDynamicLibExt = error $ unwords
46 [ "Undefined file extension for dynamic libraries"
47 , "in Idris' Util.DynamicLinker."
51data ForeignFun = forall a. Fun { fun_name :: String
52 , fun_handle :: FunPtr a
55data DynamicLib = Lib { lib_name :: String
59instance Eq DynamicLib where
60 (Lib a _) == (Lib b _) = a == b
62firstExisting :: [FilePath] -> IO (Maybe FilePath)
63firstExisting [] = return Nothing
64firstExisting (f:fs) = do exists <- doesFileExist f
69libFileName :: [FilePath] -> String -> IO String
70libFileName dirs lib = do let names = [lib, lib ++ "." ++ hostDynamicLibExt]
71 cwd <- getCurrentDirectory
72 found <- firstExisting $
73 map ("."</>) names ++ [d </> f | d <- cwd:dirs, f <- names]
74 return $ maybe (lib ++ "." ++ hostDynamicLibExt) id found
76#ifndef mingw32_HOST_OS
77-- Load a dynamic library on POSIX systems.
78-- In the simple case, we just find the appropriate filename and call dlopen().
79-- In the complicated case our "foo.so" isn't actually a library. Some of the
80-- .so files on modern Linux systems are linker scripts instead. dlopen()
81-- doesn't know anything about those. We need to look inside the script for the
82-- actual library path and load that. This is a horrible hack, the correct
83-- method would be to actually parse the scripts and execute them. The approach
84-- below is what GHC does.
85tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
86tryLoadLib dirs lib = do
87 filename <- libFileName dirs lib
88 res :: Either IOException DL <- try $
89 dlopen filename [RTLD_NOW, RTLD_GLOBAL]
91 Right handle -> return $ Just handle
94 -- dlopen failed, run a regex to see if the error message looks like it
95 -- could be a linker script.
96 case matchAllText invalidLibRegex (show ex) of
98 if inRange (bounds x) 1
100 -- filename above may be a relative path. Get the full path out of
101 -- the error message.
102 let realPath = fst $ x ! 1
103 fileLines <- lines <$> readFile realPath
104 -- Go down the linker script line by line looking for .so
105 -- filenames and try each one.
106 let matches = catMaybes $ map
107 (getLastMatch . matchAllText linkerScriptRegex)
109 mapMFirst (\f -> dlopen f [RTLD_NOW, RTLD_GLOBAL]) matches
113 Left ex -> throwIO ex
116 Just handle -> if undl handle == nullPtr
118 else return . Just $ Lib lib handle
119 Nothing -> return Nothing
121getLastMatch :: [MatchText String] -> Maybe String
122getLastMatch [] = Nothing
123getLastMatch (x:_) = case bounds x of
124 (low, high) -> if low > high
126 else Just $ fst $ x ! high
128mapMFirst :: (a -> IO b) -> [a] -> IO (Maybe b)
129mapMFirst f [] = return Nothing
130mapMFirst f (a:as) = do res <- try (f a)
132 Left (ex :: IOException) -> mapMFirst f as
133 Right res -> return $ Just res
135-- Both regexes copyright 2009-2011 Howard B. Golden, CJ van den Berg and Ian
136-- Lynagh. From the Glasgow Haskell Compiler. BSD licensed.
137invalidLibRegex :: Regex
138invalidLibRegex = makeRegex "(([^ \t()])+\\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short)"
140linkerScriptRegex :: Regex
141linkerScriptRegex = makeRegex "(GROUP|INPUT) *\\( *([^ )]+)"
143tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
144tryLoadFn fn (Lib _ h) = do cFn <- dlsym h fn
147 else return . Just $ Fun fn cFn
149tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
150tryLoadLib dirs lib = do filename <- libFileName dirs lib
151 handle <- Exception.catch (loadLibrary filename) nullPtrOnException
154 else return . Just $ Lib lib handle
155 where nullPtrOnException :: Exception.IOException -> IO DL
156 nullPtrOnException e = return nullPtr
157 -- `show e` will however give broken error message
159tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
160tryLoadFn fn (Lib _ h) = do cFn <- getProcAddress h fn
163 else return . Just $ Fun fn (castPtrToFunPtr cFn)
166-- no libffi, just add stubbs.
168data DynamicLib = Lib { lib_name :: String
173data ForeignFun = forall a. Fun { fun_name :: String
177tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
178tryLoadLib fps lib = do putStrLn $ "WARNING: Cannot load '" ++ lib ++ "' at compile time because Idris was compiled without libffi support."
181tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
182tryLoadFn fn lib = do putStrLn $ "WARNING: Cannot load '" ++ fn ++ "' at compile time because Idris was compiled without libffi support."
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list