diff options
author | Joey Hess <joey@kitenet.net> | 2013-12-24 13:13:17 -0400 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2013-12-24 13:13:17 -0400 |
commit | 1a73f7a85de82a93e1f2f82466d2426c1a14e508 (patch) | |
tree | 8b05ee69b35abc008181f5613769b74dd1622487 | |
parent | c885a444dbacb7fa35fc3de3429e3ca1c35d1de7 (diff) |
convert hacky shell linux mklibs code to haskell ; fixing symlink bug
The shell code was nasty, and buggy. New haskell code is much nicer,
and it's easy to do complicated calculations to properly convert possibly
absolute symlinks between libraries into relative links using it.
-rw-r--r-- | Build/LinuxMkLibs.hs | 128 | ||||
-rw-r--r-- | Makefile | 50 | ||||
-rw-r--r-- | Utility/Directory.hs | 2 |
3 files changed, 138 insertions, 42 deletions
diff --git a/Build/LinuxMkLibs.hs b/Build/LinuxMkLibs.hs new file mode 100644 index 000000000..77e75f62a --- /dev/null +++ b/Build/LinuxMkLibs.hs @@ -0,0 +1,128 @@ +{- Linux library copier and binary shimmer + - + - Copyright 2013 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Main where + +import Control.Applicative +import System.Environment +import Data.Maybe +import System.FilePath +import System.Directory +import Control.Monad +import Data.List +import Data.List.Utils +import System.Posix.Files +import Data.Char + +import Utility.PartialPrelude +import Utility.Directory +import Utility.Process +import Utility.Monad +import Utility.Path +import Utility.FileMode +import Utility.CopyFile + +main :: IO () +main = getArgs >>= go + where + go [] = error "specify LINUXSTANDALONE_DIST" + go (top:_) = mklibs top + +mklibs :: FilePath -> IO () +mklibs top = do + fs <- dirContentsRecursive top + exes <- filterM checkExe fs + libs <- parseLdd <$> readProcess "ldd" exes + glibclibs <- glibcLibs + let libs' = nub $ libs ++ glibclibs + libdirs <- nub . catMaybes <$> mapM (installLib top) libs' + writeFile (top </> "libdirs") (unlines libdirs) + writeFile (top </> "linker") + (Prelude.head $ filter ("ld-linux" `isInfixOf`) libs') + writeFile (top </> "gconvdir") + (Prelude.head $ filter ("/gconv/" `isInfixOf`) glibclibs) + + mapM_ (installLinkerShim top) exes + +installLib :: FilePath -> FilePath -> IO (Maybe FilePath) +installLib top lib = ifM (doesFileExist lib) + ( do + installFile top lib + s <- getSymbolicLinkStatus lib + when (isSymbolicLink s) $ do + l <- readSymbolicLink (inTop top lib) + let absl = absPathFrom (parentDir lib) l + let target = relPathDirToFile (parentDir lib) absl + installFile top absl + nukeFile (top ++ lib) + createSymbolicLink target (inTop top lib) + return $ Just $ parentDir lib + , return Nothing + ) + +{- Installs a linker shim script around a binary. + - + - Note that each binary is put into its own separate directory, + - to avoid eg git looking for binaries in its directory rather + - than in PATH.-} +installLinkerShim :: FilePath -> FilePath -> IO () +installLinkerShim top exe = do + createDirectoryIfMissing True shimdir + renameFile exe exedest + writeFile exe $ unlines + [ "#!/bin/sh" + , "exec \"$GIT_ANNEX_LINKER\" --library-path \"$GIT_ANNEX_LD_LIBRARY_PATH\" \"$GIT_ANNEX_SHIMMED/" ++ base ++ "/" ++ base ++ "\" \"$@\"" + ] + modifyFileMode exe $ addModes executeModes + where + base = takeFileName exe + shimdir = top </> "shimmed" </> base + exedest = shimdir </> base + +installFile :: FilePath -> FilePath -> IO () +installFile top f = do + createDirectoryIfMissing True destdir + void $ copyFileExternal f destdir + where + -- Note: This is an absolute, not a relative, directory. + dir = parentDir f + destdir = inTop top dir + +-- Note that f is not relative, so cannot use </> +inTop :: FilePath -> FilePath -> FilePath +inTop top f = top ++ f -- + +checkExe :: FilePath -> IO Bool +checkExe f + | ".so" `isSuffixOf` f = return False + | otherwise = ifM (isExecutable . fileMode <$> getFileStatus f) + ( checkFileExe <$> readProcess "file" [f] + , return False + ) + +{- Check that file(1) thinks it's a Linux ELF executable, or possibly + - a shared library (a few executables like ssh appear as shared libraries). -} +checkFileExe :: String -> Bool +checkFileExe s = and + [ "ELF" `isInfixOf` s + , "executable" `isInfixOf` s || "shared object" `isInfixOf` s + ] + +{- Parse ldd output, getting all the libraries that the input files + - link to. Note that some of the libraries may not exist + - (eg, linux-vdso.so) -} +parseLdd :: String -> [FilePath] +parseLdd = catMaybes . map (getlib . dropWhile isSpace) . lines + where + getlib l = headMaybe . words =<< lastMaybe (split " => " l) + +{- Get all glibc libs and other support files, including gconv files + - + - XXX Debian specific. -} +glibcLibs :: IO [FilePath] +glibcLibs = lines <$> readProcess "sh" + ["-c", "dpkg -L libc6 | egrep '\\.so|gconv'"] @@ -79,19 +79,21 @@ clean: rm -rf tmp dist git-annex $(mans) configure *.tix .hpc \ doc/.ikiwiki html dist tags Build/SysConfig.hs build-stamp \ Setup Build/InstallDesktopFile Build/EvilSplicer \ - Build/Standalone Build/OSXMkLibs Build/DistributionUpdate \ + Build/Standalone Build/OSXMkLibs Build/LinuxMkLibs Build/DistributionUpdate \ git-union-merge find . -name \*.o -exec rm {} \; find . -name \*.hi -exec rm {} \; Build/InstallDesktopFile: Build/InstallDesktopFile.hs - $(GHC) --make $@ + $(GHC) --make $@ -Wall Build/EvilSplicer: Build/EvilSplicer.hs - $(GHC) --make $@ + $(GHC) --make $@ -Wall Build/Standalone: Build/Standalone.hs Build/SysConfig.hs - $(GHC) --make $@ + $(GHC) --make $@ -Wall Build/OSXMkLibs: Build/OSXMkLibs.hs - $(GHC) --make $@ + $(GHC) --make $@ -Wall +Build/LinuxMkLibs: Build/LinuxMkLibs.hs + $(GHC) --make $@ -Wall sdist: clean $(mans) ./Build/make-sdist.sh @@ -103,7 +105,7 @@ hackage: sdist LINUXSTANDALONE_DEST=tmp/git-annex.linux linuxstandalone: $(MAKE) git-annex linuxstandalone-nobuild -linuxstandalone-nobuild: Build/Standalone +linuxstandalone-nobuild: Build/Standalone Build/LinuxMkLibs rm -rf "$(LINUXSTANDALONE_DEST)" mkdir -p tmp cp -R standalone/linux "$(LINUXSTANDALONE_DEST)" @@ -121,42 +123,8 @@ linuxstandalone-nobuild: Build/Standalone (cd "$(shell git --exec-path)" && tar c .) | (cd "$(LINUXSTANDALONE_DEST)"/git-core && tar x) install -d "$(LINUXSTANDALONE_DEST)/templates" - touch "$(LINUXSTANDALONE_DEST)/libdirs.tmp" - for lib in $$(ldd "$(LINUXSTANDALONE_DEST)"/bin/* $$(find "$(LINUXSTANDALONE_DEST)"/git-core/ -type f) | grep -v "not a dynamic executable" | egrep '^ ' | sed 's/^\t//' | sed 's/.*=> //' | cut -d ' ' -f 1 | sort | uniq); do \ - dir=$$(dirname "$$lib"); \ - install -d "$(LINUXSTANDALONE_DEST)/$$dir"; \ - echo "$$dir" >> "$(LINUXSTANDALONE_DEST)/libdirs.tmp"; \ - cp "$$lib" "$(LINUXSTANDALONE_DEST)/$$dir"; \ - if [ -L "$lib" ]; then \ - link=$$(readlink -f "$$lib"); \ - cp "$$link" "$(LINUXSTANDALONE_DEST)/$$(dirname "$$link")"; \ - fi; \ - done - sort "$(LINUXSTANDALONE_DEST)/libdirs.tmp" | uniq > "$(LINUXSTANDALONE_DEST)/libdirs" - rm -f "$(LINUXSTANDALONE_DEST)/libdirs.tmp" - - # Ensure bundle includes all glibc libs, and other support - # files it loads. - # XXX Debian specific. - cd $(LINUXSTANDALONE_DEST) && dpkg -L libc6 | egrep '\.so|gconv'|tar c --files-from=- | tar x - - find $(LINUXSTANDALONE_DEST) -type d -name gconv | head -n 1 | sed 's!$(LINUXSTANDALONE_DEST)/*!!' > $(LINUXSTANDALONE_DEST)/gconvdir - find $(LINUXSTANDALONE_DEST) | grep ld-linux | head -n 1 | sed 's!$(LINUXSTANDALONE_DEST)/*!!' > $(LINUXSTANDALONE_DEST)/linker + ./Build/LinuxMkLibs "$(LINUXSTANDALONE_DEST)" - # Install linker shim for each binary. Note that each binary is put - # in its own separate directory, to avoid eg git looking for - # binaries in its directory rather than in PATH. - for file in $$(find "$(LINUXSTANDALONE_DEST)" -type f | grep -v \.so); do \ - if file "$$file" | grep ELF | egrep -q 'executable|shared object' && test -x "$$file"; then \ - base=$$(basename "$$file"); \ - mkdir -p "$(LINUXSTANDALONE_DEST)/shimmed/$$base"; \ - mv "$$file" "$(LINUXSTANDALONE_DEST)/shimmed/$$base/"; \ - echo "#!/bin/sh" > "$$file"; \ - echo "exec \"\$$GIT_ANNEX_LINKER\" --library-path \"\$$GIT_ANNEX_LD_LIBRARY_PATH\" \"\$$GIT_ANNEX_SHIMMED/$$base/$$base\" \"\$$@\"" >> "$$file"; \ - chmod +x "$$file"; \ - fi; \ - done - $(MAKE) install-mans DESTDIR="$(LINUXSTANDALONE_DEST)" cd tmp/git-annex.linux && find . -type f > git-annex.MANIFEST diff --git a/Utility/Directory.hs b/Utility/Directory.hs index 2e3508e8d..6caee7efa 100644 --- a/Utility/Directory.hs +++ b/Utility/Directory.hs @@ -37,7 +37,7 @@ dirContents d = map (d </>) . filter (not . dirCruft) <$> getDirectoryContents d {- Gets files in a directory, and then its subdirectories, recursively, - and lazily. - - - Follows symlinks to other subdirectories. + - Does not follow symlinks to other subdirectories. - - When the directory does not exist, no exception is thrown, - instead, [] is returned. -} |