jarify-1.0: Jarify.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
module Main where
import Codec.Archive.Zip
( addEntryToArchive
, emptyArchive
, fromArchive
, toArchive
, toEntry
)
import Control.Monad (unless)
import Data.Text (pack, strip, unpack)
import Data.List (intercalate, isInfixOf)
import qualified Data.ByteString.Lazy as BS
import Paths_jarify
import System.Directory (copyFile, doesFileExist)
import System.Environment (getArgs, getExecutablePath)
import System.FilePath ((</>), (<.>), isAbsolute, takeBaseName, takeFileName)
import System.Info (os)
import System.IO (hPutStrLn, stderr)
import System.IO.Temp (withSystemTempFile)
import System.Process (callProcess, readProcess)
import Text.Regex.TDFA
stripString :: String -> String
stripString = unpack . strip . pack
-- | Add @$ORIGIN@ to RPATH and dependency on @libHSjarify.so@.
patchElf :: FilePath -> IO ()
patchElf exe = do
dyndir <- getDynLibDir
rpath <- readProcess "patchelf" ["--print-rpath", exe] ""
let newrpath = intercalate ":" ["$ORIGIN", dyndir, rpath]
callProcess "patchelf" ["--set-rpath", newrpath, exe]
doPackage :: FilePath -> FilePath -> IO ()
doPackage baseJar cmd = do
jarbytes <- BS.readFile baseJar
cmdpath <- doesFileExist cmd >>= \case
False -> stripString <$> readProcess "which" [cmd] ""
True -> return cmd
(hsapp, libs) <- withSystemTempFile "hsapp" $ \tmp _ -> do
copyFile cmdpath tmp
patchElf tmp
ldd <- case os of
"darwin" -> do
hPutStrLn
stderr
"WARNING: JAR not self contained on OS X (shared libraries not copied)."
return ""
_ -> readProcess "ldd" [tmp] ""
self <- getExecutablePath
selfldd <- readProcess "ldd" [self] ""
let unresolved =
map fst $
filter (not . isAbsolute . snd) $
map (\xs -> (xs !! 1, xs !! 2)) (ldd =~ "(.+) => (.+)" :: [[String]])
matchOutput xs =
map (!! 1) (xs =~ " => (.*) \\(0x[0-9a-f]+\\)" :: [[String]])
libs =
filter
(\x -> not $ any (`isInfixOf` x) ["libc.so", "libpthread.so"])
(matchOutput ldd) ++
-- Guarantee that libHSjarify is part of libs set.
filter
("libHSjarify" `isInfixOf`)
(matchOutput selfldd)
unless (null unresolved) $
fail $
"Unresolved libraries in " ++
cmdpath ++
":\n" ++
unlines unresolved
(, libs) <$> BS.readFile tmp
libentries <- mapM mkEntry libs
let cmdentry = toEntry "hsapp" 0 hsapp
appzip =
toEntry "jarify-app.zip" 0 $
fromArchive $
foldr addEntryToArchive emptyArchive (cmdentry : libentries)
newjarbytes = fromArchive $ addEntryToArchive appzip (toArchive jarbytes)
BS.writeFile ("." </> takeBaseName cmd <.> "jar") newjarbytes
where
mkEntry file = toEntry (takeFileName file) 0 <$> BS.readFile file
main :: IO ()
main = do
argv <- getArgs
case argv of
["--base-jar", baseJar, path] -> doPackage baseJar path
[path] -> do
dir <- getDataDir
-- Use executables' base jar by default.
doPackage (dir </> "build/libs/stub.jar") path
_ -> fail "Usage: jarify [--base-jar FILE] <command>"