packages feed

self-extract 0.2.1 → 0.3.0

raw patch · 7 files changed

+195/−150 lines, 7 filesdep +self-extractdep +ztardep ~basenew-component:exe:self-bundle

Dependencies added: self-extract, ztar

Dependency ranges changed: base

Files

CHANGELOG.md view
@@ -1,3 +1,14 @@+## self-extract 0.3.0++Breaking fixes:+* `bundle` now works standalone, without needing to be in a Cabal hook+* Use `getExe` in Cabal hooks instead++Other fixes:+* Add tests+* Add dev flag+* Upgrade to ztar-0.1.0+ ## self-extract 0.2.0.0  Fixed bug due to stripping the exes after bundling, which is only a problem when bundling multiple
README.md view
@@ -4,56 +4,69 @@  ## Usage -### .cabal file+### Basic  ```-...-build-type: Custom-...+import Codec.SelfExtract (extractTo)+import System.Environment (getArgs) +main :: IO ()+main = do+  dir <- head <$> getArgs+  extractTo dir+```++```+$ stack ghc Example.hs+$ mkdir artifacts && touch artifacts/hello.txt artifacts/world.txt+$ stack build self-extract && stack exec -- self-bundle ./Example artifacts/+$ ./Example dist+$ ls dist+hello.txt+world.txt+```++### With Cabal hooks++* Add `self-extract` to the Cabal file++``` custom-setup   setup-depends: base, Cabal, self-extract -...- executable name-of-executable-  build-depends: self-extract, ...-  ...+  build-depends: self-extract ``` -### Setup.hs+* Call `bundle` in `Setup.hs`  ```-import Codec.SelfExtract.Distribution (bundle)+import Codec.SelfExtract (bundle)+import Codec.SelfExtract.Distribution (getExe) import Distribution.Simple  main = defaultMainWithHooks simpleUserHooks   { postCopy = \args cf pd lbi -> do       postCopy simpleUserHooks args cf pd lbi-      bundle "name-of-executable" "dir-to-bundle" lbi+      exe <- getExe lbi "name-of-executable"+      bundle exe "dir-to-bundle"   } ``` -### Executable file+* Call `extractTo` in the executable  ```-import Codec.SelfExtract (extractTo)+import Codec.SelfExtract  main = do-  extractTo "dir" -- will extract to $CWD/dir-  extractTo "/usr/local/lib"-  ...-```--Extract to a temporary directory:+  -- will extract to $CWD/dir+  extractTo "dir" -```-import Codec.SelfExtract (withExtractToTemp)-import System.Directory (removeDirectory)+  -- will extract to /usr/local/lib+  extractTo "/usr/local/lib" -main = do-  withExtractToTemp $ \tmp -> do-    ...+  -- will extract to a temporary directory+  withExtractToTemp $ \dir -> ... ```  ### Details@@ -64,10 +77,9 @@ When the executable containing `extractTo` is built, some space will be allocated to contain the size of the binary. -`bundle` will find the executable from `LocalBuildInfo`'s `buildDir`. It will take the directory-specified and run `tar` on it. It will also get the size of the executable and write the size into-the space allocated by `extractTo`. Then `bundle` will replace the executable with the executable-itself concatenated with the tar archive.+`bundle` will take the directory specified and run `tar` on it. It will also get the size of the+given executable and write the size into the space allocated by `extractTo`. Then `bundle` will+replace the executable with the executable itself concatenated with the tar archive. -When `extractTo` is called, it will read the size of the executable that was written in `bundle`.+When `extractTo` is called, it will read the size of the executable that was written with `bundle`. After seeking to the size of the binary, the tar archive can be extracted to the desired directory.
+ exe/Bundle.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE LambdaCase #-}++import Codec.SelfExtract (bundle)+import System.Environment (getArgs)++-- | Given an executable and a directory, bundle the directory into the executable to be extracted+-- when the executable is run.+--+-- The executable should import `extractTo` from `Codec.SelfExtract` to extract the directory+-- bundled with it.+main :: IO ()+main = getArgs >>= \case+  [exe, dir] -> bundle exe dir+  _ -> fail "Usage: self-bundle EXE DIR"
self-extract.cabal view
@@ -1,5 +1,5 @@ name:                self-extract-version:             0.2.1+version:             0.3.0 license:             BSD3 license-file:        LICENSE.md author:              Brandon Chinn <brandonchinn178@gmail.com>@@ -15,12 +15,16 @@   type: git   location: https://github.com/brandonchinn178/self-extract.git +flag dev+  description:        Turn on development settings.+  manual:             True+  default:            False+ library   hs-source-dirs:      src   default-language:    Haskell2010   exposed-modules:     Codec.SelfExtract                        Codec.SelfExtract.Distribution-                       Codec.SelfExtract.Tar   build-depends:       base >= 4.7 && < 5                      , Cabal >= 2.0 && < 3                      , binary >= 0.8.5 && < 0.9@@ -31,6 +35,29 @@                      , path-io >= 1.3 && < 1.4                      , process >= 1.6 && < 1.7                      , unix-compat >= 0.5 && < 0.6-  ghc-options:-    -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat-    -Wredundant-constraints -Wnoncanonical-monad-instances+                     , ztar >= 0.1 && < 0.2+  ghc-options: -Wall+  if flag(dev)+    ghc-options:      -Werror+  if flag(dev) && impl(ghc >= 8.0)+    ghc-options:      -Wcompat+                      -Wincomplete-record-updates+                      -Wincomplete-uni-patterns+                      -Wnoncanonical-monad-instances+                      -Wnoncanonical-monadfail-instances++executable self-bundle+  main-is:              Bundle.hs+  hs-source-dirs:       exe+  default-language:     Haskell2010+  build-depends:        base+                      , self-extract+  ghc-options: -Wall+  if flag(dev)+    ghc-options:      -Werror+  if flag(dev) && impl(ghc >= 8.0)+    ghc-options:      -Wcompat+                      -Wincomplete-record-updates+                      -Wincomplete-uni-patterns+                      -Wnoncanonical-monad-instances+                      -Wnoncanonical-monadfail-instances
src/Codec/SelfExtract.hs view
@@ -7,27 +7,45 @@ Defines functions that should be used in a self-extractable executable. -} +{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}  module Codec.SelfExtract   ( extractTo   , withExtractToTemp+  , bundle   , extractTo'   , withExtractToTemp'+  , bundle'   ) where +import Codec.Archive.ZTar (Compression(..), create, extract) import Control.Monad ((>=>))-import Data.Binary (Word32, decode)+import Control.Monad.Extra (unlessM)+import Data.Binary (Word32, decode, encode) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS-import Data.FileEmbed (dummySpaceWith)-import Path (Abs, Dir, Path, fromAbsDir, fromAbsFile, parseAbsFile)-import Path.IO (resolveDir', withSystemTempDir, withSystemTempFile)+import Data.FileEmbed (dummySpaceWith, injectFileWith)+import Path+    ( Abs+    , Dir+    , File+    , Path+    , fromAbsDir+    , fromAbsFile+    , parseAbsFile+    , relfile+    , toFilePath+    , (</>)+    )+import Path.IO+    (doesFileExist, renameFile, resolveDir', resolveFile', withSystemTempDir, withSystemTempFile) import System.Environment (getExecutablePath) import System.IO (IOMode(..), SeekMode(..), hClose, hSeek, withFile)+import qualified System.PosixCompat.Files as Posix -import Codec.SelfExtract.Tar (untar)+{- With FilePaths -}  -- | Extract the self-bundled executable to the given path. --@@ -39,14 +57,24 @@ extractTo = resolveDir' >=> extractTo'  -- | Extract the self-bundled executable to a temporary path.+withExtractToTemp :: (FilePath -> IO ()) -> IO ()+withExtractToTemp action = withExtractToTemp' (action . fromAbsDir)++-- | Bundle the given directory into the executable with the given name. --+-- For example, to bundle the @static/@ directory in the executable named @install-files@:+-- -- @--- withExtractToTemp $ \tmp -> do---   ...+-- bundle "./install-files" ".\/static\/" -- @-withExtractToTemp :: (FilePath -> IO ()) -> IO ()-withExtractToTemp action = withExtractToTemp' (action . fromAbsDir)+bundle :: FilePath -> FilePath -> IO ()+bundle exe dir = do+  exe' <- resolveFile' exe+  dir' <- resolveDir' dir+  bundle' exe' dir' +{- With Paths -}+ -- | Same as 'extractTo', except using the 'Path' library. -- -- @@@ -62,12 +90,57 @@       BS.hGetContents hSelf >>= BS.hPut hTemp      hClose hTemp-    untar archive dir+    extract (toFilePath dir) $ fromAbsFile archive  -- | Same as 'withExtractToTemp', except using the 'Path' library. withExtractToTemp' :: (Path Abs Dir -> IO ()) -> IO () withExtractToTemp' action = withSystemTempDir "" $ \dir -> extractTo' dir >> action dir +-- | Same as 'bundle', except using the 'Path' library.+--+-- @+-- bundle' [relfile|install-files|] [reldir|static|]+-- @+bundle' :: Path b File -> Path b Dir -> IO ()+bundle' exe dir = do+  unlessM (doesFileExist exe) $ error $ "Executable does not exist: " ++ toFilePath exe++  size <- getFileSize exe++  withSystemTempDir "self-extract" $ \tempDir -> do+    let exeWithSize = tempDir </> [relfile|exe_with_size|]+    injectFileWith "self-extract"+      (LBS.toStrict $ encode size)+      (toFilePath exe)+      (fromAbsFile exeWithSize)++    let archive = tempDir </> [relfile|bundle.tar.gz|]+    create GZip (fromAbsFile archive) $ toFilePath dir++    let combined = tempDir </> [relfile|exe_and_bundle|]+    cat [exeWithSize, archive] combined++    renameFile combined exe++  Posix.setFileMode (toFilePath exe) executeMode+  where+    -- 755 permissions+    executeMode = Posix.unionFileModes Posix.stdFileMode Posix.ownerExecuteMode++{- Helpers -}+ -- | The size of executable that will be rewritten by `bundle`. exeSize :: Word32 exeSize = decode $ LBS.fromStrict $(dummySpaceWith "self-extract" 32)++-- | Get the size of the given file.+getFileSize :: Path b File -> IO Word32+getFileSize = fmap getSize . Posix.getFileStatus . toFilePath+  where+    getSize = fromIntegral . Posix.fileSize++-- | Concatenate the given files and write to the given file.+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
src/Codec/SelfExtract/Distribution.hs view
@@ -12,101 +12,39 @@ {-# LANGUAGE QuasiQuotes #-}  module Codec.SelfExtract.Distribution-  ( bundle-  , bundle'+  ( getExe+  , getExe'   ) 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 (InstallDirs(..), LocalBuildInfo(..), fromPathTemplate) import Distribution.Simple.Setup (ConfigFlags(..), fromFlag)-import Path-    ( Abs-    , Dir-    , File-    , Path-    , fromAbsFile-    , parseAbsDir-    , parseRelFile-    , relfile-    , toFilePath-    , (</>)-    )-import Path.IO (doesFileExist, renameFile, resolveDir', withSystemTempDir)-import qualified System.PosixCompat.Files as Posix--import Codec.SelfExtract.Tar (tar)+import Path (Abs, File, Path, parseAbsDir, parseRelFile, toFilePath, (</>)) --- | Bundle the given directory into the executable with the given name.------ For example, to bundle the @static/@ directory in the executable named @install-files@:+-- | Get the executable with the given name with the given LocalBuildInfo. -- -- @ -- main = defaultMainWithHooks simpleUserHooks --   { postCopy = \args cf pd lbi -> do --       postCopy simpleUserHooks args cf pd lbi---       bundle "install-files" ".\/static\/" lbi+--       exe <- getExe lbi "name-of-executable"+--       bundle exe ".\/static\/" --   } -- @-bundle :: String -> FilePath -> LocalBuildInfo -> IO ()-bundle exe dir lbi = do-  dir' <- resolveDir' dir-  bundle' exe dir' lbi+getExe :: LocalBuildInfo -> String -> IO FilePath+getExe lbi exeName = toFilePath <$> getExe' lbi exeName --- | Same as 'bundle', except using the 'Path' library.+-- | Same as 'getExe', except using the 'Path' library. -- -- @ -- main = defaultMainWithHooks simpleUserHooks --   { postCopy = \args cf pd lbi -> do --       postCopy simpleUserHooks args cf pd lbi---       bundle' "install-files" [reldir|.\/static\/|] lbi+--       exe <- getExe' lbi "name-of-executable"+--       bundle' exe [reldir|.\/static\/|] --   } -- @-bundle' :: String -> Path b Dir -> LocalBuildInfo -> IO ()-bundle' exeName dir lbi = do-  exe <- getExe lbi exeName-  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--  Posix.setFileMode (fromAbsFile exe) executeMode-  where-    -- 755 permissions-    executeMode = Posix.unionFileModes Posix.stdFileMode Posix.ownerExecuteMode---- | Get the executable to be made self-extracting.-getExe :: LocalBuildInfo -> String -> IO (Path Abs File)-getExe LocalBuildInfo{configFlags} exeName = do+getExe' :: LocalBuildInfo -> String -> IO (Path Abs File)+getExe' LocalBuildInfo{configFlags} exeName = do   binDir <- parseAbsDir $ fromPathTemplate $ fromFlag $ bindir $ configInstallDirs configFlags   exe <- parseRelFile exeName   return $ binDir </> exe---- | Get the size of the given file.-getFileSize :: Path b File -> IO Word32-getFileSize = fmap getSize . Posix.getFileStatus . toFilePath-  where-    getSize = fromIntegral . Posix.fileSize---- | Concatenate the given files and write to the given file.-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
− src/Codec/SelfExtract/Tar.hs
@@ -1,30 +0,0 @@-{-|-Module      :  Codec.SelfExtract.Tar-Maintainer  :  Brandon Chinn <brandonchinn178@gmail.com>-Stability   :  experimental-Portability :  portable--Defines utilities for creating/extracting TAR archives.--These functions shell out to the 'tar' process because Haskell packages that implement Tar would-need some C header files, which we don't want to require on the user end.--}--module Codec.SelfExtract.Tar-  ( tar-  , untar-  ) where--import Path (Dir, File, Path, toFilePath)-import Path.IO (ensureDir)-import System.Process (callProcess)---- | Zip the given directory into the given archive.-tar :: Path b0 Dir -> Path b1 File -> IO ()-tar src archive = callProcess "tar" ["-czf", toFilePath archive, "-C", toFilePath src, "."]---- | Extract the given archive to the given directory.-untar :: Path b0 File -> Path b1 Dir -> IO ()-untar archive dest = do-  ensureDir dest-  callProcess "tar" ["-xzf", toFilePath archive, "-C", toFilePath dest]