{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Codec.SelfExtract.Distribution
( bundle
, bundle'
) where
import Control.Monad.Extra (unlessM)
import Data.Binary (Word32, encode)
import Data.ByteString as BS
import Data.ByteString.Lazy as LBS
import Data.FileEmbed (injectFileWith)
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..))
import Path (Dir, File, Path, fromAbsFile, parseRelDir, parseRelFile, relfile, toFilePath, (</>))
import Path.IO (doesFileExist, renameFile, resolveDir', withSystemTempDir)
import System.PosixCompat.Files (fileSize, getFileStatus)
import Codec.SelfExtract.Tar (tar)
bundle :: String -> FilePath -> LocalBuildInfo -> IO ()
bundle exe dir lbi = do
dir' <- resolveDir' dir
bundle' exe dir' lbi
bundle' :: String -> Path b Dir -> LocalBuildInfo -> IO ()
bundle' exeName dir LocalBuildInfo{buildDir} = do
exeDir <- resolveDir' buildDir
exeNameDir <- parseRelDir exeName
exeNameFile <- parseRelFile exeName
let exe = exeDir </> exeNameDir </> exeNameFile
unlessM (doesFileExist exe) $ error $ "Executable does not exist: " ++ exeName
size <- getFileSize exe
withSystemTempDir "self-extract" $ \tempDir -> do
let exeWithSize = tempDir </> [relfile|exe_with_size|]
injectFileWith "self-extract"
(LBS.toStrict $ encode size)
(fromAbsFile exe)
(fromAbsFile exeWithSize)
let zippedDir = tempDir </> [relfile|bundle.tar.gz|]
tar dir zippedDir
let combined = tempDir </> [relfile|exe_and_bundle|]
cat [exeWithSize, zippedDir] combined
renameFile combined exe
getFileSize :: Path b File -> IO Word32
getFileSize = fmap getSize . getFileStatus . toFilePath
where
getSize = fromIntegral . fileSize
cat :: [Path b File] -> Path b File -> IO ()
cat srcs dest = do
contents <- BS.concat <$> mapM (BS.readFile . toFilePath) srcs
BS.writeFile (toFilePath dest) contents