diff --git a/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs b/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs index fb3b3454ee3..e8e354fda83 100644 --- a/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs +++ b/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs @@ -37,4 +37,4 @@ md5CheckGenericPackageDescription proxy = md5Check proxy md5CheckLocalBuildInfo :: Proxy LocalBuildInfo -> Assertion md5CheckLocalBuildInfo proxy = md5Check proxy - 0x3398bd7f316ecb8f535cbe78498a89a4 + 0x82d22c1d21566e21c9b384cc63bd93bf diff --git a/Cabal/src/Distribution/Simple/GHC/Build/Link.hs b/Cabal/src/Distribution/Simple/GHC/Build/Link.hs index 683c22426da..43cd8c059bf 100644 --- a/Cabal/src/Distribution/Simple/GHC/Build/Link.hs +++ b/Cabal/src/Distribution/Simple/GHC/Build/Link.hs @@ -127,11 +127,10 @@ linkOrLoadComponent [ "-static" | withFullyStaticExe lbi ] - -- Pass extra `ld-options` given - -- through to GHC's linker. + -- Pass extra `ld-options` given through to GHC's linker. ++ maybe [] - programOverrideArgs + programDriverArgs (lookupProgram ldProgram (withPrograms lbi)) , ghcOptLinkLibs = if withFullyStaticExe lbi diff --git a/Cabal/src/Distribution/Simple/GHC/Internal.hs b/Cabal/src/Distribution/Simple/GHC/Internal.hs index 6959b8f48b6..abc7c27965b 100644 --- a/Cabal/src/Distribution/Simple/GHC/Internal.hs +++ b/Cabal/src/Distribution/Simple/GHC/Internal.hs @@ -209,15 +209,61 @@ configureToolchain _implInfo ghcProg ghcInfo = | otherwise -> tokenizeQuotedWords flags configureGcc :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram - configureGcc _v gccProg = do + configureGcc v gccProg = do + gccProg' <- configureGcc' v gccProg return - gccProg + gccProg' { programDefaultArgs = - programDefaultArgs gccProg + programDefaultArgs gccProg' ++ ccFlags ++ gccLinkerFlags } + -- We probe whether the linker driver (the C compiler) supports -no-pie + -- with a test file, like GHC's build system does + -- (FP_GCC_SUPPORTS_NO_PIE). GHC passes -no-pie to the linker driver + -- when joining objects or linking, but with a custom linker (see + -- -pgml, 'ghcOptLinkerProgram') it does not know whether the flag is + -- supported and refuses to pass it, unless told otherwise with + -- -pgml-supports-no-pie (GHC #15319). Note that some compilers only + -- warn on unknown flags, hence -Werror and the output check, mirroring + -- GHC's own probe. + configureGcc' :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram + configureGcc' verbosity gccProg = do + supportsNoPie <- withTempFile ".c" $ \testcfile testchnd -> + withTempFile ".o" $ \testofile testohnd -> do + hPutStrLn testchnd "int main() { return 0; }" + hClose testchnd + hClose testohnd + runProgram + verbosity + ghcProg + [ "-hide-all-packages" + , "-c" + , testcfile + , "-o" + , testofile + ] + withTempFile "" $ \testbinfile testbinhnd -> + do + hClose testbinhnd + output <- + getProgramOutput + verbosity + gccProg + ["-no-pie", "-Werror", testofile, "-o", testbinfile] + return (not ("unrecognized" `isInfixOf` map toLower output)) + `catchIO` (\_ -> return False) + `catchExit` (\_ -> return False) + return + gccProg + { programProperties = + Map.insert + "Supports -no-pie" + (if supportsNoPie then "YES" else "NO") + (programProperties gccProg) + } + configureGpp :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram configureGpp _v gppProg = do return @@ -258,7 +304,7 @@ configureToolchain _implInfo ghcProg ghcInfo = _ <- getProgramOutput verbosity - ldProg + (suppressOverrideArgs ldProg) ["-x", "-r", testofile, "-o", testofile'] return True `catchIO` (\_ -> return False) @@ -597,6 +643,34 @@ linkGhcOptions verbosity lbi bi clbi = (mkVersion [9, 4]) (compiler lbi) (maybeToFlag $ programPath <$> lookupProgram gppProgram (withPrograms lbi)) + , -- Use -pgml to ensure GHC drives the final link with the C compiler + -- Cabal resolved (as with -pgmc above, so that cc-options and + -- ld-options are interpreted by the selected toolchain, #4435, + -- #9801). Note that GHC's linker does not follow -pgmc: without + -- -pgml the final link is driven by the C compiler from GHC's + -- settings file. + -- As with -pgmc, we can only do this on GHC >= 9.4: with a custom + -- linker GHC stops passing -no-pie + -- (https://gitlab.haskell.org/ghc/ghc/-/issues/15319), which + -- breaks linking on toolchains that default to PIE. Whether the + -- resolved C compiler supports -no-pie is probed when the + -- compiler is configured (see 'configureGcc''), and passed along + -- as -pgml-supports-no-pie by renderGhcOptions. + -- see example in cabal-testsuite/PackageTests/FFI/ForeignOptsPgml + ghcOptLinkerProgram = + ghcOptionsSince + (mkVersion [9, 4]) + (compiler lbi) + (maybeToFlag $ programPath <$> lookupProgram gccProgram (withPrograms lbi)) + , ghcOptLinkerSupportsNoPie = + ghcOptionsSince + (mkVersion [9, 4]) + (compiler lbi) + ( maybeToFlag $ do + gccProg <- lookupProgram gccProgram (withPrograms lbi) + guard (Map.lookup "Supports -no-pie" (programProperties gccProg) == Just "YES") + pure True + ) } where exe_paths = diff --git a/Cabal/src/Distribution/Simple/Program/Builtin.hs b/Cabal/src/Distribution/Simple/Program/Builtin.hs index c66d9362f0c..53ca3f203ee 100644 --- a/Cabal/src/Distribution/Simple/Program/Builtin.hs +++ b/Cabal/src/Distribution/Simple/Program/Builtin.hs @@ -317,7 +317,7 @@ ldProgram = ldHelpOutput <- getProgramInvocationOutput verbosity - (programInvocation ldProg ["--help"]) + (programInvocation (suppressOverrideArgs ldProg) ["--help"]) -- In case the linker does not support '--help'. Eg the LLVM linker, -- `lld` only accepts `-help`. `catchIO` (\_ -> return "") diff --git a/Cabal/src/Distribution/Simple/Program/Db.hs b/Cabal/src/Distribution/Simple/Program/Db.hs index 4143e32c66a..4a4b127039d 100644 --- a/Cabal/src/Distribution/Simple/Program/Db.hs +++ b/Cabal/src/Distribution/Simple/Program/Db.hs @@ -318,10 +318,31 @@ userSpecifyArgs name args' = prog { programOverrideArgs = programOverrideArgs prog + ++ interpretUserOptions name args' + , programDriverArgs = + programDriverArgs prog ++ args' } ) +-- | Interpret options written for the linker driver (the form documented +-- for @ld-options@, see Note [ld-options and the linker driver]) into the +-- options @ld@ itself accepts (#10789). +-- +-- A driver option @-Wl,a,b@ means \"pass @a@ and @b@ to the linker\", so it +-- turns into @[a, b]@. All other options are passed through unchanged. +interpretUserOptions :: String -> [String] -> [String] +interpretUserOptions "ld" = concatMap ldOptionToLdFlag + where + ldOptionToLdFlag opt + | "-Wl," `isPrefixOf` opt = splitOnCommas (drop 4 opt) + | otherwise = [opt] + + splitOnCommas s = case break (== ',') s of + (w, []) -> [w] + (w, _ : rest) -> w : splitOnCommas rest +interpretUserOptions _ = id + -- | Like 'userSpecifyPath' but for a list of progs and their paths. userSpecifyPaths :: [(String, FilePath)] @@ -439,7 +460,9 @@ configureUnconfiguredProgram verbosity prog progdb = do { programId = name , programVersion = version , programDefaultArgs = [] - , programOverrideArgs = userSpecifiedArgs prog progdb + , programOverrideArgs = + interpretUserOptions name (userSpecifiedArgs prog progdb) + , programDriverArgs = userSpecifiedArgs prog progdb , programOverrideEnv = [("PATH", Just newPath)] ++ progOverrideEnv progdb , programProperties = Map.empty , programLocation = location diff --git a/Cabal/src/Distribution/Simple/Program/GHC.hs b/Cabal/src/Distribution/Simple/Program/GHC.hs index 1b6a1857d11..b97703b4ae1 100644 --- a/Cabal/src/Distribution/Simple/Program/GHC.hs +++ b/Cabal/src/Distribution/Simple/Program/GHC.hs @@ -532,6 +532,23 @@ data GhcOptions = GhcOptions -- ^ Program to use for the C compiler; the @ghc -pgmc@ flag. , ghcOptGppProgram :: Flag FilePath -- ^ Program to use for the C++ compiler; the @ghc -pgmcxx@ flag. + , ghcOptLinkerProgram :: Flag FilePath + -- ^ Program to use as the linker; the @ghc -pgml@ flag. + -- + -- Note that GHC drives the final link with C compiler style options + -- (@-Wl,..@, @-no-pie@): the linker program is a C compiler driver, which + -- by default is the C compiler from GHC's settings. Since a custom + -- linker's support for @-no-pie@ is unknown to GHC, it stops passing the + -- flag altogether (see @-pgml-supports-no-pie@ and GHC issue #15319); + -- renderGhcOptions therefore passes @-pgml-supports-no-pie@ along with + -- this option depending on 'ghcOptLinkerSupportsNoPie', as the linker + -- Cabal selects is the probed C compiler. + , ghcOptLinkerSupportsNoPie :: Flag Bool + -- ^ Whether the linker program (see 'ghcOptLinkerProgram') supports the + -- @-no-pie@ flag; the @ghc -pgml-supports-no-pie@ flag. GHC refuses to + -- pass @-no-pie@ to a custom linker (see GHC issue #15319) unless this + -- flag is given, so Cabal probes the resolved C compiler when it is + -- configured. , ---------------------------- -- Language and extensions @@ -889,6 +906,14 @@ renderGhcOptions comp _platform@(Platform _arch os) opts , ["-opta" ++ opt | opt <- ghcOptAsmOptions opts] , concat [["-pgmc", cc] | cc <- flag ghcOptCcProgram] , concat [["-pgmcxx", cxx] | cxx <- flag ghcOptGppProgram] + , -- -pgml-supports-no-pie has to be passed after -pgml: GHC refuses + -- to pass -no-pie to a custom linker (see 'ghcOptLinkerProgram' + -- and GHC issue #15319) unless the probe of the resolved compiler + -- found support for it. + concat + [ ["-pgml", ld] ++ ["-pgml-supports-no-pie" | flagBool ghcOptLinkerSupportsNoPie] + | ld <- flag ghcOptLinkerProgram + ] , ----------------- -- Linker stuff diff --git a/Cabal/src/Distribution/Simple/Program/Types.hs b/Cabal/src/Distribution/Simple/Program/Types.hs index fb5a9986c15..3ae012a3969 100644 --- a/Cabal/src/Distribution/Simple/Program/Types.hs +++ b/Cabal/src/Distribution/Simple/Program/Types.hs @@ -122,7 +122,21 @@ data ConfiguredProgram = ConfiguredProgram , programOverrideArgs :: [String] -- ^ Override command-line args for this program. -- These flags will appear last on the command line, so they override - -- all earlier flags. + -- all earlier flags. They hold the user's options for this program + -- ('programDriverArgs') interpreted for the program's own command + -- line, which for @ld@ differs from the form the user gives them in + , programDriverArgs :: [String] + -- ^ The user's options for this program, as given by the user (e.g. + -- from @--ld-options@), before they are interpreted for the program's + -- own command line. + -- + -- For most programs these are the options for the program itself, and + -- equal to 'programOverrideArgs'. The exception is @ld@: its options + -- are documented as options for GHC's linking phase (GHC receives them + -- as @-optl@ arguments, which forwards them to the C compiler driver + -- acting as the linker), so options for the linker proper have to be + -- given with a @-Wl,@ prefix — see + -- They are not passed to @ld@ when Cabal invokes it directly. , programOverrideEnv :: [(String, Maybe String)] -- ^ Override environment variables for this program. -- These env vars will extend\/override the prevailing environment of @@ -164,7 +178,11 @@ programPath = locationPath . programLocation -- | Suppress any extra arguments added by the user. suppressOverrideArgs :: ConfiguredProgram -> ConfiguredProgram -suppressOverrideArgs prog = prog{programOverrideArgs = []} +suppressOverrideArgs prog = + prog + { programOverrideArgs = [] + , programDriverArgs = [] + } -- | Make a simple 'ConfiguredProgram'. -- @@ -176,6 +194,7 @@ simpleConfiguredProgram name loc = , programVersion = Nothing , programDefaultArgs = [] , programOverrideArgs = [] + , programDriverArgs = [] , programOverrideEnv = [] , programProperties = Map.empty , programLocation = loc diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/Main.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/Main.hs new file mode 100644 index 00000000000..279bfc119b5 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/Main.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Main where + +import Foreign.C (CInt (..)) + +foreign import ccall "pgmllib.h meaning_of_life_pgml" + meaning_of_life_pgml :: IO CInt + +main :: IO () +main = do + secret <- meaning_of_life_pgml + -- The value 66 comes from __wrap_meaning_of_life_pgml: it is only + -- linked in when GHC's linker is driven by scripts/cc-wrapper.sh, + -- which Cabal passes to GHC as -pgml (--with-gcc selects it). + if secret == 66 + then putStrLn ("The secret is " ++ show secret) + else error ("Expected value 66, got " ++ show secret) diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.project b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.project new file mode 100644 index 00000000000..e6fdbadb439 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.test.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.test.hs new file mode 100644 index 00000000000..be988ca001d --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cabal.test.hs @@ -0,0 +1,26 @@ +import Test.Cabal.Prelude + +-- ForeignOptsPgml, cabal (v2) variant: an explicitly passed -pgml has to +-- drive GHC's final link, taking precedence over the -pgml that Cabal +-- injects itself (user ghc-options come last on the GHC command line). +-- The wrapper redirects an FFI call with -Wl,--wrap; if GHC's link is not +-- driven by the wrapper, the executable fails at runtime. +-- +-- Furthermore, whether the linker supports -no-pie is probed by Cabal +-- (like GHC's build system probes its own C compiler): the probe of the +-- resolved C compiler succeeds, so -pgml-supports-no-pie is passed after +-- -pgml. +main = do + skipIfWindows "requires a POSIX shell script as the compiler wrapper" + skipIfOSX "ld64 does not support --wrap" + cabalTest $ recordMode DoNotRecord $ do + -- Cabal only passes -pgml (like -pgmc) on GHC >= 9.4, see the + -- ForeignOptsPgmc test for the rationale. + skipUnlessGhcVersion ">= 9.4" + env <- getTestEnv + let wrapper = testCurrentDir env "scripts" "cc-wrapper.sh" + res <- cabal' "v2-build" ["-v2", "--ghc-options=-pgml " ++ wrapper, "foreign-opts-pgml-exe"] + assertOutputContains "-pgml-supports-no-pie" res + withPlan $ do + resExe <- runPlanExe' "foreign-opts-pgml" "foreign-opts-pgml-exe" [] + assertOutputContains "The secret is 66" resExe diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.c b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.c new file mode 100644 index 00000000000..b9478496013 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.c @@ -0,0 +1,16 @@ +#include "pgmllib.h" + +/* The "real" implementation - returns 0, the wrong value. + * When the linker is driven by scripts/cc-wrapper.sh (passed to GHC by + * Cabal as -pgml, because the wrapper is the --with-gcc compiler), the + * linker option -Wl,--wrap=meaning_of_life_pgml redirects all calls to + * this function to __wrap_meaning_of_life_pgml below. */ +int meaning_of_life_pgml(void) { + return 0; +} + +/* The wrapper the linker substitutes in place of the real function. + * Returns 66 - see scripts/cc-wrapper.sh. */ +int __wrap_meaning_of_life_pgml(void) { + return 66; +} diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.h b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.h new file mode 100644 index 00000000000..9a79f98d7bd --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/cbits/pgmllib.h @@ -0,0 +1,6 @@ +#ifndef PGMLLIB_H +#define PGMLLIB_H + +int meaning_of_life_pgml(void); + +#endif diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/foreign-opts-pgml.cabal b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/foreign-opts-pgml.cabal new file mode 100644 index 00000000000..7766f0a103d --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/foreign-opts-pgml.cabal @@ -0,0 +1,11 @@ +cabal-version: 2.2 +name: foreign-opts-pgml +version: 0.1 +build-type: Simple + +executable foreign-opts-pgml-exe + main-is: Main.hs + build-depends: base + default-language: Haskell2010 + include-dirs: cbits + c-sources: cbits/pgmllib.c diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/scripts/cc-wrapper.sh b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/scripts/cc-wrapper.sh new file mode 100755 index 00000000000..916db941894 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/scripts/cc-wrapper.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Used as the linker driver of GHC's final link: +# +# - in the cabal (v2) test, via the user's explicit -pgml (--ghc-options); +# - in the setup (v1) test, via Cabal's own -pgml: it points GHC's linker at +# the C compiler resolved with --with-gcc. +# +# GHC drives the link with C compiler style options (-Wl,...), so the +# wrapper delegates to `cc`. It adds -no-pie (GHC does not pass -no-pie to a +# custom linker, see GHC #15319) and -Wl,--wrap=meaning_of_life_pgml, which +# redirects all calls to the "real" function to +# __wrap_meaning_of_life_pgml. Compilation invocations (which carry -c) are +# passed through unchanged. +for arg in "$@"; do + if [ "$arg" = "-c" ]; then + exec cc "$@" + fi +done +exec cc -no-pie -Wl,--wrap=meaning_of_life_pgml "$@" diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/scripts/no-pie-wrapper.sh b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/scripts/no-pie-wrapper.sh new file mode 100755 index 00000000000..fc27d243ea3 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/scripts/no-pie-wrapper.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Emulates a C compiler driver without -no-pie support (like GCC before +# version 6) whose default output is not PIE, so linking GHC's objects +# works without the flag: it fails if it ever sees -no-pie (which is the +# shape of Cabal's support probe), and asks the linker itself for non-PIE +# output instead. Used by the ForeignOptsPgml test to assert that Cabal +# probes the resolved C compiler and does not pass -pgml-supports-no-pie +# to GHC when the probe fails. +for arg in "$@"; do + if [ "$arg" = "-no-pie" ]; then + echo "unsupported option -no-pie" >&2 + exit 1 + fi +done +exec cc -Wl,--no-pie "$@" diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/setup.test.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/setup.test.hs new file mode 100644 index 00000000000..3c06d022420 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgml/setup.test.hs @@ -0,0 +1,32 @@ +import Test.Cabal.Prelude + +-- ForeignOptsPgml, setup (v1) variant: the C compiler resolved by Cabal +-- (--with-gcc) has to drive GHC's final link, which is how Cabal passes +-- -pgml to GHC; the wrapper redirects an FFI call with -Wl,--wrap. If +-- GHC's link is not driven by the wrapper, the executable fails at +-- runtime. +main = do + skipIfWindows "requires a POSIX shell script as the compiler wrapper" + skipIfOSX "ld64 does not support --wrap" + setupTest $ recordMode DoNotRecord $ do + -- Cabal only passes -pgml (like -pgmc) on GHC >= 9.4, see the + -- ForeignOptsPgmc test for the rationale. + skipUnlessGhcVersion ">= 9.4" + env <- getTestEnv + let wrapper = testCurrentDir env "scripts" "cc-wrapper.sh" + noPieWrapper = testCurrentDir env "scripts" "no-pie-wrapper.sh" + setup "configure" ["--with-gcc=" ++ wrapper] + res <- setup' "build" [] + -- The probe of the wrapper compiler (which drives the link, as it is + -- also passed as -pgml) finds -no-pie support. + assertOutputContains "-pgml-supports-no-pie" res + resExe <- runExe' "foreign-opts-pgml-exe" [] + assertOutputContains "The secret is 66" resExe + -- A linker driver without -no-pie support (emulated by + -- scripts/no-pie-wrapper.sh, which also defaults to non-PIE output) is + -- not claimed to support it, so -pgml-supports-no-pie is not passed; + -- GHC's link still succeeds without the flag. + setup "configure" ["--with-gcc=" ++ noPieWrapper] + resNoPie <- setup' "build" ["-v2"] + assertOutputContains "-pgml " resNoPie + assertOutputDoesNotContain "-pgml-supports-no-pie" resNoPie diff --git a/cabal-testsuite/PackageTests/Regression/T10789/app/Main.hs b/cabal-testsuite/PackageTests/Regression/T10789/app/Main.hs new file mode 100644 index 00000000000..87ecf4a8188 --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/app/Main.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Main (main) where + +import Foreign.C (CInt (..)) +import Lib (greeting) + +-- With `ld-options: -Wl,--wrap=meaning_of_life_ld_real` in cabal.project, +-- the linker redirects all calls to `meaning_of_life_ld_real` to +-- `__wrap_meaning_of_life_ld_real`, which returns 55. +foreign import ccall "ldlib.h meaning_of_life_ld_real" + meaning_of_life_ld_real :: IO CInt + +main :: IO () +main = do + secret <- meaning_of_life_ld_real + -- The value 55 comes from __wrap_meaning_of_life_ld_real, see + -- `ld-options` in cabal.project. + if secret == 55 + then putStrLn ("The secret is " ++ show secret) + else error ("Expected value 55, got " ++ show secret) + putStrLn greeting diff --git a/cabal-testsuite/PackageTests/Regression/T10789/cabal.project b/cabal-testsuite/PackageTests/Regression/T10789/cabal.project new file mode 100644 index 00000000000..29f73ff41be --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/cabal.project @@ -0,0 +1,4 @@ +packages: . + +program-options + ld-options: -Wl,--allow-multiple-definition -Wl,--wrap=meaning_of_life_ld_real diff --git a/cabal-testsuite/PackageTests/Regression/T10789/cabal.test.hs b/cabal-testsuite/PackageTests/Regression/T10789/cabal.test.hs new file mode 100644 index 00000000000..03349324bc5 --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/cabal.test.hs @@ -0,0 +1,28 @@ +import Test.Cabal.Prelude + +-- Regression test for #10789: inconsistent use of `ld-options`. +-- +-- Project-level `ld-options` (`--ld-options`) are documented as flags for +-- GHC's linking phase: they are passed to GHC as `-optl` arguments, which +-- forwards them to the C compiler driver acting as the linker. Cabal also +-- invokes the `ld` program directly (when probing its capabilities and when +-- combining object files into a library for GHCi); previously the user's +-- `-Wl,`-prefixed options were passed to `ld` verbatim, which broke the +-- probes (silently disabling `--enable-library-for-ghci`) and could fail +-- the build, while the very same options worked fine when passed to GHC. +main = do + -- The assertions below require a linker that reports support for + -- relocatable output; `lld` (Windows) and `ld64` (macOS) do not. + skipIfWindows "lld does not support relocatable output" + skipIfOSX "ld64 does not support relocatable output" + cabalTest $ recordMode DoNotRecord $ do + -- The `-Wl,`-prefixed `ld-options` must not break the `ld` + -- capability probes: the library for GHCi has to be built. + cabal "v2-build" ["--enable-library-for-ghci", "all"] + _ <- assertGlobMatchesTestDir testDistDir "**/HSt10789-0.1-inplace.o" + + -- The same `ld-options` must still reach the linker through GHC + -- (as `-optl` flags). + withPlan $ do + res <- runPlanExe' "t10789" "wrap-exe" [] + assertOutputContains "The secret is 55" res diff --git a/cabal-testsuite/PackageTests/Regression/T10789/cbits/ldlib.c b/cabal-testsuite/PackageTests/Regression/T10789/cbits/ldlib.c new file mode 100644 index 00000000000..7b88a4237a9 --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/cbits/ldlib.c @@ -0,0 +1,9 @@ +#include "ldlib.h" + +int meaning_of_life_ld_real(void) { + return 0; +} + +int __wrap_meaning_of_life_ld_real(void) { + return 55; +} diff --git a/cabal-testsuite/PackageTests/Regression/T10789/cbits/ldlib.h b/cabal-testsuite/PackageTests/Regression/T10789/cbits/ldlib.h new file mode 100644 index 00000000000..504439aee6b --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/cbits/ldlib.h @@ -0,0 +1,14 @@ +#ifndef LDLIB_H +#define LDLIB_H + +/* The "real" implementation - returns 0, the wrong value. + * With `ld-options: -Wl,--wrap=meaning_of_life_ld_real`, the linker + * redirects all calls to this function to __wrap_meaning_of_life_ld_real + * below. */ +int meaning_of_life_ld_real(void); + +/* The wrapper that the linker substitutes in place of the real function. + * Returns 55 - see `ld-options` in cabal.project. */ +int __wrap_meaning_of_life_ld_real(void); + +#endif diff --git a/cabal-testsuite/PackageTests/Regression/T10789/src/Lib.hs b/cabal-testsuite/PackageTests/Regression/T10789/src/Lib.hs new file mode 100644 index 00000000000..f7e6d68a12a --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/src/Lib.hs @@ -0,0 +1,4 @@ +module Lib (greeting) where + +greeting :: String +greeting = "hello from t10789" diff --git a/cabal-testsuite/PackageTests/Regression/T10789/t10789.cabal b/cabal-testsuite/PackageTests/Regression/T10789/t10789.cabal new file mode 100644 index 00000000000..8025a8817c7 --- /dev/null +++ b/cabal-testsuite/PackageTests/Regression/T10789/t10789.cabal @@ -0,0 +1,19 @@ +cabal-version: 2.2 +name: t10789 +version: 0.1 +build-type: Simple + +library + exposed-modules: Lib + hs-source-dirs: src + build-depends: base + default-language: Haskell2010 + +executable wrap-exe + main-is: Main.hs + hs-source-dirs: app + build-depends: base, + t10789 + default-language: Haskell2010 + c-sources: cbits/ldlib.c + include-dirs: cbits diff --git a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out index 1d21331c5fa..f1b28f8d985 100644 --- a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out +++ b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out @@ -15,11 +15,11 @@ Warning: Complex [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a Preprocessing executable 'Complex' for Complex-0.1.0.0... Building executable 'Complex' for Complex-0.1.0.0... # show-build-info Complex exe:Complex -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"exe","name":"exe:Complex","unit-id":"Complex-0.1.0.0-inplace-Complex","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-i","-iapp","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-this-unit-id","Complex-0.1.0.0-inplace-Complex","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010","-threaded","-rtsopts","-with-rtsopts=-N -T","-Wredundant-constraints"],"modules":["Other","Paths_Complex"],"src-files":["Main.lhs"],"hs-src-dirs":["app"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"exe","name":"exe:Complex","unit-id":"Complex-0.1.0.0-inplace-Complex","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-i","-iapp","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-pgml","","-this-unit-id","Complex-0.1.0.0-inplace-Complex","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010","-threaded","-rtsopts","-with-rtsopts=-N -T","-Wredundant-constraints"],"modules":["Other","Paths_Complex"],"src-files":["Main.lhs"],"hs-src-dirs":["app"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Up to date # show-build-info Complex lib -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"lib","name":"lib","unit-id":"Complex-0.1.0.0-inplace","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-i","-isrc","-idoesnt-exist","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-this-unit-id","Complex-0.1.0.0-inplace","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-XHaskell2010","-Wall"],"modules":["A","B","C","D","Paths_Complex"],"src-files":[],"hs-src-dirs":["src","doesnt-exist"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"lib","name":"lib","unit-id":"Complex-0.1.0.0-inplace","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-i","-isrc","-idoesnt-exist","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-pgml","","-this-unit-id","Complex-0.1.0.0-inplace","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-XHaskell2010","-Wall"],"modules":["A","B","C","D","Paths_Complex"],"src-files":[],"hs-src-dirs":["src","doesnt-exist"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Build profile: -w ghc- -O1 In order, the following will be built: @@ -34,7 +34,7 @@ Warning: Complex [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a Preprocessing benchmark 'complex-benchmarks' for Complex-0.1.0.0... Building benchmark 'complex-benchmarks' for Complex-0.1.0.0... # show-build-info Complex bench:complex-benchmarks -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"bench","name":"bench:complex-benchmarks","unit-id":"Complex-0.1.0.0-inplace-complex-benchmarks","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-i","-ibenchmark","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-this-unit-id","Complex-0.1.0.0-inplace-complex-benchmarks","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010","-Wall","-rtsopts","-threaded","-with-rtsopts=-N"],"modules":["Paths_Complex"],"src-files":["Main.hs"],"hs-src-dirs":["benchmark"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"bench","name":"bench:complex-benchmarks","unit-id":"Complex-0.1.0.0-inplace-complex-benchmarks","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-i","-ibenchmark","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-pgml","","-this-unit-id","Complex-0.1.0.0-inplace-complex-benchmarks","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010","-Wall","-rtsopts","-threaded","-with-rtsopts=-N"],"modules":["Paths_Complex"],"src-files":["Main.hs"],"hs-src-dirs":["benchmark"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Build profile: -w ghc- -O1 In order, the following will be built: @@ -49,7 +49,7 @@ Warning: Complex [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a Preprocessing test suite 'func-test' for Complex-0.1.0.0... Building test suite 'func-test' for Complex-0.1.0.0... # show-build-info Complex test:func-test -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:func-test","unit-id":"Complex-0.1.0.0-inplace-func-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-this-unit-id","Complex-0.1.0.0-inplace-func-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["FuncMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:func-test","unit-id":"Complex-0.1.0.0-inplace-func-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-pgml","","-this-unit-id","Complex-0.1.0.0-inplace-func-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["FuncMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Build profile: -w ghc- -O1 In order, the following will be built: @@ -64,4 +64,4 @@ Warning: Complex [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a Preprocessing test suite 'unit-test' for Complex-0.1.0.0... Building test suite 'unit-test' for Complex-0.1.0.0... # show-build-info Complex test:unit-test -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:unit-test","unit-id":"Complex-0.1.0.0-inplace-unit-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-this-unit-id","Complex-0.1.0.0-inplace-unit-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["UnitMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:unit-test","unit-id":"Complex-0.1.0.0-inplace-unit-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen/cabal_macros.h","-pgmc","","-pgmcxx","","-pgml","","-this-unit-id","Complex-0.1.0.0-inplace-unit-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["UnitMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} diff --git a/cabal-testsuite/Setup.hs b/cabal-testsuite/Setup.hs index fa07806b9c5..6016461282f 100644 --- a/cabal-testsuite/Setup.hs +++ b/cabal-testsuite/Setup.hs @@ -14,6 +14,8 @@ import Distribution.Types.UnqualComponentName import Distribution.Utils.Path (getSymbolicPath) import Distribution.Verbosity +import Data.List (isPrefixOf) + import System.Directory import System.FilePath @@ -65,7 +67,15 @@ generateScriptEnvModule lbi verbosity = do , "lbiPackages = read " ++ show (show (cabalTestsPackages lbi)) , "" , "lbiProgramDb :: ProgramDb" - , "lbiProgramDb = read " ++ show (show (withPrograms lbi)) + -- The `Show`/`Read` round trip below crosses Cabal versions: this + -- setup script is compiled against the released Cabal pinned in the + -- `custom-setup` stanza, while the generated module is read by the + -- test suite compiled against the in-tree Cabal, whose types may + -- have gained fields. Splice the fields added to `ConfiguredProgram` + -- since the pinned release, so that the newer `Read` accepts the + -- generated value (in the spirit of the `lbiCompiler` workaround + -- above). + , "lbiProgramDb = read " ++ show (addMissingConfiguredProgramFields (show (withPrograms lbi))) , "" , "lbiWithSharedLib :: Bool" , "lbiWithSharedLib = " ++ show (withSharedLib lbi) @@ -78,6 +88,23 @@ generateScriptEnvModule lbi verbosity = do -- fixme: use component-specific folder libAutogenDir = autogenPackageModulesDir lbi +-- | `programDriverArgs` was added to `ConfiguredProgram` after the Cabal +-- release this setup script is compiled against; its derived `Show` +-- therefore omits the field that the test suite's newer `Read` expects. +-- Splice it into every record of the generated `show` output. +-- +-- The spliced label is assumed not to occur inside any of the shown string +-- values, which holds for the program arguments occurring in practice. +addMissingConfiguredProgramFields :: String -> String +addMissingConfiguredProgramFields = go + where + needle = ", programOverrideEnv = " + replacement = ", programDriverArgs = [], programOverrideEnv = " + go [] = [] + go s@(c : rest) + | needle `isPrefixOf` s = replacement ++ go (drop (length needle) s) + | otherwise = c : go rest + -- | Convert package database into absolute path, so that -- if we change working directories in a subprocess we get the correct database. canonicalizePackageDB :: PackageDB -> IO PackageDB diff --git a/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs b/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs index 67b450dd0fb..41e7000da36 100644 --- a/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs +++ b/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs @@ -138,7 +138,16 @@ normalizeOutput nenv = . resub "\"-pgmcxx\",\"[^\"]+\"" "\"-pgmcxx\",\"\"" - -- Remove cabal version output from show-build-info output + -- Normalize the linker path embedded in -pgml. + . resub + "\"-pgml\",\"[^\"]+\"" + "\"-pgml\",\"\"" + -- Whether -pgml-supports-no-pie is passed depends on whether the + -- probe of the resolved C compiler found -no-pie support. + . resub + ",\"-pgml-supports-no-pie\"" + "" + -- Remove cabal version output from show-build-info output. . resub ("{\"cabal-lib-version\":\"" ++ posixRegexEscape (display (normalizerCabalVersion nenv)) ++ "\"") "{\"cabal-lib-version\":\"\"" diff --git a/changelog.d/12321.md b/changelog.d/12321.md new file mode 100644 index 00000000000..89ca5680bf3 --- /dev/null +++ b/changelog.d/12321.md @@ -0,0 +1,44 @@ +--- +synopsis: Consistently translate `-Wl,` in `ld-options` when invoking `ld` directly +packages: [Cabal] +prs: 12321 +issues: [10789] +--- + +`ld-options` (both the package field and the program option) hold options for +GHC's linking phase: Cabal passes them to GHC as `-optl` arguments, which +forwards them to the C compiler driver acting as the linker. In that form, +options destined for the linker itself are written with the `-Wl,` prefix. + +However, Cabal also invokes the `ld` program directly, in a few places: + +- when probing the linker's capabilities (`ld --help`, `ld -x -r`), and +- when combining object files into a library for GHCi (`ld -r`). + +There, the same options were passed to `ld` verbatim, in a form that `ld` +does not understand. As a consequence, there was no way to spell an option +that worked everywhere: options in the documented `-Wl,` form made the +probes fail (silently disabling `--enable-library-for-ghci`) and failed the +`ld -r` invocations, while options without the `-Wl,` prefix (the form `ld` +understands) failed at GHC's linking phase, where the C compiler driver +requires `-Wl,`. + +Now the documented `-Wl,` form works consistently: the options recorded for +the `ld` program are split into the form `ld` itself understands (with +`-Wl,a,b` translated into the raw linker options `a b`) for Cabal's direct +`ld` invocations, and the as-given driver form for GHC's linking phase. The +linker capability probes no longer include user-specified options at all. + +```diff + $ cabal build -v3 + Running: /usr/bin/ld --help + Running: /usr/bin/ar -r dist/build/libHSpkg-0.1-inplace.a '@dist/tmp/ar.rsp' +- Running: /usr/bin/ld -x -r -o dist/build/libHSpkg-0.1-inplace.o '@dist/tmp/ld.rsp' -Wl,--allow-multiple-definition +- x86_64-linux-gnu-ld.bfd: unrecognized option '-Wl,--allow-multiple-definition' +- x86_64-linux-gnu-ld.bfd: use the --help option for usage information +- Error: [Cabal-7125] +- Failed to build pkg-0.1-inplace. ++ Running: /usr/bin/ld -x -r -o dist/build/libHSpkg-0.1-inplace.o '@dist/tmp/ld.rsp' --allow-multiple-definition + Running: ghc --make ... -pgmc /usr/bin/gcc -optl-Wl,--allow-multiple-definition ... -o dist/build/pkg/pkg ++ Linking dist/build/pkg/pkg ... +``` diff --git a/changelog.d/12322.md b/changelog.d/12322.md new file mode 100644 index 00000000000..3d84aa2181e --- /dev/null +++ b/changelog.d/12322.md @@ -0,0 +1,29 @@ +--- +synopsis: Always pass `-pgml` to GHC so that linking uses the toolchain Cabal resolved +packages: [Cabal] +prs: 12322 +--- + +Cabal always passes GHC's `-pgml` flag (on GHC >= 9.4, like `-pgmc`) pointing +at the C compiler it resolved (`--with-gcc`, or the one reported by +`ghc --info`), so GHC drives the final link with the same toolchain that +`cc-options` and `ld-options` were resolved against. + +This closes a gap: GHC's linker does not follow `-pgmc` — without `-pgml` the +final link was driven by the C compiler from GHC's settings file, which can +differ from the compiler Cabal resolved, and `--with-gcc` had no effect on +linking: + +```diff + $ cabal build -v3 --with-gcc=$PWD/my-cc-wrapper.sh + Running: ghc ... -pgmc $PWD/my-cc-wrapper.sh -pgmcxx /usr/bin/g++ ... +- ... link driven by the C compiler from GHC's settings ... ++ Running: ghc ... -pgmc $PWD/my-cc-wrapper.sh -pgmcxx /usr/bin/g++ ++ -pgml $PWD/my-cc-wrapper.sh -pgml-supports-no-pie ... +``` + +Since GHC does not pass `-no-pie` to a custom linker — it cannot know whether +the flag is supported (see GHC issue #15319) — Cabal probes the resolved C +compiler the same way GHC's own build system does, by linking a test file +with `-no-pie -Werror` and checking for unrecognized-option diagnostics, and +only then passes `-pgml-supports-no-pie` after `-pgml`.