From cdd2f7af9ff40b8c7c851e6adc98dd08001eded1 Mon Sep 17 00:00:00 2001 From: mangoiv Date: Wed, 1 Oct 2025 18:16:40 +0200 Subject: [PATCH] cabal-install: don't pass exe name to external commands Previously the executable name of the external command was passed to external commands as the first argument. This behaviour was adapated from cargo which does this because of reasons that are internal to rust that do not affect GHC Haskell, and are even orthogonal to patterns that see common use in Haskell. Additionally, it complicates the 'simple' case which is what we should optimize for when building such a feature. The previous use case (one executable that serves multiple external subcommands) is still possible by the following means: - using a wrapper around the executable - using a symlink and check argv[0] in the executable Resolves #10275 --- cabal-install/src/Distribution/Client/Main.hs | 11 +++++++---- .../ExternalCommandHelp/setup-test/AAAA.hs | 2 +- changelog.d/pr-11232.md | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 changelog.d/pr-11232.md diff --git a/cabal-install/src/Distribution/Client/Main.hs b/cabal-install/src/Distribution/Client/Main.hs index 01dd3edaa23..2648ea1cc66 100644 --- a/cabal-install/src/Distribution/Client/Main.hs +++ b/cabal-install/src/Distribution/Client/Main.hs @@ -372,17 +372,20 @@ mainWorker args = do -> [String] -> IO (CommandParse Action) delegateToExternal commands' name cmdArgs = do + -- we rely on cabal's implementation of findProgramOnSearchPath not following + -- symlinks here. If that ever happens, then the argv[0] of the called executable + -- will be different to the intended one and will break tools that work by reading it. mCommand <- findProgramOnSearchPath normal defaultProgramSearchPath ("cabal-" <> name) case mCommand of - Just (exec, _) -> return (CommandReadyToGo $ \_ -> callExternal exec name cmdArgs) + Just (exec, _) -> return (CommandReadyToGo $ \_ -> callExternal exec cmdArgs) Nothing -> defaultCommandFallback commands' name cmdArgs - callExternal :: String -> String -> [String] -> IO () - callExternal exec name cmdArgs = do + callExternal :: String -> [String] -> IO () + callExternal exec cmdArgs = do cur_env <- getEnvironment cabal_exe <- getExecutablePath let new_env = ("CABAL", cabal_exe) : cur_env - result <- try $ createProcess ((proc exec (name : cmdArgs)){env = Just new_env}) + result <- try $ createProcess ((proc exec cmdArgs){env = Just new_env}) case result of Left ex -> printErrors ["Error executing external command: " ++ show (ex :: SomeException)] Right (_, _, _, ph) -> waitForProcess ph >>= exitWith diff --git a/cabal-testsuite/PackageTests/ExternalCommandHelp/setup-test/AAAA.hs b/cabal-testsuite/PackageTests/ExternalCommandHelp/setup-test/AAAA.hs index dd139b905da..10fe05988d8 100644 --- a/cabal-testsuite/PackageTests/ExternalCommandHelp/setup-test/AAAA.hs +++ b/cabal-testsuite/PackageTests/ExternalCommandHelp/setup-test/AAAA.hs @@ -5,5 +5,5 @@ import System.Environment main = do args <- getArgs case args of - ["aaaa" , "--help"] -> putStrLn "I am helping with the aaaa command" + ["--help"] -> putStrLn "I am helping with the aaaa command" _ -> putStrLn "aaaa" diff --git a/changelog.d/pr-11232.md b/changelog.d/pr-11232.md new file mode 100644 index 00000000000..151f640bbcf --- /dev/null +++ b/changelog.d/pr-11232.md @@ -0,0 +1,19 @@ +--- +synopsis: don't pass exe name to external commands +packages: [cabal-install] +prs: 11232 +issues: [10275] +significance: significant +--- + +Previously the executable name of the external command was passed to external commands as the first argument. + +This behaviour was adapted from cargo which does this because of reasons that are internal to rust that do not affect GHC Haskell, and are even orthogonal to patterns that see common use in Haskell. + +Additionally, it complicates the 'simple' case which is what we should optimize for when building such a feature - with this change, for any executable `cabal-foo` in your search-path, `cabal foo` will be a valid invocation of that command. + +The previous use case (one executable that serves multiple external subcommands) is still possible by the following means: + +- using a wrapper around the executable +- using a symlink and check argv\[0\] in the executable +