Skip to content

Commit 11ec034

Browse files
authored
Merge pull request #6519 from phadej/copy_on_windows
Copy on windows
2 parents 1a31242 + 2a9534a commit 11ec034

File tree

9 files changed

+124
-66
lines changed

9 files changed

+124
-66
lines changed

Cabal/Distribution/Simple/Flag.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module Distribution.Simple.Flag (
2222
toFlag,
2323
fromFlag,
2424
fromFlagOrDefault,
25+
flagElim,
2526
flagToMaybe,
2627
flagToList,
2728
maybeToFlag,
@@ -105,6 +106,11 @@ flagToMaybe :: Flag a -> Maybe a
105106
flagToMaybe (Flag x) = Just x
106107
flagToMaybe NoFlag = Nothing
107108

109+
-- | @since 3.4.0.0
110+
flagElim :: b -> (a -> b) -> Flag a -> b
111+
flagElim n _ NoFlag = n
112+
flagElim _ f (Flag x) = f x
113+
108114
flagToList :: Flag a -> [a]
109115
flagToList (Flag x) = [x]
110116
flagToList NoFlag = []

Cabal/doc/nix-local-build.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,12 +559,13 @@ repository, this command will build cabal-install HEAD and symlink the
559559

560560
$ cabal v2-install exe:cabal
561561

562-
Where symlinking is not possible (eg. on Windows), ``--install-method=copy``
563-
can be used:
562+
Where symlinking is not possible (eg. on some Windows versions) the ``copy``
563+
method is used by default. You can specify the install method
564+
by using ``--install-method`` flag:
564565

565566
::
566567

567-
$ cabal v2-install exe:cabal --install-method=copy --installdir=~/bin
568+
$ cabal v2-install exe:cabal --install-method=copy --installdir=$HOME/bin
568569

569570
Note that copied executables are not self-contained, since they might use
570571
data-files from the store.

cabal-install/Distribution/Client/CmdInstall.hs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,11 @@ import Distribution.Client.DistDirLayout
8585
import Distribution.Client.RebuildMonad
8686
( runRebuild )
8787
import Distribution.Client.InstallSymlink
88-
( OverwritePolicy(..), symlinkBinary )
88+
( OverwritePolicy(..), symlinkBinary, trySymlink )
89+
import Distribution.Simple.Flag
90+
( fromFlagOrDefault, flagToMaybe, flagElim )
8991
import Distribution.Simple.Setup
90-
( Flag(..), HaddockFlags, TestFlags, BenchmarkFlags
91-
, fromFlagOrDefault, flagToMaybe )
92+
( Flag(..), HaddockFlags, TestFlags, BenchmarkFlags )
9293
import Distribution.Solver.Types.SourcePackage
9394
( SourcePackage(..) )
9495
import Distribution.Simple.Command
@@ -104,7 +105,7 @@ import Distribution.Simple.GHC
104105
, GhcEnvironmentFileEntry(..)
105106
, renderGhcEnvironmentFile, readGhcEnvironmentFile, ParseErrorExc )
106107
import Distribution.System
107-
( Platform )
108+
( Platform , buildOS, OS (Windows) )
108109
import Distribution.Types.UnitId
109110
( UnitId )
110111
import Distribution.Types.UnqualComponentName
@@ -141,7 +142,6 @@ import System.Directory
141142
import System.FilePath
142143
( (</>), (<.>), takeDirectory, takeBaseName )
143144

