From 032c802d45c7872f704de1faf0733740b256444d Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 22 Apr 2013 13:33:29 -0400 Subject: handle rpath in OSXMkLibs Now oberon has some binaries and libraries that use rpath, so I had to put in this ugly hack to replace the @rapth/lib with the lib in the app. This was particularly tricky for libraries that use @rpath because I could not find a way to extract the rpath from the library. (Only from the executable, by running it.. ugh!) The hack I put in place may fail if multiple different libraries use rpath to refer to other libraries, and the "@rpath/lib" string is the same, but actually refers to different files. --- Build/OSXMkLibs.hs | 71 ++++++++++++++++++++++++++++++++++++++--------------- Build/Standalone.hs | 13 ++++++---- 2 files changed, 59 insertions(+), 25 deletions(-) (limited to 'Build') diff --git a/Build/OSXMkLibs.hs b/Build/OSXMkLibs.hs index 5225f12bf..ed12a945f 100644 --- a/Build/OSXMkLibs.hs +++ b/Build/OSXMkLibs.hs @@ -22,6 +22,7 @@ import Utility.Process import Utility.Monad import Utility.SafeCommand import Utility.Path +import Utility.Exception import qualified Data.Map as M import qualified Data.Set as S @@ -29,16 +30,16 @@ import qualified Data.Set as S type LibMap = M.Map FilePath String {- Recursively find and install libs, until nothing new to install is found. -} -mklibs :: FilePath -> [FilePath] -> LibMap -> IO () -mklibs appbase libdirs libmap = do - (new, libmap') <- installLibs appbase libmap +mklibs :: FilePath -> [FilePath] -> [(FilePath, FilePath)] -> LibMap -> IO () +mklibs appbase libdirs replacement_libs libmap = do + (new, replacement_libs', libmap') <- installLibs appbase replacement_libs libmap unless (null new) $ - mklibs appbase (libdirs++new) libmap' + mklibs appbase (libdirs++new) replacement_libs' libmap' {- Returns directories into which new libs were installed. -} -installLibs :: FilePath -> LibMap -> IO ([FilePath], LibMap) -installLibs appbase libmap = do - (needlibs, libmap') <- otool appbase libmap +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 libs <- forM needlibs $ \lib -> do let shortlib = fromMaybe (error "internal") (M.lookup lib libmap') let fulllib = dropWhile (== '/') lib @@ -54,24 +55,54 @@ installLibs appbase libmap = do _ <- boolSystem "ln" [Param "-s", File fulllib, File symdest] return $ Just appbase ) - return (catMaybes libs, libmap') + return (catMaybes libs, replacement_libs', libmap') {- Returns libraries to install. -} -otool :: FilePath -> LibMap -> IO ([FilePath], LibMap) -otool appbase libmap = do +otool :: FilePath -> [(FilePath, FilePath)] -> LibMap -> IO ([FilePath], [(FilePath, FilePath)], LibMap) +otool appbase replacement_libs libmap = do files <- filterM doesFileExist =<< dirContentsRecursive appbase - process [] files libmap + process [] files replacement_libs libmap where want s = not ("@executable_path" `isInfixOf` s) && not (".framework" `isInfixOf` s) && not ("libSystem.B" `isInfixOf` s) - process c [] m = return (nub $ concat c, m) - process c (file:rest) m = do + process c [] rls m = return (nub $ concat c, rls, m) + process c (file:rest) rls m = do _ <- boolSystem "chmod" [Param "755", File file] libs <- filter want . parseOtool <$> readProcess "otool" ["-L", file] - m' <- install_name_tool file libs m - process (libs:c) rest m' + 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' + +{- 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 + return $ map (replace m) libs + | otherwise = return libs + where + probe c = "DYLD_PRINT_RPATHS=1 " ++ c ++ " --getting-rpath-dummy-option 2>&1 | grep RPATH" + parse s = case words s of + ("RPATH":"successful":"expansion":"of":old:"to:":new:[]) -> + Just (old, new) + _ -> Nothing + replace m l = fromMaybe l $ M.lookup l m parseOtool :: String -> [FilePath] parseOtool = catMaybes . map parse . lines @@ -82,10 +113,10 @@ parseOtool = catMaybes . map parse . lines {- Adjusts binaries to use libraries bundled with it, rather than the - system libraries. -} -install_name_tool :: FilePath -> [FilePath] -> LibMap -> IO LibMap -install_name_tool _ [] libmap = return libmap -install_name_tool binary libs libmap = do - let (libnames, libmap') = getLibNames libs libmap +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 let params = concatMap change $ zip libs libnames ok <- boolSystem "install_name_tool" $ params ++ [File binary] unless ok $ @@ -123,4 +154,4 @@ main :: IO () main = getArgs >>= go where go [] = error "specify OSXAPP_BASE" - go (appbase:_) = mklibs appbase [] M.empty + go (appbase:_) = mklibs appbase [] [] M.empty diff --git a/Build/Standalone.hs b/Build/Standalone.hs index 163e3ea79..aa4521730 100644 --- a/Build/Standalone.hs +++ b/Build/Standalone.hs @@ -60,12 +60,15 @@ progDir topdir = topdir progDir topdir = topdir "bin" #endif -installProg :: FilePath -> FilePath -> IO () +installProg :: FilePath -> FilePath -> IO (FilePath, FilePath) installProg dir prog = searchPath prog >>= go where go Nothing = error $ "cannot find " ++ prog ++ " in PATH" - go (Just f) = unlessM (boolSystem "install" [File f, File dir]) $ - error $ "install failed for " ++ prog + go (Just f) = do + let dest = dir takeFileName f + unlessM (boolSystem "install" [File f, File dest]) $ + error $ "install failed for " ++ prog + return (dest, f) main = getArgs >>= go where @@ -73,5 +76,5 @@ main = getArgs >>= go go (topdir:_) = do let dir = progDir topdir createDirectoryIfMissing True dir - forM_ thirdpartyProgs $ installProg dir - + installed <- forM thirdpartyProgs $ installProg dir + writeFile "tmp/standalone-installed" (show installed) -- cgit v1.2.3