mirror of
https://github.com/simplex-chat/haskell.nix.git
synced 2026-06-03 09:17:32 +00:00
273 lines
16 KiB
Haskell
273 lines
16 KiB
Haskell
{-# language LambdaCase #-}
|
|
import Distribution.Simple
|
|
import Distribution.Simple.Setup
|
|
import Distribution.Types.PackageDescription
|
|
import Distribution.Types.LocalBuildInfo
|
|
import Distribution.Simple.PackageIndex
|
|
import Distribution.Types.InstalledPackageInfo hiding (includeDirs)
|
|
import qualified Distribution.Types.InstalledPackageInfo as IPI
|
|
import Distribution.Types.PackageName
|
|
import System.FilePath
|
|
import Control.Monad (filterM, forM_, forM, unless)
|
|
import System.Directory (doesFileExist)
|
|
import Distribution.Types.Library (libBuildInfo, Library(..))
|
|
import Distribution.Types.BuildInfo (cSources, jsSources, includeDirs, emptyBuildInfo, options, extraBundledLibs)
|
|
import Distribution.Simple.BuildTarget (readBuildTargets, BuildTarget(..), readUserBuildTargets)
|
|
import Distribution.Verbosity (silent, verbose)
|
|
import Distribution.Types.ComponentName
|
|
import Distribution.Simple.Program.Types (programPath)
|
|
import Distribution.Simple.Program.Db (lookupKnownProgram, lookupProgram, knownPrograms)
|
|
import Distribution.Simple.Program (Program, gccProgram, arProgram, runDbProgram, simpleProgram, ghcProgram)
|
|
import Distribution.Simple.Utils (createDirectoryIfMissingVerbose)
|
|
import Distribution.Types.HookedBuildInfo
|
|
import Data.List (isPrefixOf, isSuffixOf, intercalate)
|
|
import System.Environment (getArgs, getProgName)
|
|
import Distribution.Simple.LocalBuildInfo (Component (..), withAllComponentsInBuildOrder, componentBuildDir)
|
|
import Distribution.Types.TestSuite (TestSuite(..))
|
|
import Distribution.Types.TestSuiteInterface (TestSuiteInterface(..) )
|
|
import Distribution.Simple.Test.LibV09 (stubName)
|
|
import Distribution.Types.Executable (exeName, Executable(..))
|
|
import Distribution.Types.Benchmark (Benchmark(..))
|
|
import Distribution.Types.TestSuite (TestSuite(..))
|
|
import Distribution.Types.UnqualComponentName (unUnqualComponentName, mkUnqualComponentName)
|
|
|
|
|
|
emarProgram :: Program
|
|
emarProgram = simpleProgram "emar"
|
|
|
|
buildEMCCLib :: PackageDescription -> LocalBuildInfo -> IO ()
|
|
buildEMCCLib desc lbi = do
|
|
let verbosity = verbose
|
|
-- get build dir
|
|
createDirectoryIfMissingVerbose verbosity True ((buildDir lbi) </> "emcc")
|
|
--
|
|
case library desc of
|
|
Just lib -> do
|
|
-- Let's see if we are going to export anything. If not there is likely no point in compiling anything
|
|
-- from the C code.
|
|
names <- forM (jsSources . libBuildInfo $ lib) $ \src -> do
|
|
unwords . concatMap (drop 2 . words) . filter (isPrefixOf "// EMCC:EXPORTED_FUNCTIONS") . lines <$> readFile src
|
|
|
|
unless (null names) $ do
|
|
let depIncludeDirs = concatMap IPI.includeDirs (topologicalOrder $ installedPkgs lbi)
|
|
-- alright, let's compile all .c files into .o files with emcc, which is the `gcc` program.
|
|
forM_ (cSources . libBuildInfo $ lib) $ \src -> do
|
|
let dst = (buildDir lbi) </> "emcc" </> (src -<.> "o")
|
|
createDirectoryIfMissingVerbose verbosity True (takeDirectory dst)
|
|
runDbProgram verbosity gccProgram (withPrograms lbi) $
|
|
["-c", src, "-o", dst] ++ ["-I" <> incDir | incDir <- (includeDirs . libBuildInfo $ lib) ++ depIncludeDirs]
|
|
|
|
-- and now construct a canonical `.js_a` file, *if* we have any cSources we turned into objects.
|
|
unless (null . cSources . libBuildInfo $ lib) $ do
|
|
let dstLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
|
|
runDbProgram verbosity emarProgram (withPrograms lbi) $
|
|
[ "-r", dstLib ] ++ [ (buildDir lbi) </> "emcc" </> (src -<.> "o") | src <- cSources . libBuildInfo $ lib ]
|
|
|
|
let expLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported.js_a"
|
|
writeFile expLib (unwords names)
|
|
|
|
-- if there's no lib, this is a fairly pointless exercise
|
|
Nothing -> return ()
|
|
|
|
-- This is here so that we can link multiple libEMCC* libraries fromd ependencies together with emcc.
|
|
-- however we don't have figured out how to get the EXPORTED_FUNCTIONS from each dependency merged yet.
|
|
--
|
|
linkEMCCLib :: PackageDescription -> LocalBuildInfo -> IO ()
|
|
linkEMCCLib desc lbi = linkCLib ("emcc" </> "lib.js") desc lbi
|
|
|
|
linkEMCCTHLib :: PackageDescription -> LocalBuildInfo -> IO ()
|
|
linkEMCCTHLib desc lbi = linkCLib ("th-support.js") desc lbi
|
|
|
|
linkCLib :: String -> PackageDescription -> LocalBuildInfo -> IO ()
|
|
linkCLib libname desc lbi = do
|
|
withAllComponentsInBuildOrder desc lbi $ \comp clbi -> do
|
|
let extraLibs = [ "-l" <> l | l <- concatMap IPI.extraLibraries (topologicalOrder $ installedPkgs lbi)
|
|
, l /= "m"
|
|
, l /= "dl" ]
|
|
libDirs = [ "-L" <> path | path <- concatMap IPI.libraryDirs (topologicalOrder $ installedPkgs lbi) ]
|
|
|
|
let verbosity = verbose
|
|
libs <- filterM doesFileExist $
|
|
concatMap (\x -> [ libDir </> "libEMCC" <> (unPackageName . pkgName . sourcePackageId $ x) <> ".js_a"
|
|
| libDir <- libraryDirs x ])
|
|
(topologicalOrder $ installedPkgs lbi)
|
|
exff <- filterM doesFileExist $
|
|
concatMap (\x -> [ libDir </> "libEMCC" <> (unPackageName . pkgName . sourcePackageId $ x) <> ".exported.js_a"
|
|
| libDir <- libraryDirs x ])
|
|
(topologicalOrder $ installedPkgs lbi)
|
|
print exff
|
|
exfns <- concat <$> forM exff (fmap words . readFile)
|
|
unless (null libs && null exfns) $ do
|
|
libs <- case libs of
|
|
[] -> do writeFile (buildDir lbi </> "emcc_linking_dummy.c") ""
|
|
runDbProgram verbosity gccProgram (withPrograms lbi) $
|
|
["-c", buildDir lbi </> "emcc_linking_dummy.c", "-o", buildDir lbi </> "emcc_linking_dummy.o"]
|
|
return [(buildDir lbi </> "emcc_linking_dummy.o")]
|
|
_ -> return libs
|
|
|
|
let dst = if libname == "emcc" </> "lib.js" then buildDir lbi
|
|
-- who designed this shit in cabal?
|
|
else case comp of
|
|
(CTest test@(TestSuite { testInterface = TestSuiteLibV09 _ _ })) -> buildDir lbi </> stubName test </> stubName test ++ "-tmp"
|
|
(CTest test@(TestSuite { testInterface = TestSuiteExeV10 _ _ })) -> buildDir lbi </> unUnqualComponentName (testName test) </> unUnqualComponentName (testName test) ++ "-tmp"
|
|
(CExe exe) -> buildDir lbi </> unUnqualComponentName (exeName exe) </> unUnqualComponentName (exeName exe) ++ "-tmp"
|
|
_ -> componentBuildDir lbi clbi
|
|
dst' = dst </> libname
|
|
createDirectoryIfMissingVerbose verbosity True (takeDirectory dst')
|
|
runDbProgram verbosity gccProgram (withPrograms lbi) $
|
|
[ "-o", dst'
|
|
, "-s", "WASM=0"
|
|
, "-s", "ALLOW_TABLE_GROWTH" -- we need this for addFunction/removeFunction
|
|
-- addFunction, removeFunction are for dynamic functions.
|
|
-- getTempRet0/setTempRet0 are for 64bit legalization.
|
|
, "-s", "EXPORTED_RUNTIME_METHODS=['printErr','addFunction','removeFunction','getTempRet0','setTempRet0']"
|
|
--
|
|
, "-s", "EXPORTED_FUNCTIONS=['" <> intercalate "', '" exfns <> "']"
|
|
] ++ libs ++ libDirs ++ extraLibs
|
|
|
|
postBuildHook :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
|
|
postBuildHook args flags desc lbi = do
|
|
case (takeFileName . programPath <$> lookupProgram ghcProgram (withPrograms lbi)) of
|
|
Just "js-unknown-ghcjs-ghc" ->
|
|
readBuildTargets silent desc (buildArgs flags) >>= \case
|
|
[BuildTargetComponent (CLibName _)] -> print "OK. Lib (Build)" >> buildEMCCLib desc lbi
|
|
[BuildTargetComponent (CExeName _)] -> print "OK. Exe"
|
|
[BuildTargetComponent (CTestName _)] -> print "OK. Test"
|
|
[BuildTargetComponent (CBenchName _)] -> print "OK. Bench"
|
|
_ -> print "EEk!"
|
|
_ -> return ()
|
|
|
|
postConfHook :: Args -> ConfigFlags -> PackageDescription -> LocalBuildInfo -> IO ()
|
|
postConfHook args flags desc lbi = do
|
|
case (takeFileName . programPath <$> lookupProgram ghcProgram (withPrograms lbi)) of
|
|
Just "js-unknown-ghcjs-ghc" -> do
|
|
-- always link the TH lib
|
|
-- this is technically only needed if the package uses TH somewhere.
|
|
linkEMCCTHLib desc lbi
|
|
-- only link the final lib if we want to produce an output.
|
|
readBuildTargets silent desc (configArgs flags) >>= \case
|
|
[BuildTargetComponent (CLibName _)] -> print "OK. Lib" >> postConf simpleUserHooks args flags desc lbi
|
|
[BuildTargetComponent (CExeName _)] -> print "OK. Exe (Link)" >> linkEMCCLib desc lbi
|
|
[BuildTargetComponent (CTestName _)] -> print "OK. Test (Link)" >> linkEMCCLib desc lbi
|
|
[BuildTargetComponent (CBenchName _)] -> print "OK. Bench (Link)" >> linkEMCCLib desc lbi
|
|
_ -> print "EEk!"
|
|
-- defer to default postConf. XXX we should do this for the above cases in linkEMCCLib as well!
|
|
_ -> postConf simpleUserHooks args flags desc lbi
|
|
|
|
--
|
|
-- Injecting emcc/lib.js as needed.
|
|
--
|
|
-- We inject jsSources: dist/build/emcc/lib.js, the amalgamated emcc library,
|
|
-- into Executables, Tests and Benchmarks.
|
|
emccBuildHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()
|
|
emccBuildHook desc lbi hooks flags = do
|
|
let lbi' = lbi { localPkgDescr = updatePackageDescription (localPkgDescr lbi) }
|
|
-- for some reason tests/benchmarks seem to rely on the description, whereas libraries and executables depend on the local build info...
|
|
desc' = updatePackageDescription desc
|
|
doesFileExist jsLib >>= \x -> print $ jsLib <> " " <> (if x then "exists" else "doesn't exist!")
|
|
doesFileExist jsLib >>= \case
|
|
True -> buildHook simpleUserHooks desc' lbi' hooks flags
|
|
False -> buildHook simpleUserHooks desc lbi hooks flags
|
|
|
|
where
|
|
jsLib = "dist/build" </> "emcc" </> "lib.js"
|
|
extraOpts = PerCompilerFlavor [jsLib] []
|
|
-- don't inject it for libraries, only for exe, test, bench.
|
|
updateLibrary :: Library -> Library
|
|
updateLibrary = id -- lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { options = options bi <> extraOpts } }
|
|
updateExe :: Executable -> Executable
|
|
updateExe exe@Executable{ buildInfo = bi } = exe { buildInfo = bi { options = options bi <> extraOpts } }
|
|
updateTest :: TestSuite -> TestSuite
|
|
updateTest test@TestSuite{ testBuildInfo = bi } = test { testBuildInfo = bi { options = options bi <> extraOpts } }
|
|
updateBench :: Benchmark -> Benchmark
|
|
updateBench bench@Benchmark{ benchmarkBuildInfo = bi } = bench { benchmarkBuildInfo = bi { options = options bi <> extraOpts } }
|
|
updatePackageDescription :: PackageDescription -> PackageDescription
|
|
updatePackageDescription desc = desc
|
|
{ library = updateLibrary <$> library desc
|
|
, executables = updateExe <$> executables desc
|
|
, testSuites = updateTest <$> testSuites desc
|
|
, benchmarks = updateBench <$> benchmarks desc
|
|
}
|
|
|
|
--
|
|
-- Injecting EMCC<name> extra libraries as needed
|
|
-- this one we only need alongside library components; as it's shipped and installed
|
|
-- into the package databse. This is not necesary for executables/test/benchmarks, that
|
|
-- are not installed into the package database
|
|
--
|
|
emccCopyHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> CopyFlags -> IO ()
|
|
emccCopyHook desc lbi hooks flags = do
|
|
emccLibs <- filterM (\l -> doesFileExist (buildDir lbi </> "lib" <> l <> ".js_a"))
|
|
[ "EMCC" <> (unPackageName . pkgName . package $ desc)
|
|
, "EMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported" ]
|
|
print $ "EMCC extra lib files: " ++ intercalate ", " emccLibs
|
|
let lbi' = lbi { localPkgDescr = updatePackageDescription emccLibs (localPkgDescr lbi) }
|
|
desc' = updatePackageDescription emccLibs desc
|
|
copyHook simpleUserHooks desc' lbi' hooks flags
|
|
where
|
|
emccLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
|
|
-- don't inject it for libraries, only for exe, test, bench.
|
|
updateLibrary :: [String] -> Library -> Library
|
|
updateLibrary extraLibs lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { extraBundledLibs = extraBundledLibs bi <> extraLibs } }
|
|
updatePackageDescription :: [String] -> PackageDescription -> PackageDescription
|
|
updatePackageDescription extraLibs desc = desc { library = updateLibrary extraLibs <$> library desc }
|
|
|
|
emccRegHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> RegisterFlags -> IO ()
|
|
emccRegHook desc lbi hooks flags = do
|
|
emccLibs <- filterM (\l -> doesFileExist (buildDir lbi </> "lib" <> l <> ".js_a"))
|
|
[ "EMCC" <> (unPackageName . pkgName . package $ desc)
|
|
, "EMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported" ]
|
|
print $ "EMCC extra lib files: " ++ intercalate ", " emccLibs
|
|
let lbi' = lbi { localPkgDescr = updatePackageDescription emccLibs (localPkgDescr lbi) }
|
|
desc' = updatePackageDescription emccLibs desc
|
|
regHook simpleUserHooks desc' lbi' hooks flags
|
|
where
|
|
emccLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
|
|
-- don't inject it for libraries, only for exe, test, bench.
|
|
updateLibrary :: [String] -> Library -> Library
|
|
updateLibrary extraLibs lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { extraBundledLibs = extraBundledLibs bi <> extraLibs } }
|
|
updatePackageDescription :: [String] -> PackageDescription -> PackageDescription
|
|
updatePackageDescription extraLibs desc = desc { library = updateLibrary extraLibs <$> library desc }
|
|
|
|
emccUnregHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> RegisterFlags -> IO ()
|
|
emccUnregHook desc lbi hooks flags = do
|
|
emccLibs <- filterM (\l -> doesFileExist (buildDir lbi </> "lib" <> l <> ".js_a"))
|
|
[ "EMCC" <> (unPackageName . pkgName . package $ desc)
|
|
, "EMCC" <> (unPackageName . pkgName . package $ desc) <> ".exported" ]
|
|
print $ "EMCC extra lib files: " ++ intercalate ", " emccLibs
|
|
let lbi' = lbi { localPkgDescr = updatePackageDescription emccLibs (localPkgDescr lbi) }
|
|
desc' = updatePackageDescription emccLibs desc
|
|
unregHook simpleUserHooks desc' lbi' hooks flags
|
|
where
|
|
emccLib = (buildDir lbi) </> "libEMCC" <> (unPackageName . pkgName . package $ desc) <> ".js_a"
|
|
-- don't inject it for libraries, only for exe, test, bench.
|
|
updateLibrary :: [String] -> Library -> Library
|
|
updateLibrary extraLibs lib@Library{ libBuildInfo = bi } = lib { libBuildInfo = bi { extraBundledLibs = extraBundledLibs bi <> extraLibs } }
|
|
updatePackageDescription :: [String] -> PackageDescription -> PackageDescription
|
|
updatePackageDescription extraLibs desc = desc { library = updateLibrary extraLibs <$> library desc }
|
|
--
|
|
-- Main
|
|
--
|
|
main :: IO ()
|
|
main = do
|
|
args <- getArgs
|
|
defaultMainWithHooksArgs emccHooks (injectEmar args)
|
|
where
|
|
injectEmar :: [String] -> [String]
|
|
injectEmar [] = []
|
|
injectEmar (x:xs) | "--with-gcc=" `isPrefixOf` x
|
|
, "emcc" `isSuffixOf` x
|
|
= x:("--with-emar="<> (takeDirectory $ drop 11 $ x) </> "emar"):injectEmar xs
|
|
injectEmar (x:xs) = x:injectEmar xs
|
|
|
|
emccHooks :: UserHooks
|
|
emccHooks = simpleUserHooks
|
|
{ postConf = postConfHook
|
|
, buildHook = emccBuildHook
|
|
, postBuild = postBuildHook
|
|
, copyHook = emccCopyHook
|
|
, regHook = emccRegHook
|
|
, unregHook = emccUnregHook
|
|
, hookedPrograms = [emarProgram]
|
|
}
|