2 {-# LANGUAGE DeriveGeneric #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE RecordWildCards #-}
6 -----------------------------------------------------------------------------
8 -----------------------------------------------------------------------------
11 -- Module : Distribution.Client.InstallSymlink
12 -- Copyright : (c) Duncan Coutts 2008
15 -- Maintainer : cabal-devel@haskell.org
16 -- Stability : provisional
17 -- Portability : portable
19 -- Managing installing binaries with symlinks.
20 module Distribution
.Client
.InstallSymlink
29 import Distribution
.Client
.Compat
.Prelude
hiding (ioError)
32 import Distribution
.Client
.InstallPlan
(InstallPlan
)
33 import qualified Distribution
.Client
.InstallPlan
as InstallPlan
34 import Distribution
.Client
.Setup
35 ( InstallFlags
(installSymlinkBinDir
)
37 import Distribution
.Client
.Types
39 , ConfiguredPackage
(..)
42 import Distribution
.Solver
.Types
.OptionalStanza
43 import Distribution
.Solver
.Types
.SourcePackage
45 import Distribution
.Compiler
48 import Distribution
.Package
54 import Distribution
.PackageDescription
57 import qualified Distribution
.PackageDescription
as PackageDescription
58 import Distribution
.PackageDescription
.Configuration
61 import Distribution
.Simple
.Compiler
66 import qualified Distribution
.Simple
.InstallDirs
as InstallDirs
67 import Distribution
.Simple
.Setup
73 import Distribution
.Simple
.Utils
(info
, withTempDirectory
)
74 import Distribution
.System
77 import Distribution
.Types
.DependencySatisfaction
78 ( DependencySatisfaction
(..)
80 import Distribution
.Types
.UnqualComponentName
82 import System
.Directory
84 , getTemporaryDirectory
87 import System
.FilePath
95 import Control
.Exception
98 import System
.IO.Error
100 , isDoesNotExistError
103 import Distribution
.Client
.Compat
.Directory
(createFileLink
, getSymbolicLinkTarget
, pathIsSymbolicLink
)
104 import Distribution
.Client
.Init
.Prompt
(promptYesNo
)
105 import Distribution
.Client
.Init
.Types
(DefaultPrompt
(MandatoryPrompt
), runPromptIO
)
106 import Distribution
.Client
.Types
.OverwritePolicy
108 import qualified Data
.ByteString
as BS
109 import qualified Data
.ByteString
.Char8
as BS8
111 -- | We would like by default to install binaries into some location that is on
112 -- the user's PATH. For per-user installations on Unix systems that basically
113 -- means the @~/bin/@ directory. On the majority of platforms the @~/bin/@
114 -- directory will be on the user's PATH. However some people are a bit nervous
115 -- about letting a package manager install programs into @~/bin/@.
117 -- A compromise solution is that instead of installing binaries directly into
118 -- @~/bin/@, we could install them in a private location under @~/.cabal/bin@
119 -- and then create symlinks in @~/bin/@. We can be careful when setting up the
120 -- symlinks that we do not overwrite any binary that the user installed. We can
121 -- check if it was a symlink we made because it would point to the private dir
122 -- where we install our binaries. This means we can install normally without
123 -- worrying and in a later phase set up symlinks, and if that fails then we
124 -- report it to the user, but even in this case the package is still in an OK
127 -- This is an optional feature that users can choose to use or not. It is
128 -- controlled from the config file. Of course it only works on POSIX systems
129 -- with symlinks so is not available to Windows users.
138 -> IO [(PackageIdentifier
, UnqualComponentName
, FilePath)]
147 case flagToMaybe
(installSymlinkBinDir installFlags
) of
150 |
null exes
-> return []
152 publicBinDir
<- canonicalizePath symlinkBinDir
153 -- TODO: do we want to do this here? :
154 -- createDirectoryIfMissing True publicBinDir
158 privateBinDir
<- pkgBinDir pkg ipid
165 (prettyShow publicExeName
)
175 , privateBinDir
</> privateExeName
178 |
(rpkg
, pkg
, exe
) <- exes
179 , let pkgid
= packageId pkg
180 -- This is a bit dodgy; probably won't work for Backpack packages
181 ipid
= installedUnitId rpkg
182 publicExeName
= PackageDescription
.exeName exe
183 privateExeName
= prefix
++ unUnqualComponentName publicExeName
++ suffix
184 prefix
= substTemplate pkgid ipid prefixTemplate
185 suffix
= substTemplate pkgid ipid suffixTemplate
190 | InstallPlan
.Configured cpkg
<- InstallPlan
.toList plan
191 , case InstallPlan
.lookupBuildOutcome cpkg buildOutcomes
of
192 Just
(Right _success
) -> True
194 , let pkg
:: PackageDescription
195 pkg
= pkgDescription cpkg
196 , exe
<- PackageDescription
.executables pkg
197 , PackageDescription
.buildable
(PackageDescription
.buildInfo exe
)
203 (SourcePackage _ gpd _ _
)
210 (enableStanzas stanzas
)
216 Left _
-> error "finalizePD ReadyPackage failed"
217 Right
(desc
, _
) -> desc
219 -- This is sadly rather complicated. We're kind of re-doing part of the
220 -- configuration for the package. :-(
221 pkgBinDir
:: PackageDescription
-> UnitId
-> IO FilePath
222 pkgBinDir pkg ipid
= do
224 InstallDirs
.defaultInstallDirs
226 (fromFlag
(configUserInstall configFlags
))
227 (PackageDescription
.hasLibs pkg
)
229 InstallDirs
.combineInstallDirs
232 (configInstallDirs configFlags
)
234 InstallDirs
.absoluteInstallDirs
238 InstallDirs
.NoCopyDest
241 canonicalizePath
(InstallDirs
.bindir absoluteDirs
)
243 substTemplate pkgid ipid
=
244 InstallDirs
.fromPathTemplate
245 . InstallDirs
.substPathTemplate env
248 InstallDirs
.initialPathTemplateEnv
254 fromFlagTemplate
= fromFlagOrDefault
(InstallDirs
.toPathTemplate
"")
255 prefixTemplate
= fromFlagTemplate
(configProgPrefix configFlags
)
256 suffixTemplate
= fromFlagTemplate
(configProgSuffix configFlags
)
257 cinfo
= compilerInfo comp
258 (CompilerId compilerFlavor _
) = compilerInfoId cinfo
260 -- | A record needed to either check if a symlink is possible or to create a
261 -- symlink. Also used if copying instead of symlinking.
262 data Symlink
= Symlink
263 { overwritePolicy
:: OverwritePolicy
264 -- ^ Whether to force overwrite an existing file.
265 , publicBindir
:: FilePath
266 -- ^ The canonical path of the public bin dir eg @/home/user/bin@.
267 , privateBindir
:: FilePath
268 -- ^ The canonical path of the private bin dir eg @/home/user/.cabal/bin@.
269 , publicName
:: FilePath
270 -- ^ The name of the executable to go in the public bin dir, eg @foo@.
271 , privateName
:: String
272 -- ^ The name of the executable to in the private bin dir, eg @foo-1.0@.
275 -- | After checking if a target is writeable given the overwrite policy,
276 -- dispatch to an appropriate action;
277 -- * @onMissing@ if the target doesn't exist
278 -- * @onOverwrite@ if the target exists and we are allowed to overwrite it
279 -- * @onNever@ if the target exists and we are never allowed to overwrite it
280 -- * @onPrompt@ if the target exists and we are allowed to overwrite after prompting
285 -- ^ Overwrite action
292 onSymlinkBinary onMissing onOverwrite onNever onPrompt Symlink
{..} = do
295 (publicBindir
</> publicName
)
296 (privateBindir
</> privateName
)
298 NotExists
-> onMissing
299 OkToOverwrite
-> onOverwrite
301 case overwritePolicy
of
302 NeverOverwrite
-> onNever
303 AlwaysOverwrite
-> onOverwrite
304 PromptOverwrite
-> onPrompt
306 -- | Can we symlink a binary?
308 -- @True@ if creating the symlink would be succeed, being optimistic that the user will
309 -- agree if prompted to overwrite.
310 symlinkableBinary
:: Symlink
-> IO Bool
311 symlinkableBinary
= onSymlinkBinary
(return True) (return True) (return False) (return True)
315 -- The paths are take in pieces, so we can make relative link when possible.
316 -- @True@ if creating the symlink was successful. @False@ if there was another
317 -- file there already that we did not own. Other errors like permission errors
318 -- just propagate as exceptions.
319 symlinkBinary
:: Symlink
-> IO Bool
320 symlinkBinary inputs
@Symlink
{publicBindir
, privateBindir
, publicName
, privateName
} = do
321 onSymlinkBinary mkLink overwrite
(return False) maybeOverwrite inputs
323 relativeBindir
= makeRelative
(normalise publicBindir
) privateBindir
326 mkLink
= True <$ createFileLink
(relativeBindir
</> privateName
) (publicBindir
</> publicName
)
329 rmLink
= True <$ removeFile (publicBindir
</> publicName
)
332 overwrite
= rmLink
*> mkLink
334 maybeOverwrite
:: IO Bool
337 "Existing file found while installing symlink. Do you want to overwrite that file? (y/n)"
340 promptRun
:: String -> IO Bool -> IO Bool
342 a
<- runPromptIO
$ promptYesNo s MandatoryPrompt
343 if a
then m
else pure a
345 -- | Check a file path of a symlink that we would like to create to see if it
346 -- is OK. For it to be OK to overwrite it must either not already exist yet or
347 -- be a symlink to our target (in which case we can assume ownership).
350 -- ^ The file path of the symlink to the private
351 -- binary that we would like to create
353 -- ^ The canonical path of the private binary.
354 -- Use 'canonicalizePath' to make this.
356 targetOkToOverwrite symlink target
= handleNotExist
$ do
357 isLink
<- pathIsSymbolicLink symlink
359 then return NotOurFile
361 target
' <- canonicalizePath
=<< getSymbolicLinkTarget symlink
362 -- This partially relies on canonicalizePath handling symlinks
364 then return OkToOverwrite
365 else return NotOurFile
367 handleNotExist action
= catchIO action
$ \ioexception
->
368 -- If the target doesn't exist then there's no problem overwriting it!
369 if isDoesNotExistError ioexception
370 then return NotExists
371 else ioError ioexception
374 = -- | The file doesn't exist so we can make a symlink.
376 |
-- | A symlink already exists, though it is ours. We'll
377 -- have to delete it first before we make a new symlink.
379 |
-- | A file already exists and it is not one of our existing
380 -- symlinks (either because it is not a symlink or because
381 -- it points somewhere other than our managed space).
385 -- | Take two canonical paths and produce a relative path to get from the first
386 -- to the second, even if it means adding @..@ path components.
387 makeRelative
:: FilePath -> FilePath -> FilePath
389 assert
(isAbsolute a
&& isAbsolute b
) $
392 commonLen
= length $ takeWhile id $ zipWith (==) as bs
394 [".." | _
<- drop commonLen
as]
397 -- | Try to make a symlink in a temporary directory.
399 -- If this works, we can try to symlink: even on Windows.
400 trySymlink
:: Verbosity
-> IO Bool
401 trySymlink verbosity
= do
402 tmp
<- getTemporaryDirectory
403 withTempDirectory verbosity tmp
"cabal-symlink-test" $ \tmpDirPath
-> do
404 let from
= tmpDirPath
</> "file.txt"
405 let to
= tmpDirPath
</> "file2.txt"
408 BS
.writeFile from
(BS8
.pack
"TEST")
410 -- create a symbolic link
411 let create
:: IO Bool
413 createFileLink from to
414 info verbosity
$ "Symlinking seems to work"
417 create `catchIO`
\exc
-> do
418 info verbosity
$ "Symlinking doesn't seem to be working: " ++ show exc