@@ -0,0 +1,272 @@
1+{-# language LambdaCase #-}
2+import Distribution.Simple
3+import Distribution.Simple.Setup
4+import Distribution.Types.PackageDescription
5+import Distribution.Types.LocalBuildInfo
6+import Distribution.Simple.PackageIndex
7+import Distribution.Types.InstalledPackageInfo hiding (includeDirs)
8+import qualified Distribution.Types.InstalledPackageInfo as IPI
9+import Distribution.Types.PackageName
10+import System.FilePath
11+import Control.Monad (filterM, forM_, forM, unless)
12+import System.Directory (doesFileExist)
13+import Distribution.Types.Library (libBuildInfo, Library(..))
14+import Distribution.Types.BuildInfo (cSources, jsSources, includeDirs, emptyBuildInfo, options, extraBundledLibs)
15+import Distribution.Simple.BuildTarget (readBuildTargets, BuildTarget(..), readUserBuildTargets)
16+import Distribution.Verbosity (silent, verbose)
17+import Distribution.Types.ComponentName
18+import Distribution.Simple.Program.Types (programPath)
19+import Distribution.Simple.Program.Db (lookupKnownProgram, lookupProgram, knownPrograms)
20+import Distribution.Simple.Program (Program, gccProgram, arProgram, runDbProgram, simpleProgram, ghcProgram)
21+import Distribution.Simple.Utils (createDirectoryIfMissingVerbose)
22+import Distribution.Types.HookedBuildInfo
23+import Data.List (isPrefixOf, isSuffixOf, intercalate)
24+import System.Environment (getArgs, getProgName)
25+import Distribution.Simple.LocalBuildInfo (Component (..), withAllComponentsInBuildOrder, componentBuildDir)
26+import Distribution.Types.TestSuite (TestSuite(..))
27+import Distribution.Types.TestSuiteInterface (TestSuiteInterface(..) )
28+import Distribution.Simple.Test.LibV09 (stubName)
29+import Distribution.Types.Executable (exeName, Executable(..))
30+import Distribution.Types.Benchmark (Benchmark(..))
31+import Distribution.Types.TestSuite (TestSuite(..))
32+import Distribution.Types.UnqualComponentName (unUnqualComponentName, mkUnqualComponentName)
35+emarProgram :: Program
36+emarProgram = simpleProgram "emar"
38+buildEMCCLib :: PackageDescription -> LocalBuildInfo -> IO ()
39+buildEMCCLib desc lbi = do
40+ let verbosity = verbose
42+ createDirectoryIfMissingVerbose verbosity True ((buildDir lbi) </> "emcc")
46+ -- Let's see if we are going to export anything. If not there is likely no point in compiling anything
48+ names <- forM (jsSources . libBuildInfo $ lib) $ \src -> do
49+ unwords . concatMap (drop 2 . words) . filter (isPrefixOf "// EMCC:EXPORTED_FUNCTIONS") . lines <$> readFile src
51+ unless (null names) $ do
52+ let depIncludeDirs = concatMap IPI.includeDirs (topologicalOrder $ installedPkgs lbi)
53+ -- alright, let's compile all .c files into .o files with emcc, which is the `gcc` program.
54+ forM_ (cSources . libBuildInfo $ lib) $ \src -> do
55+ let dst = (buildDir lbi) </> "emcc" </> (src -<.> "o")
56+ createDirectoryIfMissingVerbose verbosity True (takeDirectory dst)
57+ runDbProgram verbosity gccProgram (withPrograms lbi) $
58+ ["-c", src, "-o", dst] ++ ["-I" <> incDir | incDir <- (includeDirs . libBuildInfo $ lib) ++ depIncludeDirs]
60+ -- and now construct a canonical `.js_a` file, *if* we have any cSources we turned into objects.
61+ unless (null . cSources . libBuildInfo $ lib) $ do
62+ let dstLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
63+ runDbProgram verbosity emarProgram (withPrograms lbi) $
64+ [ "-r", dstLib ] ++ [ (buildDir lbi) </> "emcc" </> (src -<.> "o") | src <- cSources . libBuildInfo $ lib ]
66+ let expLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported.js_a"
67+ writeFile expLib (unwords names)
69+ -- if there's no lib, this is a fairly pointless exercise
72+-- This is here so that we can link multiple libEMCC* libraries fromd ependencies together with emcc.
73+-- however we don't have figured out how to get the EXPORTED_FUNCTIONS from each dependency merged yet.
75+linkEMCCLib :: PackageDescription -> LocalBuildInfo -> IO ()
76+linkEMCCLib desc lbi = linkCLib ("emcc" </> "lib.js") desc lbi
78+linkEMCCTHLib :: PackageDescription -> LocalBuildInfo -> IO ()
79+linkEMCCTHLib desc lbi = linkCLib ("th-support.js") desc lbi
81+linkCLib :: String -> PackageDescription -> LocalBuildInfo -> IO ()
82+linkCLib libname desc lbi = do
83+ withAllComponentsInBuildOrder desc lbi $ \comp clbi -> do
84+ let extraLibs = [ "-l" <> l | l <- concatMap IPI.extraLibraries (topologicalOrder $ installedPkgs lbi)
87+ libDirs = [ "-L" <> path | path <- concatMap IPI.libraryDirs (topologicalOrder $ installedPkgs lbi) ]
89+ let verbosity = verbose
90+ libs <- filterM doesFileExist $
91+ concatMap (\x -> [ libDir </> "libEMCC" <> (unPackageName . pkgName . sourcePackageId $ x) <> ".js_a"
92+ | libDir <- libraryDirs x ])
93+ (topologicalOrder $ installedPkgs lbi)
94+ exff <- filterM doesFileExist $
95+ concatMap (\x -> [ libDir </> "libEMCC" <> (unPackageName . pkgName . sourcePackageId $ x) <> ".exported.js_a"
96+ | libDir <- libraryDirs x ])
97+ (topologicalOrder $ installedPkgs lbi)
99+ exfns <- concat <$> forM exff (fmap words . readFile)
100+ unless (null libs && null exfns) $ do
101+ libs <- case libs of
102+ [] -> do writeFile (buildDir lbi </> "emcc_linking_dummy.c") ""
103+ runDbProgram verbosity gccProgram (withPrograms lbi) $
104+ ["-c", buildDir lbi </> "emcc_linking_dummy.c", "-o", buildDir lbi </> "emcc_linking_dummy.o"]
105+ return [(buildDir lbi </> "emcc_linking_dummy.o")]
108+ let dst = if libname == "emcc" </> "lib.js" then buildDir lbi
109+ -- who designed this shit in cabal?
111+ (CTest test@(TestSuite { testInterface = TestSuiteLibV09 _ _ })) -> buildDir lbi </> stubName test </> stubName test ++ "-tmp"
112+ (CTest test@(TestSuite { testInterface = TestSuiteExeV10 _ _ })) -> buildDir lbi </> unUnqualComponentName (testName test) </> unUnqualComponentName (testName test) ++ "-tmp"
113+ (CExe exe) -> buildDir lbi </> unUnqualComponentName (exeName exe) </> unUnqualComponentName (exeName exe) ++ "-tmp"
114+ _ -> componentBuildDir lbi clbi
115+ dst' = dst </> libname
116+ createDirectoryIfMissingVerbose verbosity True (takeDirectory dst')
117+ runDbProgram verbosity gccProgram (withPrograms lbi) $
120+ , "-s", "ALLOW_TABLE_GROWTH" -- we need this for addFunction/removeFunction
121+ -- addFunction, removeFunction are for dynamic functions.
122+ -- getTempRet0/setTempRet0 are for 64bit legalization.
123+ , "-s", "EXPORTED_RUNTIME_METHODS=['printErr','addFunction','removeFunction','getTempRet0','setTempRet0']"
125+ , "-s", "EXPORTED_FUNCTIONS=['" <> intercalate "', '" exfns <> "']"
126+ ] ++ libs ++ libDirs ++ extraLibs
128+postBuildHook :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
129+postBuildHook args flags desc lbi = do
130+ case (takeFileName . programPath <$> lookupProgram ghcProgram (withPrograms lbi)) of
131+ Just "js-unknown-ghcjs-ghc" ->
132+ readBuildTargets silent desc (buildArgs flags) >>= \case
133+ [BuildTargetComponent (CLibName _)] -> print "OK. Lib (Build)" >> buildEMCCLib desc lbi
134+ [BuildTargetComponent (CExeName _)] -> print "OK. Exe"
135+ [BuildTargetComponent (CTestName _)] -> print "OK. Test"
136+ [BuildTargetComponent (CBenchName _)] -> print "OK. Bench"
140+postConfHook :: Args -> ConfigFlags -> PackageDescription -> LocalBuildInfo -> IO ()
141+postConfHook args flags desc lbi = do
142+ case (takeFileName . programPath <$> lookupProgram ghcProgram (withPrograms lbi)) of
143+ Just "js-unknown-ghcjs-ghc" -> do
144+ -- always link the TH lib
145+ -- this is technically only needed if the package uses TH somewhere.
146+ linkEMCCTHLib desc lbi
147+ -- only link the final lib if we want to produce an output.
148+ readBuildTargets silent desc (configArgs flags) >>= \case
149+ [BuildTargetComponent (CLibName _)] -> print "OK. Lib" >> postConf simpleUserHooks args flags desc lbi
150+ [BuildTargetComponent (CExeName _)] -> print "OK. Exe (Link)" >> linkEMCCLib desc lbi
151+ [BuildTargetComponent (CTestName _)] -> print "OK. Test (Link)" >> linkEMCCLib desc lbi
152+ [BuildTargetComponent (CBenchName _)] -> print "OK. Bench (Link)" >> linkEMCCLib desc lbi
154+ -- defer to default postConf. XXX we should do this for the above cases in linkEMCCLib as well!
155+ _ -> postConf simpleUserHooks args flags desc lbi
158+-- Injecting emcc/lib.js as needed.
160+-- We inject jsSources: dist/build/emcc/lib.js, the amalgamated emcc library,
161+-- into Executables, Tests and Benchmarks.
162+emccBuildHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()
163+emccBuildHook desc lbi hooks flags = do
164+ let lbi' = lbi { localPkgDescr = updatePackageDescription (localPkgDescr lbi) }
165+ -- for some reason tests/benchmarks seem to rely on the description, whereas libraries and executables depend on the local build info...
166+ desc' = updatePackageDescription desc
167+ doesFileExist jsLib >>= \x -> print $ jsLib <> " " <> (if x then "exists" else "doesn't exist!")
168+ doesFileExist jsLib >>= \case
169+ True -> buildHook simpleUserHooks desc' lbi' hooks flags
170+ False -> buildHook simpleUserHooks desc lbi hooks flags
173+ jsLib = "dist/build" </> "emcc" </> "lib.js"
174+ extraOpts = PerCompilerFlavor [jsLib] []
175+ -- don't inject it for libraries, only for exe, test, bench.
176+ updateLibrary :: Library -> Library
177+ updateLibrary = id -- lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { options = options bi <> extraOpts } }
178+ updateExe :: Executable -> Executable
179+ updateExe exe@Executable{ buildInfo = bi } = exe { buildInfo = bi { options = options bi <> extraOpts } }
180+ updateTest :: TestSuite -> TestSuite
181+ updateTest test@TestSuite{ testBuildInfo = bi } = test { testBuildInfo = bi { options = options bi <> extraOpts } }
182+ updateBench :: Benchmark -> Benchmark
183+ updateBench bench@Benchmark{ benchmarkBuildInfo = bi } = bench { benchmarkBuildInfo = bi { options = options bi <> extraOpts } }
184+ updatePackageDescription :: PackageDescription -> PackageDescription
185+ updatePackageDescription desc = desc
186+ { library = updateLibrary <$> library desc
187+ , executables = updateExe <$> executables desc
188+ , testSuites = updateTest <$> testSuites desc
189+ , benchmarks = updateBench <$> benchmarks desc
193+-- Injecting EMCC<name> extra libraries as needed
194+-- this one we only need alongside library components; as it's shipped and installed
195+-- into the package databse. This is not necesary for executables/test/benchmarks, that
196+-- are not installed into the package database
198+emccCopyHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> CopyFlags -> IO ()
199+emccCopyHook desc lbi hooks flags = do
200+ emccLibs <- filterM (\l -> doesFileExist (buildDir lbi </> "lib" <> l <> ".js_a"))
201+ [ "EMCC" <> (unPackageName . pkgName . package $ desc)
202+ , "EMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported" ]
203+ print $ "EMCC extra lib files: " ++ intercalate ", " emccLibs
204+ let lbi' = lbi { localPkgDescr = updatePackageDescription emccLibs (localPkgDescr lbi) }
205+ desc' = updatePackageDescription emccLibs desc
206+ copyHook simpleUserHooks desc' lbi' hooks flags
208+ emccLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
209+ -- don't inject it for libraries, only for exe, test, bench.
210+ updateLibrary :: [String] -> Library -> Library
211+ updateLibrary extraLibs lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { extraBundledLibs = extraBundledLibs bi <> extraLibs } }
212+ updatePackageDescription :: [String] -> PackageDescription -> PackageDescription
213+ updatePackageDescription extraLibs desc = desc { library = updateLibrary extraLibs <$> library desc }
215+emccRegHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> RegisterFlags -> IO ()
216+emccRegHook desc lbi hooks flags = do
217+ emccLibs <- filterM (\l -> doesFileExist (buildDir lbi </> "lib" <> l <> ".js_a"))
218+ [ "EMCC" <> (unPackageName . pkgName . package $ desc)
219+ , "EMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported" ]
220+ print $ "EMCC extra lib files: " ++ intercalate ", " emccLibs
221+ let lbi' = lbi { localPkgDescr = updatePackageDescription emccLibs (localPkgDescr lbi) }
222+ desc' = updatePackageDescription emccLibs desc
223+ regHook simpleUserHooks desc' lbi' hooks flags
225+ emccLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
226+ -- don't inject it for libraries, only for exe, test, bench.
227+ updateLibrary :: [String] -> Library -> Library
228+ updateLibrary extraLibs lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { extraBundledLibs = extraBundledLibs bi <> extraLibs } }
229+ updatePackageDescription :: [String] -> PackageDescription -> PackageDescription
230+ updatePackageDescription extraLibs desc = desc { library = updateLibrary extraLibs <$> library desc }
232+emccUnregHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> RegisterFlags -> IO ()
233+emccUnregHook desc lbi hooks flags = do
234+ emccLibs <- filterM (\l -> doesFileExist (buildDir lbi </> "lib" <> l <> ".js_a"))
235+ [ "EMCC" <> (unPackageName . pkgName . package $ desc)
236+ , "EMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported" ]
237+ print $ "EMCC extra lib files: " ++ intercalate ", " emccLibs
238+ let lbi' = lbi { localPkgDescr = updatePackageDescription emccLibs (localPkgDescr lbi) }
239+ desc' = updatePackageDescription emccLibs desc
240+ unregHook simpleUserHooks desc' lbi' hooks flags
242+ emccLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
243+ -- don't inject it for libraries, only for exe, test, bench.
244+ updateLibrary :: [String] -> Library -> Library
245+ updateLibrary extraLibs lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { extraBundledLibs = extraBundledLibs bi <> extraLibs } }
246+ updatePackageDescription :: [String] -> PackageDescription -> PackageDescription
247+ updatePackageDescription extraLibs desc = desc { library = updateLibrary extraLibs <$> library desc }
254+ defaultMainWithHooksArgs emccHooks (injectEmar args)
256+ injectEmar :: [String] -> [String]
258+ injectEmar (x:xs) | "--with-gcc=" `isPrefixOf` x
259+ , "emcc" `isSuffixOf` x
260+ = x:("--with-emar="<> (takeDirectory $ drop 11 $ x) </> "emar"):injectEmar xs
261+ injectEmar (x:xs) = x:injectEmar xs
263+ emccHooks :: UserHooks
264+ emccHooks = simpleUserHooks
265+ { postConf = postConfHook
266+ , buildHook = emccBuildHook
267+ , postBuild = postBuildHook
268+ , copyHook = emccCopyHook
269+ , regHook = emccRegHook
270+ , unregHook = emccUnregHook
271+ , hookedPrograms = [emarProgram]