144-
145145
installCommand :: CommandUI ( ConfigFlags, ConfigExFlags, InstallFlags
146146
, HaddockFlags, TestFlags, BenchmarkFlags
147147
, ClientInstallFlags
@@ -658,6 +658,10 @@ installExes verbosity baseCtx buildCtx platform compiler
658658
pure <$> cinstInstalldir clientInstallFlags
659659
createDirectoryIfMissingVerbose verbosity False installdir
660660
warnIfNoExes verbosity buildCtx
661+
662+
installMethod <- flagElim defaultMethod return $
663+
cinstInstallMethod clientInstallFlags
664+
661665
let
662666
doInstall = installUnitExes
663667
verbosity
@@ -668,8 +672,18 @@ installExes verbosity baseCtx buildCtx platform compiler
668672
where
669673
overwritePolicy = fromFlagOrDefault NeverOverwrite $
670674
cinstOverwritePolicy clientInstallFlags
671-
installMethod = fromFlagOrDefault InstallMethodSymlink $
672-
cinstInstallMethod clientInstallFlags
675+
isWindows = buildOS == Windows
676+
677+
-- This is in IO as we will make environment checks,
678+
-- to decide which method is best
679+
defaultMethod :: IO InstallMethod
680+
defaultMethod
681+
-- Try symlinking in temporary directory, if it works default to
682+
-- symlinking even on windows
683+
| isWindows = do
684+
symlinks <- trySymlink verbosity
685+
return $ if symlinks then InstallMethodSymlink else InstallMethodCopy
686+
| otherwise = return InstallMethodSymlink
673687

674688
-- | Install any built library by adding it to the default ghc environment
675689
installLibraries

cabal-install/Distribution/Client/CmdInstall/ClientInstallFlags.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ clientInstallOptions _ =
8181
"How to install the executables."
8282
cinstInstallMethod (\v flags -> flags { cinstInstallMethod = v })
8383
$ reqArg
84-
"copy|symlink"
84+
"default|copy|symlink"
8585
readInstallMethodFlag
8686
showInstallMethodFlag
8787
, option [] ["installdir"]
@@ -103,6 +103,7 @@ showOverwritePolicyFlag NoFlag = []
103103

104104
readInstallMethodFlag :: ReadE (Flag InstallMethod)
105105
readInstallMethodFlag = ReadE $ \case
106+
"default" -> Right $ NoFlag
106107
"copy" -> Right $ Flag InstallMethodCopy
107108
"symlink" -> Right $ Flag InstallMethodSymlink
108109
method -> Left $ "'" <> method <> "' isn't a valid install-method"
Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,58 @@
11
{-# LANGUAGE CPP #-}
2-
module Distribution.Client.Compat.Directory (setModificationTime) where
2+
module Distribution.Client.Compat.Directory (
3+
setModificationTime,
4+
createFileLink,
5+
pathIsSymbolicLink,
6+
getSymbolicLinkTarget,
7+
) where
38

49
#if MIN_VERSION_directory(1,2,3)
510
import System.Directory (setModificationTime)
611
#else
7-
812
import Data.Time.Clock (UTCTime)
13+
#endif
14+
15+
#if MIN_VERSION_directory(1,3,1)
16+
import System.Directory (createFileLink, getSymbolicLinkTarget, pathIsSymbolicLink)
17+
#elif defined(MIN_VERSION_unix)
18+
import System.Posix.Files (createSymbolicLink, getSymbolicLinkStatus, isSymbolicLink, readSymbolicLink)
19+
#endif
20+
21+
-------------------------------------------------------------------------------
22+
-- setModificationTime
23+
-------------------------------------------------------------------------------
24+
25+
#if !MIN_VERSION_directory(1,2,3)
926

1027
setModificationTime :: FilePath -> UTCTime -> IO ()
1128
setModificationTime _fp _t = return ()
1229

1330
#endif
31+
32+
-------------------------------------------------------------------------------
33+
-- Symlink
34+
-------------------------------------------------------------------------------
35+
36+
#if MIN_VERSION_directory(1,3,1)
37+
#elif defined(MIN_VERSION_unix)
38+
createFileLink :: FilePath -> FilePath -> IO ()
39+
createFileLink = createSymbolicLink
40+
41+
pathIsSymbolicLink :: FilePath -> IO Bool
42+
pathIsSymbolicLink fp = do
43+
status <- getSymbolicLinkStatus fp
44+
return (isSymbolicLink status)
45+
46+
getSymbolicLinkTarget :: FilePath -> IO FilePath
47+
getSymbolicLinkTarget = readSymbolicLink
48+
49+
#else
50+
createFileLink :: FilePath -> FilePath -> IO ()
51+
createFileLink _ _ = fail "Symlinking feature not available"
52+
53+
pathIsSymbolicLink :: FilePath -> IO Bool
54+
pathIsSymbolicLink _ = fail "Symlinking feature not available"
55+
56+
getSymbolicLinkTarget :: FilePath -> IO FilePath
57+
getSymbolicLinkTarget _ = fail "Symlinking feature not available"
58+
#endif

cabal-install/Distribution/Client/InstallSymlink.hs

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,9 @@ module Distribution.Client.InstallSymlink (
1616
OverwritePolicy(..),
1717
symlinkBinaries,
1818
symlinkBinary,
19+
trySymlink,
1920
) where
2021

21-
#ifdef mingw32_HOST_OS
22-
23-
import Distribution.Compat.Binary
24-
( Binary )
25-
import Distribution.Utils.Structured
26-
( Structured )
27-
28-
import Distribution.Package (PackageIdentifier)
29-
import Distribution.Types.UnqualComponentName
30-
import Distribution.Client.InstallPlan (InstallPlan)
31-
import Distribution.Client.Types (BuildOutcomes)
32-
import Distribution.Client.Setup (InstallFlags)
33-
import Distribution.Simple.Setup (ConfigFlags)
34-
import Distribution.Simple.Compiler
35-
import Distribution.System
36-
import GHC.Generics (Generic)
37-
38-
data OverwritePolicy = NeverOverwrite | AlwaysOverwrite
39-
deriving (Show, Eq, Generic, Bounded, Enum)
40-
41-
instance Binary OverwritePolicy
42-
instance Structured OverwritePolicy
43-
44-
symlinkBinaries :: Platform -> Compiler
45-
-> OverwritePolicy
46-
-> ConfigFlags
47-
-> InstallFlags
48-
-> InstallPlan
49-
-> BuildOutcomes
50-
-> IO [(PackageIdentifier, UnqualComponentName, FilePath)]
51-
symlinkBinaries _ _ _ _ _ _ _ = return []
52-
53-
symlinkBinary :: OverwritePolicy
54-
-> FilePath -> FilePath -> FilePath -> String
55-
-> IO Bool
56-
symlinkBinary _ _ _ _ _ = fail "Symlinking feature not available on Windows"
57-
58-
#else
59-
6022
import Distribution.Compat.Binary
6123
( Binary )
6224
import Distribution.Utils.Structured
@@ -91,12 +53,11 @@ import Distribution.System
9153
( Platform )
9254
import Distribution.Deprecated.Text
9355
( display )
56+
import Distribution.Verbosity ( Verbosity )
57+
import Distribution.Simple.Utils ( info, withTempDirectory )
9458

95-
import System.Posix.Files
96-
( getSymbolicLinkStatus, isSymbolicLink, createSymbolicLink
97-
, removeLink )
9859
import System.Directory
99-
( canonicalizePath )
60+
( canonicalizePath, getTemporaryDirectory, removeFile )
10061
import System.FilePath
10162
( (</>), splitPath, joinPath, isAbsolute )
10263

@@ -111,6 +72,11 @@ import Data.Maybe
11172
import GHC.Generics
11273
( Generic )
11374

75+
import Distribution.Client.Compat.Directory ( createFileLink, getSymbolicLinkTarget, pathIsSymbolicLink )
76+
77+
import qualified Data.ByteString as BS
78+
import qualified Data.ByteString.Char8 as BS8
79+
11480
data OverwritePolicy = NeverOverwrite | AlwaysOverwrite
11581
deriving (Show, Eq, Generic, Bounded, Enum)
11682

@@ -246,9 +212,8 @@ symlinkBinary overwritePolicy publicBindir privateBindir publicName privateName
246212
AlwaysOverwrite -> rmLink >> mkLink >> return True
247213
where
248214
relativeBindir = makeRelative publicBindir privateBindir
249-
mkLink = createSymbolicLink (relativeBindir </> privateName)
250-
(publicBindir </> publicName)
251-
rmLink = removeLink (publicBindir </> publicName)
215+
mkLink = createFileLink (relativeBindir </> privateName) (publicBindir </> publicName)
216+
rmLink = removeFile (publicBindir </> publicName)
252217

253218
-- | Check a file path of a symlink that we would like to create to see if it
254219
-- is OK. For it to be OK to overwrite it must either not already exist yet or
@@ -260,11 +225,11 @@ targetOkToOverwrite :: FilePath -- ^ The file path of the symlink to the private
260225
-- Use 'canonicalizePath' to make this.
261226
-> IO SymlinkStatus
262227
targetOkToOverwrite symlink target = handleNotExist $ do
263-
status <- getSymbolicLinkStatus symlink
264-
if not (isSymbolicLink status)
228+
isLink <- pathIsSymbolicLink symlink
229+
if not isLink
265230
then return NotOurFile
266-
else do target' <- canonicalizePath symlink
267-
-- This relies on canonicalizePath handling symlinks
231+
else do target' <- canonicalizePath =<< getSymbolicLinkTarget symlink
232+
-- This partially relies on canonicalizePath handling symlinks
268233
if target == target'
269234
then return OkToOverwrite
270235
else return NotOurFile
@@ -296,4 +261,27 @@ makeRelative a b = assert (isAbsolute a && isAbsolute b) $
296261
in joinPath $ [ ".." | _ <- drop commonLen as ]
297262
++ drop commonLen bs
298263

299-
#endif
264+
-- | Try to make a symlink in a temporary directory.
265+
--
266+
-- If this works, we can try to symlink: even on Windows.
267+
--
268+
trySymlink :: Verbosity -> IO Bool
269+
trySymlink verbosity = do
270+
tmp <- getTemporaryDirectory
271+
withTempDirectory verbosity tmp "cabal-symlink-test" $ \tmpDirPath -> do
272+
let from = tmpDirPath </> "file.txt"
273+
let to = tmpDirPath </> "file2.txt"
274+
275+
-- create a file
276+
BS.writeFile from (BS8.pack "TEST")
277+
278+
-- create a symbolic link
279+
let create :: IO Bool
280+
create = do
281+
createFileLink from to
282+
info verbosity $ "Symlinking seems to work"
283+
return True
284+
285+
create `catchIO` \exc -> do
286+
info verbosity $ "Symlinking doesn't seem to be working: " ++ show exc
287+
return False

cabal-install/cabal-install.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,8 @@ executable cabal
356356
build-depends: resolv >= 0.1.1 && < 0.2
357357

358358
if os(windows)
359-
build-depends: Win32 >= 2 && < 3
359+
-- newer directory for symlinks
360+
build-depends: Win32 >= 2 && < 3, directory >=1.3.1.0
360361
else
361362
build-depends: unix >= 2.5 && < 2.9
362363

cabal-install/cabal-install.cabal.pp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
build-depends: resolv >= 0.1.1 && < 0.2
5959

6060
if os(windows)
61-
build-depends: Win32 >= 2 && < 3
61+
-- newer directory for symlinks
62+
build-depends: Win32 >= 2 && < 3, directory >=1.3.1.0
6263
else
6364
build-depends: unix >= 2.5 && < 2.9
6465

cabal-install/changelog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* Use `hackage-security-0.6`
3535
([#6388](https://github.com/haskell/cabal/pull/6388))
3636
* Other dependency upgrades
37+
* On windows use copy as the default install method for executables
3738

3839
3.0.0.0 Mikhail Glushenkov <[email protected]> August 2019
3940
* `v2-haddock` fails on `haddock` failures (#5977)

0 commit comments

Comments
 (0)