Build and copy libffi shared libraries correctly and enable dynamically linking ghc.
[ghc.git] / hadrian / src / Rules / Libffi.hs
1 module Rules.Libffi (libffiRules, libffiDependencies, libffiName) where
2
3 import Hadrian.Utilities
4
5 import Packages
6 import Settings.Builders.Common
7 import Target
8 import Utilities
9
10 {-
11 Note [Hadrian: install libffi hack]
12 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13
14 There are 2 important steps in handling libffi's .a and .so files:
15
16 1. libffi's .a and .so|.dynlib|.dll files are copied from the libffi build dir
17 to the rts build dir. This is because libffi is ultimately bundled with the
18 rts package. Relevant code is in the libffiRules function.
19 2. The rts is "installed" via the hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
20 copyPackage action. This uses the "cabal copy" command which (among other
21 things) attempts to copy the bundled .a and .so|.dynlib|.dll files from the
22 rts build dir to the install dir.
23
24 There is an issue in step 1. that the name of the shared library files is not
25 know untill after libffi is built. As a workaround, the rts package needs just
26 the libffiDependencies, and the corresponding rule (defined below in
27 libffiRules) does the extra work of installing the shared library files into the
28 rts build directory after building libffi.
29 -}
30
31 -- | Context for @libffi@.
32 libffiContext :: Stage -> Action Context
33 libffiContext stage = do
34 ways <- interpretInContext
35 (Context stage libffi (error "libffiContext: way not set"))
36 getLibraryWays
37 return . Context stage libffi $ if any (wayUnit Dynamic) ways
38 then dynamic
39 else vanilla
40
41 -- | The name of the (locally built) library
42 libffiName :: Expr String
43 libffiName = do
44 windows <- expr windowsHost
45 way <- getWay
46 return $ libffiName' windows (Dynamic `wayUnit` way)
47
48 -- | The name of the (locally built) library
49 libffiName' :: Bool -> Bool -> String
50 libffiName' windows dynamic
51 = (if dynamic then "" else "C")
52 ++ (if windows then "ffi-6" else "ffi")
53
54 libffiDependencies :: [FilePath]
55 libffiDependencies = ["ffi.h", "ffitarget.h"]
56
57 libffiLibrary :: FilePath
58 libffiLibrary = "inst/lib/libffi.a"
59
60 rtsLibffiLibrary :: Stage -> Way -> Action FilePath
61 rtsLibffiLibrary stage way = do
62 name <- libffiLibraryName
63 suf <- libsuf way
64 rtsPath <- rtsBuildPath stage
65 return $ rtsPath -/- "lib" ++ name ++ suf
66
67 fixLibffiMakefile :: FilePath -> String -> String
68 fixLibffiMakefile top =
69 replace "-MD" "-MMD"
70 . replace "@toolexeclibdir@" "$(libdir)"
71 . replace "@INSTALL@" ("$(subst ../install-sh," ++ top ++ "/install-sh,@INSTALL@)")
72
73 -- TODO: check code duplication w.r.t. ConfCcArgs
74 configureEnvironment :: Stage -> Action [CmdOption]
75 configureEnvironment stage = do
76 context <- libffiContext stage
77 cFlags <- interpretInContext context $ mconcat
78 [ cArgs
79 , getStagedSettingList ConfCcArgs ]
80 ldFlags <- interpretInContext context ldArgs
81 sequence [ builderEnvironment "CC" $ Cc CompileC stage
82 , builderEnvironment "CXX" $ Cc CompileC stage
83 , builderEnvironment "LD" (Ld stage)
84 , builderEnvironment "AR" (Ar Unpack stage)
85 , builderEnvironment "NM" Nm
86 , builderEnvironment "RANLIB" Ranlib
87 , return . AddEnv "CFLAGS" $ unwords cFlags ++ " -w"
88 , return . AddEnv "LDFLAGS" $ unwords ldFlags ++ " -w" ]
89
90 libffiRules :: Rules ()
91 libffiRules = forM_ [Stage1 ..] $ \stage -> do
92 root <- buildRootRules
93 let path = root -/- stageString stage
94 libffiPath = path -/- pkgName libffi -/- "build"
95 libffiOuts = [libffiPath -/- libffiLibrary] ++
96 fmap ((path -/- "rts/build") -/-) libffiDependencies
97
98 -- We set a higher priority because this rule overlaps with the build rule
99 -- for static libraries 'Rules.Library.libraryRules'.
100 -- See [Hadrian: install libffi hack], this rule installs libffi into the
101 -- rts build path.
102 priority 2.0 $ libffiOuts &%> \_ -> do
103 context <- libffiContext stage
104 useSystemFfi <- flag UseSystemFfi
105 rtsPath <- rtsBuildPath stage
106 if useSystemFfi
107 then do
108 ffiIncludeDir <- setting FfiIncludeDir
109 putBuild "| System supplied FFI library will be used"
110 forM_ ["ffi.h", "ffitarget.h"] $ \file ->
111 copyFile (ffiIncludeDir -/- file) (rtsPath -/- file)
112 putSuccess "| Successfully copied system FFI library header files"
113 else do
114 build $ target context (Make libffiPath) [] []
115
116 -- Here we produce 'libffiDependencies'
117 headers <- liftIO $ getDirectoryFilesIO "" [libffiPath -/- "inst/include/*"]
118 forM_ headers $ \header -> do
119 let target = rtsPath -/- takeFileName header
120 copyFileUntracked header target
121 produces [target]
122
123 -- Find ways.
124 ways <- interpretInContext context
125 (getLibraryWays <> getRtsWays)
126 let (dynamicWays, staticWays) = partition (wayUnit Dynamic) ways
127
128 -- Install static libraries.
129 forM_ staticWays $ \way -> do
130 rtsLib <- rtsLibffiLibrary stage way
131 copyFileUntracked (libffiPath -/- "inst/lib/libffi.a") rtsLib
132 produces [rtsLib]
133
134 -- Install dynamic libraries.
135 when (not $ null dynamicWays) $ do
136 -- Find dynamic libraries.
137 windows <- windowsHost
138 osx <- osxHost
139 let libffiName'' = libffiName' windows True
140 (dynLibsSrcDir, dynLibFiles) <- if windows
141 then do
142 let libffiDll = "lib" ++ libffiName'' ++ ".dll"
143 return (libffiPath -/- "inst/bin", [libffiDll])
144 else do
145 let libffiLibPath = libffiPath -/- "inst/lib"
146 dynLibsRelative <- liftIO $ getDirectoryFilesIO
147 libffiLibPath
148 (if osx
149 then ["lib" ++ libffiName'' ++ ".dylib*"]
150 else ["lib" ++ libffiName'' ++ ".so*"])
151 return (libffiLibPath, dynLibsRelative)
152
153 -- Install dynamic libraries.
154 rtsPath <- rtsBuildPath stage
155 forM_ dynLibFiles $ \dynLibFile -> do
156 let target = rtsPath -/- dynLibFile
157 copyFileUntracked (dynLibsSrcDir -/- dynLibFile) target
158
159 -- On OSX the dylib's id must be updated to a relative path.
160 when osx $ cmd
161 [ "install_name_tool"
162 , "-id", "@rpath/" ++ dynLibFile
163 , target
164 ]
165
166 produces [target]
167
168 putSuccess "| Successfully bundled custom library 'libffi' with rts"
169
170 fmap (libffiPath -/-) ["Makefile.in", "configure" ] &%> \[mkIn, _] -> do
171 -- Extract libffi tar file
172 context <- libffiContext stage
173 removeDirectory libffiPath
174 tarball <- unifyPath . fromSingleton "Exactly one LibFFI tarball is expected"
175 <$> getDirectoryFiles "" ["libffi-tarballs/libffi*.tar.gz"]
176
177 need [tarball]
178 -- Go from 'libffi-3.99999+git20171002+77e130c.tar.gz' to 'libffi-3.99999'
179 let libname = takeWhile (/= '+') $ takeFileName tarball
180
181 -- Move extracted directory to libffiPath.
182 root <- buildRoot
183 removeDirectory (root -/- libname)
184 actionFinally (do
185 build $ target context (Tar Extract) [tarball] [path]
186 moveDirectory (path -/- libname) libffiPath) $
187 -- And finally:
188 removeFiles (path) [libname <//> "*"]
189
190 top <- topDirectory
191 fixFile mkIn (fixLibffiMakefile top)
192
193 files <- liftIO $ getDirectoryFilesIO "." [libffiPath <//> "*"]
194 produces files
195
196 fmap (libffiPath -/-) ["Makefile", "config.guess", "config.sub"] &%> \[mk, _, _] -> do
197 context <- libffiContext stage
198
199 -- This need rule extracts the libffi tar file to libffiPath.
200 need [mk <.> "in"]
201
202 -- Configure.
203 forM_ ["config.guess", "config.sub"] $ \file -> do
204 copyFile file (libffiPath -/- file)
205 env <- configureEnvironment stage
206 buildWithCmdOptions env $
207 target context (Configure libffiPath) [mk <.> "in"] [mk]
208
209 dir <- setting BuildPlatform
210 files <- liftIO $ getDirectoryFilesIO "." [libffiPath -/- dir <//> "*"]
211 produces files