Skip to content

Commit f6565d2

Browse files
authored
Merge pull request #9063 from yvan-sraka/fix-7825
[cabal-7825] Implement external command system
2 parents afe3b6a + b27fd21 commit f6565d2

File tree

7 files changed

+51
-13
lines changed

7 files changed

+51
-13
lines changed

Cabal/src/Distribution/Make.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ defaultMainArgs :: [String] -> IO ()
8888
defaultMainArgs = defaultMainHelper
8989

9090
defaultMainHelper :: [String] -> IO ()
91-
defaultMainHelper args =
92-
case commandsRun (globalCommand commands) commands args of
91+
defaultMainHelper args = do
92+
command <- commandsRun (globalCommand commands) commands args
93+
case command of
94+
CommandDelegate -> pure ()
9395
CommandHelp help -> printHelp help
9496
CommandList opts -> printOptionsList opts
9597
CommandErrors errs -> printErrors errs
@@ -98,6 +100,7 @@ defaultMainHelper args =
98100
_
99101
| fromFlag (globalVersion flags) -> printVersion
100102
| fromFlag (globalNumericVersion flags) -> printNumericVersion
103+
CommandDelegate -> pure ()
101104
CommandHelp help -> printHelp help
102105
CommandList opts -> printOptionsList opts
103106
CommandErrors errs -> printErrors errs

Cabal/src/Distribution/Simple.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ defaultMainWithHooksNoReadArgs hooks pkg_descr =
168168
defaultMainHelper :: UserHooks -> Args -> IO ()
169169
defaultMainHelper hooks args = topHandler $ do
170170
args' <- expandResponse args
171-
case commandsRun (globalCommand commands) commands args' of
171+
command <- commandsRun (globalCommand commands) commands args'
172+
case command of
173+
CommandDelegate -> pure ()
172174
CommandHelp help -> printHelp help
173175
CommandList opts -> printOptionsList opts
174176
CommandErrors errs -> printErrors errs
@@ -177,6 +179,7 @@ defaultMainHelper hooks args = topHandler $ do
177179
_
178180
| fromFlag (globalVersion flags) -> printVersion
179181
| fromFlag (globalNumericVersion flags) -> printNumericVersion
182+
CommandDelegate -> pure ()
180183
CommandHelp help -> printHelp help
181184
CommandList opts -> printOptionsList opts
182185
CommandErrors errs -> printErrors errs

Cabal/src/Distribution/Simple/Command.hs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,15 @@ module Distribution.Simple.Command
8585
import Distribution.Compat.Prelude hiding (get)
8686
import Prelude ()
8787

