Introduce `stack run` command line option #3952
Conversation
Does |
@AshleyYakeley No, just like with |
OK, so this does not implement #233, which is for a |
@AshleyYakeley good catch, the original issue was with a build before execution. I went off @johnynek suggestion with running the first available executable without building like I'll be implementing that after my exams are over. |
OK, so will it build the entire project, or just enough to build the executable in question? |
I'm tending towards only building the package which contains the executable. Given that the dependencies are properly defined, it shouldn't be required to build all packages, at least that's my line of thinking. Thoughts? |
I think that's sensible. |
I have edited my pull request to reflect these changes. |
Thanks for the PR! I've left some comments below, let me know if you have any questions. |
getRunCmd args = do | ||
pkgComponents <- liftM (map lpvComponents . Map.elems . lpProject) getLocalPackages | ||
let executables = filter isCExe $ concatMap Set.toList pkgComponents | ||
let argExe = if not (null args) then find (\x -> x == (CExe $ T.pack $ head args)) executables |
snoyberg
Jun 15, 2018
Contributor
I realize that the not (null args)
ensures that head args
is total. However, I still prefer to avoid partial function calls, since they can easily turn into bugs with a refactor. Would it be possible to replace this with a pattern match, e.g.:
case args of
[] -> Nothing
firstArg:_ -> ...
I realize that the not (null args)
ensures that head args
is total. However, I still prefer to avoid partial function calls, since they can easily turn into bugs with a refactor. Would it be possible to replace this with a pattern match, e.g.:
case args of
[] -> Nothing
firstArg:_ -> ...
let executables = filter isCExe $ concatMap Set.toList pkgComponents | ||
let argExe = if not (null args) then find (\x -> x == (CExe $ T.pack $ head args)) executables | ||
else Nothing | ||
let firstExe = if not (null executables) then Just $ head executables else Nothing |
snoyberg
Jun 15, 2018
Contributor
Same applies here, though I think you can also use listToMaybe
.
Same applies here, though I think you can also use listToMaybe
.
let argExe = if not (null args) then find (\x -> x == (CExe $ T.pack $ head args)) executables | ||
else Nothing | ||
let firstExe = if not (null executables) then Just $ head executables else Nothing | ||
let (exe, args') = if isJust argExe then (argExe, tail args) else (firstExe, args) |
snoyberg
Jun 15, 2018
Contributor
And this could be combined with the pattern match in the above let
binding to avoid the partial tail
call.
And this could be combined with the pattern match in the above let
binding to avoid the partial tail
call.
let (exe, args') = if isJust argExe then (argExe, tail args) else (firstExe, args) | ||
case exe of | ||
Just (CExe exe') -> do | ||
Stack.Build.build (const (return ())) Nothing defaultBuildOptsCLI{boptsCLITargets = [T.concat [T.pack ":", exe']]} |
snoyberg
Jun 15, 2018
Contributor
Slightly more idiomatic may be: T.cons ':' exe'
.
Slightly more idiomatic may be: T.cons ':' exe'
.
Stack.Build.build (const (return ())) Nothing defaultBuildOptsCLI{boptsCLITargets = [T.concat [T.pack ":", exe']]} | ||
return (T.unpack exe', args') | ||
_ -> liftIO $ do | ||
hPutStrLn stderr "No executables found." |
snoyberg
Jun 15, 2018
Contributor
This should probably use logError
instead, which would need to be outside of liftIO
.
This should probably use logError
instead, which would need to be outside of liftIO
.
@@ -786,6 +792,7 @@ execCmd ExecOpts {..} go@GlobalOpts{..} = | |||
ExecOptsPlain -> do | |||
(cmd, args) <- case (eoCmd, eoArgs) of | |||
(ExecCmd cmd, args) -> return (cmd, args) | |||
(ExecRun, args) -> return ("", args) |
snoyberg
Jun 15, 2018
Contributor
Can you explain why we need an empty command case here? Is this a code path which shouldn't be triggered? If so, I would rather generate an error message here, since the exec
call below will fail anyway.
Can you explain why we need an empty command case here? Is this a code path which shouldn't be triggered? If so, I would rather generate an error message here, since the exec
call below will fail anyway.
Hello @snoyberg, thank you for your code review, it has been very insightful. I have updated my pull request according to your suggestions. I have also added docker support, as it was the code path ( I have re-tested everything and also tested it with docker enabled. |
LGTM! |
|
…askelly-dev#84) `stack run` is now a built-in command (see commercialhaskell/stack#3952)
This pull request introduces
stack run
which allows directly building and running a specified executable similar tocabal run
. If no executable is specified, it defaults to the first available executable in the project. This implements the whishlist issue: #233I tested it by running the newly built stack through stack:
stack exec stack run
stack exec -- stack run help
stack exec -- stack run -- help
I also tested it with a simple project which prints out the command line arguments:
stack run
->[]
stack run "Hello, world!"
->["Hello world"]
stack run "Hello" "World"
->["Hello", "World"]
stack run -- "Hello" "World"
->["Hello", "World"]
stack run hello-exe "Hello World"
->["Hello world"]
stack run hello-exe "Hello" "World"
->["Hello", "World"]
stack run -- hello-exe "Hello" "World"
->["Hello", "World"]
After removing all executables from the project:
stack run
->No executables found.
Any thoughts on the code? improvements? I'm pretty new to Haskell and stack.