Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy on windows #6519

Merged
merged 2 commits into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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