Skip to content

Commit 57b109e

Browse files
committed
Fix #5968 New approach to overwriting existing Stack executable
Also, on Windows, does not offer the `sudo` command alternatives if attempting to write to the original file name of the running Stack executable results in a 'Permission' error. Also, makes related Stack messages prettier.
1 parent 1337c3f commit 57b109e

File tree

3 files changed

+112
-67
lines changed

3 files changed

+112
-67
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Behavior changes:
1616
* Removed `--ghc-paths`, `--global-stack-root` and `--local-bin-path` flags for
1717
`stack path`, deprecated in Stack 1.1.0 in favour of `--programs`,
1818
`--stack-root` and `local-bin` respectively.
19+
* On Windows, `stack upgrade` always renames the file of the running Stack
20+
executable (adding extension `.old`) before attempting to write to the
21+
original file name.
22+
* On Windows, `stack upgrade` does not offer `sudo` command alternatives if
23+
attempting to write to the original file name of the running Stack exectuable
24+
results in a 'Permission' error.
1925

2026
Other enhancements:
2127

src/Stack/Setup.hs

Lines changed: 104 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ import Network.HTTP.Simple ( getResponseHeader )
6868
import Pantry.Internal.AesonExtended
6969
( Value (..), WithJSONWarnings (..), logJSONWarnings )
7070
import Path
71-
( (</>), dirname, filename, parent, parseAbsDir, parseAbsFile
72-
, parseRelDir, parseRelFile, toFilePath
71+
( (</>), addExtension, dirname, filename, parent, parseAbsDir
72+
, parseAbsFile, parseRelDir, parseRelFile, toFilePath
7373
)
7474
import Path.CheckInstall ( warnInstallSearchPathIssues )
7575
import Path.Extended ( fileExtension )
@@ -2535,9 +2535,10 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
25352535
let loop [] = throwIO $ StackBinaryArchiveNotFound (map snd platforms0)
25362536
loop ((isWindows, p'):ps) = do
25372537
let p = T.pack p'
2538-
logInfo $
2539-
"Querying for archive location for platform: "
2540-
<> fromString p'
2538+
prettyInfoL
2539+
[ flow "Querying for archive location for platform:"
2540+
, style Current (fromString p') <> "."
2541+
]
25412542
case findArchive archiveInfo p of
25422543
Just x -> pure (isWindows, x)
25432544
Nothing -> loop ps
@@ -2553,7 +2554,10 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
25532554
, destDir </> relFileStackDotTmp
25542555
)
25552556

2556-
logInfo $ "Downloading from: " <> display archiveURL
2557+
prettyInfoL
2558+
[ flow "Downloading from:"
2559+
, style Url (fromString $ T.unpack archiveURL) <> "."
2560+
]
25572561

25582562
liftIO $
25592563
if | ".tar.gz" `T.isSuffixOf` archiveURL ->
@@ -2562,30 +2566,23 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
25622566
throwIO StackBinaryArchiveZipUnsupportedBug
25632567
| otherwise -> throwIO $ StackBinaryArchiveUnsupported archiveURL
25642568

2565-
logInfo "Download complete, testing executable"
2566-
2567-
platform <- view platformL
2569+
prettyInfoS "Download complete, testing executable."
25682570

25692571
-- We need to call getExecutablePath before we overwrite the
25702572
-- currently running binary: after that, Linux will append
25712573
-- (deleted) to the filename.
2572-
currExe <- liftIO getExecutablePath
2574+
currExe <- liftIO getExecutablePath >>= parseAbsFile
25732575

25742576
liftIO $ do
25752577
setFileExecutable (toFilePath tmpFile)
2576-
25772578
testExe tmpFile
25782579

2579-
case platform of
2580-
Platform _ Cabal.Windows | FP.equalFilePath (toFilePath destFile) currExe -> do
2581-
old <- parseAbsFile (toFilePath destFile ++ ".old")
2582-
renameFile destFile old
2583-
renameFile tmpFile destFile
2584-
_ -> renameFile tmpFile destFile
2580+
relocateStackExeFile currExe tmpFile destFile
25852581

2586-
logInfo $
2587-
"New Stack executable available at "
2588-
<> fromString (toFilePath destFile)
2582+
prettyInfoL
2583+
[ flow "New Stack executable available at:"
2584+
, pretty destFile <> "."
2585+
]
25892586

25902587
destDir' <- liftIO . D.canonicalizePath . toFilePath $ destDir
25912588
warnInstallSearchPathIssues destDir' ["stack"]
@@ -2636,60 +2633,102 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
26362633
| isWindows = "stack.exe"
26372634
| otherwise = "stack"
26382635

2636+
relocateStackExeFile ::
2637+
HasTerm env
2638+
=> Path Abs File
2639+
-- ^ Path to the currently running executable
2640+
-> Path Abs File
2641+
-- ^ Path to the executable file to be relocated
2642+
-> Path Abs File
2643+
-- ^ Path to the new location for the excutable file
2644+
-> RIO env ()
2645+
relocateStackExeFile currExeFile newExeFile destExeFile = do
2646+
when (osIsWindows && destExeFile == currExeFile) $ do
2647+
-- Windows allows a running executable's file to be renamed, but not to be
2648+
-- overwritten.
2649+
old <- addExtension ".old" currExeFile
2650+
prettyInfoL
2651+
[ flow "Renaming existing:"
2652+
, pretty currExeFile
2653+
, "as:"
2654+
, pretty old <> "."
2655+
]
2656+
renameFile currExeFile old
2657+
renameFile newExeFile destExeFile
2658+
26392659
-- | Ensure that the Stack executable download is in the same location as the
26402660
-- currently running executable. See:
26412661
-- https://github.com/commercialhaskell/stack/issues/3232
26422662
performPathChecking ::
26432663
HasConfig env
2644-
=> Path Abs File -- ^ location of the newly downloaded file
2645-
-> String -- ^ currently running executable
2664+
=> Path Abs File
2665+
-- ^ Path to the newly downloaded file
2666+
-> Path Abs File
2667+
-- ^ Path to the currently running executable
26462668
-> RIO env ()
2647-
performPathChecking newFile executablePath = do
2648-
executablePath' <- parseAbsFile executablePath
2649-
unless (toFilePath newFile == executablePath) $ do
2650-
logInfo $ "Also copying Stack executable to " <> fromString executablePath
2651-
tmpFile <- parseAbsFile $ executablePath ++ ".tmp"
2652-
eres <- tryIO $ do
2653-
liftIO $ copyFile newFile tmpFile
2654-
setFileExecutable (toFilePath tmpFile)
2655-
liftIO $ renameFile tmpFile executablePath'
2656-
logInfo "Stack executable copied successfully!"
2669+
performPathChecking newExeFile currExeFile = do
2670+
unless (newExeFile == currExeFile) $ do
2671+
prettyInfoL
2672+
[ flow "Also copying Stack executable to:"
2673+
, pretty currExeFile <> "."
2674+
]
2675+
tmpFile <- toFilePath <$> addExtension ".tmp" currExeFile
2676+
eres <- tryIO $
2677+
relocateStackExeFile currExeFile newExeFile currExeFile
26572678
case eres of
2658-
Right () -> pure ()
2679+
Right () -> prettyInfoS "Stack executable copied successfully!"
26592680
Left e
2660-
| isPermissionError e -> do
2661-
prettyWarn $
2662-
flow "Permission error when trying to copy:"
2663-
<> blankLine
2664-
<> string (displayException e)
2665-
<> blankLine
2666-
<> flow "Should I try to perform the file copy using sudo? This \
2667-
\may fail."
2668-
toSudo <- promptBool "Try using sudo? (y/n) "
2669-
when toSudo $ do
2670-
let run cmd args = do
2671-
ec <- proc cmd args runProcess
2672-
when (ec /= ExitSuccess) $
2681+
| isPermissionError e -> if osIsWindows
2682+
then do
2683+
prettyWarn $
2684+
flow "Permission error when trying to copy:"
2685+
<> blankLine
2686+
<> string (displayException e)
2687+
else do
2688+
prettyWarn $
2689+
flow "Permission error when trying to copy:"
2690+
<> blankLine
2691+
<> string (displayException e)
2692+
<> blankLine
2693+
<> fillSep
2694+
[ flow "Should I try to perform the file copy using"
2695+
, style Shell "sudo" <> "?"
2696+
, flow "This may fail."
2697+
]
2698+
toSudo <- promptBool "Try using sudo? (y/n) "
2699+
when toSudo $ do
2700+
let run cmd args = do
2701+
ec <- proc cmd args runProcess
2702+
when (ec /= ExitSuccess) $
26732703
throwIO $ ProcessExited ec cmd args
2674-
commands =
2675-
[ ("sudo",
2676-
[ "cp"
2677-
, toFilePath newFile
2678-
, toFilePath tmpFile
2679-
])
2680-
, ("sudo",
2681-
[ "mv"
2682-
, toFilePath tmpFile
2683-
, executablePath
2684-
])
2685-
]
2686-
logInfo "Going to run the following commands:"
2687-
logInfo ""
2688-
forM_ commands $ \(cmd, args) ->
2689-
logInfo $ "- " <> mconcat (intersperse " " (fromString <$> (cmd:args)))
2690-
mapM_ (uncurry run) commands
2691-
logInfo ""
2692-
logInfo "sudo file copy worked!"
2704+
commands =
2705+
[ ("sudo",
2706+
[ "cp"
2707+
, toFilePath newExeFile
2708+
, tmpFile
2709+
])
2710+
, ("sudo",
2711+
[ "mv"
2712+
, tmpFile
2713+
, toFilePath currExeFile
2714+
])
2715+
]
2716+
prettyInfo $
2717+
flow "Going to run the following commands:"
2718+
<> blankLine
2719+
<> bulletedList
2720+
( map
2721+
( \(cmd, args) ->
2722+
style Shell $ fillSep
2723+
$ fromString cmd
2724+
: map fromString args
2725+
)
2726+
commands
2727+
)
2728+
mapM_ (uncurry run) commands
2729+
prettyInfo $
2730+
line
2731+
<> flow "sudo file copy worked!"
26932732
| otherwise -> throwM e
26942733

26952734
getDownloadVersion :: StackReleaseInfo -> Maybe Version

src/Stack/Upgrade.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ upgrade builtHash (UpgradeOpts mbo mso) =
167167
prettyWarnL
168168
[ flow "Exception occurred when trying to perform binary upgrade:"
169169
, fromString . show $ e
170-
, line <> flow "Falling back to source upgrade"
170+
, line <> flow "Falling back to source upgrade."
171171
]
172172
source so
173173
where
@@ -205,7 +205,7 @@ binaryUpgrade (BinaryOpts mplatform force' mver morg mrepo) =
205205
Just downloadVersion -> do
206206
prettyInfoL
207207
[ flow "Current Stack version:"
208-
, fromString (versionString stackVersion) <> ","
208+
, fromString (versionString stackVersion) <> ";"
209209
, flow "available download version:"
210210
, fromString (versionString downloadVersion) <> "."
211211
]

0 commit comments

Comments
 (0)