88+
import Control.Exception (try)
8889
import qualified Data.Array as Array
8990
import qualified Data.List as List
9091
import Distribution.Compat.Lens (ALens', (#~), (^#))
9192
import qualified Distribution.GetOpt as GetOpt
9293
import Distribution.ReadE
9394
import Distribution.Simple.Utils
95+
import System.Directory (findExecutable)
96+
import System.Process (callProcess)
9497

9598
data CommandUI flags = CommandUI
9699
{ commandName :: String
@@ -596,11 +599,13 @@ data CommandParse flags
596599
| CommandList [String]
597600
| CommandErrors [String]
598601
| CommandReadyToGo flags
602+
| CommandDelegate
599603
instance Functor CommandParse where
600604
fmap _ (CommandHelp help) = CommandHelp help
601605
fmap _ (CommandList opts) = CommandList opts
602606
fmap _ (CommandErrors errs) = CommandErrors errs
603607
fmap f (CommandReadyToGo flags) = CommandReadyToGo (f flags)
608+
fmap _ CommandDelegate = CommandDelegate
604609

605610
data CommandType = NormalCommand | HiddenCommand
606611
data Command action
@@ -631,25 +636,38 @@ commandsRun
631636
:: CommandUI a
632637
-> [Command action]
633638
-> [String]
634-
-> CommandParse (a, CommandParse action)
639+
-> IO (CommandParse (a, CommandParse action))
635640
commandsRun globalCommand commands args =
636641
case commandParseArgs globalCommand True args of
637-
CommandHelp help -> CommandHelp help
638-
CommandList opts -> CommandList (opts ++ commandNames)
639-
CommandErrors errs -> CommandErrors errs
642+
CommandDelegate -> pure CommandDelegate
643+
CommandHelp help -> pure $ CommandHelp help
644+
CommandList opts -> pure $ CommandList (opts ++ commandNames)
645+
CommandErrors errs -> pure $ CommandErrors errs
640646
CommandReadyToGo (mkflags, args') -> case args' of
641-
("help" : cmdArgs) -> handleHelpCommand cmdArgs
647+
("help" : cmdArgs) -> pure $ handleHelpCommand cmdArgs
642648
(name : cmdArgs) -> case lookupCommand name of
643649
[Command _ _ action _] ->
644-
CommandReadyToGo (flags, action cmdArgs)
645-
_ -> CommandReadyToGo (flags, badCommand name)
646-
[] -> CommandReadyToGo (flags, noCommand)
650+
pure $ CommandReadyToGo (flags, action cmdArgs)
651+
_ -> do
652+
mCommand <- findExecutable $ "cabal-" <> name
653+
case mCommand of
654+
Just exec -> callExternal flags exec cmdArgs
655+
Nothing -> pure $ CommandReadyToGo (flags, badCommand name)
656+
[] -> pure $ CommandReadyToGo (flags, noCommand)
647657
where
648658
flags = mkflags (commandDefaultFlags globalCommand)
649659
where
650660
lookupCommand cname =
651661
[ cmd | cmd@(Command cname' _ _ _) <- commands', cname' == cname
652662
]
663+
664+
callExternal :: a -> String -> [String] -> IO (CommandParse (a, CommandParse action))
665+
callExternal flags exec cmdArgs = do
666+
result <- try $ callProcess exec cmdArgs
667+
case result of
668+
Left ex -> pure $ CommandErrors ["Error executing external command: " ++ show (ex :: SomeException)]
669+
Right _ -> pure $ CommandReadyToGo (flags, CommandDelegate)
670+
653671
noCommand = CommandErrors ["no command given (try --help)\n"]
654672

655673
-- Print suggested command if edit distance is < 5
@@ -679,6 +697,7 @@ commandsRun globalCommand commands args =
679697
-- furthermore, support "prog help command" as "prog command --help"
680698
handleHelpCommand cmdArgs =
681699
case commandParseArgs helpCommandUI True cmdArgs of
700+
CommandDelegate -> CommandDelegate
682701
CommandHelp help -> CommandHelp help
683702
CommandList list -> CommandList (list ++ commandNames)
684703
CommandErrors _ -> CommandHelp globalHelp

cabal-install/src/Distribution/Client/Main.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,10 @@ warnIfAssertionsAreEnabled =
322322
-- into IO actions for execution.
323323
mainWorker :: [String] -> IO ()
324324
mainWorker args = do
325-
topHandler $
326-
case commandsRun (globalCommand commands) commands args of
325+
topHandler $ do
326+
command <- commandsRun (globalCommand commands) commands args
327+
case command of
328+
CommandDelegate -> pure ()
327329
CommandHelp help -> printGlobalHelp help
328330
CommandList opts -> printOptionsList opts
329331
CommandErrors errs -> printErrors errs
@@ -334,6 +336,7 @@ mainWorker args = do
334336
printVersion
335337
| fromFlagOrDefault False (globalNumericVersion globalFlags) ->
336338
printNumericVersion
339+
CommandDelegate -> pure ()
337340
CommandHelp help -> printCommandHelp help
338341
CommandList opts -> printOptionsList opts
339342
CommandErrors errs -> do

cabal-install/src/Distribution/Client/SavedFlags.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ readCommandFlags :: FilePath -> CommandUI flags -> IO flags
5151
readCommandFlags path command = do
5252
savedArgs <- fmap (fromMaybe []) (readSavedArgs path)
5353
case (commandParseArgs command True savedArgs) of
54+
CommandDelegate -> error "CommandDelegate Flags evaluated, this should never occur"
5455
CommandHelp _ -> throwIO (SavedArgsErrorHelp savedArgs)
5556
CommandList _ -> throwIO (SavedArgsErrorList savedArgs)
5657
CommandErrors errs -> throwIO (SavedArgsErrorOther savedArgs errs)

doc/external-commands.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
External Commands
2+
=================
3+
4+
Cabal provides a system for external commands, akin to the ones used by tools like ``git`` or ``cargo``.
5+
6+
If you execute ``cabal my-custom-command``, Cabal will search the path for an executable named ``cabal-my-custom-command`` and execute it, passing any remaining arguments to this external command. An error will be thrown in case the custom command is not found.
7+
8+
For ideas or existing external commands, visit `this Discourse thread <https://discourse.haskell.org/t/an-external-command-system-for-cabal-what-would-you-do-with-it/7114>`_.

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ Welcome to the Cabal User Guide
1818
buildinfo-fields-reference
1919
bugs-and-stability
2020
nix-integration
21+
external-commands

0 commit comments

Comments
 (0)