Skip to content

Commit

Permalink
Merge pull request #6519 from phadej/copy_on_windows
Browse files Browse the repository at this point in the history
Copy on windows
  • Loading branch information
phadej authored Feb 7, 2020
2 parents 1a31242 + 2a9534a commit 11ec034
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 66 deletions.
6 changes: 6 additions & 0 deletions Cabal/Distribution/Simple/Flag.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Distribution.Simple.Flag (
toFlag,
fromFlag,
fromFlagOrDefault,
flagElim,
flagToMaybe,
flagToList,
maybeToFlag,
Expand Down Expand Up @@ -105,6 +106,11 @@ flagToMaybe :: Flag a -> Maybe a
flagToMaybe (Flag x) = Just x
flagToMaybe NoFlag = Nothing

-- | @since 3.4.0.0
flagElim :: b -> (a -> b) -> Flag a -> b
flagElim n _ NoFlag = n
flagElim _ f (Flag x) = f x

flagToList :: Flag a -> [a]
flagToList (Flag x) = [x]
flagToList NoFlag = []
Expand Down
7 changes: 4 additions & 3 deletions Cabal/doc/nix-local-build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -559,12 +559,13 @@ repository, this command will build cabal-install HEAD and symlink the

$ cabal v2-install exe:cabal

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

::

$ cabal v2-install exe:cabal --install-method=copy --installdir=~/bin
$ cabal v2-install exe:cabal --install-method=copy --installdir=$HOME/bin

Note that copied executables are not self-contained, since they might use
data-files from the store.
Expand Down
28 changes: 21 additions & 7 deletions cabal-install/Distribution/Client/CmdInstall.hs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@ import Distribution.Client.DistDirLayout
import Distribution.Client.RebuildMonad
( runRebuild )
import Distribution.Client.InstallSymlink
( OverwritePolicy(..), symlinkBinary )
( OverwritePolicy(..), symlinkBinary, trySymlink )
import Distribution.Simple.Flag
( fromFlagOrDefault, flagToMaybe, flagElim )
import Distribution.Simple.Setup
( Flag(..), HaddockFlags, TestFlags, BenchmarkFlags
, fromFlagOrDefault, flagToMaybe )
( Flag(..), HaddockFlags, TestFlags, BenchmarkFlags )
import Distribution.Solver.Types.SourcePackage
( SourcePackage(..) )
import Distribution.Simple.Command
Expand All @@ -104,7 +105,7 @@ import Distribution.Simple.GHC
, GhcEnvironmentFileEntry(..)
, renderGhcEnvironmentFile, readGhcEnvironmentFile, ParseErrorExc )
import Distribution.System
( Platform )
( Platform , buildOS, OS (Windows) )
import Distribution.Types.UnitId
( UnitId )
import Distribution.Types.UnqualComponentName
Expand Down Expand Up @@ -141,7 +142,6 @@ import System.Directory
import System.FilePath
( (</>), (<.>), takeDirectory, takeBaseName )


