-
Notifications
You must be signed in to change notification settings - Fork 11
Hiroto/update system #231
Hiroto/update system #231
Changes from 11 commits
4137d33
701aff6
7b9e70b
ddb7517
a108ac4
cacb20b
38b7541
a0b03ed
a130606
7d815eb
2324ade
65881ad
14f635d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
dist/* | ||
dist-newstyle/* | ||
.ghc.environment.* | ||
stack.yaml.lock | ||
|
||
# From daedalus-bridge | ||
node_modules/* | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
{-| Update module | ||
-} | ||
|
||
{-# LANGUAGE CPP #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
|
||
module Cardano.Shell.Update.Lib | ||
( UpdaterData(..) | ||
, UpdateError(..) | ||
, RunCmdFunc | ||
, updaterData | ||
, runUpdater | ||
, runUpdater' | ||
) where | ||
|
||
import Cardano.Prelude | ||
|
||
import qualified Data.Text as T | ||
import Distribution.System (OS (..), buildOS) | ||
import Prelude (String) | ||
import System.Directory (doesFileExist, removeFile) | ||
import System.Environment (getExecutablePath) | ||
import System.Process (proc, waitForProcess, withCreateProcess) | ||
#ifdef mingw32_HOST_OS | ||
import System.Win32.Process (getCurrentProcessId) | ||
#endif | ||
|
||
-- | Updater path, args, windows runner path, archive path | ||
data UpdaterData = UpdaterData | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you can have |
||
{ udPath :: !FilePath | ||
, udArgs :: ![Text] | ||
, udWindowsPath :: Maybe FilePath | ||
, udArchivePath :: Maybe FilePath | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need this since you know whether it's a Windows platform or not. |
||
} | ||
|
||
-- Windows: https://github.com/input-output-hk/daedalus/blob/develop/installers/dhall/win64.dhall#L32-L35 | ||
-- MacOS: https://github.com/input-output-hk/daedalus/blob/develop/installers/dhall/macos64.dhall#L31-L34 | ||
-- Linux: https://github.com/input-output-hk/daedalus/blob/develop/installers/dhall/linux64.dhall#L29-L32 | ||
updaterData :: UpdaterData | ||
updaterData = case buildOS of | ||
Windows -> UpdaterData | ||
"Installer.exe" | ||
[] | ||
(Just "Installer.bat") | ||
Nothing | ||
OSX -> UpdaterData | ||
"/usr/bin/open" | ||
["-FW"] | ||
Nothing | ||
(Just "\\${HOME}/Library/Application Support/Daedalus/installer.pkg") | ||
_ -> UpdaterData | ||
"/bin/update-runner" | ||
[] | ||
Nothing | ||
(Just "\\${XDG_DATA_HOME}/Daedalus/installer.sh") | ||
|
||
data UpdateError | ||
= UpdateFailed Int | ||
| UpdaterDoesNotExist | ||
deriving (Eq, Show) | ||
|
||
-- | Run the update system | ||
-- | ||
-- For UNIX system: | ||
-- | ||
-- Check that @udPath@ exists, then run the command @udPath udArgs udArchivePath@ | ||
-- | ||
-- For Windows: | ||
-- | ||
-- Check that @udPath@ exists, but instead of running the command directly, you | ||
-- first have to generate a @.bat@ file which will act as a script. | ||
-- After it being generated, you run that script. | ||
runUpdater :: UpdaterData -> IO (Either UpdateError ExitCode) | ||
runUpdater = runUpdater' runCmd | ||
where | ||
runCmd :: FilePath -> [String] -> FilePath -> IO ExitCode | ||
runCmd path args archive = | ||
withCreateProcess (proc path (args <> [archive])) | ||
$ \_in _out _err ph -> waitForProcess ph | ||
|
||
type RunCmdFunc = FilePath -> [String] -> FilePath -> IO ExitCode | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please inline this in the function type. |
||
|
||
-- | @runUpdater@ but can inject any runCommand function. | ||
-- This is used for testing. | ||
runUpdater' :: RunCmdFunc -> UpdaterData -> IO (Either UpdateError ExitCode) | ||
runUpdater' runCommand ud = do | ||
let path = udPath ud | ||
let args = map toS $ udArgs ud | ||
let mWindowPath = udWindowsPath ud | ||
let archive = maybe mempty (\arch -> toS arch) (udArchivePath ud) | ||
updaterExists <- doesFileExist path | ||
if updaterExists | ||
then do | ||
exitCode <- case mWindowPath of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it should be optional. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is optional since this field is not required when you're on Mac/Linux. You can have it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You know whether you are on |
||
Nothing -> runCommand path args archive | ||
Just windowsPath -> do | ||
writeWindowsUpdaterRunner windowsPath | ||
runCommand windowsPath args archive | ||
case exitCode of | ||
ExitSuccess -> do | ||
whenJust (udArchivePath ud) $ \updateArchivePath -> do | ||
removeFile updateArchivePath | ||
return . Right $ ExitSuccess | ||
ExitFailure code -> return . Left $ UpdateFailed code | ||
else | ||
return . Left $ UpdaterDoesNotExist | ||
where | ||
whenJust :: (Monad m) => Maybe a -> (a -> m ()) -> m () | ||
whenJust (Just a) f = f a | ||
whenJust Nothing _ = return () | ||
|
||
-- | Create @.bat@ file on given @FilePath@ | ||
-- | ||
-- https://github.com/input-output-hk/cardano-sl/blob/develop/tools/src/launcher/Main.hs#L585 | ||
-- | ||
-- The installer cant write to cardano-launcher.exe while it is running | ||
-- so you must fully stop launcher before you can start the installer. | ||
-- Because of this, we need a @.bat@ file which will run the update procedure and | ||
-- re-launch the launcher. | ||
-- Only Windows has this problem. | ||
writeWindowsUpdaterRunner :: FilePath -> IO () | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment where you copied this from and why. |
||
writeWindowsUpdaterRunner runnerPath = do | ||
exePath <- getExecutablePath | ||
launcherArgs <- getArgs | ||
#ifdef mingw32_HOST_OS | ||
selfPid <- getCurrentProcessId | ||
#else | ||
let (selfPid :: Integer) = 0 -- This will never be run on non-Windows | ||
#endif | ||
writeFile (toS runnerPath) $ T.unlines | ||
[ "TaskKill /PID "<> show selfPid <>" /F" | ||
-- Run updater | ||
, "%*" | ||
-- Delete updater | ||
, "del %1" | ||
-- Run launcher again | ||
, "start \"cardano launcher\" /b " <> (quote $ toS exePath) <> " " | ||
<> (T.unwords $ map (quote . toS) launcherArgs) | ||
-- Delete the bat file | ||
, "(goto) 2>nul & del \"%~f0\"" | ||
] | ||
where | ||
quote :: Text -> Text | ||
quote str = "\"" <> str <> "\"" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,8 +3,7 @@ module Cardano.Shell.Update.Types where | |
import Cardano.Prelude | ||
|
||
import qualified Data.Map as M | ||
|
||
import Test.QuickCheck | ||
import Test.QuickCheck (Gen, choose, frequency, listOf1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, better. |
||
|
||
|
||
-- Types we need to check the behaviour. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,5 @@ extra-deps: | |
- containers-0.5.11.0 | ||
- libsystemd-journal-1.4.4 | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
nix: | ||
shell-file: stack-shell.nix |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
|
||
module UpdaterSpec where | ||
|
||
import Cardano.Prelude | ||
|
||
import Prelude (String) | ||
import Test.Hspec (Spec, describe, it) | ||
import Test.Hspec.QuickCheck (prop) | ||
import Test.QuickCheck (Arbitrary (..), elements) | ||
import Test.QuickCheck.Monadic (assert, monadicIO, run) | ||
|
||
import Cardano.Shell.Update.Lib (UpdateError (..), UpdaterData (..), | ||
runUpdater, runUpdater') | ||
|
||
updaterSpec :: Spec | ||
updaterSpec = describe "Update system" $ do | ||
it "should be successful" $ monadicIO $ do | ||
eExitCode <- run $ runUpdater testUpdaterData | ||
assert $ eExitCode == (Right ExitSuccess) | ||
|
||
it "should return error when updater is not found" $ monadicIO $ do | ||
eExitCode <- run $ runUpdater testUpdaterDataNoPath | ||
assert $ eExitCode == (Left UpdaterDoesNotExist) | ||
|
||
prop "should return expected error" $ \(exitNum :: ExitNum) -> monadicIO $ do | ||
eExitCode <- run $ runUpdater' (testRunCmd exitNum) testUpdaterData | ||
assert $ eExitCode == (Left . UpdateFailed . getExitNum $ exitNum) | ||
|
||
testUpdaterData :: UpdaterData | ||
testUpdaterData = | ||
UpdaterData | ||
"./test/test.sh" | ||
[] | ||
Nothing | ||
Nothing | ||
|
||
testUpdaterDataNoPath :: UpdaterData | ||
testUpdaterDataNoPath = | ||
UpdaterData | ||
"This path does not exist" | ||
[] | ||
Nothing | ||
Nothing | ||
|
||
testRunCmd :: ExitNum -> FilePath -> [String] -> FilePath -> IO ExitCode | ||
testRunCmd (ExitNum num) _ _ _ = return $ ExitFailure num | ||
|
||
newtype ExitNum = ExitNum { | ||
getExitNum :: Int | ||
} deriving Show | ||
|
||
-- http://tldp.org/LDP/abs/html/exitcodes.html | ||
instance Arbitrary ExitNum where | ||
arbitrary = ExitNum <$> elements [1, 2, 126, 127, 128, 130, 255] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
exit 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this.
We need a simple
FilePath
when somebody wants to call therunUpdater
- then it will try to run the installer on theFilePath
.