PageRenderTime 1336ms CodeModel.GetById 42ms RepoModel.GetById 7ms app.codeStats 1ms

/packages/cabal-install-0.6.2/Distribution/Client/InstallSymlink.hs

https://github.com/Lainepress/hp-2009.2.0.2
Haskell | 240 lines | 159 code | 19 blank | 62 comment | 10 complexity | 1d80b340f613c24235ee66e9ecfa68a4 MD5 | raw file
  1. {-# OPTIONS -cpp #-}
  2. -- OPTIONS required for ghc-6.4.x compat, and must appear first
  3. {-# LANGUAGE CPP #-}
  4. {-# OPTIONS_GHC -cpp #-}
  5. {-# OPTIONS_NHC98 -cpp #-}
  6. {-# OPTIONS_JHC -fcpp #-}
  7. -----------------------------------------------------------------------------
  8. -- |
  9. -- Module : Distribution.Client.InstallSymlink
  10. -- Copyright : (c) Duncan Coutts 2008
  11. -- License : BSD-like
  12. --
  13. -- Maintainer : cabal-devel@haskell.org
  14. -- Stability : provisional
  15. -- Portability : portable
  16. --
  17. -- Managing installing binaries with symlinks.
  18. -----------------------------------------------------------------------------
  19. module Distribution.Client.InstallSymlink (
  20. symlinkBinaries,
  21. symlinkBinary,
  22. ) where
  23. #if mingw32_HOST_OS || mingw32_TARGET_OS
  24. import Distribution.Package (PackageIdentifier)
  25. import Distribution.Client.InstallPlan (InstallPlan)
  26. import Distribution.Client.Setup (InstallFlags)
  27. import Distribution.Simple.Setup (ConfigFlags)
  28. symlinkBinaries :: ConfigFlags
  29. -> InstallFlags
  30. -> InstallPlan
  31. -> IO [(PackageIdentifier, String, FilePath)]
  32. symlinkBinaries _ _ _ = return []
  33. symlinkBinary :: FilePath -> FilePath -> String -> String -> IO Bool
  34. symlinkBinary _ _ _ _ = fail "Symlinking feature not available on Windows"
  35. #else
  36. import Distribution.Client.Types
  37. ( AvailablePackage(..), ConfiguredPackage(..) )
  38. import Distribution.Client.Setup
  39. ( InstallFlags(installSymlinkBinDir) )
  40. import qualified Distribution.Client.InstallPlan as InstallPlan
  41. import Distribution.Client.InstallPlan (InstallPlan)
  42. import Distribution.Package
  43. ( PackageIdentifier, Package(packageId) )
  44. import Distribution.Compiler
  45. ( CompilerId(..) )
  46. import qualified Distribution.PackageDescription as PackageDescription
  47. import Distribution.PackageDescription
  48. ( PackageDescription )
  49. import Distribution.PackageDescription.Configuration
  50. ( finalizePackageDescription )
  51. import Distribution.Simple.Setup
  52. ( ConfigFlags(..), fromFlag, fromFlagOrDefault, flagToMaybe )
  53. import qualified Distribution.Simple.InstallDirs as InstallDirs
  54. import Distribution.Simple.PackageIndex (PackageIndex)
  55. import Distribution.System
  56. ( Platform(Platform) )
  57. import System.Posix.Files
  58. ( getSymbolicLinkStatus, isSymbolicLink, readSymbolicLink
  59. , createSymbolicLink, removeLink )
  60. import System.Directory
  61. ( canonicalizePath )
  62. import System.FilePath
  63. ( (</>), takeDirectory, splitPath, joinPath, isAbsolute )
  64. import System.IO.Error
  65. ( catch, isDoesNotExistError, ioError )
  66. import Control.Exception
  67. ( assert )
  68. import Data.Maybe
  69. ( catMaybes )
  70. -- | We would like by default to install binaries into some location that is on
  71. -- the user's PATH. For per-user installations on Unix systems that basically
  72. -- means the @~/bin/@ directory. On the majority of platforms the @~/bin/@
  73. -- directory will be on the user's PATH. However some people are a bit nervous
  74. -- about letting a package manager install programs into @~/bin/@.
  75. --
  76. -- A comprimise solution is that instead of installing binaries directly into
  77. -- @~/bin/@, we could install them in a private location under @~/.cabal/bin@
  78. -- and then create symlinks in @~/bin/@. We can be careful when setting up the
  79. -- symlinks that we do not overwrite any binary that the user installed. We can
  80. -- check if it was a symlink we made because it would point to the private dir
  81. -- where we install our binaries. This means we can install normally without
  82. -- worrying and in a later phase set up symlinks, and if that fails then we
  83. -- report it to the user, but even in this case the package is still in an ok
  84. -- installed state.
  85. --
  86. -- This is an optional feature that users can choose to use or not. It is
  87. -- controlled from the config file. Of course it only works on posix systems
  88. -- with symlinks so is not available to Windows users.
  89. --
  90. symlinkBinaries :: ConfigFlags
  91. -> InstallFlags
  92. -> InstallPlan
  93. -> IO [(PackageIdentifier, String, FilePath)]
  94. symlinkBinaries configFlags installFlags plan =
  95. case flagToMaybe (installSymlinkBinDir installFlags) of
  96. Nothing -> return []
  97. Just symlinkBinDir
  98. | null exes -> return []
  99. | otherwise -> do
  100. publicBinDir <- canonicalizePath symlinkBinDir
  101. -- TODO: do we want to do this here? :
  102. -- createDirectoryIfMissing True publicBinDir
  103. fmap catMaybes $ sequence
  104. [ do privateBinDir <- pkgBinDir pkg
  105. ok <- symlinkBinary
  106. publicBinDir privateBinDir
  107. publicExeName privateExeName
  108. if ok
  109. then return Nothing
  110. else return (Just (pkgid, publicExeName,
  111. privateBinDir </> privateExeName))
  112. | (pkg, exe) <- exes
  113. , let publicExeName = PackageDescription.exeName exe
  114. privateExeName = prefix ++ publicExeName ++ suffix
  115. pkgid = packageId pkg
  116. prefix = substTemplate pkgid prefixTemplate
  117. suffix = substTemplate pkgid suffixTemplate ]
  118. where
  119. exes =
  120. [ (pkg, exe)
  121. | InstallPlan.Installed cpkg _ <- InstallPlan.toList plan
  122. , let pkg = pkgDescription cpkg
  123. , exe <- PackageDescription.executables pkg
  124. , PackageDescription.buildable (PackageDescription.buildInfo exe) ]
  125. pkgDescription :: ConfiguredPackage -> PackageDescription
  126. pkgDescription (ConfiguredPackage (AvailablePackage _ pkg _) flags _) =
  127. case finalizePackageDescription flags
  128. (Nothing :: Maybe (PackageIndex PackageDescription))
  129. os arch compilerId [] pkg of
  130. Left _ -> error "finalizePackageDescription ConfiguredPackage failed"
  131. Right (desc, _) -> desc
  132. -- This is sadly rather complicated. We're kind of re-doing part of the
  133. -- configuration for the package. :-(
  134. pkgBinDir :: PackageDescription -> IO FilePath
  135. pkgBinDir pkg = do
  136. defaultDirs <- InstallDirs.defaultInstallDirs
  137. compilerFlavor
  138. (fromFlag (configUserInstall configFlags))
  139. (PackageDescription.hasLibs pkg)
  140. let templateDirs = InstallDirs.combineInstallDirs fromFlagOrDefault
  141. defaultDirs (configInstallDirs configFlags)
  142. absoluteDirs = InstallDirs.absoluteInstallDirs
  143. (packageId pkg) compilerId InstallDirs.NoCopyDest
  144. templateDirs
  145. canonicalizePath (InstallDirs.bindir absoluteDirs)
  146. substTemplate pkgid = InstallDirs.fromPathTemplate
  147. . InstallDirs.substPathTemplate env
  148. where env = InstallDirs.initialPathTemplateEnv pkgid compilerId
  149. fromFlagTemplate = fromFlagOrDefault (InstallDirs.toPathTemplate "")
  150. prefixTemplate = fromFlagTemplate (configProgPrefix configFlags)
  151. suffixTemplate = fromFlagTemplate (configProgSuffix configFlags)
  152. (Platform arch os) = InstallPlan.planPlatform plan
  153. compilerId@(CompilerId compilerFlavor _) = InstallPlan.planCompiler plan
  154. symlinkBinary :: FilePath -- ^ The canonical path of the public bin dir
  155. -- eg @/home/user/bin@
  156. -> FilePath -- ^ The canonical path of the private bin dir
  157. -- eg @/home/user/.cabal/bin@
  158. -> String -- ^ The name of the executable to go in the public
  159. -- bin dir, eg @foo@
  160. -> String -- ^ The name of the executable to in the private bin
  161. -- dir, eg @foo-1.0@
  162. -> IO Bool -- ^ If creating the symlink was sucessful. @False@
  163. -- if there was another file there already that we
  164. -- did not own. Other errors like permission errors
  165. -- just propagate as exceptions.
  166. symlinkBinary publicBindir privateBindir publicName privateName = do
  167. ok <- targetOkToOverwrite (publicBindir </> publicName) privateBindir
  168. case ok of
  169. NotOurFile -> return False
  170. NotExists -> mkLink >> return True
  171. OkToOverwrite -> rmLink >> mkLink >> return True
  172. where
  173. relativeBindir = makeRelative publicBindir privateBindir
  174. mkLink = createSymbolicLink (relativeBindir </> privateName)
  175. (publicBindir </> publicName)
  176. rmLink = removeLink (publicBindir </> publicName)
  177. -- | Check a filepath of a symlink that we would like to create to see if it
  178. -- is ok. For it to be ok to overwrite it must either not already exist yet or
  179. -- be a symlink to our private bin dir (in which case we can assume ownership).
  180. --
  181. targetOkToOverwrite :: FilePath -- ^ The filepath of the symlink to the private
  182. -- binary that we would like to create
  183. -> FilePath -- ^ The canonical path of the private bin
  184. -- directory. Use 'canonicalizePath'.
  185. -> IO SymlinkStatus
  186. targetOkToOverwrite symlink privateBinDir = handleNotExist $ do
  187. status <- getSymbolicLinkStatus symlink
  188. if not (isSymbolicLink status)
  189. then return NotOurFile
  190. else return
  191. . (\ok -> if ok then OkToOverwrite else NotOurFile)
  192. . (== privateBinDir)
  193. . takeDirectory
  194. =<< canonicalizePath
  195. . (symlink </>)
  196. =<< readSymbolicLink symlink
  197. where
  198. handleNotExist action = catch action $ \ioexception ->
  199. -- If the target doesn't exist then there's no problem overwriting it!
  200. if isDoesNotExistError ioexception
  201. then return NotExists
  202. else ioError ioexception
  203. data SymlinkStatus
  204. = NotExists -- ^ The file doesn't exist so we can make a symlink.
  205. | OkToOverwrite -- ^ A symlink already exists, though it is ours. We'll
  206. -- have to delete it first bemore we make a new symlink.
  207. | NotOurFile -- ^ A file already exists and it is not one of our existing
  208. -- symlinks (either because it is not a symlink or because
  209. -- it points somewhere other than our managed space).
  210. deriving Show
  211. -- | Take two canonical paths and produce a relative path to get from the first
  212. -- to the second, even if it means adding @..@ path components.
  213. --
  214. makeRelative :: FilePath -> FilePath -> FilePath
  215. makeRelative a b = assert (isAbsolute a && isAbsolute b) $
  216. let as = splitPath a
  217. bs = splitPath b
  218. commonLen = length $ takeWhile id $ zipWith (==) as bs
  219. in joinPath $ [ ".." | _ <- drop commonLen as ]
  220. ++ [ b' | b' <- drop commonLen bs ]
  221. #endif