installCommand :: CommandUI ( ConfigFlags, ConfigExFlags, InstallFlags
, HaddockFlags, TestFlags, BenchmarkFlags
, ClientInstallFlags
Expand Down Expand Up @@ -658,6 +658,10 @@ installExes verbosity baseCtx buildCtx platform compiler
pure <$> cinstInstalldir clientInstallFlags
createDirectoryIfMissingVerbose verbosity False installdir
warnIfNoExes verbosity buildCtx

installMethod <- flagElim defaultMethod return $
cinstInstallMethod clientInstallFlags

let
doInstall = installUnitExes
verbosity
Expand All @@ -668,8 +672,18 @@ installExes verbosity baseCtx buildCtx platform compiler
where
overwritePolicy = fromFlagOrDefault NeverOverwrite $
cinstOverwritePolicy clientInstallFlags
installMethod = fromFlagOrDefault InstallMethodSymlink $
cinstInstallMethod clientInstallFlags
isWindows = buildOS == Windows

-- This is in IO as we will make environment checks,
-- to decide which method is best
defaultMethod :: IO InstallMethod
defaultMethod
-- Try symlinking in temporary directory, if it works default to
-- symlinking even on windows
| isWindows = do
symlinks <- trySymlink verbosity
return $ if symlinks then InstallMethodSymlink else InstallMethodCopy
| otherwise = return InstallMethodSymlink

-- | Install any built library by adding it to the default ghc environment
installLibraries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ clientInstallOptions _ =
"How to install the executables."
cinstInstallMethod (\v flags -> flags { cinstInstallMethod = v })
$ reqArg
"copy|symlink"
"default|copy|symlink"
readInstallMethodFlag
showInstallMethodFlag
, option [] ["installdir"]
Expand All @@ -103,6 +103,7 @@ showOverwritePolicyFlag NoFlag = []

readInstallMethodFlag :: ReadE (Flag InstallMethod)
readInstallMethodFlag = ReadE $ \case
"default" -> Right $ NoFlag
"copy" -> Right $ Flag InstallMethodCopy
"symlink" -> Right $ Flag InstallMethodSymlink
method -> Left $ "'" <> method <> "' isn't a valid install-method"
Expand Down
49 changes: 47 additions & 2 deletions cabal-install/Distribution/Client/Compat/Directory.hs
Original file line number Diff line number Diff line change
@@ -1,13 +1,58 @@
{-# LANGUAGE CPP #-}
module Distribution.Client.Compat.Directory (setModificationTime) where
module Distribution.Client.Compat.Directory (
setModificationTime,
createFileLink,
pathIsSymbolicLink,
getSymbolicLinkTarget,
) where

#if MIN_VERSION_directory(1,2,3)
import System.Directory (setModificationTime)
#else

import Data.Time.Clock (UTCTime)
#endif

#if MIN_VERSION_directory(1,3,1)
import System.Directory (createFileLink, getSymbolicLinkTarget, pathIsSymbolicLink)
#elif defined(MIN_VERSION_unix)
import System.Posix.Files (createSymbolicLink, getSymbolicLinkStatus, isSymbolicLink, readSymbolicLink)
#endif

-------------------------------------------------------------------------------
-- setModificationTime
-------------------------------------------------------------------------------

#if !MIN_VERSION_directory(1,2,3)

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

#endif

-------------------------------------------------------------------------------
-- Symlink
-------------------------------------------------------------------------------

#if MIN_VERSION_directory(1,3,1)
#elif defined(MIN_VERSION_unix)
createFileLink :: FilePath -> FilePath -> IO ()
createFileLink = createSymbolicLink

pathIsSymbolicLink :: FilePath -> IO Bool
pathIsSymbolicLink fp = do
status <- getSymbolicLinkStatus fp
return (isSymbolicLink status)

getSymbolicLinkTarget :: FilePath -> IO FilePath
getSymbolicLinkTarget = readSymbolicLink

#else
createFileLink :: FilePath -> FilePath -> IO ()
createFileLink _ _ = fail "Symlinking feature not available"

pathIsSymbolicLink :: FilePath -> IO Bool
pathIsSymbolicLink _ = fail "Symlinking feature not available"

getSymbolicLinkTarget :: FilePath -> IO FilePath
getSymbolicLinkTarget _ = fail "Symlinking feature not available"
#endif
90 changes: 39 additions & 51 deletions cabal-install/Distribution/Client/InstallSymlink.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,9 @@ module Distribution.Client.InstallSymlink (
OverwritePolicy(..),
symlinkBinaries,
symlinkBinary,
trySymlink,
) where

#ifdef mingw32_HOST_OS

import Distribution.Compat.Binary
( Binary )
import Distribution.Utils.Structured
( Structured )

import Distribution.Package (PackageIdentifier)
import Distribution.Types.UnqualComponentName
import Distribution.Client.InstallPlan (InstallPlan)
import Distribution.Client.Types (BuildOutcomes)
import Distribution.Client.Setup (InstallFlags)
import Distribution.Simple.Setup (ConfigFlags)
import Distribution.Simple.Compiler
import Distribution.System
import GHC.Generics (Generic)

data OverwritePolicy = NeverOverwrite | AlwaysOverwrite
deriving (Show, Eq, Generic, Bounded, Enum)

instance Binary OverwritePolicy
instance Structured OverwritePolicy

symlinkBinaries :: Platform -> Compiler
-> OverwritePolicy
-> ConfigFlags
-> InstallFlags
-> InstallPlan
-> BuildOutcomes
-> IO [(PackageIdentifier, UnqualComponentName, FilePath)]
symlinkBinaries _ _ _ _ _ _ _ = return []

symlinkBinary :: OverwritePolicy
-> FilePath -> FilePath -> FilePath -> String
-> IO Bool
symlinkBinary _ _ _ _ _ = fail "Symlinking feature not available on Windows"

#else

import Distribution.Compat.Binary
( Binary )
import Distribution.Utils.Structured
Expand Down Expand Up @@ -91,12 +53,11 @@ import Distribution.System
( Platform )
import Distribution.Deprecated.Text
( display )
import Distribution.Verbosity ( Verbosity )
import Distribution.Simple.Utils ( info, withTempDirectory )

import System.Posix.Files
( getSymbolicLinkStatus, isSymbolicLink, createSymbolicLink
, removeLink )
import System.Directory
( canonicalizePath )
( canonicalizePath, getTemporaryDirectory, removeFile )
import System.FilePath
( (</>), splitPath, joinPath, isAbsolute )

Expand All @@ -111,6 +72,11 @@ import Data.Maybe
import GHC.Generics
( Generic )

import Distribution.Client.Compat.Directory ( createFileLink, getSymbolicLinkTarget, pathIsSymbolicLink )

import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8

data OverwritePolicy = NeverOverwrite | AlwaysOverwrite
deriving (Show, Eq, Generic, Bounded, Enum)

Expand Down Expand Up @@ -246,9 +212,8 @@ symlinkBinary overwritePolicy publicBindir privateBindir publicName privateName
AlwaysOverwrite -> rmLink >> mkLink >> return True
where
relativeBindir = makeRelative publicBindir privateBindir
mkLink = createSymbolicLink (relativeBindir </> privateName)
(publicBindir </> publicName)
rmLink = removeLink (publicBindir </> publicName)
mkLink = createFileLink (relativeBindir </> privateName) (publicBindir </> publicName)
rmLink = removeFile (publicBindir </> publicName)

-- | Check a file path of a symlink that we would like to create to see if it
-- is OK. For it to be OK to overwrite it must either not already exist yet or
Expand All @@ -260,11 +225,11 @@ targetOkToOverwrite :: FilePath -- ^ The file path of the symlink to the private
-- Use 'canonicalizePath' to make this.
-> IO SymlinkStatus
targetOkToOverwrite symlink target = handleNotExist $ do
status <- getSymbolicLinkStatus symlink
if not (isSymbolicLink status)
isLink <- pathIsSymbolicLink symlink
if not isLink
then return NotOurFile
else do target' <- canonicalizePath symlink
-- This relies on canonicalizePath handling symlinks
else do target' <- canonicalizePath =<< getSymbolicLinkTarget symlink
-- This partially relies on canonicalizePath handling symlinks
if target == target'
then return OkToOverwrite
else return NotOurFile
Expand Down Expand Up @@ -296,4 +261,27 @@ makeRelative a b = assert (isAbsolute a && isAbsolute b) $
in joinPath $ [ ".." | _ <- drop commonLen as ]
++ drop commonLen bs

#endif
-- | Try to make a symlink in a temporary directory.
--
-- If this works, we can try to symlink: even on Windows.
--
trySymlink :: Verbosity -> IO Bool
trySymlink verbosity = do
tmp <- getTemporaryDirectory
withTempDirectory verbosity tmp "cabal-symlink-test" $ \tmpDirPath -> do
let from = tmpDirPath </> "file.txt"
let to = tmpDirPath </> "file2.txt"

-- create a file
BS.writeFile from (BS8.pack "TEST")

-- create a symbolic link
let create :: IO Bool
create = do
createFileLink from to
info verbosity $ "Symlinking seems to work"
return True

create `catchIO` \exc -> do
info verbosity $ "Symlinking doesn't seem to be working: " ++ show exc
return False
3 changes: 2 additions & 1 deletion cabal-install/cabal-install.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,8 @@ executable cabal
build-depends: resolv >= 0.1.1 && < 0.2

if os(windows)
build-depends: Win32 >= 2 && < 3
-- newer directory for symlinks
build-depends: Win32 >= 2 && < 3, directory >=1.3.1.0
else
build-depends: unix >= 2.5 && < 2.9

Expand Down
3 changes: 2 additions & 1 deletion cabal-install/cabal-install.cabal.pp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
build-depends: resolv >= 0.1.1 && < 0.2

if os(windows)
build-depends: Win32 >= 2 && < 3
-- newer directory for symlinks
build-depends: Win32 >= 2 && < 3, directory >=1.3.1.0
else
build-depends: unix >= 2.5 && < 2.9

Expand Down
1 change: 1 addition & 0 deletions cabal-install/changelog
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* Use `hackage-security-0.6`
([#6388](https://github.com/haskell/cabal/pull/6388))
* Other dependency upgrades
* On windows use copy as the default install method for executables

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

0 comments on commit 11ec034

Please sign in to comment.