module Development.CabalBundleCLib ( mainWithCLib , module Development.CabalBundleCLib.Types ) where import Data.Time.Clock (getCurrentTime) import Development.CabalBundleCLib.Types import qualified Distribution.Compat.Lens as Lens import qualified Distribution.PackageDescription as Cabal hiding (Flag) import qualified Distribution.Simple as Cabal import qualified Distribution.Simple.Setup as Cabal import qualified Distribution.Types.BuildInfo.Lens as CabalLens import qualified Distribution.Types.Library.Lens as CabalLens import qualified Distribution.Types.LocalBuildInfo as Cabal import qualified Distribution.Types.PackageDescription.Lens as CabalLens import System.Directory (removeFile) import System.FilePath (()) import System.IO.Temp (writeTempFile) mainWithCLib :: FilePath -- ^c project root -> [String] -- ^bundled libraries -> [FilePath] -- ^parent dirs of libraries (relative to build root) -> (BuildAction -> BuildDirs -> IO ()) -- ^builder -- The three 'FilePath's are source dir, build dir and installation -- dir, respectively -> IO () mainWithCLib cProjRoot bundledLibs bundledLibDirs builder = Cabal.defaultMainWithHooks Cabal.simpleUserHooks { Cabal.confHook = customConfigure bundledLibs , Cabal.postConf = \_ _ _ _ -> pure () -- remove the check for foreign libs , Cabal.buildHook = customBuild cProjRoot bundledLibs bundledLibDirs builder } customConfigure :: [String] -> (Cabal.GenericPackageDescription, Cabal.HookedBuildInfo) -> Cabal.ConfigFlags -> IO Cabal.LocalBuildInfo customConfigure bundledLibs gpkgDesc configFlags = do lbi <- Cabal.confHook Cabal.simpleUserHooks gpkgDesc configFlags let localPkgDescr = Cabal.localPkgDescr lbi localPkgDescr' = Lens.over CabalLens.library updateLibrary localPkgDescr pure $ lbi { Cabal.localPkgDescr = localPkgDescr' } where updateLibrary :: Maybe Cabal.Library -> Maybe Cabal.Library updateLibrary = fmap $ Lens.over CabalLens.libBuildInfo updateBuildInfo updateBuildInfo :: Cabal.BuildInfo -> Cabal.BuildInfo updateBuildInfo bi = bi { Cabal.extraLibs = bundledLibs ++ Cabal.extraLibs bi , Cabal.extraBundledLibs = bundledLibs ++ Cabal.extraBundledLibs bi } customBuild :: FilePath -- ^c project root -> [String] -- ^bundled libs -> [FilePath] -- bundled lib dirs -> (BuildAction -> BuildDirs -> IO ()) -> Cabal.PackageDescription -> Cabal.LocalBuildInfo -> Cabal.UserHooks -> Cabal.BuildFlags -> IO () customBuild cProjRoot bundledLibs bundledLibDirs builder packageDesc localBuildInfo userHooks buildFlags = do currentTime <- getCurrentTime let versionInfo = "const char *clibver = \"" ++ show currentTime ++ "\";\n" let buildDir = Cabal.buildDir localBuildInfo clibVersionFile <- writeTempFile buildDir "clibver.c" versionInfo let updateLibrary :: Cabal.Library -> Cabal.Library updateLibrary lib = let lib' = Lens.over (CabalLens.libBuildInfo . CabalLens.cSources) (clibVersionFile :) lib lib'' = Lens.over (CabalLens.libBuildInfo . CabalLens.extraLibDirs) ((fmap (\dir -> buildDir dir) bundledLibDirs) ++) lib' in Lens.over (CabalLens.libBuildInfo . CabalLens.extraLibs) (bundledLibs ++) lib'' let packageDesc' = Lens.over CabalLens.library (fmap updateLibrary) packageDesc builder (BuildActionBuild (getBuildMode localBuildInfo)) (BuildDirs cProjRoot (buildDir "clibbuild") buildDir) -- TODO use some unique build dir name simpleBuildHook packageDesc' localBuildInfo userHooks buildFlags removeFile clibVersionFile getBuildMode :: Cabal.LocalBuildInfo -> BuildMode getBuildMode localBuildInfo = case Cabal.configOptimization . Cabal.configFlags $ localBuildInfo of Cabal.Flag level -> case level of Cabal.MaximumOptimisation -> BuildModeRelease _ -> BuildModeDebug _ -> BuildModeDebug simpleBuildHook :: Cabal.PackageDescription -> Cabal.LocalBuildInfo -> Cabal.UserHooks -> Cabal.BuildFlags -> IO () simpleBuildHook = Cabal.buildHook Cabal.simpleUserHooks