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(..)
16import System.Directory
18import qualified Control.Exception as Exception (IOException, catch)
19import Foreign.Ptr (FunPtr, castPtrToFunPtr, nullFunPtr, nullPtr)
20import System.FilePath.Windows ((</>))
21import System.Win32.DLL
22import System.Win32.Types
24import Control.Exception (IOException, throwIO, try)
25import Foreign.Ptr (FunPtr, nullFunPtr, nullPtr)
27import Data.Array (bounds, inRange, (!))
28import Data.Functor ((<$>))
29import Data.Maybe (catMaybes)
31import Data.Array (bounds, (!))
33import System.FilePath.Posix ((</>))
34import System.Posix.DynamicLinker
42hostDynamicLibExt :: String
43#if defined(linux_HOST_OS) || defined(freebsd_HOST_OS) \
44 || defined(dragonfly_HOST_OS) || defined(openbsd_HOST_OS) \
45 || defined(netbsd_HOST_OS)
46hostDynamicLibExt = "so"
47#elif defined(darwin_HOST_OS)
48hostDynamicLibExt = "dylib"
49#elif defined(mingw32_HOST_OS)
50hostDynamicLibExt = "dll"
52hostDynamicLibExt = error $ unwords
53 [ "Undefined file extension for dynamic libraries"
54 , "in Idris' Util.DynamicLinker."
58data ForeignFun = forall a. Fun { fun_name :: String
59 , fun_handle :: FunPtr a
62data DynamicLib = Lib { lib_name :: String
66instance Eq DynamicLib where
67 (Lib a _) == (Lib b _) = a == b
69firstExisting :: [FilePath] -> IO (Maybe FilePath)
70firstExisting [] = return Nothing
71firstExisting (f:fs) = do exists <- doesFileExist f
76libFileName :: [FilePath] -> String -> IO String
77libFileName dirs lib = do let names = [lib, lib ++ "." ++ hostDynamicLibExt]
78 cwd <- getCurrentDirectory
79 found <- firstExisting $
80 map ("."</>) names ++ [d </> f | d <- cwd:dirs, f <- names]
81 return $ maybe (lib ++ "." ++ hostDynamicLibExt) id found
83#ifndef mingw32_HOST_OS
84-- Load a dynamic library on POSIX systems.
85-- In the simple case, we just find the appropriate filename and call dlopen().
86-- In the complicated case our "foo.so" isn't actually a library. Some of the
87-- .so files on modern Linux systems are linker scripts instead. dlopen()
88-- doesn't know anything about those. We need to look inside the script for the
89-- actual library path and load that. This is a horrible hack, the correct
90-- method would be to actually parse the scripts and execute them. The approach
91-- below is what GHC does.
92tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
93tryLoadLib dirs lib = do
94 filename <- libFileName dirs lib
95 res :: Either IOException DL <- try $
96 dlopen filename [RTLD_NOW, RTLD_GLOBAL]
98 Right handle -> return $ Just handle
101 -- dlopen failed, run a regex to see if the error message looks like it
102 -- could be a linker script.
103 case matchAllText invalidLibRegex (show ex) of
105 if inRange (bounds x) 1
107 -- filename above may be a relative path. Get the full path out of
108 -- the error message.
109 let realPath = fst $ x ! 1
110 fileLines <- lines <$> readFile realPath
111 -- Go down the linker script line by line looking for .so
112 -- filenames and try each one.
113 let matches = catMaybes $ map
114 (getLastMatch . matchAllText linkerScriptRegex)
116 mapMFirst (\f -> dlopen f [RTLD_NOW, RTLD_GLOBAL]) matches
120 Left ex -> throwIO ex
123 Just handle -> if undl handle == nullPtr
125 else return . Just $ Lib lib handle
126 Nothing -> return Nothing
128getLastMatch :: [MatchText String] -> Maybe String
129getLastMatch [] = Nothing
130getLastMatch (x:_) = case bounds x of
131 (low, high) -> if low > high
133 else Just $ fst $ x ! high
135mapMFirst :: (a -> IO b) -> [a] -> IO (Maybe b)
136mapMFirst f [] = return Nothing
137mapMFirst f (a:as) = do res <- try (f a)
139 Left (ex :: IOException) -> mapMFirst f as
140 Right res -> return $ Just res
142-- Both regexes copyright 2009-2011 Howard B. Golden, CJ van den Berg and Ian
143-- Lynagh. From the Glasgow Haskell Compiler. BSD licensed.
144invalidLibRegex :: Regex
145invalidLibRegex = makeRegex "(([^ \t()])+\\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short)"
147linkerScriptRegex :: Regex
148linkerScriptRegex = makeRegex "(GROUP|INPUT) *\\( *([^ )]+)"
150tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
151tryLoadFn fn (Lib _ h) = do cFn <- dlsym h fn
154 else return . Just $ Fun fn cFn
156tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
157tryLoadLib dirs lib = do filename <- libFileName dirs lib
158 handle <- Exception.catch (loadLibrary filename) nullPtrOnException
161 else return . Just $ Lib lib handle
162 where nullPtrOnException :: Exception.IOException -> IO DL
163 nullPtrOnException e = return nullPtr
164 -- `show e` will however give broken error message
166tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
167tryLoadFn fn (Lib _ h) = do cFn <- getProcAddress h fn
170 else return . Just $ Fun fn (castPtrToFunPtr cFn)
173-- no libffi, just add stubbs.
175data DynamicLib = Lib { lib_name :: String
180data ForeignFun = forall a. Fun { fun_name :: String
184tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
185tryLoadLib fps lib = do putStrLn $ "WARNING: Cannot load '" ++ lib ++ "' at compile time because Idris was compiled without libffi support."
188tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
189tryLoadFn 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