diff --git a/Cabal/src/Distribution/Simple/Bench.hs b/Cabal/src/Distribution/Simple/Bench.hs index c3b3802876fc27ca9b285a0f048569784114e4db..78f169f255afaabbcd873295bc4f7f5988c59eea 100644 --- a/Cabal/src/Distribution/Simple/Bench.hs +++ b/Cabal/src/Distribution/Simple/Bench.hs @@ -22,7 +22,6 @@ module Distribution.Simple.Bench import Distribution.Compat.Prelude import Prelude () -import Distribution.Compat.Environment import qualified Distribution.PackageDescription as PD import Distribution.Pretty import Distribution.Simple.Build (addInternalBuildTools) @@ -89,15 +88,12 @@ bench args pkg_descr lbi flags = do dieWithException verbosity $ NoBenchMarkProgram cmd - existingEnv <- getEnvironment - -- Compute the appropriate environment for running the benchmark let progDb = LBI.withPrograms lbiForBench pathVar = progSearchPath progDb envOverrides = progOverrideEnv progDb newPath <- programSearchPathAsPATHVar pathVar - overrideEnv <- fromMaybe [] <$> getEffectiveEnvironment ([("PATH", Just newPath)] ++ envOverrides) - let shellEnv = overrideEnv ++ existingEnv + shellEnv <- getFullEnvironment ([("PATH", Just newPath)] ++ envOverrides) -- Add (DY)LD_LIBRARY_PATH if needed shellEnv' <- diff --git a/Cabal/src/Distribution/Simple/Program/Run.hs b/Cabal/src/Distribution/Simple/Program/Run.hs index 0a9ddb0e7b5d07fae7d731155fb4ee79f2a9cc29..a0e0346cb2c3b4aaac31ba5530c303ba5f2816ff 100644 --- a/Cabal/src/Distribution/Simple/Program/Run.hs +++ b/Cabal/src/Distribution/Simple/Program/Run.hs @@ -29,6 +29,7 @@ module Distribution.Simple.Program.Run , getProgramInvocationOutputAndErrors , getProgramInvocationLBSAndErrors , getEffectiveEnvironment + , getFullEnvironment ) where import Distribution.Compat.Prelude @@ -237,6 +238,12 @@ getProgramInvocationIODataAndErrors -- | Return the current environment extended with the given overrides. -- If an entry is specified twice in @overrides@, the second entry takes -- precedence. +-- +-- getEffectiveEnvironment returns 'Nothing' when there are no overrides. +-- It returns an argument that is suitable to pass directly to 'CreateProcess' to +-- override the environment. +-- If you need the full environment to manipulate further, even when there are no overrides, +-- then call 'getFullEnvironment'. getEffectiveEnvironment :: [(String, Maybe String)] -> IO (Maybe [(String, String)]) @@ -248,6 +255,17 @@ getEffectiveEnvironment overrides = update (var, Nothing) = Map.delete var update (var, Just val) = Map.insert var val +-- | Like 'getEffectiveEnvironment', but when no overrides are specified, +-- returns the full environment instead of 'Nothing'. +getFullEnvironment + :: [(String, Maybe String)] + -> IO [(String, String)] +getFullEnvironment overrides = do + menv <- getEffectiveEnvironment overrides + case menv of + Just env -> return env + Nothing -> getEnvironment + -- | Like the unix xargs program. Useful for when we've got very long command -- lines that might overflow an OS limit on command line length and so you -- need to invoke a command multiple times to get all the args in. diff --git a/Cabal/src/Distribution/Simple/Test/ExeV10.hs b/Cabal/src/Distribution/Simple/Test/ExeV10.hs index 8e20efffe51b480b29d41d5f7fe5e1b3006d13c0..3ad112af2bbde0fdc5326b4f2a49f372c1a27262 100644 --- a/Cabal/src/Distribution/Simple/Test/ExeV10.hs +++ b/Cabal/src/Distribution/Simple/Test/ExeV10.hs @@ -8,7 +8,6 @@ module Distribution.Simple.Test.ExeV10 import Distribution.Compat.Prelude import Prelude () -import Distribution.Compat.Environment import qualified Distribution.PackageDescription as PD import Distribution.Simple.BuildPaths import Distribution.Simple.Compiler @@ -64,8 +63,6 @@ runTest pkg_descr lbi clbi hpcMarkupInfo flags suite = do way = guessWay lbi tixDir_ = i $ tixDir distPref way - existingEnv <- getEnvironment - let cmd = i (LBI.buildDir lbi) </> testName' @@ -92,13 +89,18 @@ runTest pkg_descr lbi clbi hpcMarkupInfo flags suite = do pathVar = progSearchPath progDb envOverrides = progOverrideEnv progDb newPath <- programSearchPathAsPATHVar pathVar - overrideEnv <- fromMaybe [] <$> getEffectiveEnvironment ([("PATH", Just newPath)] ++ envOverrides) let opts = map (testOption pkg_descr lbi suite) (testOptions flags) tixFile = packageRoot (testCommonFlags flags) </> getSymbolicPath (tixFilePath distPref way (testName')) - shellEnv = [("HPCTIXFILE", tixFile) | isCoverageEnabled] ++ overrideEnv ++ existingEnv + + shellEnv <- + getFullEnvironment + ( [("PATH", Just newPath)] + ++ [("HPCTIXFILE", Just tixFile) | isCoverageEnabled] + ++ envOverrides + ) -- Add (DY)LD_LIBRARY_PATH if needed shellEnv' <- diff --git a/Cabal/src/Distribution/Simple/Test/LibV09.hs b/Cabal/src/Distribution/Simple/Test/LibV09.hs index 3807cc9ae9d40100bb798ad136583477bb2338b2..1d2de6f8a415a662257c3221e7c2b10f9f4e980e 100644 --- a/Cabal/src/Distribution/Simple/Test/LibV09.hs +++ b/Cabal/src/Distribution/Simple/Test/LibV09.hs @@ -17,7 +17,6 @@ import Distribution.Compat.Prelude import Distribution.Types.UnqualComponentName import Prelude () -import Distribution.Compat.Environment import Distribution.Compat.Internal.TempFile import Distribution.Compat.Process (proc) import Distribution.ModuleName @@ -70,7 +69,6 @@ runTest pkg_descr lbi clbi hpcMarkupInfo flags suite = do way = guessWay lbi let mbWorkDir = LBI.mbWorkDirLBI lbi - existingEnv <- getEnvironment let cmd = interpretSymbolicPath mbWorkDir (LBI.buildDir lbi) @@ -100,15 +98,17 @@ runTest pkg_descr lbi clbi hpcMarkupInfo flags suite = do pathVar = progSearchPath progDb envOverrides = progOverrideEnv progDb newPath <- programSearchPathAsPATHVar pathVar - overrideEnv <- fromMaybe [] <$> getEffectiveEnvironment ([("PATH", Just newPath)] ++ envOverrides) -- Run test executable let opts = map (testOption pkg_descr lbi suite) $ testOptions flags tixFile = i $ tixFilePath distPref way testName' - shellEnv = - [("HPCTIXFILE", tixFile) | isCoverageEnabled] - ++ overrideEnv - ++ existingEnv + + shellEnv <- + getFullEnvironment + ( [("PATH", Just newPath)] + ++ [("HPCTIXFILE", Just tixFile) | isCoverageEnabled] + ++ envOverrides + ) -- Add (DY)LD_LIBRARY_PATH if needed shellEnv' <- if LBI.withDynExe lbi diff --git a/cabal-install/src/Distribution/Client/Run.hs b/cabal-install/src/Distribution/Client/Run.hs index 7ff4c8bb5e84e66c267021a1f0fa76013527e1c5..88671a9f53ccd0a1cf6c613eb6a7e0931d91e2a0 100644 --- a/cabal-install/src/Distribution/Client/Run.hs +++ b/cabal-install/src/Distribution/Client/Run.hs @@ -59,7 +59,6 @@ import Distribution.Types.UnqualComponentName import qualified Distribution.Simple.GHCJS as GHCJS import Distribution.Client.Errors -import Distribution.Compat.Environment (getEnvironment) import Distribution.Utils.Path -- | Return the executable to run and any extra arguments that should be @@ -178,13 +177,11 @@ run verbosity lbi exe exeArgs = do return (p, []) -- Compute the appropriate environment for running the executable - existingEnv <- getEnvironment let progDb = withPrograms lbiForExe pathVar = progSearchPath progDb envOverrides = progOverrideEnv progDb newPath <- programSearchPathAsPATHVar pathVar - overrideEnv <- fromMaybe [] <$> getEffectiveEnvironment ([("PATH", Just newPath)] ++ envOverrides) - let env = overrideEnv ++ existingEnv + env <- getFullEnvironment ([("PATH", Just newPath)] ++ envOverrides) -- Add (DY)LD_LIBRARY_PATH if needed env' <- diff --git a/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.out b/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.out new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.project b/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.project new file mode 100644 index 0000000000000000000000000000000000000000..58d710ed2dd372f739b85af661956ea6702b2959 --- /dev/null +++ b/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.project @@ -0,0 +1 @@ +packages: p diff --git a/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.test.hs b/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.test.hs new file mode 100644 index 0000000000000000000000000000000000000000..f02f935010238577d9e057be98a2c4abe4b34223 --- /dev/null +++ b/cabal-testsuite/PackageTests/DuplicateEnvVars/cabal.test.hs @@ -0,0 +1,5 @@ +import Test.Cabal.Prelude + +main = cabalTest $ recordMode DoNotRecord $ do + res <- cabal' "test" ["all"] + assertOutputContains "No duplicate environment variables found" res diff --git a/cabal-testsuite/PackageTests/DuplicateEnvVars/p/Main.hs b/cabal-testsuite/PackageTests/DuplicateEnvVars/p/Main.hs new file mode 100644 index 0000000000000000000000000000000000000000..42bf24965539f11b6b7a45cc82d7956fc83ab77a --- /dev/null +++ b/cabal-testsuite/PackageTests/DuplicateEnvVars/p/Main.hs @@ -0,0 +1,16 @@ +module Main where + +import Data.List (group, sort) +import System.Environment (getEnvironment) + +main = do + env <- getEnvironment + let sortedEnv = sort env + duplicates = filter (\g -> length g > 1) $ group $ map fst sortedEnv + + if null duplicates + then putStrLn "No duplicate environment variables found." + else do + putStrLn "Found duplicate environment variables:" + mapM_ (\d -> putStrLn $ " - " ++ head d) duplicates + fail "Test failed due to duplicate environment variables" diff --git a/cabal-testsuite/PackageTests/DuplicateEnvVars/p/p.cabal b/cabal-testsuite/PackageTests/DuplicateEnvVars/p/p.cabal new file mode 100644 index 0000000000000000000000000000000000000000..23bc3d599027336608677b5468b2a32532e155e4 --- /dev/null +++ b/cabal-testsuite/PackageTests/DuplicateEnvVars/p/p.cabal @@ -0,0 +1,10 @@ +cabal-version: 3.0 +name: p +version: 0.1.0.0 +build-type: Simple + +test-suite env-test + default-language: Haskell2010 + type: exitcode-stdio-1.0 + main-is: Main.hs + build-depends: base diff --git a/changelog.d/pr-10827.md b/changelog.d/pr-10827.md new file mode 100644 index 0000000000000000000000000000000000000000..9fb5b7b832883b4c4887bf129d419fc628f78044 --- /dev/null +++ b/changelog.d/pr-10827.md @@ -0,0 +1,11 @@ +--- +synopsis: "Fix duplicate environment variables in test and benchmark runs" +packages: [Cabal, cabal-install] +prs: 10827 +issues: 10718 +--- + +Cabal no longer creates duplicate environment variables when running test +suites, benchmarks, or internal executables. Previously, when setting up the +environment for these processes, Cabal would append the overridden environment +to the existing environment, creating duplicates of the same variable.