2012-11-26 18:09:40 +00:00
|
|
|
{- OSX library copier
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
- Copyright 2012 Joey Hess <id@joeyh.name>
|
2012-11-26 18:09:40 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2013-04-17 15:57:46 +00:00
|
|
|
module Main where
|
|
|
|
|
2014-01-14 20:28:05 +00:00
|
|
|
import System.Environment (getArgs)
|
2012-11-26 18:09:40 +00:00
|
|
|
import Data.Maybe
|
|
|
|
import System.FilePath
|
|
|
|
import Control.Monad
|
2016-01-14 21:56:21 +00:00
|
|
|
import Control.Monad.IfElse
|
2012-11-26 18:09:40 +00:00
|
|
|
import Data.List
|
2014-01-14 20:28:05 +00:00
|
|
|
import Data.String.Utils
|
2015-12-28 17:16:46 +00:00
|
|
|
import Control.Applicative
|
|
|
|
import Prelude
|
2012-11-26 18:09:40 +00:00
|
|
|
|
|
|
|
import Utility.PartialPrelude
|
|
|
|
import Utility.Directory
|
|
|
|
import Utility.Process
|
|
|
|
import Utility.Monad
|
|
|
|
import Utility.SafeCommand
|
|
|
|
import Utility.Path
|
2013-04-22 17:33:29 +00:00
|
|
|
import Utility.Exception
|
2014-01-14 20:28:05 +00:00
|
|
|
import Utility.Env
|
2012-11-26 18:09:40 +00:00
|
|
|
|
2012-12-08 21:44:10 +00:00
|
|
|
import qualified Data.Map as M
|
|
|
|
import qualified Data.Set as S
|
|
|
|
|
|
|
|
type LibMap = M.Map FilePath String
|
|
|
|
|
2012-11-26 18:09:40 +00:00
|
|
|
{- Recursively find and install libs, until nothing new to install is found. -}
|
2013-04-22 17:33:29 +00:00
|
|
|
mklibs :: FilePath -> [FilePath] -> [(FilePath, FilePath)] -> LibMap -> IO ()
|
|
|
|
mklibs appbase libdirs replacement_libs libmap = do
|
|
|
|
(new, replacement_libs', libmap') <- installLibs appbase replacement_libs libmap
|
2012-12-09 17:02:59 +00:00
|
|
|
unless (null new) $
|
2013-04-22 17:33:29 +00:00
|
|
|
mklibs appbase (libdirs++new) replacement_libs' libmap'
|
2012-11-26 18:09:40 +00:00
|
|
|
|
|
|
|
{- Returns directories into which new libs were installed. -}
|
2013-04-22 17:33:29 +00:00
|
|
|
installLibs :: FilePath -> [(FilePath, FilePath)] -> LibMap -> IO ([FilePath], [(FilePath, FilePath)], LibMap)
|
|
|
|
installLibs appbase replacement_libs libmap = do
|
|
|
|
(needlibs, replacement_libs', libmap') <- otool appbase replacement_libs libmap
|
2012-12-08 21:44:10 +00:00
|
|
|
libs <- forM needlibs $ \lib -> do
|
2014-01-15 17:05:07 +00:00
|
|
|
pathlib <- findLibPath lib
|
2012-12-09 16:20:00 +00:00
|
|
|
let shortlib = fromMaybe (error "internal") (M.lookup lib libmap')
|
2012-12-09 17:42:30 +00:00
|
|
|
let fulllib = dropWhile (== '/') lib
|
2012-12-09 17:17:55 +00:00
|
|
|
let dest = appbase </> fulllib
|
2012-12-09 16:35:50 +00:00
|
|
|
let symdest = appbase </> shortlib
|
2016-03-07 16:55:01 +00:00
|
|
|
-- This is a hack; libraries need to be in the same
|
|
|
|
-- directory as the program, so also link them into the
|
|
|
|
-- extra directory.
|
|
|
|
let symdestextra = appbase </> "extra" </> shortlib
|
2012-11-26 18:09:40 +00:00
|
|
|
ifM (doesFileExist dest)
|
|
|
|
( return Nothing
|
|
|
|
, do
|
2015-01-09 17:11:56 +00:00
|
|
|
createDirectoryIfMissing True (parentDir dest)
|
2014-01-15 17:05:07 +00:00
|
|
|
putStrLn $ "installing " ++ pathlib ++ " as " ++ shortlib
|
2016-01-14 21:56:21 +00:00
|
|
|
unlessM (boolSystem "cp" [File pathlib, File dest]
|
|
|
|
<&&> boolSystem "chmod" [Param "644", File dest]
|
2016-03-07 16:55:01 +00:00
|
|
|
<&&> boolSystem "ln" [Param "-s", File fulllib, File symdest]
|
|
|
|
<&&> boolSystem "ln" [Param "-s", File (".." </> fulllib), File symdestextra]) $
|
2016-01-14 21:56:21 +00:00
|
|
|
error "library install failed"
|
2012-12-08 16:29:09 +00:00
|
|
|
return $ Just appbase
|
2012-11-26 18:09:40 +00:00
|
|
|
)
|
2013-04-22 17:33:29 +00:00
|
|
|
return (catMaybes libs, replacement_libs', libmap')
|
2012-11-26 18:09:40 +00:00
|
|
|
|
2014-01-14 20:28:05 +00:00
|
|
|
{- Returns libraries to install.
|
|
|
|
-
|
2014-01-15 17:05:07 +00:00
|
|
|
- Note that otool -L ignores DYLD_LIBRARY_PATH, so the
|
|
|
|
- library files returned may need to be run through findLibPath
|
|
|
|
- to find the actual libraries to install.
|
2014-01-14 20:28:05 +00:00
|
|
|
-}
|
2013-04-22 17:33:29 +00:00
|
|
|
otool :: FilePath -> [(FilePath, FilePath)] -> LibMap -> IO ([FilePath], [(FilePath, FilePath)], LibMap)
|
|
|
|
otool appbase replacement_libs libmap = do
|
2012-11-26 18:09:40 +00:00
|
|
|
files <- filterM doesFileExist =<< dirContentsRecursive appbase
|
2013-04-22 17:33:29 +00:00
|
|
|
process [] files replacement_libs libmap
|
2012-12-08 16:29:09 +00:00
|
|
|
where
|
2012-12-13 01:34:59 +00:00
|
|
|
want s = not ("@executable_path" `isInfixOf` s)
|
|
|
|
&& not (".framework" `isInfixOf` s)
|
2012-12-13 16:17:12 +00:00
|
|
|
&& not ("libSystem.B" `isInfixOf` s)
|
2013-04-22 17:33:29 +00:00
|
|
|
process c [] rls m = return (nub $ concat c, rls, m)
|
|
|
|
process c (file:rest) rls m = do
|
2012-12-09 16:15:24 +00:00
|
|
|
_ <- boolSystem "chmod" [Param "755", File file]
|
2014-01-15 17:05:07 +00:00
|
|
|
libs <- filter want . parseOtool
|
2012-12-08 21:44:10 +00:00
|
|
|
<$> readProcess "otool" ["-L", file]
|
2013-04-22 17:33:29 +00:00
|
|
|
expanded_libs <- expand_rpath libs replacement_libs file
|
|
|
|
let rls' = nub $ rls ++ (zip libs expanded_libs)
|
|
|
|
m' <- install_name_tool file libs expanded_libs m
|
|
|
|
process (expanded_libs:c) rest rls' m'
|
|
|
|
|
2014-01-14 20:28:05 +00:00
|
|
|
findLibPath :: FilePath -> IO FilePath
|
|
|
|
findLibPath l = go =<< getEnv "DYLD_LIBRARY_PATH"
|
|
|
|
where
|
|
|
|
go Nothing = return l
|
|
|
|
go (Just p) = fromMaybe l
|
|
|
|
<$> firstM doesFileExist (map (</> f) (split ":" p))
|
|
|
|
f = takeFileName l
|
|
|
|
|
2013-04-22 17:33:29 +00:00
|
|
|
{- Expands any @rpath in the list of libraries.
|
|
|
|
-
|
|
|
|
- This is done by the nasty method of running the command with a dummy
|
|
|
|
- option (so it doesn't do anything.. hopefully!) and asking the dynamic
|
|
|
|
- linker to print expanded rpaths.
|
|
|
|
-}
|
|
|
|
expand_rpath :: [String] -> [(FilePath, FilePath)] -> FilePath -> IO [String]
|
|
|
|
expand_rpath libs replacement_libs cmd
|
|
|
|
| any ("@rpath" `isInfixOf`) libs = do
|
|
|
|
installed <- M.fromList . Prelude.read
|
|
|
|
<$> readFile "tmp/standalone-installed"
|
|
|
|
let origcmd = case M.lookup cmd installed of
|
|
|
|
Nothing -> cmd
|
|
|
|
Just cmd' -> cmd'
|
|
|
|
s <- catchDefaultIO "" $ readProcess "sh" ["-c", probe origcmd]
|
|
|
|
let m = if (null s)
|
|
|
|
then M.fromList replacement_libs
|
|
|
|
else M.fromList $ mapMaybe parse $ lines s
|
2014-01-14 20:44:04 +00:00
|
|
|
return $ map (replacem m) libs
|
2013-04-22 17:33:29 +00:00
|
|
|
| otherwise = return libs
|
|
|
|
where
|
2014-10-09 18:53:13 +00:00
|
|
|
probe c = "DYLD_PRINT_RPATHS=1 " ++ c ++ " --getting-rpath-dummy-option 2>&1 | grep RPATH"
|
2013-04-22 17:33:29 +00:00
|
|
|
parse s = case words s of
|
|
|
|
("RPATH":"successful":"expansion":"of":old:"to:":new:[]) ->
|
|
|
|
Just (old, new)
|
|
|
|
_ -> Nothing
|
2014-01-14 20:44:04 +00:00
|
|
|
replacem m l = fromMaybe l $ M.lookup l m
|
2012-11-26 18:09:40 +00:00
|
|
|
|
|
|
|
parseOtool :: String -> [FilePath]
|
|
|
|
parseOtool = catMaybes . map parse . lines
|
|
|
|
where
|
|
|
|
parse l
|
|
|
|
| "\t" `isPrefixOf` l = headMaybe $ words l
|
|
|
|
| otherwise = Nothing
|
|
|
|
|
2012-12-09 17:29:30 +00:00
|
|
|
{- Adjusts binaries to use libraries bundled with it, rather than the
|
|
|
|
- system libraries. -}
|
2013-04-22 17:33:29 +00:00
|
|
|
install_name_tool :: FilePath -> [FilePath] -> [FilePath] -> LibMap -> IO LibMap
|
|
|
|
install_name_tool _ [] _ libmap = return libmap
|
|
|
|
install_name_tool binary libs expanded_libs libmap = do
|
|
|
|
let (libnames, libmap') = getLibNames expanded_libs libmap
|
2012-12-09 17:29:30 +00:00
|
|
|
let params = concatMap change $ zip libs libnames
|
|
|
|
ok <- boolSystem "install_name_tool" $ params ++ [File binary]
|
|
|
|
unless ok $
|
2012-12-09 17:38:08 +00:00
|
|
|
error $ "install_name_tool failed for " ++ binary
|
2012-12-09 17:29:30 +00:00
|
|
|
return libmap'
|
|
|
|
where
|
|
|
|
change (lib, libname) =
|
2012-12-08 15:07:59 +00:00
|
|
|
[ Param "-change"
|
|
|
|
, File lib
|
2012-12-08 21:44:10 +00:00
|
|
|
, Param $ "@executable_path/" ++ libname
|
2012-12-08 15:07:59 +00:00
|
|
|
]
|
2012-12-09 17:29:30 +00:00
|
|
|
|
|
|
|
getLibNames :: [FilePath] -> LibMap -> ([FilePath], LibMap)
|
|
|
|
getLibNames libs libmap = go [] libs libmap
|
|
|
|
where
|
|
|
|
go c [] m = (reverse c, m)
|
|
|
|
go c (l:rest) m =
|
|
|
|
let (f, m') = getLibName l m
|
|
|
|
in go (f:c) rest m'
|
2012-12-08 21:44:10 +00:00
|
|
|
|
|
|
|
{- Uses really short names for the library files it installs, because
|
|
|
|
- binaries have arbitrarily short RPATH field limits. -}
|
|
|
|
getLibName :: FilePath -> LibMap -> (FilePath, LibMap)
|
|
|
|
getLibName lib libmap = case M.lookup lib libmap of
|
|
|
|
Just n -> (n, libmap)
|
2012-12-09 16:44:02 +00:00
|
|
|
Nothing -> (nextfreename, M.insert lib nextfreename libmap)
|
2012-12-08 21:44:10 +00:00
|
|
|
where
|
2013-11-06 05:18:22 +00:00
|
|
|
names = map pure ['A' .. 'Z'] ++
|
2012-12-09 17:08:57 +00:00
|
|
|
[[n, l] | n <- ['0' .. '9'], l <- ['A' .. 'Z']]
|
2012-12-08 21:44:10 +00:00
|
|
|
used = S.fromList $ M.elems libmap
|
|
|
|
nextfreename = fromMaybe (error "ran out of short library names!") $
|
|
|
|
headMaybe $ dropWhile (`S.member` used) names
|
2012-12-08 15:07:59 +00:00
|
|
|
|
2012-11-26 18:09:40 +00:00
|
|
|
main :: IO ()
|
|
|
|
main = getArgs >>= go
|
|
|
|
where
|
|
|
|
go [] = error "specify OSXAPP_BASE"
|
2013-04-22 17:33:29 +00:00
|
|
|
go (appbase:_) = mklibs appbase [] [] M.empty
|