2 {-# LANGUAGE DeriveGeneric #-}
3 -----------------------------------------------------------------------------
5 -- Module : Distribution.Client.InstallSymlink
6 -- Copyright : (c) Duncan Coutts 2008
9 -- Maintainer : cabal-devel@haskell.org
10 -- Stability : provisional
11 -- Portability : portable
13 -- Managing installing binaries with symlinks.
14 -----------------------------------------------------------------------------
15 module Distribution
.Client
.InstallSymlink
(
22 import Distribution
.Client
.Compat
.Prelude
hiding (ioError)
25 import Distribution
.Client
.Types
26 ( ConfiguredPackage
(..), BuildOutcomes
)
27 import Distribution
.Client
.Setup
28 ( InstallFlags
(installSymlinkBinDir
) )
29 import qualified Distribution
.Client
.InstallPlan
as InstallPlan
30 import Distribution
.Client
.InstallPlan
(InstallPlan
)
32 import Distribution
.Solver
.Types
.SourcePackage
33 import Distribution
.Solver
.Types
.OptionalStanza
35 import Distribution
.Package
36 ( PackageIdentifier
, Package
(packageId
), UnitId
, installedUnitId
)
37 import Distribution
.Types
.UnqualComponentName
38 import Distribution
.Compiler
40 import qualified Distribution
.PackageDescription
as PackageDescription
41 import Distribution
.PackageDescription
42 ( PackageDescription
)
43 import Distribution
.PackageDescription
.Configuration
45 import Distribution
.Simple
.Setup
46 ( ConfigFlags
(..), fromFlag
, fromFlagOrDefault
, flagToMaybe
)
47 import qualified Distribution
.Simple
.InstallDirs
as InstallDirs
48 import Distribution
.Simple
.Compiler
49 ( Compiler
, compilerInfo
, CompilerInfo
(..) )
50 import Distribution
.System
52 import Distribution
.Simple
.Utils
( info
, withTempDirectory
)
54 import System
.Directory
55 ( canonicalizePath
, getTemporaryDirectory
, removeFile )
56 import System
.FilePath
57 ( (</>), splitPath
, joinPath
, isAbsolute
)
59 import System
.IO.Error
60 ( isDoesNotExistError, ioError )
61 import Control
.Exception
64 import Distribution
.Client
.Compat
.Directory
( createFileLink
, getSymbolicLinkTarget
, pathIsSymbolicLink
)
65 import Distribution
.Client
.Types
.OverwritePolicy
66 import Distribution
.Client
.Init
.Types
( DefaultPrompt
(MandatoryPrompt
) )
67 import Distribution
.Client
.Init
.Prompt
( promptYesNo
)
69 import qualified Data
.ByteString
as BS
70 import qualified Data
.ByteString
.Char8
as BS8
72 -- | We would like by default to install binaries into some location that is on
73 -- the user's PATH. For per-user installations on Unix systems that basically
74 -- means the @~/bin/@ directory. On the majority of platforms the @~/bin/@
75 -- directory will be on the user's PATH. However some people are a bit nervous
76 -- about letting a package manager install programs into @~/bin/@.
78 -- A compromise solution is that instead of installing binaries directly into
79 -- @~/bin/@, we could install them in a private location under @~/.cabal/bin@
80 -- and then create symlinks in @~/bin/@. We can be careful when setting up the
81 -- symlinks that we do not overwrite any binary that the user installed. We can
82 -- check if it was a symlink we made because it would point to the private dir
83 -- where we install our binaries. This means we can install normally without
84 -- worrying and in a later phase set up symlinks, and if that fails then we
85 -- report it to the user, but even in this case the package is still in an OK
88 -- This is an optional feature that users can choose to use or not. It is
89 -- controlled from the config file. Of course it only works on POSIX systems
90 -- with symlinks so is not available to Windows users.
92 symlinkBinaries
:: Platform
-> Compiler
98 -> IO [(PackageIdentifier
, UnqualComponentName
, FilePath)]
99 symlinkBinaries platform comp overwritePolicy
100 configFlags installFlags
102 case flagToMaybe
(installSymlinkBinDir installFlags
) of
105 |
null exes
-> return []
107 publicBinDir
<- canonicalizePath symlinkBinDir
108 -- TODO: do we want to do this here? :
109 -- createDirectoryIfMissing True publicBinDir
110 fmap catMaybes $ sequenceA
111 [ do privateBinDir
<- pkgBinDir pkg ipid
114 publicBinDir privateBinDir
115 (prettyShow publicExeName
) privateExeName
118 else return (Just
(pkgid
, publicExeName
,
119 privateBinDir
</> privateExeName
))
120 |
(rpkg
, pkg
, exe
) <- exes
121 , let pkgid
= packageId pkg
122 -- This is a bit dodgy; probably won't work for Backpack packages
123 ipid
= installedUnitId rpkg
124 publicExeName
= PackageDescription
.exeName exe
125 privateExeName
= prefix
++ unUnqualComponentName publicExeName
++ suffix
126 prefix
= substTemplate pkgid ipid prefixTemplate
127 suffix
= substTemplate pkgid ipid suffixTemplate
]
131 | InstallPlan
.Configured cpkg
<- InstallPlan
.toList plan
132 , case InstallPlan
.lookupBuildOutcome cpkg buildOutcomes
of
133 Just
(Right _success
) -> True
135 , let pkg
:: PackageDescription
136 pkg
= pkgDescription cpkg
137 , exe
<- PackageDescription
.executables pkg
138 , PackageDescription
.buildable
(PackageDescription
.buildInfo exe
) ]
140 pkgDescription
(ConfiguredPackage _
(SourcePackage _ gpd _ _
)
142 case finalizePD flags
(enableStanzas stanzas
)
144 platform cinfo
[] gpd
of
145 Left _
-> error "finalizePD ReadyPackage failed"
146 Right
(desc
, _
) -> desc
148 -- This is sadly rather complicated. We're kind of re-doing part of the
149 -- configuration for the package. :-(
150 pkgBinDir
:: PackageDescription
-> UnitId
-> IO FilePath
151 pkgBinDir pkg ipid
= do
152 defaultDirs
<- InstallDirs
.defaultInstallDirs
154 (fromFlag
(configUserInstall configFlags
))
155 (PackageDescription
.hasLibs pkg
)
156 let templateDirs
= InstallDirs
.combineInstallDirs fromFlagOrDefault
157 defaultDirs
(configInstallDirs configFlags
)
158 absoluteDirs
= InstallDirs
.absoluteInstallDirs
160 cinfo InstallDirs
.NoCopyDest
161 platform templateDirs
162 canonicalizePath
(InstallDirs
.bindir absoluteDirs
)
164 substTemplate pkgid ipid
= InstallDirs
.fromPathTemplate
165 . InstallDirs
.substPathTemplate env
166 where env
= InstallDirs
.initialPathTemplateEnv pkgid ipid
169 fromFlagTemplate
= fromFlagOrDefault
(InstallDirs
.toPathTemplate
"")
170 prefixTemplate
= fromFlagTemplate
(configProgPrefix configFlags
)
171 suffixTemplate
= fromFlagTemplate
(configProgSuffix configFlags
)
172 cinfo
= compilerInfo comp
173 (CompilerId compilerFlavor _
) = compilerInfoId cinfo
177 -- The paths are take in pieces, so we can make relative link when possible.
180 OverwritePolicy
-- ^ Whether to force overwrite an existing file
181 -> FilePath -- ^ The canonical path of the public bin dir eg
183 -> FilePath -- ^ The canonical path of the private bin dir eg
184 -- @/home/user/.cabal/bin@
185 -> FilePath -- ^ The name of the executable to go in the public bin
187 -> String -- ^ The name of the executable to in the private bin
189 -> IO Bool -- ^ If creating the symlink was successful. @False@ if
190 -- there was another file there already that we did
191 -- not own. Other errors like permission errors just
192 -- propagate as exceptions.
193 symlinkBinary overwritePolicy publicBindir privateBindir publicName privateName
= do
194 ok
<- targetOkToOverwrite
(publicBindir
</> publicName
)
195 (privateBindir
</> privateName
)
198 OkToOverwrite
-> overwrite
200 case overwritePolicy
of
201 NeverOverwrite
-> return False
202 AlwaysOverwrite
-> overwrite
203 PromptOverwrite
-> maybeOverwrite
205 relativeBindir
= makeRelative publicBindir privateBindir
207 mkLink
= True <$ createFileLink
(relativeBindir
</> privateName
) (publicBindir
</> publicName
)
209 rmLink
= True <$ removeFile (publicBindir
</> publicName
)
211 overwrite
= rmLink
*> mkLink
212 maybeOverwrite
:: IO Bool
215 "Existing file found while installing symlink. Do you want to overwrite that file? (y/n)"
218 promptRun
:: String -> IO Bool -> IO Bool
220 a
<- promptYesNo s MandatoryPrompt
221 if a
then m
else pure a
223 -- | Check a file path of a symlink that we would like to create to see if it
224 -- is OK. For it to be OK to overwrite it must either not already exist yet or
225 -- be a symlink to our target (in which case we can assume ownership).
227 targetOkToOverwrite
:: FilePath -- ^ The file path of the symlink to the private
228 -- binary that we would like to create
229 -> FilePath -- ^ The canonical path of the private binary.
230 -- Use 'canonicalizePath' to make this.
232 targetOkToOverwrite symlink target
= handleNotExist
$ do
233 isLink
<- pathIsSymbolicLink symlink
235 then return NotOurFile
236 else do target
' <- canonicalizePath
=<< getSymbolicLinkTarget symlink
237 -- This partially relies on canonicalizePath handling symlinks
239 then return OkToOverwrite
240 else return NotOurFile
243 handleNotExist action
= catchIO action
$ \ioexception
->
244 -- If the target doesn't exist then there's no problem overwriting it!
245 if isDoesNotExistError ioexception
246 then return NotExists
247 else ioError ioexception
250 = NotExists
-- ^ The file doesn't exist so we can make a symlink.
251 | OkToOverwrite
-- ^ A symlink already exists, though it is ours. We'll
252 -- have to delete it first before we make a new symlink.
253 | NotOurFile
-- ^ A file already exists and it is not one of our existing
254 -- symlinks (either because it is not a symlink or because
255 -- it points somewhere other than our managed space).
258 -- | Take two canonical paths and produce a relative path to get from the first
259 -- to the second, even if it means adding @..@ path components.
261 makeRelative
:: FilePath -> FilePath -> FilePath
262 makeRelative a b
= assert
(isAbsolute a
&& isAbsolute b
) $
265 commonLen
= length $ takeWhile id $ zipWith (==) as bs
266 in joinPath
$ [ ".." | _
<- drop commonLen
as ]
269 -- | Try to make a symlink in a temporary directory.
271 -- If this works, we can try to symlink: even on Windows.
273 trySymlink
:: Verbosity
-> IO Bool
274 trySymlink verbosity
= do
275 tmp
<- getTemporaryDirectory
276 withTempDirectory verbosity tmp
"cabal-symlink-test" $ \tmpDirPath
-> do
277 let from
= tmpDirPath
</> "file.txt"
278 let to
= tmpDirPath
</> "file2.txt"
281 BS
.writeFile from
(BS8
.pack
"TEST")
283 -- create a symbolic link
284 let create
:: IO Bool
286 createFileLink from to
287 info verbosity
$ "Symlinking seems to work"
290 create `catchIO`
\exc
-> do
291 info verbosity
$ "Symlinking doesn't seem to be working: " ++ show exc