macosx-make-standalone (empty) → 0.1
raw patch · 9 files changed
+726/−0 lines, 9 filesdep +basedep +containersdep +data-lenssetup-changed
Dependencies added: base, containers, data-lens, data-lens-template, deepseq, directory, filepath, graph-visit, mtl, process, transformers, unix
Files
- LICENSE +1/−0
- Setup.hs +2/−0
- macosx-make-standalone.cabal +40/−0
- src/Cmds.hs +142/−0
- src/LibDepGraph.hs +69/−0
- src/MakeStandalone.hs +185/−0
- src/Opts.hs +93/−0
- src/Plan.hs +91/−0
- src/State.hs +103/−0
+ LICENSE view
@@ -0,0 +1,1 @@+BSD3
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ macosx-make-standalone.cabal view
@@ -0,0 +1,40 @@+Name: macosx-make-standalone+Version: 0.1+Synopsis: Make a macosx app standalone deployable+Description: Make a macosx app standalone by bundling all dylibs into it.+License: BSD3+license-file: LICENSE+Author: Atze Dijkstra+Maintainer: atze@uu.nl+Homepage: https://github.com/atzedijkstra/macosx-make-standalone+Bug-Reports: https://github.com/atzedijkstra/macosx-make-standalone/issues+Copyright: 2012 Utrecht University+Category: Development+Build-type: Simple+Cabal-version: >=1.6+source-repository head+ type: git+ location: git://github.com/atzedijkstra/macosx-make-standalone.git+++Executable macosx-make-standalone+ HS-Source-Dirs: src+ Main-is: MakeStandalone.hs+ GHC-Options: + Extensions: ScopedTypeVariables, TemplateHaskell, DeriveDataTypeable+ Build-depends:+ base >= 4 && < 5,+ data-lens >= 2 && < 3,+ data-lens-template >= 2 && < 3,+ mtl >= 2 && < 3,+ containers >= 0.4 && < 0.5,+ transformers >= 0.3 && < 0.4,+ directory >= 1 && < 2,+ filepath >= 1 && < 2,+ process >= 1 && < 2,+ unix >= 2 && < 3,+ deepseq >= 1 && < 2,+ graph-visit >= 0.1.0.1 && < 0.2++ Other-modules:+ Opts, State, LibDepGraph, Plan, Cmds
+ src/Cmds.hs view
@@ -0,0 +1,142 @@+module Cmds+ ( OToolOutput+ + , libNm+ , libUses+ + , cmdOTool+ , cmdCP+ , cmdChmod+ , cmdMkdir+ , cmdInstallNameToolId+ , cmdInstallNameToolChange+ ) where++-------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------++import Data.Lens.Common+import Data.Lens.Template+import Data.Lens.Strict+import Data.Typeable+import Data.List++import Control.DeepSeq++import Control.Monad+-- import Control.Monad.State.Strict+import Control.Monad.Trans+-- import Control.Monad.Error.Class++import qualified Control.Exception as CE++-- import System.Console.GetOpt+-- import System.Environment+import System.IO+-- import System.Exit+import System.FilePath+-- import System.Directory+-- import System.Cmd+import System.Process+-- import System.Posix.Process++import Opts+import State++-------------------------------------------------------------------------+-- Cmd run+-------------------------------------------------------------------------++showCmdSpec (ShellCommand s) = s+showCmdSpec (RawCommand f as) = concat $ intersperse " " (f:as)++cmdRunPlain :: Opts -> CreateProcess -> IO ()+cmdRunPlain opts p = do+ when (opts ^. optVerbose) (putStrLn (showCmdSpec $ cmdspec p))+ (_, _, _, phandle) <-createProcess p+ waitForProcess phandle+ return ()++cmdRunOutputPipe :: Opts -> CreateProcess -> IO Handle+cmdRunOutputPipe opts p = do+ when (opts ^. optVerbose) (putStrLn (showCmdSpec $ cmdspec p))+ (_, Just hout, _, phandle) <- createProcess (p {std_out = CreatePipe})+ waitForProcess phandle+ return hout++cmdRun :: String -> String -> [FilePath] -> StRun ()+cmdRun c m fs = do+ opts <- access opts+ liftIO $ cmdRunPlain opts $ proc c (m : fs)++-------------------------------------------------------------------------+-- otool+-------------------------------------------------------------------------++data OToolOutput = OToolOutput+ { _libNm :: !FilePath+ , _libUses :: ![FilePath]+ }+ deriving (Show,Typeable)++instance NFData OToolOutput where+ rnf (OToolOutput n u) = rnf n `seq` rnf u++makeLens ''OToolOutput++parseOToolOutput :: String -> OToolOutput+parseOToolOutput s = OToolOutput (takeWhile (/= ':') $ head lib) (map head uses)+ where (lib:uses) = filter (not . null) $ map words $ lines s++otoolOutputFilterOutUnwanted :: Opts -> OToolOutput -> OToolOutput+otoolOutputFilterOutUnwanted opts = libUses ^%= filter (\f -> okExt f && okLoc f)+ where okExt f = takeExtension f `elem` opts ^. optIncludeExtensions+ okLoc f = not $ any (`isPrefixOf` f) $ opts ^. optExcludePrefixes++cmdOTool :: FilePath -> StRun OToolOutput+cmdOTool f = do+ opts <- access opts+ liftIO $ do+ hout <- cmdRunOutputPipe opts $ proc "otool" ["-L", f]+ o <- fmap (otoolOutputFilterOutUnwanted opts . parseOToolOutput) $ hGetContents hout+ o `deepseq` hClose hout+ return o++-------------------------------------------------------------------------+-- cp+-------------------------------------------------------------------------++cmdCP :: String -> FilePath -> FilePath -> StRun ()+cmdCP o fFr fTo = cmdRun "cp" o [fFr, fTo]++-------------------------------------------------------------------------+-- chmod+-------------------------------------------------------------------------++cmdChmod :: String -> FilePath -> StRun ()+cmdChmod o f = cmdRun "chmod" o [f]++-------------------------------------------------------------------------+-- mkdir+-------------------------------------------------------------------------++cmdMkdir :: String -> FilePath -> StRun ()+cmdMkdir o f = cmdRun "mkdir" o [f]++-------------------------------------------------------------------------+-- install_name_tool+-------------------------------------------------------------------------++cmdInstallNameToolChange :: FilePath -> FilePath -> FilePath -> StRun ()+cmdInstallNameToolChange fIn fFr fTo = cmdRun "install_name_tool" "-change" [fFr, fTo, fIn]++cmdInstallNameToolId :: FilePath -> FilePath -> StRun ()+cmdInstallNameToolId fIn fTo = cmdRun "install_name_tool" "-id" [fTo, fIn]++{-+ install_name_tool -id @executable_path/../libs/libwx_osx_cocoau_xrc-2.9.4.0.0.dylib ./Dazzle.app/Contents/libs/libwx_osx_cocoau_xrc-2.9.4.0.0.dylib ++* Fixing dependencies on ./Dazzle.app/Contents/libs/libwx_osx_cocoau_xrc-2.9.4.0.0.dylib + install_name_tool -change /Volumes/Work/.cabal/lib/wxc-0.90.0.4/ghc-7.4.1/libwxc.dylib @executable_path/../libs/libwxc.dylib ./Dazzle.app/Contents/libs/libwx_osx_cocoau_xrc-2.9.4.0.0.dylib +-}
+ src/LibDepGraph.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}++module LibDepGraph+ ( LibDepGraph+ , initLibDepGraph+ + , ldepRoot+ , ldepGraph+ , ldepSymLinks+ + , ldepResolveSymlink+ )+ where++-------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------++import Data.Lens.Common+import Data.Lens.Template+-- import Data.Graph.GraphVisit+import qualified Data.Map as Map+import Data.Typeable+-- import qualified Data.Set as Set++-- import Control.Monad+-- import Control.Monad.State.Strict+-- import Control.Monad.Trans+-- import Control.Monad.Error.Class++-- import qualified Control.Exception as CE++-- import System.Console.GetOpt+-- import System.Environment+-- import System.IO+-- import System.Exit+-- import System.FilePath+-- import System.Directory+-- import System.Cmd+-- import System.Process+-- import System.Posix.Process++-- import Opts+-- import State+import Cmds (OToolOutput)++-------------------------------------------------------------------------+-- Library dependency graph+-------------------------------------------------------------------------++data LibDepGraph = LibDepGraph+ { _ldepRoot :: FilePath+ , _ldepGraph :: Map.Map FilePath OToolOutput+ , _ldepSymLinks :: Map.Map FilePath FilePath+ }+ deriving (Show,Typeable)++initLibDepGraph :: FilePath -> LibDepGraph+initLibDepGraph f = LibDepGraph f Map.empty Map.empty++makeLens ''LibDepGraph++-------------------------------------------------------------------------+-- Resolve symlink via LibDepGraph+-------------------------------------------------------------------------++-- | Resolve symlink via LibDepGraph+ldepResolveSymlink :: LibDepGraph -> FilePath -> FilePath+ldepResolveSymlink ldep fnm = maybe fnm (ldepResolveSymlink ldep) $ Map.lookup fnm $ ldep ^. ldepSymLinks
+ src/MakeStandalone.hs view
@@ -0,0 +1,185 @@+module Main where++-------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------++import Data.Lens.Common+import Data.Graph.GraphVisit+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Sequence as Seq++import Control.Monad+import Control.Monad.State.Strict+import Control.Monad.Trans+import Control.Monad.Error.Class++import qualified Control.Exception as CE++import System.Console.GetOpt+import System.Environment+import System.IO+import System.Exit+import System.FilePath+import System.Directory+import System.Cmd+import System.Process+import System.Posix.Process+import System.Posix.Files++import Opts+import State+import Cmds+import LibDepGraph+import Plan++-------------------------------------------------------------------------+-- Main+-------------------------------------------------------------------------++main :: IO ()+main = do+ args <- getArgs+ progName <- getProgName++ let optsInit = optProgName ^= progName $ defaultOpts+ oo@(o,n,errs) = getOpt Permute cmdLineOpts args+ opts = foldr ($) optsInit o++ case (errs, opts ^. optImmediateCommands, n) of+ (es@(_:_),_ ,_ ) -> forM_ es (hPutStr stderr)+ (_ ,os@(_:_),_ ) -> forM_ os (handleImmediateCommand opts)+ (_ ,_ ,[fnm]) -> doIt opts fnm+ _ -> do handleImmediateCommand opts ImmediateCommand_Help+ exitFailure++doIt :: Opts -> FilePath -> IO ()+doIt opts fnmApp = do+ pid <- getProcessID+ tmpdir <- getTemporaryDirectory+ let st = initSt opts (RunEnv tmpdir pid fnm)+ flip evalStateT st $+ catchError (do { thework ; cleanup })+ handleerr+ where+ fnm = fpathOfExec opts fnmApp+ thework = do+ ldep <- otoolGraphVisit2LibDepGraph fnm+ when (opts ^. optDebug) (liftIO $ putStrLn (show ldep))+ let plan = seqToList $ ldepGraph2Plan opts fnmApp ldep+ when (opts ^. optDebug) (liftIO $ forM_ plan (putStrLn . show))+ forM_ plan planCmdExec+ return ()+ -- f <- srFreshTmpName+ -- liftIO $ putStrLn f+ cleanup = srRmFilesToRm+ handleerr (e :: CE.IOException) = do+ liftIO $ hPutStrLn stderr (show fnm ++ ": " ++ show e)+ cleanup++-------------------------------------------------------------------------+-- Immediate command handling+-------------------------------------------------------------------------++-- | Handle an immediate command+handleImmediateCommand :: Opts -> ImmediateCommand -> IO ()+handleImmediateCommand opts ImmediateCommand_Help = putStrLn (usageInfo ("Usage: " ++ opts ^. optProgName ++ " [options] <mac app>\n\noptions:") cmdLineOpts)++-------------------------------------------------------------------------+-- Graph walk over the results provided by otool, gathering the library dependency graph+-------------------------------------------------------------------------++otoolGraphVisit2LibDepGraph :: FilePath -> StRun LibDepGraph+otoolGraphVisit2LibDepGraph f = fmap fst $ graphVisitM visit (Set.singleton f) () (initLibDepGraph f)+ where visit t _ fnm = do+ o <- cmdOTool fnm+ (fnm', links) <- liftIO $ symlinkResolve fnm+ return+ ( ldepSymLinks ^%= Map.union (Map.fromList [ (l,fnm') | l <- links ])+ $ ldepGraph ^%= Map.insert fnm' o+ $ t+ , Set.fromList $ o ^. libUses+ )++-------------------------------------------------------------------------+-- Graph walk over the library dependency graph, constructing the modification plan+-------------------------------------------------------------------------++-- ldepGraphVisit2Plan :: LibDepGraph -> StRun Plan++-------------------------------------------------------------------------+-- Compute the modification plan+-------------------------------------------------------------------------++ldepGraph2Plan :: Opts -> FilePath -> LibDepGraph -> Plan+ldepGraph2Plan opts fnmApp ldep =+ Seq.fromList+ [ PlanCmd_CP o n | (n,((o:_),_)) <- Map.toList filesToCopy ]+ Seq.><+ foldr (Seq.><) Seq.empty+ [ Seq.fromList $ PlanCmd_IntlRename n ri : mkModfRef filesToCopy n o+ | (n,((o:_),ri)) <- Map.toList filesToCopy+ ]+ Seq.><+ foldr (Seq.><) Seq.empty+ [ Seq.fromList $ mkModfRef filesToCopy o o+ | (_,((o:_),_)) <- Map.toList filesRoot+ ]+ where+ filesToCopy = mkFilesToCopyMp (Set.delete (ldep ^. ldepRoot) $ Map.keysSet $ ldep ^. ldepGraph)+ filesRoot = Map.fromList [ (o,([o],o)) | o <- [ldep ^. ldepRoot] ]+ mkFilesToCopyMp fs = Map.fromListWith (\(l1,r1) (l2,_) -> (l1++l2,r1))+ [ (n, ([l],r))+ | l <- Set.toList fs+ , let (n,r) = fpathOfNewLib opts fnmApp l+ ]+ mkFilesToCopyMpRev fMp = Map.fromList [ (o,r) | (n,(os,r)) <- Map.toList fMp, o <- os ]+ mkModfRef fMp n o =+ [ PlanCmd_ModfRef n u rr+ | u <- maybe [] (^. libUses) $ Map.lookup o $ ldep ^. ldepGraph+ , let u2 = ldepResolveSymlink ldep u+ rr = Map.findWithDefault u2 u2 fMpRev+ ]+ where fMpRev = mkFilesToCopyMpRev fMp++-------------------------------------------------------------------------+-- File name manipulation+-------------------------------------------------------------------------++-- | Given app bundle name, return the location of the executable+fpathOfExec :: Opts -> FilePath -> FilePath+fpathOfExec opts fnm = fnm </> opts ^. optInAppLocOfExec </> f+ where (df,e) = splitExtension fnm+ (d,f) = splitFileName df++-- | Given app bundle name, return the location of the new lib loc plus new name as it is to be used for referring to+fpathOfNewLib :: Opts -> FilePath -> FilePath -> (FilePath,FilePath)+fpathOfNewLib opts fnmApp fnmLib =+ ( fnmApp </> opts ^. optInAppCpLocOfLibDest </> fl+ , opts ^. optInAppRenameLocOfLibDest </> fl+ )+ where+ (_,fl) = splitFileName fnmLib++-- | Normalise path, on top of normal normalise also remove ".."+fpathNormalise :: FilePath -> FilePath+fpathNormalise fnm = joinPath $ reverse $ n [] $ splitDirectories $ normalise fnm+ where n (_:acc) ("..":f) = n acc f+ n acc (x :f) = n (x:acc) f+ n acc [] = acc++-------------------------------------------------------------------------+-- Symbolic link resolution+-------------------------------------------------------------------------++-- | Possibly resolve symbolic link, returning the actual filename + symlinks to it+symlinkResolve :: FilePath -> IO (FilePath, [FilePath])+symlinkResolve fnm = do+ stat <- getSymbolicLinkStatus fnm+ -- putStrLn $ fnm ++ ": " ++ show (isSymbolicLink stat)+ if isSymbolicLink stat+ then do fnmLinkedTo <- fmap (fpathNormalise . (takeDirectory fnm </>)) $ readSymbolicLink fnm+ (fnm',links) <- symlinkResolve fnmLinkedTo+ return (fnm', fnm : links)+ else return (fnm, [])
+ src/Opts.hs view
@@ -0,0 +1,93 @@+module Opts+ ( ImmediateCommand(..)+ + , Opts+ + , optImmediateCommands+ , optVerbose+ , optDebug+ , optProgName+ , optExcludePrefixes+ , optIncludeExtensions+ , optInAppLocOfExec + , optInAppCpLocOfLibDest + , optInAppRenameLocOfLibDest+++ , defaultOpts+ , cmdLineOpts+ ) where++-------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------++import Prelude hiding ((.))+import Control.Category++import Data.Lens.Common+import Data.Lens.Template+import Data.Typeable++import System.Console.GetOpt++-------------------------------------------------------------------------+-- Immediate commands+-------------------------------------------------------------------------++-- | Immediate command, that is, being done before actually doing the work of the program+data ImmediateCommand+ = ImmediateCommand_Help++-------------------------------------------------------------------------+-- Options+-------------------------------------------------------------------------++-- | Options+data Opts + = Opts+ { _optVerbose :: Bool -- ^ be verbose+ , _optDebug :: Bool -- ^ dump debug info+ , _optImmediateCommands :: [ImmediateCommand] -- ^ e.g. help+ , _optProgName :: String -- ^ the name of this program+ , _optExcludePrefixes :: [FilePath] -- ^ prefixes of locations which may not be copied+ , _optIncludeExtensions :: [String] -- ^ extensions which must be copied (if outside)+ , _optInAppLocOfExec :: FilePath -- ^ relative location of executable in app bundle+ , _optInAppCpLocOfLibDest :: FilePath -- ^ relative location of where copied libraries should end up in app bundle+ , _optInAppRenameLocOfLibDest :: FilePath -- ^ relative location of where to renaming should be done in app bundle+ }+ deriving (Typeable)++-- dylibbundler -od -b -x ./Dazzle.app/Contents/MacOS/Dazzle -d ./Dazzle.app/Contents/libs/ -p @executable_path/../libs/++makeLens ''Opts++defaultOpts + = Opts+ { _optVerbose = False+ , _optDebug = False+ , _optImmediateCommands = []+ , _optProgName = "??"+ , _optExcludePrefixes = [ "/usr/lib" + , "/System/Library/Frameworks"+ ]+ , _optIncludeExtensions = [ ".dylib" + ]+ , _optInAppLocOfExec = "Contents/MacOS"+ , _optInAppCpLocOfLibDest = "Contents/lib"+ , _optInAppRenameLocOfLibDest = "@executable_path/../lib"+ }++-------------------------------------------------------------------------+-- Cmdline opts+-------------------------------------------------------------------------++cmdLineOpts+ = [ Option "" ["help"] (NoArg $ optImmediateCommands ^%= ([ImmediateCommand_Help] ++))+ "output this help"+ , Option "v" ["verbose"] (NoArg $ optVerbose ^= True)+ "be verbose"+ , Option "d" ["debug"] (NoArg $ (optVerbose ^= True) . (optDebug ^= True))+ "dump debug info"+ ]+
+ src/Plan.hs view
@@ -0,0 +1,91 @@+module Plan+ ( PlanCmd(..)+ , Plan+ + , planCmdExec+ + , seqToList+ )+ where++-------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------++import Data.Lens.Common+import Data.Lens.Template+-- import Data.Graph.GraphVisit+-- import qualified Data.Map as Map+import Data.Sequence+import Data.Typeable+-- import qualified Data.Set as Set++-- import Control.Monad+-- import Control.Monad.State.Strict+-- import Control.Monad.Trans+-- import Control.Monad.Error.Class++-- import qualified Control.Exception as CE++-- import System.Console.GetOpt+-- import System.Environment+-- import System.IO+-- import System.Exit+import System.FilePath+-- import System.Directory+-- import System.Cmd+-- import System.Process+-- import System.Posix.Process++import Opts+import State+import Cmds++-------------------------------------------------------------------------+-- Execution plan for modification commands+-------------------------------------------------------------------------++data PlanCmd+ = PlanCmd_CP -- ^ Copy file+ { _pcFrom :: FilePath+ , _pcTo :: FilePath+ }+ | PlanCmd_ModfRef -- ^ Modify ref in file+ { _pcInFile :: FilePath+ , _pcFrom :: FilePath+ , _pcTo :: FilePath+ }+ | PlanCmd_IntlRename -- ^ Modify internal name in file+ { _pcInFile :: FilePath+ , _pcTo :: FilePath+ }+ deriving (Show,Typeable)++makeLens ''PlanCmd++type Plan = Seq PlanCmd++-------------------------------------------------------------------------+-- Actual execution+-------------------------------------------------------------------------++planCmdExec :: PlanCmd -> StRun ()+planCmdExec pcmd = case pcmd of+ PlanCmd_CP fFr fTo -> do+ cmdMkdir "-p" $ takeDirectory fTo+ cmdCP "-f" fFr fTo+ cmdChmod "+w" fTo+ PlanCmd_ModfRef fIn fFr fTo -> do+ cmdInstallNameToolChange fIn fFr fTo+ PlanCmd_IntlRename fIn fTo -> do+ cmdInstallNameToolId fIn fTo++-------------------------------------------------------------------------+-- Seq extension+-------------------------------------------------------------------------++seqToList :: Seq a -> [a]+seqToList s = case viewl s of+ EmptyL -> []+ hd :< tl -> hd : seqToList tl+
+ src/State.hs view
@@ -0,0 +1,103 @@+module State+ ( ImmediateCommand(..)+ + , RunEnv(RunEnv)++ , State+ , initSt++ , opts+ , pid+ + , StRun+ + , srRmFilesToRm+ , srFreshTmpName+ , srRegisterFileForRm+ ) where++-------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------++import Data.Lens.Common+import Data.Lens.Template+import Data.Lens.Strict+import Data.Typeable+-- import qualified Data.Set as Set++import Control.Monad.State.Strict+import qualified Control.Exception as CE++import System.Posix.Types+import System.FilePath+import System.IO+-- import System.IO.Temp+import System.Directory++import Opts++-------------------------------------------------------------------------+-- Bits of info about runtime env+-------------------------------------------------------------------------++data RunEnv = RunEnv+ { _tmpdir :: FilePath+ , _pid :: ProcessID+ , _argFile :: FilePath+ }+ deriving Typeable++makeLens ''RunEnv++-------------------------------------------------------------------------+-- State+-------------------------------------------------------------------------++data St = St+ { _opts :: Opts+ , _runEnv :: RunEnv+ , _uniq :: Int+ , _filesToRm :: [FilePath]+ }+ deriving Typeable++initSt :: Opts -> RunEnv -> St+initSt o e = St o e 0 []++makeLens ''St++-------------------------------------------------------------------------+-- St monad+-------------------------------------------------------------------------++type StRun a = StateT St IO a++-- | get a fresh uniq nr+srFreshUniq :: StRun Int+srFreshUniq = do+ i <- access uniq+ uniq += 1+ return i++-- | register file for removal+srRegisterFileForRm :: FilePath -> StRun [FilePath]+srRegisterFileForRm f = filesToRm %= ([f]++)++-- | remove all files registered for removal+srRmFilesToRm :: StRun ()+srRmFilesToRm = do+ files <- filesToRm %%= (\f -> (f,[]))+ liftIO $ forM_ files rm+ where rm f = CE.catch (removeFile f)+ (\(e :: CE.SomeException) -> hPutStrLn stderr (show f ++ ": " ++ show e))++-- | get a fresh tmp filename+srFreshTmpName :: StRun FilePath+srFreshTmpName = do+ i <- srFreshUniq+ e <- access runEnv+ let t = e ^. tmpdir </> takeBaseName (e ^. argFile) ++ "-" ++ show (e ^. pid) ++ "-" ++ show i+ srRegisterFileForRm t+ return t+