From cc36041b6dfa9dbb39b2bf8508a79cc716a77f1c Mon Sep 17 00:00:00 2001 From: Andrea Bedini Date: Tue, 11 Aug 2026 15:49:57 +0800 Subject: [PATCH 1/3] fix(Cabal-syntax): make CabalSpecV3_20 latest CabalSpecV3_18 and CabalSpecV3_20 are both known to the development tree, but cabalSpecLatest remained CabalSpecV3_16. cabal-install consequently treated Simple packages that declare cabal-version 3.18 as future-format packages and selected a broken external setup path. Move the supported-version boundary to CabalSpecV3_20. Also map that spec version to SPDX license list 3.28 so default SPDX parsing does not fall through to license list 3.0. Fixes #12271 --- .../src/Distribution/CabalSpecVersion.hs | 2 +- .../Distribution/SPDX/LicenseListVersion.hs | 1 + changelog.d/cabal-spec-latest-3-20.md | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 changelog.d/cabal-spec-latest-3-20.md diff --git a/Cabal-syntax/src/Distribution/CabalSpecVersion.hs b/Cabal-syntax/src/Distribution/CabalSpecVersion.hs index fa98b5d2f66..721570a982c 100644 --- a/Cabal-syntax/src/Distribution/CabalSpecVersion.hs +++ b/Cabal-syntax/src/Distribution/CabalSpecVersion.hs @@ -71,7 +71,7 @@ showCabalSpecVersion CabalSpecV1_2 = "1.2" showCabalSpecVersion CabalSpecV1_0 = "1.0" cabalSpecLatest :: CabalSpecVersion -cabalSpecLatest = CabalSpecV3_16 +cabalSpecLatest = CabalSpecV3_20 -- | Parse 'CabalSpecVersion' from version digits. -- diff --git a/Cabal-syntax/src/Distribution/SPDX/LicenseListVersion.hs b/Cabal-syntax/src/Distribution/SPDX/LicenseListVersion.hs index 37e76ec045c..2764d24b2ca 100644 --- a/Cabal-syntax/src/Distribution/SPDX/LicenseListVersion.hs +++ b/Cabal-syntax/src/Distribution/SPDX/LicenseListVersion.hs @@ -30,6 +30,7 @@ data LicenseListVersion deriving (Eq, Ord, Show, Enum, Bounded) cabalSpecVersionToSPDXListVersion :: CabalSpecVersion -> LicenseListVersion +cabalSpecVersionToSPDXListVersion CabalSpecV3_20 = LicenseListVersion_3_28 cabalSpecVersionToSPDXListVersion CabalSpecV3_18 = LicenseListVersion_3_28 cabalSpecVersionToSPDXListVersion CabalSpecV3_16 = LicenseListVersion_3_26 cabalSpecVersionToSPDXListVersion CabalSpecV3_14 = LicenseListVersion_3_25 diff --git a/changelog.d/cabal-spec-latest-3-20.md b/changelog.d/cabal-spec-latest-3-20.md new file mode 100644 index 00000000000..dc50dd32ffc --- /dev/null +++ b/changelog.d/cabal-spec-latest-3-20.md @@ -0,0 +1,34 @@ +--- +synopsis: Make 3.20 the latest known .cabal specification version +packages: [Cabal-syntax, cabal-install] +prs: 12275 +issues: 12271 +significance: significant +--- + +`cabalSpecLatest` was left at `CabalSpecV3_16` when `CabalSpecV3_18` and +`CabalSpecV3_20` were introduced, so the newest specification version the +library knew about lagged two releases behind the tree. It now points at +`CabalSpecV3_20`, the version under development. + +`cabal-install` treated a `build-type: Simple` package whose `cabal-version` +exceeded `cabalSpecLatest` as a future-format package. It then tried to compile +an external `Setup.hs` without a Cabal setup dependency. Consequently, packages +that declared the released `cabal-version: 3.18` could fail to build. Affected +packages now use the in-process setup path. + +Two further changes come with the bump: + +- `cabalSpecVersionToSPDXListVersion` gained an explicit `CabalSpecV3_20` case, + so standalone `Distribution.SPDX` parsing (and Cabal-QuickCheck's generators) + now default to SPDX license list 3.28 rather than 3.26, matching released + Cabal 3.18. Without the new case the bump would have fallen through to the + catch-all and silently downgraded that default to 3.0 instead. + +- `cabal.project` files are parsed at `cabalSpecLatest`, so the + `deprecatedSince CabalSpecV3_20` marker on `prefer-oldest` becomes reachable + for the first time: using that field now warns and points at `prefer-version`. + +This does not change which `cabal-version` values a `.cabal` file may declare — +the parser accepts any version known to `cabalSpecFromVersionDigits` +independently of `cabalSpecLatest`. From 3197150d152589d4ea171446c3c3502e75ea933e Mon Sep 17 00:00:00 2001 From: Andrea Bedini Date: Thu, 27 Aug 2026 14:34:46 +0800 Subject: [PATCH 2/3] fix(Cabal): do not pass -optJSP to GHC older than 9.12 The `jspp-options` field is rendered as `ghc -optJSP`, but that flag only exists since GHC 9.12. Passing it to an older GHC makes the invocation fail outright, so a package using `jspp-options` could not be built at all with GHC < 9.12. Gate the flag on the compiler version instead. --- .../Distribution/Simple/Program/GHC.hs | 27 +++++++++++++++++-- Cabal/src/Distribution/Simple/Program/GHC.hs | 10 +++++-- changelog.d/jspp-options-ghc-912.md | 11 ++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 changelog.d/jspp-options-ghc-912.md diff --git a/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs b/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs index daf64ccbb54..10b052bebe2 100644 --- a/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs +++ b/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs @@ -10,8 +10,8 @@ import Distribution.Types.ParStrat import Distribution.Simple.Flag import Distribution.Simple.Compiler (Compiler(..), CompilerId(..), CompilerFlavor(..), AbiTag(NoAbiTag)) import Distribution.PackageDescription (emptyPackageDescription) -import Distribution.Simple.Program.GHC (normaliseGhcArgs, renderGhcOptions, ghcOptNumJobs) -import Distribution.Version (mkVersion) +import Distribution.Simple.Program.GHC (GhcOptions, normaliseGhcArgs, renderGhcOptions, ghcOptNumJobs, ghcOptJSppOptions) +import Distribution.Version (mkVersion, Version) tests :: TestTree tests = testGroup "Distribution.Simple.Program.GHC" @@ -59,9 +59,32 @@ tests = testGroup "Distribution.Simple.Program.GHC" (Platform X86_64 Linux) (mempty { ghcOptNumJobs = Flag (NumJobs (Just 4)) }) assertListEquals flags ["-j4", "-clear-package-db"] + , testCase "JS preprocessor options (GHC >= 9.12)" $ do + let flags = renderWith (mkVersion [9,12,1]) + (mempty { ghcOptJSppOptions = ["-DJS"] }) + assertBool ("expected -optJSP-DJS in " ++ show flags) ("-optJSP-DJS" `elem` flags) + , testCase "JS preprocessor options gated out before GHC 9.12" $ do + let flags = renderWith (mkVersion [9,10,1]) + (mempty { ghcOptJSppOptions = ["-DJS"] }) + assertBool ("unexpected -optJSP in " ++ show flags) ("-optJSP-DJS" `notElem` flags) ] ] +-- | Render 'GhcOptions' as GHC arguments for a GHC of the given version. +renderWith :: Version -> GhcOptions -> [String] +renderWith ver = + renderGhcOptions + (Compiler + { compilerId = CompilerId GHC ver + , compilerAbiTag = NoAbiTag + , compilerCompat = [] + , compilerLanguages = [] + , compilerExtensions = [] + , compilerProperties = Map.singleton "Support parallel --make" "YES" + , compilerWiredInUnitIds = Nothing + }) + (Platform X86_64 Linux) + assertListEquals :: (Eq a, Show a) => [a] -> [a] -> Assertion assertListEquals xs ys | xs == ys = return () diff --git a/Cabal/src/Distribution/Simple/Program/GHC.hs b/Cabal/src/Distribution/Simple/Program/GHC.hs index 1b6a1857d11..7c260184b4d 100644 --- a/Cabal/src/Distribution/Simple/Program/GHC.hs +++ b/Cabal/src/Distribution/Simple/Program/GHC.hs @@ -521,7 +521,8 @@ data GhcOptions = GhcOptions , ghcOptCppOptions :: [String] -- ^ Options to pass through to CPP; the @ghc -optP@ flag. , ghcOptJSppOptions :: [String] - -- ^ Options to pass through to CPP; the @ghc -optJSP@ flag. @since 3.16.0.0 + -- ^ Options to pass through to the JavaScript preprocessor; the + -- @ghc -optJSP@ flag (GHC >= 9.12). @since 3.16.0.0 , ghcOptCppIncludePath :: NubListR (SymbolicPath Pkg (Dir Include)) -- ^ Search path for CPP includes like header files; the @ghc -I@ flag. , ghcOptCppIncludes :: NubListR (SymbolicPath Pkg File) @@ -874,7 +875,12 @@ renderGhcOptions comp _platform@(Platform _arch os) opts ["-I" ++ u dir | dir <- flags ghcOptCppIncludePath] , ["-optP" ++ opt | opt <- ghcOptCppOptions opts] - , ["-optJSP" ++ opt | opt <- ghcOptJSppOptions opts] + , -- The JavaScript preprocessor flag (-optJSP) only exists since GHC + -- 9.12; passing it to an older GHC is an error. + let ghc912 = case compilerCompatVersion GHC comp of + Just v -> v >= mkVersion [9, 12] + Nothing -> False + in ["-optJSP" ++ opt | ghc912, opt <- ghcOptJSppOptions opts] , concat [ ["-optP-include", "-optP" ++ u inc] | inc <- flags ghcOptCppIncludes diff --git a/changelog.d/jspp-options-ghc-912.md b/changelog.d/jspp-options-ghc-912.md new file mode 100644 index 00000000000..7b11fd67af9 --- /dev/null +++ b/changelog.d/jspp-options-ghc-912.md @@ -0,0 +1,11 @@ +--- +synopsis: Do not pass `-optJSP` to GHC older than 9.12 +packages: [Cabal] +prs: 12288 +significance: +--- + +The `jspp-options` field is rendered as `ghc -optJSP`, but that flag only +exists since GHC 9.12. Passing it to an older GHC made the invocation fail, so +a package using `jspp-options` could not be built at all with GHC < 9.12. The +options are now omitted when the compiler is older than 9.12. From 13dbe0811a81c517e6deda56c8230d27666546bb Mon Sep 17 00:00:00 2001 From: Andrea Bedini Date: Thu, 6 Mar 2025 17:54:46 +0800 Subject: [PATCH 3/3] feat(Cabal,Cabal-syntax): add per-file options to extra source files The extra-source fields (c-sources, cxx-sources, asm-sources, cmm-sources, js-sources) now accept per-file options, in parentheses after the file name: c-sources: foo.c (-DFOO -O2) bar.c cmm-sources: Jumps_V32.cmm (-mavx2) GHC gets these options only when it compiles that one file. 'ExtraSourceKind' names the kind of a source, and 'addExtraSourceOpts' maps that kind to the one 'GhcOptions' field GHC reads for it: c-sources CSourceKind ghcOptCcOptions -optc cxx-sources CxxSourceKind ghcOptCxxOptions -optcxx asm-sources AsmSourceKind ghcOptAsmOptions -opta cmm-sources CmmSourceKind ghcOptExtra plain GHC options js-sources JsSourceKind ghcOptJSppOptions -optJSP Cabal already compiles each extra source with its own 'ghc -c' run, so per-file scoping only means appending the options to that run's component-wide ones. They come last, so they win over 'cc-options' and its siblings. Only C-- options are plain GHC options, because GHC compiles C-- itself and there is no -optcmm. The other four kinds are options for a tool GHC invokes, so they cannot go in 'ghcOptExtra': 'ghc -DFOO -c foo.c' defines a Haskell CPP macro instead of passing -DFOO to the C compiler. Keeping the kind, rather than an already-prefixed string, also leaves the spelling to Cabal at render time, which it has to be, because the prefix depends on the compiler version. GHC added -optcxx in 8.10, so below that C++ options go through -optc and 'splitCandCxxOptions' clears the C options to keep the two languages apart; GHC added -optJSP in 9.12, so below that the options are dropped and Cabal warns. One shared field would also duplicate C-- options, since GHC passes -optc flags to the C-- C pre-processor too. An older Cabal does not reject this syntax but it misreads it, so 'cabal-version: 3.20' gates the syntax as an error and not a warning. The five fields hold 'ExtraSource' (a path plus its options) instead of a bare 'SymbolicPath'; 'extraSourceFromPath' makes one with no options. 'Distribution.Simple.SetupHooks' re-exports both, so a 'Hooks' package that sets 'cSources' needs no Cabal-syntax dependency. --- .../src/Test/QuickCheck/Instances/Cabal.hs | 20 +++ Cabal-described/src/Distribution/Described.hs | 25 ++- .../src/Distribution/Simple/SetupHooks.hs | 2 + Cabal-syntax/Cabal-syntax.cabal | 1 + .../src/Distribution/PackageDescription.hs | 4 + .../PackageDescription/FieldGrammar.hs | 25 +-- .../src/Distribution/Types/BuildInfo.hs | 18 ++- .../src/Distribution/Types/BuildInfo/Lens.hs | 11 +- .../src/Distribution/Types/ExtraSource.hs | 146 ++++++++++++++++++ Cabal-tests/Cabal-tests.cabal | 1 + Cabal-tests/tests/NoThunks.hs | 2 + Cabal-tests/tests/ParserTests.hs | 2 + .../errors/extra-source-opts-old-spec.cabal | 12 ++ .../errors/extra-source-opts-old-spec.errors | 5 + .../regressions/extra-source-opts.cabal | 20 +++ .../regressions/extra-source-opts.expr | 146 ++++++++++++++++++ .../regressions/extra-source-opts.format | 16 ++ .../tests/ParserTests/regressions/libpq1.expr | 6 +- .../tests/ParserTests/regressions/libpq2.expr | 6 +- Cabal-tests/tests/UnitTests.hs | 3 + .../Distribution/Simple/Program/GHC.hs | 6 +- .../Distribution/Types/ExtraSource.hs | 88 +++++++++++ .../Distribution/Utils/Structured.hs | 4 +- .../src/Data/TreeDiff/Instances/Cabal.hs | 2 + .../PackageDescription/Check/Target.hs | 14 +- Cabal/src/Distribution/Simple/Build.hs | 20 +-- Cabal/src/Distribution/Simple/BuildTarget.hs | 10 +- .../Simple/GHC/Build/ExtraSources.hs | 114 ++++++++------ Cabal/src/Distribution/Simple/GHC/Internal.hs | 81 +++++++--- Cabal/src/Distribution/Simple/GHCJS.hs | 36 +++-- .../Simple/SetupHooks/Internal.hs | 10 +- Cabal/src/Distribution/Simple/SrcDist.hs | 10 +- .../src/Distribution/Client/SourceFiles.hs | 11 +- .../src/Distribution/Client/TargetSelector.hs | 5 +- cabal-install/tests/IntegrationTests2.hs | 4 +- .../PackageTests/ExtraSources/cbits/test.c | 3 + .../ExtraSources/cbits/testcmm.cmm | 10 ++ .../ExtraSources/extra-sources.cabal | 15 ++ .../PackageTests/ExtraSources/setup.out | 5 + .../PackageTests/ExtraSources/setup.test.hs | 5 + .../PackageTests/ExtraSources/src/MyLib.hs | 4 + .../SetupHooks/SetupHooksNonHs/SetupHooks.hs | 3 +- changelog.d/per-file-extra-source-options.md | 40 +++++ doc/buildinfo-fields-reference.rst | 10 +- doc/cabal-package-description-file.rst | 63 +++++++- doc/file-format-changelog.rst | 9 ++ 46 files changed, 892 insertions(+), 161 deletions(-) create mode 100644 Cabal-syntax/src/Distribution/Types/ExtraSource.hs create mode 100644 Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.cabal create mode 100644 Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.errors create mode 100644 Cabal-tests/tests/ParserTests/regressions/extra-source-opts.cabal create mode 100644 Cabal-tests/tests/ParserTests/regressions/extra-source-opts.expr create mode 100644 Cabal-tests/tests/ParserTests/regressions/extra-source-opts.format create mode 100644 Cabal-tests/tests/UnitTests/Distribution/Types/ExtraSource.hs create mode 100644 cabal-testsuite/PackageTests/ExtraSources/cbits/test.c create mode 100644 cabal-testsuite/PackageTests/ExtraSources/cbits/testcmm.cmm create mode 100644 cabal-testsuite/PackageTests/ExtraSources/extra-sources.cabal create mode 100644 cabal-testsuite/PackageTests/ExtraSources/setup.out create mode 100644 cabal-testsuite/PackageTests/ExtraSources/setup.test.hs create mode 100644 cabal-testsuite/PackageTests/ExtraSources/src/MyLib.hs create mode 100644 changelog.d/per-file-extra-source-options.md diff --git a/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs b/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs index 0cdff2283e9..6f21ea83c70 100644 --- a/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs +++ b/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs @@ -23,6 +23,7 @@ import Distribution.Simple.Setup (HaddockTarget (..), TestShow import Distribution.SPDX import Distribution.System import Distribution.Types.Dependency +import Distribution.Types.ExtraSource import Distribution.Types.Flag (FlagAssignment, FlagName, mkFlagAssignment, mkFlagName, unFlagAssignment) import Distribution.Types.IncludeRenaming import Distribution.Types.LibraryName @@ -38,6 +39,7 @@ import Distribution.Types.SourceRepo import Distribution.Types.UnqualComponentName import Distribution.Types.VersionRange.Internal import Distribution.Utils.NubList +import Distribution.Utils.Path (makeSymbolicPath) import Distribution.Verbosity import Distribution.Version @@ -145,6 +147,24 @@ instance Arbitrary VersionIntervals where instance Arbitrary Bound where arbitrary = elements [ExclusiveBound, InclusiveBound] +------------------------------------------------------------------------------- +-- ExtraSource +------------------------------------------------------------------------------- + +instance Arbitrary ExtraSource where + arbitrary = ExtraSource . makeSymbolicPath + <$> arbitraryShortPath + <*> arbitraryExtraSourceOpts + where + -- The options are kept verbatim, but '(', ')' and '\\' are structural + -- in the rendered form and have to be escaped to survive a round-trip, + -- so make sure the generator produces plenty of them. + arbitraryExtraSourceOpts = + frequency [(1, pure ""), (4, shortListOf1 10 optChar)] + optChar = elements $ ' ' : '"' : ['#' .. '~'] + + shrink (ExtraSource p opts) = ExtraSource p <$> shrink opts + ------------------------------------------------------------------------------- -- Backpack ------------------------------------------------------------------------------- diff --git a/Cabal-described/src/Distribution/Described.hs b/Cabal-described/src/Distribution/Described.hs index 0fedbf15034..578dd4dd709 100644 --- a/Cabal-described/src/Distribution/Described.hs +++ b/Cabal-described/src/Distribution/Described.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DataKinds #-} module Distribution.Described ( Described (..), describeDoc, @@ -31,6 +32,7 @@ module Distribution.Described ( csUpper, csNotSpace, csNotSpaceOrComma, + csExtraSourceOptChar, -- * tasty testDescribed, ) where @@ -77,6 +79,7 @@ import Distribution.Types.Dependency (Dependency) import Distribution.Types.ExecutableScope (ExecutableScope) import Distribution.Types.ExeDependency (ExeDependency) import Distribution.Types.ExposedModule (ExposedModule) +import Distribution.Types.ExtraSource (ExtraSource) import Distribution.Types.Flag (FlagAssignment, FlagName) import Distribution.Types.ForeignLib (LibVersionInfo) import Distribution.Types.ForeignLibOption (ForeignLibOption) @@ -96,7 +99,7 @@ import Distribution.Types.SourceRepo (RepoType) import Distribution.Types.TestType (TestType) import Distribution.Types.UnitId (UnitId) import Distribution.Types.UnqualComponentName (UnqualComponentName) -import Distribution.Utils.Path (SymbolicPath, RelativePath) +import Distribution.Utils.Path (SymbolicPath, RelativePath, FileOrDir(..), Pkg) import Distribution.Verbosity (VerbosityFlags) import Distribution.Version (Version, VersionRange) import Language.Haskell.Extension (Extension, Language, knownLanguages) @@ -203,6 +206,13 @@ csNotSpace = CS.difference CS.universe $ CS.singleton ' ' csNotSpaceOrComma :: CS.CharSet csNotSpaceOrComma = CS.difference csNotSpace $ CS.singleton ',' +-- | Characters that may appear verbatim in the per-file options on an extra +-- source. Keep in sync with @parsecExtraSourceOpts@ in +-- "Distribution.Types.ExtraSource": there @(@, @)@ and @\\@ are the only +-- characters with any meaning. +csExtraSourceOptChar :: CS.CharSet +csExtraSourceOptChar = CS.difference csNotSpace $ CS.fromList "()\\" + ------------------------------------------------------------------------------- -- Special ------------------------------------------------------------------------------- @@ -403,6 +413,19 @@ instance Described ExposedModule where instance Described Extension where describe _ = RETodo +instance Described ExtraSource where + describe _ = REAppend + [ describe (Proxy :: Proxy (SymbolicPath Pkg File)) + , REOpt (RESpaces1 <> reChar '(' <> reSpacedList opt <> reChar ')') + ] + where + -- The text between the parentheses is taken verbatim; only a + -- parenthesis or a backslash has to be escaped. Parentheses may also + -- nest as long as they balance, which a regular grammar cannot + -- express; escaping them always works. + opt = REMunch1 reEps (REUnion [RECharSet csExtraSourceOptChar, escaped]) + escaped = reChar '\\' <> reChars "()\\" + instance Described FlagAssignment where describe _ = REMunch RESpaces1 $ REUnion [fromString "+", fromString "-"] <> describe (Proxy :: Proxy FlagName) diff --git a/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs b/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs index da928ad82a9..357d4360d10 100644 --- a/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs +++ b/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs @@ -210,6 +210,7 @@ module Distribution.Simple.SetupHooks , Component(..), ComponentName(..), componentName , ModuleName , BuildInfo(..), emptyBuildInfo + , ExtraSource(..), extraSourceFromPath , TargetInfo(..), ComponentLocalBuildInfo(..) -- **** Components @@ -232,6 +233,7 @@ import Distribution.PackageDescription , emptyLibrary, emptyForeignLib , emptyExecutable, emptyBenchmark, emptyTestSuite , BuildInfo(..), emptyBuildInfo + , ExtraSource(..), extraSourceFromPath , ComponentName(..), LibraryName(..) ) import Distribution.Simple.BuildPaths diff --git a/Cabal-syntax/Cabal-syntax.cabal b/Cabal-syntax/Cabal-syntax.cabal index 8e4b744fcc9..e726d20e021 100644 --- a/Cabal-syntax/Cabal-syntax.cabal +++ b/Cabal-syntax/Cabal-syntax.cabal @@ -141,6 +141,7 @@ library Distribution.Types.Executable Distribution.Types.Executable.Lens Distribution.Types.ExecutableScope + Distribution.Types.ExtraSource Distribution.Types.ExposedModule Distribution.Types.Flag Distribution.Types.ForeignLib diff --git a/Cabal-syntax/src/Distribution/PackageDescription.hs b/Cabal-syntax/src/Distribution/PackageDescription.hs index 47d46673e5f..85b8c8943b8 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription.hs @@ -48,6 +48,9 @@ module Distribution.PackageDescription , module Distribution.Types.HookedBuildInfo , module Distribution.Types.SetupBuildInfo + -- * Extra sources + , module Distribution.Types.ExtraSource + -- * Flags , module Distribution.Types.Flag @@ -99,6 +102,7 @@ import Distribution.Types.Dependency import Distribution.Types.ExeDependency import Distribution.Types.Executable import Distribution.Types.ExecutableScope +import Distribution.Types.ExtraSource import Distribution.Types.Flag import Distribution.Types.ForeignLib import Distribution.Types.ForeignLibOption diff --git a/Cabal-syntax/src/Distribution/PackageDescription/FieldGrammar.hs b/Cabal-syntax/src/Distribution/PackageDescription/FieldGrammar.hs index 05aadf7cf37..3a96dfe8767 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription/FieldGrammar.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription/FieldGrammar.hs @@ -172,6 +172,7 @@ libraryFieldGrammar , c (List CommaFSep (Identity PkgconfigDependency) PkgconfigDependency) , c (List CommaVCat (Identity Dependency) Dependency) , c (List CommaVCat (Identity Mixin) Mixin) + , c (List VCat (Identity ExtraSource) ExtraSource) , c (List CommaVCat (Identity ModuleReexport) ModuleReexport) , c (List FSep (MQuoted Extension) Extension) , c (List FSep (MQuoted Language) Language) @@ -184,7 +185,6 @@ libraryFieldGrammar , c (List FSep (SymbolicPathNT Include File) (SymbolicPath Include File)) , c (List FSep (RelativePathNT Framework File) (RelativePath Framework File)) , c (List FSep (RelativePathNT Include File) (RelativePath Include File)) - , c (List VCat (SymbolicPathNT Pkg File) (SymbolicPath Pkg File)) , c (List VCat Token String) , c (MQuoted Language) ) @@ -225,6 +225,7 @@ foreignLibFieldGrammar , c (List CommaFSep (Identity PkgconfigDependency) PkgconfigDependency) , c (List CommaVCat (Identity Dependency) Dependency) , c (List CommaVCat (Identity Mixin) Mixin) + , c (List VCat (Identity ExtraSource) ExtraSource) , c (List FSep (Identity ForeignLibOption) ForeignLibOption) , c (List FSep (MQuoted Extension) Extension) , c (List FSep (MQuoted Language) Language) @@ -236,7 +237,6 @@ foreignLibFieldGrammar , c (List FSep (RelativePathNT Framework File) (RelativePath Framework File)) , c (List FSep (RelativePathNT Include File) (RelativePath Include File)) , c (List FSep (RelativePathNT Source File) (RelativePath Source File)) - , c (List VCat (SymbolicPathNT Pkg File) (SymbolicPath Pkg File)) , c (List NoCommaFSep Token' String) , c (List VCat (MQuoted ModuleName) ModuleName) , c (List VCat Token String) @@ -267,6 +267,7 @@ executableFieldGrammar , c (List CommaFSep (Identity PkgconfigDependency) PkgconfigDependency) , c (List CommaVCat (Identity Dependency) Dependency) , c (List CommaVCat (Identity Mixin) Mixin) + , c (List VCat (Identity ExtraSource) ExtraSource) , c (List FSep (MQuoted Extension) Extension) , c (List FSep (MQuoted Language) Language) , c (List FSep (SymbolicPathNT Pkg (Dir Framework)) (SymbolicPath Pkg (Dir Framework))) @@ -276,7 +277,6 @@ executableFieldGrammar , c (List FSep (SymbolicPathNT Include File) (SymbolicPath Include File)) , c (List FSep (RelativePathNT Framework File) (RelativePath Framework File)) , c (List FSep (RelativePathNT Include File) (RelativePath Include File)) - , c (List VCat (SymbolicPathNT Pkg File) (SymbolicPath Pkg File)) , c (RelativePathNT Source File) , c (List NoCommaFSep Token' String) , c (List VCat (MQuoted ModuleName) ModuleName) @@ -343,6 +343,7 @@ testSuiteFieldGrammar , c (List CommaFSep Token String) , c (List CommaVCat (Identity Dependency) Dependency) , c (List CommaVCat (Identity Mixin) Mixin) + , c (List VCat (Identity ExtraSource) ExtraSource) , c (List FSep (MQuoted Extension) Extension) , c (List FSep (MQuoted Language) Language) , c (List NoCommaFSep Token' String) @@ -354,7 +355,6 @@ testSuiteFieldGrammar , c (List FSep (SymbolicPathNT Include File) (SymbolicPath Include File)) , c (List FSep (RelativePathNT Framework File) (RelativePath Framework File)) , c (List FSep (RelativePathNT Include File) (RelativePath Include File)) - , c (List VCat (SymbolicPathNT Pkg File) (SymbolicPath Pkg File)) , c (RelativePathNT Source File) , c (List VCat Token String) , c (MQuoted Language) @@ -490,6 +490,7 @@ benchmarkFieldGrammar , c (List CommaFSep (Identity PkgconfigDependency) PkgconfigDependency) , c (List CommaVCat (Identity Dependency) Dependency) , c (List CommaVCat (Identity Mixin) Mixin) + , c (List VCat (Identity ExtraSource) ExtraSource) , c (List FSep (MQuoted Extension) Extension) , c (List FSep (MQuoted Language) Language) , c (List NoCommaFSep Token' String) @@ -501,7 +502,6 @@ benchmarkFieldGrammar , c (List FSep (SymbolicPathNT Include File) (SymbolicPath Include File)) , c (List FSep (RelativePathNT Framework File) (RelativePath Framework File)) , c (List FSep (RelativePathNT Include File) (RelativePath Include File)) - , c (List VCat (SymbolicPathNT Pkg File) (SymbolicPath Pkg File)) , c (RelativePathNT Source File) , c (List VCat Token String) , c (MQuoted Language) @@ -595,6 +595,7 @@ buildInfoFieldGrammar , c (List CommaFSep (Identity PkgconfigDependency) PkgconfigDependency) , c (List CommaVCat (Identity Dependency) Dependency) , c (List CommaVCat (Identity Mixin) Mixin) + , c (List VCat (Identity ExtraSource) ExtraSource) , c (List FSep (MQuoted Extension) Extension) , c (List FSep (MQuoted Language) Language) , c (List NoCommaFSep Token' String) @@ -606,7 +607,6 @@ buildInfoFieldGrammar , c (List FSep (SymbolicPathNT Include File) (SymbolicPath Include File)) , c (List FSep (RelativePathNT Framework File) (RelativePath Framework File)) , c (List FSep (RelativePathNT Include File) (RelativePath Include File)) - , c (List VCat (SymbolicPathNT Pkg File) (SymbolicPath Pkg File)) , c (List VCat Token String) , c (MQuoted Language) ) @@ -649,16 +649,16 @@ buildInfoFieldGrammar = do frameworks <- monoidalFieldAla "frameworks" (alaList' FSep RelativePathNT) L.frameworks extraFrameworkDirs <- monoidalFieldAla "extra-framework-dirs" (alaList' FSep SymbolicPathNT) L.extraFrameworkDirs asmSources <- - monoidalFieldAla "asm-sources" (alaList' VCat SymbolicPathNT) L.asmSources + monoidalFieldAla "asm-sources" formatExtraSources L.asmSources ^^^ availableSince CabalSpecV3_0 [] cmmSources <- - monoidalFieldAla "cmm-sources" (alaList' VCat SymbolicPathNT) L.cmmSources + monoidalFieldAla "cmm-sources" formatExtraSources L.cmmSources ^^^ availableSince CabalSpecV3_0 [] - cSources <- monoidalFieldAla "c-sources" (alaList' VCat SymbolicPathNT) L.cSources + cSources <- monoidalFieldAla "c-sources" formatExtraSources L.cSources cxxSources <- - monoidalFieldAla "cxx-sources" (alaList' VCat SymbolicPathNT) L.cxxSources + monoidalFieldAla "cxx-sources" formatExtraSources L.cxxSources ^^^ availableSince CabalSpecV2_2 [] - jsSources <- monoidalFieldAla "js-sources" (alaList' VCat SymbolicPathNT) L.jsSources + jsSources <- monoidalFieldAla "js-sources" formatExtraSources L.jsSources hsSourceDirs <- hsSourceDirsGrammar otherModules <- monoidalFieldAla "other-modules" formatOtherModules L.otherModules virtualModules <- @@ -872,6 +872,9 @@ formatOtherExtensions = alaList' FSep MQuoted formatOtherModules :: [ModuleName] -> List VCat (MQuoted ModuleName) ModuleName formatOtherModules = alaList' VCat MQuoted +formatExtraSources :: [ExtraSource] -> List VCat (Identity ExtraSource) ExtraSource +formatExtraSources = alaList' VCat Identity + ------------------------------------------------------------------------------- -- newtypes ------------------------------------------------------------------------------- diff --git a/Cabal-syntax/src/Distribution/Types/BuildInfo.hs b/Cabal-syntax/src/Distribution/Types/BuildInfo.hs index 6e94a3f26db..91ab1fb5eb9 100644 --- a/Cabal-syntax/src/Distribution/Types/BuildInfo.hs +++ b/Cabal-syntax/src/Distribution/Types/BuildInfo.hs @@ -19,6 +19,7 @@ import Prelude () import Distribution.Types.Dependency import Distribution.Types.ExeDependency +import Distribution.Types.ExtraSource import Distribution.Types.LegacyExeDependency import Distribution.Types.Mixin import Distribution.Types.PkgconfigDependency @@ -72,13 +73,16 @@ data BuildInfo = BuildInfo -- ^ support frameworks for Mac OS X , extraFrameworkDirs :: [SymbolicPath Pkg (Dir Framework)] -- ^ extra locations to find frameworks. - , asmSources :: [SymbolicPath Pkg File] - -- ^ Assembly files. - , cmmSources :: [SymbolicPath Pkg File] - -- ^ C-- files. - , cSources :: [SymbolicPath Pkg File] - , cxxSources :: [SymbolicPath Pkg File] - , jsSources :: [SymbolicPath Pkg File] + , asmSources :: [ExtraSource] + -- ^ Assembly source files + , cmmSources :: [ExtraSource] + -- ^ C-- source files + , cSources :: [ExtraSource] + -- ^ C source files + , cxxSources :: [ExtraSource] + -- ^ C++ source files + , jsSources :: [ExtraSource] + -- ^ JavaScript source files , hsSourceDirs :: [SymbolicPath Pkg (Dir Source)] -- ^ where to look for the Haskell module hierarchy , -- NB: these are symbolic paths are not relative paths, diff --git a/Cabal-syntax/src/Distribution/Types/BuildInfo/Lens.hs b/Cabal-syntax/src/Distribution/Types/BuildInfo/Lens.hs index e554f43ebdf..a10115a4b36 100644 --- a/Cabal-syntax/src/Distribution/Types/BuildInfo/Lens.hs +++ b/Cabal-syntax/src/Distribution/Types/BuildInfo/Lens.hs @@ -15,6 +15,7 @@ import Distribution.ModuleName (ModuleName) import Distribution.Types.BuildInfo (BuildInfo) import Distribution.Types.Dependency (Dependency) import Distribution.Types.ExeDependency (ExeDependency) +import Distribution.Types.ExtraSource (ExtraSource) import Distribution.Types.LegacyExeDependency (LegacyExeDependency) import Distribution.Types.Mixin (Mixin) import Distribution.Types.PkgconfigDependency (PkgconfigDependency) @@ -83,23 +84,23 @@ class HasBuildInfo a where extraFrameworkDirs = buildInfo . extraFrameworkDirs {-# INLINE extraFrameworkDirs #-} - asmSources :: Lens' a [SymbolicPath Pkg File] + asmSources :: Lens' a [ExtraSource] asmSources = buildInfo . asmSources {-# INLINE asmSources #-} - cmmSources :: Lens' a [SymbolicPath Pkg File] + cmmSources :: Lens' a [ExtraSource] cmmSources = buildInfo . cmmSources {-# INLINE cmmSources #-} - cSources :: Lens' a [SymbolicPath Pkg File] + cSources :: Lens' a [ExtraSource] cSources = buildInfo . cSources {-# INLINE cSources #-} - cxxSources :: Lens' a [SymbolicPath Pkg File] + cxxSources :: Lens' a [ExtraSource] cxxSources = buildInfo . cxxSources {-# INLINE cxxSources #-} - jsSources :: Lens' a [SymbolicPath Pkg File] + jsSources :: Lens' a [ExtraSource] jsSources = buildInfo . jsSources {-# INLINE jsSources #-} diff --git a/Cabal-syntax/src/Distribution/Types/ExtraSource.hs b/Cabal-syntax/src/Distribution/Types/ExtraSource.hs new file mode 100644 index 00000000000..dcdb5d18e98 --- /dev/null +++ b/Cabal-syntax/src/Distribution/Types/ExtraSource.hs @@ -0,0 +1,146 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} + +module Distribution.Types.ExtraSource + ( ExtraSource (..) + , extraSourceFromPath + , extraSourceOptsSpecVersion + ) where + +import Distribution.Compat.Prelude +import Prelude () + +import Distribution.CabalSpecVersion (CabalSpecVersion (..), showCabalSpecVersion) +import Distribution.Parsec +import Distribution.Pretty +import Distribution.Utils.Path (FileOrDir (..), Pkg, SymbolicPath) + +import qualified Distribution.Compat.CharParsing as P +import Distribution.FieldGrammar.Newtypes (SymbolicPathNT (..)) +import qualified Text.PrettyPrint as PP + +-- | An entry in one of the extra-source fields (@c-sources@, @cxx-sources@, +-- @asm-sources@, @cmm-sources@, @js-sources@): a source file together with +-- any per-file options to pass to the compiler for that file. +data ExtraSource = ExtraSource + { extraSourceFile :: SymbolicPath Pkg File + , extraSourceOpts :: String + -- ^ The text between the parentheses, verbatim. It is split into individual + -- options by @splitArgs@ where it is used, so that these options obey the + -- same quoting rules as @--PROG-options@ on the command line. Empty when the + -- entry carries no options at all. + } + deriving (Generic, Show, Read, Eq, Ord, Data) + +instance Binary ExtraSource +instance Structured ExtraSource +instance NFData ExtraSource + +-- | Per-file options, spelled @file.c (-opt1 -opt2)@, are only recognised from +-- @cabal-version: 3.20@ onwards; below that they are a parse error, so that a +-- @.cabal@ file cannot express something an older 'Cabal' would misread (this +-- is the class of bug described in +-- ). An older 'Cabal' does not +-- reject the syntax: a path is any non-space token, so @Cabal-syntax-3.14@ +-- reads @foo.c (-DFOO -O2) bar.c@ as four source files and only fails when it +-- tries to compile them. 3.20 is the specification version under development; +-- 3.18 is already published, so a released 'Cabal' 3.18 would misread the +-- syntax and cannot serve as the gate. +extraSourceOptsSpecVersion :: CabalSpecVersion +extraSourceOptsSpecVersion = CabalSpecV3_20 + +instance Parsec ExtraSource where + parsec = do + SymbolicPathNT path <- parsec <* P.spaces + -- Always consume any parenthesised options so that they are not mistaken + -- for a second file name; whether they are allowed at all depends on the + -- spec version. + mopts <- P.optional parsecExtraSourceOpts + opts <- maybe (pure "") (<$ versionGuardExtraSourceOpts) mopts + return (ExtraSource path opts) + +versionGuardExtraSourceOpts :: CabalParsing m => m () +versionGuardExtraSourceOpts = do + csv <- askCabalSpecVersion + when (csv < extraSourceOptsSpecVersion) $ + fail $ + unwords + [ "Per-file options on extra source files used." + , "To use this syntax the package needs to specify at least 'cabal-version: " + ++ showCabalSpecVersion extraSourceOptsSpecVersion + ++ "'." + ] + +-- | Parse the parenthesised per-file options, taking the text between the +-- parentheses verbatim. +-- +-- @(@ and @)@ are structural: they may nest, as long as they balance, so +-- @-DX=f(1)@ needs no escaping. A backslash escapes @(@, @)@ and @\\@; any +-- other backslash stands for itself, so a Windows path such as @C:\\foo\\bar@ +-- needs no doubling. In particular @\\"@ is passed through untouched, for +-- @splitArgs@ to interpret later. +parsecExtraSourceOpts :: (P.CharParsing m, Monad m) => m String +parsecExtraSourceOpts = P.char '(' *> go (0 :: Int) <* P.spaces + where + go depth = do + chunk <- P.munch (\c -> c /= '(' && c /= ')' && c /= '\\') + let continue d s = ((chunk ++ s) ++) <$> go d + c <- P.anyChar + case c of + '\\' -> do + -- Never a parenthesis in the non-escape branch, so the nesting + -- depth below stays accurate. + e <- P.anyChar + continue depth (if e `elem` "()\\" then [e] else ['\\', e]) + '(' -> continue (depth + 1) "(" + ')' + | depth == 0 -> pure chunk + | otherwise -> continue (depth - 1) ")" + _ -> continue depth [c] + +-- | Render the per-file options, escaping only when a verbatim copy would not +-- be read back as itself by 'parsecExtraSourceOpts'. +showExtraSourceOpts :: String -> PP.Doc +showExtraSourceOpts opts + | verbatimSafe opts = PP.text opts + | otherwise = PP.text (concat (zipWith esc opts (followedBy opts))) + where + -- Once we are escaping at all every parenthesis has to be escaped, but a + -- backslash only where it would otherwise be read as escaping whatever + -- comes after it. + esc '\\' c + | c `elem` "()\\" = "\\\\" + | otherwise = "\\" + esc c _ + | c `elem` "()" = ['\\', c] + | otherwise = [c] + +-- | Whether 'showExtraSourceOpts' can print this text as it stands: the +-- parentheses have to balance, and no backslash may sit in front of a +-- character the parser would treat as escaped. +verbatimSafe :: String -> Bool +verbatimSafe opts = + balanced (0 :: Int) opts && not (or (zipWith risky opts (followedBy opts))) + where + risky '\\' c = c `elem` "()\\" + risky _ _ = False + balanced d [] = d == 0 + balanced d ('(' : cs) = balanced (d + 1) cs + balanced 0 (')' : _) = False + balanced d (')' : cs) = balanced (d - 1) cs + balanced d (_ : cs) = balanced d cs + +-- | The character each character is followed by once rendered. The last one is +-- followed by the closing parenthesis 'pretty' appends, which is why a +-- backslash at the very end still has to be escaped. +followedBy :: String -> String +followedBy opts = drop 1 opts ++ ")" + +instance Pretty ExtraSource where + pretty (ExtraSource path opts) + | null opts = pretty (SymbolicPathNT path) + | otherwise = pretty (SymbolicPathNT path) <+> PP.parens (showExtraSourceOpts opts) + +extraSourceFromPath :: SymbolicPath Pkg File -> ExtraSource +extraSourceFromPath fp = ExtraSource fp mempty diff --git a/Cabal-tests/Cabal-tests.cabal b/Cabal-tests/Cabal-tests.cabal index 6044ba1d780..c3927c69b25 100644 --- a/Cabal-tests/Cabal-tests.cabal +++ b/Cabal-tests/Cabal-tests.cabal @@ -43,6 +43,7 @@ test-suite unit-tests UnitTests.Distribution.Simple.Utils UnitTests.Distribution.SPDX UnitTests.Distribution.System + UnitTests.Distribution.Types.ExtraSource UnitTests.Distribution.Types.GenericPackageDescription UnitTests.Distribution.Utils.CharSet UnitTests.Distribution.Utils.Generic diff --git a/Cabal-tests/tests/NoThunks.hs b/Cabal-tests/tests/NoThunks.hs index 167b06102bd..5feb810f4bf 100644 --- a/Cabal-tests/tests/NoThunks.hs +++ b/Cabal-tests/tests/NoThunks.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleInstances #-} #if !(__GLASGOW_HASKELL__ >= 806 && defined(MIN_VERSION_nothunks)) module Main (main) where main :: IO () @@ -70,6 +71,7 @@ instance NoThunks ConfVar instance NoThunks Dependency instance NoThunks Executable instance NoThunks ExecutableScope +instance NoThunks ExtraSource instance NoThunks FlagName instance NoThunks ForeignLib instance NoThunks ForeignLibOption diff --git a/Cabal-tests/tests/ParserTests.hs b/Cabal-tests/tests/ParserTests.hs index 88c40fde27f..3deff0d57f2 100644 --- a/Cabal-tests/tests/ParserTests.hs +++ b/Cabal-tests/tests/ParserTests.hs @@ -142,6 +142,7 @@ errorTests = testGroup "errors" , errorTest "MiniAgda.cabal" , errorTest "big-version.cabal" , errorTest "anynone.cabal" + , errorTest "extra-source-opts-old-spec.cabal" ] errorTest :: FilePath -> TestTree @@ -208,6 +209,7 @@ regressionTests = testGroup "regressions" , regressionTest "anynone.cabal" , regressionTest "monad-param.cabal" , regressionTest "hasktorch.cabal" + , regressionTest "extra-source-opts.cabal" ] regressionTest :: FilePath -> TestTree diff --git a/Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.cabal b/Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.cabal new file mode 100644 index 00000000000..30cc471e772 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.cabal @@ -0,0 +1,12 @@ +cabal-version: 3.4 +name: extra-source-opts-old-spec +version: 0 + +-- Per-file options in extra-source fields are only available from +-- cabal-version: 3.20. Below that they must be rejected rather than +-- interpreted, so that a package cannot express something an older Cabal would +-- not understand (see #9331). +library + default-language: Haskell2010 + build-depends: base + c-sources: cbits/foo.c (-DFOO) diff --git a/Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.errors b/Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.errors new file mode 100644 index 00000000000..9e50a93964e --- /dev/null +++ b/Cabal-tests/tests/ParserTests/errors/extra-source-opts-old-spec.errors @@ -0,0 +1,5 @@ +VERSION: Just (mkVersion [3,4]) +extra-source-opts-old-spec.cabal:12:33: +unexpected Per-file options on extra source files used. To use this syntax the package needs to specify at least 'cabal-version: 3.20'. +expecting white space + diff --git a/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.cabal b/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.cabal new file mode 100644 index 00000000000..61d9c5eafd1 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.cabal @@ -0,0 +1,20 @@ +cabal-version: 3.20 +name: extra-source-opts +version: 0 + +-- Per-file options on extra source files, available from cabal-version: 3.20. +-- Accepted on all five extra-source kinds. The text between the parentheses +-- is taken verbatim; only '(' and ')' are structural (they may nest, and a +-- backslash escapes them), and the text is split into individual options the +-- same way --PROG-options is. +library + default-language: Haskell2010 + build-depends: base + c-sources: + cbits/foo.c (-DFOO -O2) + cbits/bar.c + cbits/quoted.c ("-DGREETING=\"hi there\"" -DPAREN=\) -DNESTED=f(1)) + cxx-sources: cbits/baz.cpp (-std=c++17) + asm-sources: cbits/qux.s (-DASM) + cmm-sources: cbits/rts.cmm (-DCMM) + js-sources: jsbits/glue.js (-DJS) diff --git a/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.expr b/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.expr new file mode 100644 index 00000000000..23c39f6084e --- /dev/null +++ b/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.expr @@ -0,0 +1,146 @@ +GenericPackageDescription { + packageDescription = + PackageDescription { + specVersion = CabalSpecV3_20, + package = PackageIdentifier { + pkgName = PackageName + "extra-source-opts", + pkgVersion = mkVersion [0]}, + licenseRaw = Left NONE, + licenseFiles = [], + copyright = "", + maintainer = "", + author = "", + stability = "", + testedWith = [], + homepage = "", + pkgUrl = "", + bugReports = "", + sourceRepos = [], + synopsis = "", + description = "", + category = "", + customFieldsPD = [], + buildTypeRaw = Nothing, + setupBuildInfo = Nothing, + library = Nothing, + subLibraries = [], + executables = [], + foreignLibs = [], + testSuites = [], + benchmarks = [], + dataFiles = [], + dataDir = SymbolicPath ".", + extraSrcFiles = [], + extraTmpFiles = [], + extraDocFiles = [], + extraFiles = []}, + gpdScannedVersion = Nothing, + genPackageFlags = [], + condLibrary = Just + CondNode { + condTreeData = Library { + libName = LMainLibName, + exposedModules = [], + reexportedModules = [], + signatures = [], + libExposed = True, + libVisibility = + LibraryVisibilityPublic, + libBuildInfo = BuildInfo { + buildable = True, + buildTools = [], + buildToolDepends = [], + cppOptions = [], + asmOptions = [], + cmmOptions = [], + ccOptions = [], + cxxOptions = [], + jsppOptions = [], + ldOptions = [], + hsc2hsOptions = [], + pkgconfigDepends = [], + frameworks = [], + extraFrameworkDirs = [], + asmSources = [ + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/qux.s", + extraSourceOpts = "-DASM"}], + cmmSources = [ + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/rts.cmm", + extraSourceOpts = "-DCMM"}], + cSources = [ + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/foo.c", + extraSourceOpts = "-DFOO -O2"}, + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/bar.c", + extraSourceOpts = ""}, + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/quoted.c", + extraSourceOpts = + "\"-DGREETING=\\\"hi there\\\"\" -DPAREN=) -DNESTED=f(1)"}], + cxxSources = [ + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/baz.cpp", + extraSourceOpts = + "-std=c++17"}], + jsSources = [ + ExtraSource { + extraSourceFile = SymbolicPath + "jsbits/glue.js", + extraSourceOpts = "-DJS"}], + hsSourceDirs = [], + otherModules = [], + virtualModules = [], + autogenModules = [], + defaultLanguage = Just + Haskell2010, + otherLanguages = [], + defaultExtensions = [], + otherExtensions = [], + oldExtensions = [], + extraLibs = [], + extraLibsStatic = [], + extraGHCiLibs = [], + extraBundledLibs = [], + extraLibFlavours = [], + extraDynLibFlavours = [], + extraLibDirs = [], + extraLibDirsStatic = [], + includeDirs = [], + includes = [], + autogenIncludes = [], + installIncludes = [], + options = PerCompilerFlavor + [] + [], + profOptions = PerCompilerFlavor + [] + [], + sharedOptions = + PerCompilerFlavor [] [], + profSharedOptions = + PerCompilerFlavor [] [], + staticOptions = + PerCompilerFlavor [] [], + customFieldsBI = [], + targetBuildDepends = [ + Dependency + (PackageName "base") + (OrLaterVersion (mkVersion [0])) + mainLibSet], + mixins = []}}, + condTreeComponents = []}, + condSubLibraries = [], + condForeignLibs = [], + condExecutables = [], + condTestSuites = [], + condBenchmarks = []} diff --git a/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.format b/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.format new file mode 100644 index 00000000000..2857028c4df --- /dev/null +++ b/Cabal-tests/tests/ParserTests/regressions/extra-source-opts.format @@ -0,0 +1,16 @@ +cabal-version: 3.20 +name: extra-source-opts +version: 0 + +library + asm-sources: cbits/qux.s (-DASM) + cmm-sources: cbits/rts.cmm (-DCMM) + c-sources: + cbits/foo.c (-DFOO -O2) + cbits/bar.c + cbits/quoted.c ("-DGREETING=\"hi there\"" -DPAREN=\) -DNESTED=f\(1\)) + + cxx-sources: cbits/baz.cpp (-std=c++17) + js-sources: jsbits/glue.js (-DJS) + default-language: Haskell2010 + build-depends: base diff --git a/Cabal-tests/tests/ParserTests/regressions/libpq1.expr b/Cabal-tests/tests/ParserTests/regressions/libpq1.expr index 1211515687a..cc5d2e9ce0b 100644 --- a/Cabal-tests/tests/ParserTests/regressions/libpq1.expr +++ b/Cabal-tests/tests/ParserTests/regressions/libpq1.expr @@ -157,8 +157,10 @@ GenericPackageDescription { asmSources = [], cmmSources = [], cSources = [ - SymbolicPath - "cbits/noticehandlers.c"], + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/noticehandlers.c", + extraSourceOpts = ""}], cxxSources = [], jsSources = [], hsSourceDirs = [ diff --git a/Cabal-tests/tests/ParserTests/regressions/libpq2.expr b/Cabal-tests/tests/ParserTests/regressions/libpq2.expr index 968d07ea46b..932837d1bba 100644 --- a/Cabal-tests/tests/ParserTests/regressions/libpq2.expr +++ b/Cabal-tests/tests/ParserTests/regressions/libpq2.expr @@ -162,8 +162,10 @@ GenericPackageDescription { asmSources = [], cmmSources = [], cSources = [ - SymbolicPath - "cbits/noticehandlers.c"], + ExtraSource { + extraSourceFile = SymbolicPath + "cbits/noticehandlers.c", + extraSourceOpts = ""}], cxxSources = [], jsSources = [], hsSourceDirs = [ diff --git a/Cabal-tests/tests/UnitTests.hs b/Cabal-tests/tests/UnitTests.hs index 16b80813478..9e579ed5d5b 100644 --- a/Cabal-tests/tests/UnitTests.hs +++ b/Cabal-tests/tests/UnitTests.hs @@ -29,6 +29,7 @@ import qualified UnitTests.Distribution.SPDX (spdxTests) import qualified UnitTests.Distribution.Described import qualified UnitTests.Distribution.CabalSpecVersion import qualified UnitTests.Distribution.Types.GenericPackageDescription +import qualified UnitTests.Distribution.Types.ExtraSource (extraSourceTests) tests :: TestTree tests = @@ -67,6 +68,8 @@ tests = UnitTests.Distribution.Version.versionTests , testGroup "Distribution.Types.PkgconfigVersion(Range)" UnitTests.Distribution.PkgconfigVersion.pkgconfigVersionTests + , testGroup "Distribution.Types.ExtraSource" + UnitTests.Distribution.Types.ExtraSource.extraSourceTests , testGroup "Distribution.SPDX" UnitTests.Distribution.SPDX.spdxTests , UnitTests.Distribution.Utils.CharSet.tests diff --git a/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs b/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs index 10b052bebe2..2f3dd6bcafa 100644 --- a/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs +++ b/Cabal-tests/tests/UnitTests/Distribution/Simple/Program/GHC.hs @@ -10,7 +10,7 @@ import Distribution.Types.ParStrat import Distribution.Simple.Flag import Distribution.Simple.Compiler (Compiler(..), CompilerId(..), CompilerFlavor(..), AbiTag(NoAbiTag)) import Distribution.PackageDescription (emptyPackageDescription) -import Distribution.Simple.Program.GHC (GhcOptions, normaliseGhcArgs, renderGhcOptions, ghcOptNumJobs, ghcOptJSppOptions) +import Distribution.Simple.Program.GHC (GhcOptions, normaliseGhcArgs, renderGhcOptions, ghcOptNumJobs, ghcOptExtra, ghcOptJSppOptions) import Distribution.Version (mkVersion, Version) tests :: TestTree @@ -67,6 +67,10 @@ tests = testGroup "Distribution.Simple.Program.GHC" let flags = renderWith (mkVersion [9,10,1]) (mempty { ghcOptJSppOptions = ["-DJS"] }) assertBool ("unexpected -optJSP in " ++ show flags) ("-optJSP-DJS" `notElem` flags) + , testCase "C-- options are plain GHC options at any version" $ do + let flags = renderWith (mkVersion [9,10,1]) + (mempty { ghcOptExtra = ["-mavx2"] }) + assertBool ("expected -mavx2 in " ++ show flags) ("-mavx2" `elem` flags) ] ] diff --git a/Cabal-tests/tests/UnitTests/Distribution/Types/ExtraSource.hs b/Cabal-tests/tests/UnitTests/Distribution/Types/ExtraSource.hs new file mode 100644 index 00000000000..6ae0fadfff5 --- /dev/null +++ b/Cabal-tests/tests/UnitTests/Distribution/Types/ExtraSource.hs @@ -0,0 +1,88 @@ +module UnitTests.Distribution.Types.ExtraSource (extraSourceTests) where + +import Test.Tasty +import Test.Tasty.HUnit +import Test.Tasty.QuickCheck + +import Distribution.Parsec (eitherParsec) +import Distribution.Pretty +import Distribution.Types.ExtraSource +import Distribution.Utils.Path (makeSymbolicPath) + +import Test.QuickCheck.Instances.Cabal () + +extraSourceTests :: [TestTree] +extraSourceTests = + [ testProperty "eitherParsec . prettyShow = Right" prop_parse_disp + , testGroup "parse" (map parseCase parseCases) + , testGroup "render" (map renderCase renderCases) + , testGroup "reject" (map rejectCase rejectCases) + ] + +-- | The options are kept verbatim, so a round-trip only has to survive the +-- escaping that rendering applies to parentheses and backslashes. +-- +-- Note this parses at 'cabalSpecLatest', which is at least the version that +-- introduced per-file options; below that they are rejected outright. +prop_parse_disp :: ExtraSource -> Property +prop_parse_disp es = counterexample (show (prettyShow es)) $ + eitherParsec (prettyShow es) === Right es + +extraSource :: FilePath -> String -> ExtraSource +extraSource p = ExtraSource (makeSymbolicPath p) + +-- | Everything between the parentheses is taken verbatim. Only @(@, @)@ and a +-- backslash in front of one of those (or of another backslash) has any +-- meaning; splitting into individual options happens later, at the use site. +parseCases :: [(String, ExtraSource)] +parseCases = + [ ("a.c", extraSource "a.c" "") + , ("a.c (-DFOO -O2)", extraSource "a.c" "-DFOO -O2") + , ("a.c (\"-DX=a b\")", extraSource "a.c" "\"-DX=a b\"") + -- A quote has no meaning here; splitArgs interprets it later. + , ("a.c (-DX=\"a b\")", extraSource "a.c" "-DX=\"a b\"") + -- Parentheses may nest as long as they balance. + , ("a.c (-DX=f(1))", extraSource "a.c" "-DX=f(1)") + -- An unbalanced parenthesis has to be escaped. + , ("a.c (-DPAREN=\\))", extraSource "a.c" "-DPAREN=)") + -- A backslash not in front of '(', ')' or '\\' stands for itself, so + -- Windows paths need no doubling. + , ("a.c (-DPATH=C:\\foo\\bar)", extraSource "a.c" "-DPATH=C:\\foo\\bar") + , ("a.c (\\\\)", extraSource "a.c" "\\") + ] + +parseCase :: (String, ExtraSource) -> TestTree +parseCase (input, expected) = + testCase (show input) $ eitherParsec input @?= Right expected + +-- | Rendering escapes only when a verbatim copy would not be read back as +-- itself, so options that need no escaping survive unchanged. +renderCases :: [(ExtraSource, String)] +renderCases = + [ (extraSource "a.c" "", "a.c") + , (extraSource "a.c" "-DFOO -O2", "a.c (-DFOO -O2)") + , (extraSource "a.c" "-DX=f(1)", "a.c (-DX=f(1))") + , (extraSource "a.c" "-DPAREN=)", "a.c (-DPAREN=\\))") + , (extraSource "a.c" "-DPATH=C:\\foo\\bar", "a.c (-DPATH=C:\\foo\\bar)") + -- Only the trailing backslash needs escaping: it would otherwise escape + -- the closing parenthesis. The one inside the path is left alone. + , (extraSource "a.c" "C:\\foo\\", "a.c (C:\\foo\\\\)") + ] + +renderCase :: (ExtraSource, String) -> TestTree +renderCase (es, expected) = + testCase (show expected) $ prettyShow es @?= expected + +-- | An unbalanced parenthesis ends the options early, and quoting does not +-- protect it: the scan runs before any option lexing. +rejectCases :: [String] +rejectCases = + [ "a.c (\"-DPAREN=)\")" + , "a.c (-DFOO" + ] + +rejectCase :: String -> TestTree +rejectCase input = testCase (show input) $ + case eitherParsec input :: Either String ExtraSource of + Left _ -> return () + Right es -> assertFailure ("expected a parse error, got " ++ show es) diff --git a/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs b/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs index fb3b3454ee3..dd906e47203 100644 --- a/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs +++ b/Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs @@ -33,8 +33,8 @@ md5Check proxy md5Int = structureHash proxy @?= md5FromInteger md5Int md5CheckGenericPackageDescription :: Proxy GenericPackageDescription -> Assertion md5CheckGenericPackageDescription proxy = md5Check proxy - 0x2ab5eeae7337cba494221e344aaaaafc + 0x70ad3155fcabc362a211ae29e930b525 md5CheckLocalBuildInfo :: Proxy LocalBuildInfo -> Assertion md5CheckLocalBuildInfo proxy = md5Check proxy - 0x3398bd7f316ecb8f535cbe78498a89a4 + 0x754d75bacfbebb2c1f0d8576c822b2e1 diff --git a/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs b/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs index f28af98b600..bb3a0379178 100644 --- a/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs +++ b/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs @@ -1,5 +1,6 @@ {-# OPTIONS_GHC -freduction-depth=0 #-} {-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE FlexibleInstances #-} module Data.TreeDiff.Instances.Cabal () where import Data.TreeDiff @@ -104,6 +105,7 @@ instance ToExpr ExeDependency instance ToExpr Executable instance ToExpr ExecutableScope instance ToExpr ExposedModule +instance ToExpr ExtraSource instance ToExpr FlagAssignment instance ToExpr FlagName instance ToExpr ForeignLib diff --git a/Cabal/src/Distribution/PackageDescription/Check/Target.hs b/Cabal/src/Distribution/PackageDescription/Check/Target.hs index 5e05b6fb5c6..546e89afaea 100644 --- a/Cabal/src/Distribution/PackageDescription/Check/Target.hs +++ b/Cabal/src/Distribution/PackageDescription/Check/Target.hs @@ -447,11 +447,11 @@ checkBuildInfoPathsContent bi = do -- Paths well-formedness check for BuildInfo. checkBuildInfoPathsWellFormedness :: Monad m => BuildInfo -> CheckM m () checkBuildInfoPathsWellFormedness bi = do - mapM_ (checkPath False "asm-sources" PathKindFile . getSymbolicPath) (asmSources bi) - mapM_ (checkPath False "cmm-sources" PathKindFile . getSymbolicPath) (cmmSources bi) - mapM_ (checkPath False "c-sources" PathKindFile . getSymbolicPath) (cSources bi) - mapM_ (checkPath False "cxx-sources" PathKindFile . getSymbolicPath) (cxxSources bi) - mapM_ (checkPath False "js-sources" PathKindFile . getSymbolicPath) (jsSources bi) + mapM_ (checkPath False "asm-sources" PathKindFile . getSymbolicPath . extraSourceFile) (asmSources bi) + mapM_ (checkPath False "cmm-sources" PathKindFile . getSymbolicPath . extraSourceFile) (cmmSources bi) + mapM_ (checkPath False "c-sources" PathKindFile . getSymbolicPath . extraSourceFile) (cSources bi) + mapM_ (checkPath False "cxx-sources" PathKindFile . getSymbolicPath . extraSourceFile) (cxxSources bi) + mapM_ (checkPath False "js-sources" PathKindFile . getSymbolicPath . extraSourceFile) (jsSources bi) mapM_ (checkPath False "install-includes" PathKindFile . getSymbolicPath) (installIncludes bi) @@ -517,8 +517,8 @@ checkBuildInfoFeatures bi sv = do (PackageBuildWarning CVExtensionsDeprecated) -- asm-sources, cmm-sources and friends only w/ spec ≥ 1.10 - checkCVSources (map getSymbolicPath $ asmSources bi) - checkCVSources (map getSymbolicPath $ cmmSources bi) + checkCVSources (map (getSymbolicPath . extraSourceFile) $ asmSources bi) + checkCVSources (map (getSymbolicPath . extraSourceFile) $ cmmSources bi) checkCVSources (extraBundledLibs bi) checkCVSources (extraLibFlavours bi) diff --git a/Cabal/src/Distribution/Simple/Build.hs b/Cabal/src/Distribution/Simple/Build.hs index 2e41bb1aa35..d59984e1aaa 100644 --- a/Cabal/src/Distribution/Simple/Build.hs +++ b/Cabal/src/Distribution/Simple/Build.hs @@ -654,35 +654,35 @@ generateCode codeGens nm pdesc bi lbi clbi verbosity = do addExtraCSources :: BuildInfo -> [SymbolicPath Pkg File] -> BuildInfo addExtraCSources bi extras = bi{cSources = new} where - new = ordNub (extras ++ cSources bi) + new = ordNub (map extraSourceFromPath extras ++ cSources bi) -- | Add extra C++ sources generated by preprocessing to build -- information. addExtraCxxSources :: BuildInfo -> [SymbolicPath Pkg File] -> BuildInfo addExtraCxxSources bi extras = bi{cxxSources = new} where - new = ordNub (extras ++ cxxSources bi) + new = ordNub (map extraSourceFromPath extras ++ cxxSources bi) -- | Add extra C-- sources generated by preprocessing to build -- information. addExtraCmmSources :: BuildInfo -> [SymbolicPath Pkg File] -> BuildInfo addExtraCmmSources bi extras = bi{cmmSources = new} where - new = ordNub (extras ++ cmmSources bi) + new = ordNub (map extraSourceFromPath extras ++ cmmSources bi) -- | Add extra ASM sources generated by preprocessing to build -- information. addExtraAsmSources :: BuildInfo -> [SymbolicPath Pkg File] -> BuildInfo addExtraAsmSources bi extras = bi{asmSources = new} where - new = ordNub (extras ++ asmSources bi) + new = ordNub (map extraSourceFromPath extras ++ asmSources bi) -- | Add extra JS sources generated by preprocessing to build -- information. addExtraJsSources :: BuildInfo -> [SymbolicPath Pkg File] -> BuildInfo addExtraJsSources bi extras = bi{jsSources = new} where - new = ordNub (extras ++ jsSources bi) + new = ordNub (map extraSourceFromPath extras ++ jsSources bi) -- | Add extra HS modules generated by preprocessing to build -- information. @@ -728,7 +728,7 @@ replComponent preprocessComponent pkg_descr comp lbi clbi False verbosity suffixHandlers extras <- preprocessExtras verbosity comp lbi let libbi = libBuildInfo lib - lib' = lib{libBuildInfo = libbi{cSources = cSources libbi ++ extras}} + lib' = lib{libBuildInfo = libbi{cSources = cSources libbi ++ map extraSourceFromPath extras}} replLib (verbosityHandles verbosity) replFlags pkg lbi lib' libClbi replComponent replFlags @@ -746,23 +746,23 @@ replComponent case comp of CLib lib -> do let libbi = libBuildInfo lib - lib' = lib{libBuildInfo = libbi{cSources = cSources libbi ++ extras}} + lib' = lib{libBuildInfo = libbi{cSources = cSources libbi ++ map extraSourceFromPath extras}} replLib verbHandles replFlags pkg_descr lbi lib' clbi CFLib flib -> replFLib verbHandles replFlags pkg_descr lbi flib clbi CExe exe -> do let ebi = buildInfo exe - exe' = exe{buildInfo = ebi{cSources = cSources ebi ++ extras}} + exe' = exe{buildInfo = ebi{cSources = cSources ebi ++ map extraSourceFromPath extras}} replExe verbHandles replFlags pkg_descr lbi exe' clbi CTest test@TestSuite{testInterface = TestSuiteExeV10{}} -> do let exe = testSuiteExeV10AsExe test let ebi = buildInfo exe - exe' = exe{buildInfo = ebi{cSources = cSources ebi ++ extras}} + exe' = exe{buildInfo = ebi{cSources = cSources ebi ++ map extraSourceFromPath extras}} replExe verbHandles replFlags pkg_descr lbi exe' clbi CBench bm@Benchmark{benchmarkInterface = BenchmarkExeV10{}} -> do let exe = benchmarkExeV10asExe bm let ebi = buildInfo exe - exe' = exe{buildInfo = ebi{cSources = cSources ebi ++ extras}} + exe' = exe{buildInfo = ebi{cSources = cSources ebi ++ map extraSourceFromPath extras}} replExe verbHandles replFlags pkg_descr lbi exe' clbi #if __GLASGOW_HASKELL__ < 811 -- silence pattern-match warnings prior to GHC 9.0 diff --git a/Cabal/src/Distribution/Simple/BuildTarget.hs b/Cabal/src/Distribution/Simple/BuildTarget.hs index c4219c88c8f..7bf7a1b3f32 100644 --- a/Cabal/src/Distribution/Simple/BuildTarget.hs +++ b/Cabal/src/Distribution/Simple/BuildTarget.hs @@ -493,11 +493,11 @@ pkgComponentInfo pkg = , cinfoSrcDirs = map getSymbolicPath $ hsSourceDirs bi , cinfoModules = componentModules c , cinfoHsFiles = map getSymbolicPath $ componentHsFiles c - , cinfoAsmFiles = map getSymbolicPath $ asmSources bi - , cinfoCmmFiles = map getSymbolicPath $ cmmSources bi - , cinfoCFiles = map getSymbolicPath $ cSources bi - , cinfoCxxFiles = map getSymbolicPath $ cxxSources bi - , cinfoJsFiles = map getSymbolicPath $ jsSources bi + , cinfoAsmFiles = map (getSymbolicPath . extraSourceFile) $ asmSources bi + , cinfoCmmFiles = map (getSymbolicPath . extraSourceFile) $ cmmSources bi + , cinfoCFiles = map (getSymbolicPath . extraSourceFile) $ cSources bi + , cinfoCxxFiles = map (getSymbolicPath . extraSourceFile) $ cxxSources bi + , cinfoJsFiles = map (getSymbolicPath . extraSourceFile) $ jsSources bi } | c <- pkgComponents pkg , let bi = componentBuildInfo c diff --git a/Cabal/src/Distribution/Simple/GHC/Build/ExtraSources.hs b/Cabal/src/Distribution/Simple/GHC/Build/ExtraSources.hs index 3fe16f28b73..8bc2924deec 100644 --- a/Cabal/src/Distribution/Simple/GHC/Build/ExtraSources.hs +++ b/Cabal/src/Distribution/Simple/GHC/Build/ExtraSources.hs @@ -6,6 +6,8 @@ module Distribution.Simple.GHC.Build.ExtraSources where import Control.Monad import Data.Foldable +import Distribution.Compiler (CompilerFlavor (GHC)) +import Distribution.Simple.Compiler (compilerCompatVersion) import Distribution.Simple.Flag import qualified Distribution.Simple.GHC.Internal as Internal import Distribution.Simple.Program @@ -24,8 +26,10 @@ import Distribution.Simple.LocalBuildInfo import Distribution.Simple.Setup.Common (commonSetupTempFileOptions) import Distribution.System (Arch (JavaScript), Platform (..)) import Distribution.Types.ComponentLocalBuildInfo +import Distribution.Types.ExtraSource (ExtraSource (..), extraSourceFromPath) import Distribution.Utils.Path -import Distribution.Verbosity (VerbosityHandles, VerbosityLevel, mkVerbosity, verbosityLevel) +import Distribution.Verbosity (VerbosityHandles, mkVerbosity, verbosityLevel) +import Distribution.Version (mkVersion) -- | An action that builds all the extra build sources of a component, i.e. C, -- C++, Js, Asm, C-- sources. @@ -53,57 +57,59 @@ buildAllExtraSources = , buildCmmSources ] -buildCSources - , buildCxxSources - , buildJsSources - , buildAsmSources - , buildCmmSources - :: Maybe (SymbolicPath Pkg File) - -- ^ An optional non-Haskell Main file - -> ConfiguredProgram - -- ^ The GHC configured program - -> SymbolicPath Pkg (Dir Artifacts) - -- ^ The build directory for this target - -> (Bool -> [BuildWay], Bool -> BuildWay, BuildWay) - -- ^ Needed build ways - -> VerbosityHandles - -- ^ Logging handles - -> PreBuildComponentInputs - -- ^ The context and component being built in it. - -> IO (NubListR (SymbolicPath Pkg File)) - -- ^ Returns the list of extra sources that were built +type ExtraSourceBuilder = + Maybe (SymbolicPath Pkg File) + -- ^ An optional non-Haskell Main file + -> ConfiguredProgram + -- ^ The GHC configured program + -> SymbolicPath Pkg (Dir Artifacts) + -- ^ The build directory for this target + -> (Bool -> [BuildWay], Bool -> BuildWay, BuildWay) + -- ^ Needed build ways + -> VerbosityHandles + -- ^ Logging handles + -> PreBuildComponentInputs + -- ^ The context and component being built in it. + -> IO (NubListR (SymbolicPath Pkg File)) + -- ^ Returns the list of extra sources that were built + +buildCSources :: ExtraSourceBuilder buildCSources mbMainFile = buildExtraSources "C Sources" - (Internal.splitCandCxxOptions Internal.CcProgram) + Internal.CSourceKind ( \c -> do let cFiles = cSources (componentBuildInfo c) case c of CExe{} | Just main <- mbMainFile , isC $ getSymbolicPath main -> - cFiles ++ [main] + cFiles ++ [extraSourceFromPath main] _otherwise -> cFiles ) + +buildCxxSources :: ExtraSourceBuilder buildCxxSources mbMainFile = buildExtraSources "C++ Sources" - (Internal.splitCandCxxOptions Internal.CxxProgram) + Internal.CxxSourceKind ( \c -> do let cxxFiles = cxxSources (componentBuildInfo c) case c of CExe{} | Just main <- mbMainFile , isCxx $ getSymbolicPath main -> - cxxFiles ++ [main] + cxxFiles ++ [extraSourceFromPath main] _otherwise -> cxxFiles ) + +buildJsSources :: ExtraSourceBuilder buildJsSources _mbMainFile ghcProg buildTargetDir neededWays verbHandles = do Platform hostArch _ <- hostPlatform <$> localBuildInfo let hasJsSupport = hostArch == JavaScript buildExtraSources "JS Sources" - Internal.sourcesGhcOptions + Internal.JsSourceKind ( \c -> if hasJsSupport then -- JS files are C-like with GHC's JS backend: they are @@ -117,15 +123,19 @@ buildJsSources _mbMainFile ghcProg buildTargetDir neededWays verbHandles = do buildTargetDir neededWays verbHandles + +buildAsmSources :: ExtraSourceBuilder buildAsmSources _mbMainFile = buildExtraSources "Assembler Sources" - Internal.sourcesGhcOptions + Internal.AsmSourceKind (asmSources . componentBuildInfo) + +buildCmmSources :: ExtraSourceBuilder buildCmmSources _mbMainFile = buildExtraSources "C-- Sources" - Internal.sourcesGhcOptions + Internal.CmmSourceKind (cmmSources . componentBuildInfo) -- | Create 'PreBuildComponentRules' for a given type of extra build sources @@ -134,17 +144,11 @@ buildCmmSources _mbMainFile = buildExtraSources :: String -- ^ String describing the extra sources being built, for printing. - -> ( VerbosityLevel - -> LocalBuildInfo - -> BuildInfo - -> ComponentLocalBuildInfo - -> SymbolicPath Pkg (Dir Artifacts) - -> SymbolicPath Pkg File - -> GhcOptions - ) - -- ^ Function to determine the @'GhcOptions'@ for the - -- invocation of GHC when compiling these extra sources - -> (Component -> [SymbolicPath Pkg File]) + -> Internal.ExtraSourceKind + -- ^ The kind of these extra sources, which determines the @'GhcOptions'@ for + -- the invocation of GHC when compiling them, and where their per-file + -- options go. + -> (Component -> [ExtraSource]) -- ^ View the extra sources of a component, typically from -- the build info (e.g. @'asmSources'@, @'cSources'@). -- @'Executable'@ components might additionally add the @@ -164,7 +168,7 @@ buildExtraSources -- ^ Returns the list of extra sources that were built buildExtraSources description - componentSourceGhcOptions + kind viewSources ghcProg buildTargetDir @@ -193,16 +197,17 @@ buildExtraSources platform mbWorkDir - buildAction :: SymbolicPath Pkg File -> IO () - buildAction sourceFile = do + buildAction :: ExtraSource -> IO () + buildAction extraSource = do let baseSrcOpts = - componentSourceGhcOptions + Internal.extraSourceGhcOptions + kind (verbosityLevel verbosity) lbi bi clbi buildTargetDir - sourceFile + extraSource vanillaSrcOpts = -- -fPIC is used in case you are using the repl -- of a dynamically linked GHC @@ -232,9 +237,24 @@ buildExtraSources odir = fromFlag (ghcOptObjDir vanillaSrcOpts) compileIfNeeded :: GhcOptions -> IO () - compileIfNeeded opts = do - needsRecomp <- checkNeedsRecompilation mbWorkDir sourceFile opts - when needsRecomp $ runGhcProg opts + compileIfNeeded opts' = do + needsRecomp <- checkNeedsRecompilation mbWorkDir (extraSourceFile extraSource) opts' + when needsRecomp $ runGhcProg opts' + + -- Per-file options on JavaScript sources are routed to -optJSP, + -- which only exists since GHC 9.12 and is dropped by + -- 'renderGhcOptions' before that. Say so rather than silently + -- building without them. + optionsAreDropped = + not (null (extraSourceOpts extraSource)) + && kind == Internal.JsSourceKind + && maybe True (< mkVersion [9, 12]) (compilerCompatVersion GHC comp) + + when optionsAreDropped $ + warn verbosity $ + "Ignoring the per-file options on " + ++ getSymbolicPath (extraSourceFile extraSource) + ++ ": passing options to the JavaScript preprocessor requires GHC 9.12 or later." createDirectoryIfMissingVerbose verbosity True (i odir) case targetComponent targetInfo of @@ -273,4 +293,4 @@ buildExtraSources else do info verbosity ("Building " ++ description ++ "...") traverse_ buildAction sources - return (toNubListR sources) + return (toNubListR (map extraSourceFile sources)) diff --git a/Cabal/src/Distribution/Simple/GHC/Internal.hs b/Cabal/src/Distribution/Simple/GHC/Internal.hs index 6959b8f48b6..bbf168e700b 100644 --- a/Cabal/src/Distribution/Simple/GHC/Internal.hs +++ b/Cabal/src/Distribution/Simple/GHC/Internal.hs @@ -31,6 +31,8 @@ module Distribution.Simple.GHC.Internal , optimizationCFlags , splitCandCxxOptions , SplitSource (..) + , ExtraSourceKind (..) + , extraSourceGhcOptions -- * GHC platform and version strings , ghcArchString @@ -67,11 +69,12 @@ import Distribution.Simple.GHC.ImplInfo import Distribution.Simple.LocalBuildInfo import Distribution.Simple.Program import Distribution.Simple.Program.GHC -import Distribution.Simple.Setup.Common (extraCompilationArtifacts) +import Distribution.Simple.Setup.Common (extraCompilationArtifacts, splitArgs) import Distribution.Simple.Utils import Distribution.System import Distribution.Types.BuildInfo import Distribution.Types.ComponentLocalBuildInfo +import Distribution.Types.ExtraSource (ExtraSource (..)) import Distribution.Types.GivenComponent import qualified Distribution.Types.InstalledPackageInfo as IPI import Distribution.Types.Library @@ -353,21 +356,21 @@ splitCandCxxOptions -> BuildInfo -> ComponentLocalBuildInfo -> SymbolicPath Pkg (Dir Artifacts) - -> SymbolicPath Pkg File + -> ExtraSource -> GhcOptions -splitCandCxxOptions source verbosity lbi bi clbi odir filename = case source of +splitCandCxxOptions source verbosity lbi bi clbi odir extraSource = case source of CxxProgram -> -- For C++ sources: reset ccOptions for GHC < 8.10, because on those -- old GHCs there's no -optcxx flag — all options go through -optc. -- Without this reset, C-specific flags (ccOptions) would leak into -- C++ compilation via -optc, which is wrong. - setGppProgram $ setCcOptions $ sourcesGhcOptions verbosity lbi bi clbi odir filename + setGppProgram $ setCcOptions $ sourcesGhcOptions CxxSourceKind verbosity lbi bi clbi odir extraSource CcProgram -> -- For C sources: reset cxxOptions for GHC < 8.10, because on those -- old GHCs there's no -optcxx flag — all options go through -optc. -- Without this reset, C++-specific flags (cxxOptions) would leak into -- C compilation via -optc, which is wrong. - setCcProgram $ setCxxOptions $ sourcesGhcOptions verbosity lbi bi clbi odir filename + setCcProgram $ setCxxOptions $ sourcesGhcOptions CSourceKind verbosity lbi bi clbi odir extraSource where setCcOptions xxx = xxx @@ -434,25 +437,67 @@ splitCandCxxOptions source verbosity lbi bi clbi odir filename = case source of (maybeToFlag $ programPath <$> lookupProgram gppProgram (withPrograms lbi)) } +-- | The kind of an extra source, which decides where its per-file options go. +data ExtraSourceKind + = CSourceKind + | CxxSourceKind + | AsmSourceKind + | CmmSourceKind + | JsSourceKind + deriving (Eq, Show) + sourcesGhcOptions - :: VerbosityLevel + :: ExtraSourceKind + -> VerbosityLevel -> LocalBuildInfo -> BuildInfo -> ComponentLocalBuildInfo -> SymbolicPath Pkg (Dir Artifacts) - -> SymbolicPath Pkg File + -> ExtraSource -> GhcOptions -sourcesGhcOptions verbosity lbi bi clbi odir filename = - (componentGhcOptions verbosity lbi bi clbi odir) - { ghcOptVerbosity = toFlag (min verbosity Normal) - , ghcOptMode = toFlag GhcModeCompile - , ghcOptInputFiles = toNubListR [filename] - , ghcOptObjDir = toFlag odir - , ghcOptPackages = toNubListR $ mkGhcOptPackages (promisedPkgs lbi) clbi - , -- cpp-options apply only to .hs files; GHC ignores -optP for non-Haskell - -- files (and since 9.10 this behavior is explicit/enforced) - ghcOptCppOptions = [] - } +sourcesGhcOptions kind verbosity lbi bi clbi odir extraSource = + addPerFileOpts + (componentGhcOptions verbosity lbi bi clbi odir) + { ghcOptVerbosity = toFlag (min verbosity Normal) + , ghcOptMode = toFlag GhcModeCompile + , ghcOptInputFiles = toNubListR [extraSourceFile extraSource] + , ghcOptObjDir = toFlag odir + , ghcOptPackages = toNubListR $ mkGhcOptPackages (promisedPkgs lbi) clbi + , -- cpp-options apply only to .hs files; GHC ignores -optP for non-Haskell + -- files (and since 9.10 this behavior is explicit/enforced) + ghcOptCppOptions = [] + } + where + opts = splitArgs (extraSourceOpts extraSource) + -- Append the per-file options to the one 'GhcOptions' field GHC consults + -- for this kind of source. GHC picks the flag family from the file + -- extension, so putting them anywhere else is at best ignored. + addPerFileOpts ghcOpts = case kind of + CSourceKind -> ghcOpts{ghcOptCcOptions = ghcOptCcOptions ghcOpts ++ opts} + CxxSourceKind -> ghcOpts{ghcOptCxxOptions = ghcOptCxxOptions ghcOpts ++ opts} + AsmSourceKind -> ghcOpts{ghcOptAsmOptions = ghcOptAsmOptions ghcOpts ++ opts} + -- GHC compiles C-- sources itself, so their options are plain GHC + -- options, next to the ones @cmm-options@ contributes. + CmmSourceKind -> ghcOpts{ghcOptExtra = ghcOptExtra ghcOpts ++ opts} + -- GHC only preprocesses JavaScript sources, so their options go to the + -- JavaScript preprocessor (@-optJSP@, GHC >= 9.12). + JsSourceKind -> ghcOpts{ghcOptJSppOptions = ghcOptJSppOptions ghcOpts ++ opts} + +-- | The 'GhcOptions' for compiling a single extra source of the given kind. +extraSourceGhcOptions + :: ExtraSourceKind + -> VerbosityLevel + -> LocalBuildInfo + -> BuildInfo + -> ComponentLocalBuildInfo + -> SymbolicPath Pkg (Dir Artifacts) + -> ExtraSource + -> GhcOptions +extraSourceGhcOptions CSourceKind = splitCandCxxOptions CcProgram +extraSourceGhcOptions CxxSourceKind = splitCandCxxOptions CxxProgram +extraSourceGhcOptions AsmSourceKind = sourcesGhcOptions AsmSourceKind +extraSourceGhcOptions CmmSourceKind = sourcesGhcOptions CmmSourceKind +extraSourceGhcOptions JsSourceKind = sourcesGhcOptions JsSourceKind optimizationCFlags :: LocalBuildInfo -> [String] optimizationCFlags lbi = diff --git a/Cabal/src/Distribution/Simple/GHCJS.hs b/Cabal/src/Distribution/Simple/GHCJS.hs index 73eed6eefde..f8325f5167b 100644 --- a/Cabal/src/Distribution/Simple/GHCJS.hs +++ b/Cabal/src/Distribution/Simple/GHCJS.hs @@ -545,8 +545,6 @@ buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do -- See Note [Symbolic paths] in Distribution.Utils.Path i = interpretSymbolicPathLBI lbi - u :: SymbolicPathX allowAbs Pkg to -> FilePath - u = getSymbolicPath (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi) let runGhcjsProg = runGHC verbosity ghcjsProg comp platform mbWorkDir @@ -574,7 +572,7 @@ buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do -- modules? let cLikeFiles = fromNubListR $ toNubListR (cSources libBi) <> toNubListR (cxxSources libBi) jsSrcs = jsSources libBi - cObjs = map (`replaceExtensionSymbolicPath` objExtension) cLikeFiles + cObjs = map ((`replaceExtensionSymbolicPath` objExtension) . extraSourceFile) cLikeFiles baseOpts = componentGhcOptions (verbosityLevel verbosity) lbi libBi clbi libTargetDir linkJsLibOpts = mempty @@ -582,9 +580,9 @@ buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do [ "-link-js-lib" , getHSLibraryName uid , "-js-lib-outputdir" - , u libTargetDir + , getSymbolicPath libTargetDir ] - ++ map u jsSrcs + ++ map (getSymbolicPath . extraSourceFile) jsSrcs } vanillaOptsNoJsLib = baseOpts @@ -630,6 +628,16 @@ buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do , ghcOptHPCDir = hpcdir Hpc.Dyn } + -- GHCJS does not compile JavaScript sources, it only hands them to + -- @-link-js-lib@ at link time, so there is no preprocessor invocation to + -- pass per-file options to. + for_ jsSrcs $ \jsSrc -> + unless (null (extraSourceOpts jsSrc)) $ + warn verbosity $ + "Ignoring the per-file options on " + ++ getSymbolicPath (extraSourceFile jsSrc) + ++ ": GHCJS does not preprocess JavaScript sources." + unless (forRepl || null (allLibModules lib clbi) && null jsSrcs && null cObjs) $ do let vanilla = whenVanillaLib forceVanillaLib (runGhcjsProg vanillaOpts) @@ -738,7 +746,7 @@ buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do info verbosity "Linking..." let cSharedObjs = map - (`replaceExtensionSymbolicPath` ("dyn_" ++ objExtension)) + ((`replaceExtensionSymbolicPath` ("dyn_" ++ objExtension)) . extraSourceFile) (cSources libBi ++ cxxSources libBi) compiler_id = compilerId (compiler lbi) sharedLibFilePath = libTargetDir makeRelativePathEx (mkSharedLibName (hostPlatform lbi) compiler_id uid) @@ -1126,8 +1134,8 @@ decodeMainIsArg arg -- -- Used to correctly build and link sources. data BuildSources = BuildSources - { cSourcesFiles :: [SymbolicPath Pkg File] - , cxxSourceFiles :: [SymbolicPath Pkg File] + { cSourcesFiles :: [ExtraSource] + , cxxSourceFiles :: [ExtraSource] , inputSourceFiles :: [SymbolicPath Pkg File] , inputSourceModules :: [ModuleName] } @@ -1193,11 +1201,11 @@ gbuildSources verbosity mbWorkDir pkgId specVer tmpDir bm = } else let (csf, cxxsf) - | isCxx (getSymbolicPath main) = (cSources bnfo, main : cxxSources bnfo) + | isCxx (getSymbolicPath main) = (cSources bnfo, extraSourceFromPath main : cxxSources bnfo) -- if main is not a Haskell source -- and main is not a C++ source -- then we assume that it is a C source - | otherwise = (main : cSources bnfo, cxxSources bnfo) + | otherwise = (extraSourceFromPath main : cSources bnfo, cxxSources bnfo) in return BuildSources { cSourcesFiles = csf @@ -1271,8 +1279,8 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do inputModules = inputSourceModules buildSources isGhcDynamic = isDynamic comp dynamicTooSupported = supportsDynamicToo comp - cObjs = map (`replaceExtensionSymbolicPath` objExtension) cSrcs - cxxObjs = map (`replaceExtensionSymbolicPath` objExtension) cxxSrcs + cObjs = map ((`replaceExtensionSymbolicPath` objExtension) . extraSourceFile) cSrcs + cxxObjs = map ((`replaceExtensionSymbolicPath` objExtension) . extraSourceFile) cxxSrcs needDynamic = gbuildNeedDynamic lbi bm needProfiling = withProfExe lbi @@ -1468,7 +1476,7 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do -- add a warning if this occurs. odir = fromFlag (ghcOptObjDir opts) createDirectoryIfMissingVerbose verbosity True (i odir) - needsRecomp <- checkNeedsRecompilation mbWorkDir filename opts + needsRecomp <- checkNeedsRecompilation mbWorkDir (extraSourceFile filename) opts when needsRecomp $ runGhcProg opts | filename <- cxxSrcs @@ -1504,7 +1512,7 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do | otherwise = vanillaCcOpts odir = fromFlag (ghcOptObjDir opts) createDirectoryIfMissingVerbose verbosity True (i odir) - needsRecomp <- checkNeedsRecompilation mbWorkDir filename opts + needsRecomp <- checkNeedsRecompilation mbWorkDir (extraSourceFile filename) opts when needsRecomp $ runGhcProg opts | filename <- cSrcs diff --git a/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs b/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs index 2446c0270e0..e4b67251647 100644 --- a/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs +++ b/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs @@ -927,11 +927,11 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a autogenExtraSourcesPaths = concatMap (mapMaybe relativeToAutogen) - [ cSources compBuildInfo - , cxxSources compBuildInfo - , cmmSources compBuildInfo - , asmSources compBuildInfo - , jsSources compBuildInfo + [ map extraSourceFile (cSources compBuildInfo) + , map extraSourceFile (cxxSources compBuildInfo) + , map extraSourceFile (cmmSources compBuildInfo) + , map extraSourceFile (asmSources compBuildInfo) + , map extraSourceFile (jsSources compBuildInfo) ] extraBundledLibsPaths :: [RelativePath Source File] extraBundledLibsPaths = diff --git a/Cabal/src/Distribution/Simple/SrcDist.hs b/Cabal/src/Distribution/Simple/SrcDist.hs index 883b4994c78..a78ef3aa5be 100644 --- a/Cabal/src/Distribution/Simple/SrcDist.hs +++ b/Cabal/src/Distribution/Simple/SrcDist.hs @@ -562,11 +562,11 @@ allSourcesBuildInfo verbosity rip mbWorkDir bi pps modules = do return $ sources ++ catMaybes bootFiles - ++ cSources bi - ++ cxxSources bi - ++ cmmSources bi - ++ asmSources bi - ++ jsSources bi + ++ map extraSourceFile (cSources bi) + ++ map extraSourceFile (cxxSources bi) + ++ map extraSourceFile (cmmSources bi) + ++ map extraSourceFile (asmSources bi) + ++ map extraSourceFile (jsSources bi) where nonEmpty' :: b -> ([a] -> b) -> [a] -> b nonEmpty' x _ [] = x diff --git a/cabal-install/src/Distribution/Client/SourceFiles.hs b/cabal-install/src/Distribution/Client/SourceFiles.hs index bc046e329c1..74f9009d1cc 100644 --- a/cabal-install/src/Distribution/Client/SourceFiles.hs +++ b/cabal-install/src/Distribution/Client/SourceFiles.hs @@ -29,6 +29,7 @@ import Distribution.Types.BuildInfo import Distribution.Types.Component import Distribution.Types.ComponentRequestedSpec (ComponentRequestedSpec) import Distribution.Types.Executable +import Distribution.Types.ExtraSource import Distribution.Types.ForeignLib import Distribution.Types.Library import Distribution.Types.PackageDescription @@ -184,11 +185,11 @@ needBuildInfo pkg_descr bi modules = do fpath traverse_ needIfExists $ concat - [ map getSymbolicPath $ cSources bi - , map getSymbolicPath $ cxxSources bi - , map getSymbolicPath $ jsSources bi - , map getSymbolicPath $ cmmSources bi - , map getSymbolicPath $ asmSources bi + [ map (getSymbolicPath . extraSourceFile) $ cSources bi + , map (getSymbolicPath . extraSourceFile) $ cxxSources bi + , map (getSymbolicPath . extraSourceFile) $ jsSources bi + , map (getSymbolicPath . extraSourceFile) $ cmmSources bi + , map (getSymbolicPath . extraSourceFile) $ asmSources bi , map getSymbolicPath expandedExtraSrcFiles ] for_ diff --git a/cabal-install/src/Distribution/Client/TargetSelector.hs b/cabal-install/src/Distribution/Client/TargetSelector.hs index 12e22e8c5b9..7113f10b32c 100644 --- a/cabal-install/src/Distribution/Client/TargetSelector.hs +++ b/cabal-install/src/Distribution/Client/TargetSelector.hs @@ -65,6 +65,7 @@ import Distribution.PackageDescription , BenchmarkInterface (..) , BuildInfo (..) , Executable (..) + , ExtraSource (..) , PackageDescription , TestSuite (..) , TestSuiteInterface (..) @@ -1859,8 +1860,8 @@ collectKnownComponentInfo pkg = , cinfoSrcDirs = ordNub (map getSymbolicPath (hsSourceDirs bi)) , cinfoModules = ordNub (componentModules c) , cinfoHsFiles = ordNub (componentHsFiles c) - , cinfoCFiles = ordNub (map getSymbolicPath $ cSources bi) - , cinfoJsFiles = ordNub (map getSymbolicPath $ jsSources bi) + , cinfoCFiles = ordNub (map (getSymbolicPath . extraSourceFile) $ cSources bi) + , cinfoJsFiles = ordNub (map (getSymbolicPath . extraSourceFile) $ jsSources bi) } | c <- pkgComponents pkg , let bi = componentBuildInfo c diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs index a3d9e20820a..bc648e6de1c 100644 --- a/cabal-install/tests/IntegrationTests2.hs +++ b/cabal-install/tests/IntegrationTests2.hs @@ -684,7 +684,9 @@ testTargetSelectorAmbiguous reportSubCase = do withCFiles :: Executable -> [FilePath] -> Executable withCFiles exe files = - exe{buildInfo = (buildInfo exe){cSources = map unsafeMakeSymbolicPath files}} + exe{buildInfo = (buildInfo exe){cSources = map (mkExtraSource . unsafeMakeSymbolicPath) files}} + + mkExtraSource x = ExtraSource x [] withHsSrcDirs :: Executable -> [FilePath] -> Executable withHsSrcDirs exe srcDirs = diff --git a/cabal-testsuite/PackageTests/ExtraSources/cbits/test.c b/cabal-testsuite/PackageTests/ExtraSources/cbits/test.c new file mode 100644 index 00000000000..e31c5a9b7b5 --- /dev/null +++ b/cabal-testsuite/PackageTests/ExtraSources/cbits/test.c @@ -0,0 +1,3 @@ +#ifndef DOIT +#error "It does not work" +#endif diff --git a/cabal-testsuite/PackageTests/ExtraSources/cbits/testcmm.cmm b/cabal-testsuite/PackageTests/ExtraSources/cbits/testcmm.cmm new file mode 100644 index 00000000000..04e3591f7bd --- /dev/null +++ b/cabal-testsuite/PackageTests/ExtraSources/cbits/testcmm.cmm @@ -0,0 +1,10 @@ +#include "Cmm.h" + +#ifndef DOIT +#error "It does not work" +#endif + +extraSourcesTestzh (P_ clos) +{ + return (clos); +} diff --git a/cabal-testsuite/PackageTests/ExtraSources/extra-sources.cabal b/cabal-testsuite/PackageTests/ExtraSources/extra-sources.cabal new file mode 100644 index 00000000000..4b975b86c0f --- /dev/null +++ b/cabal-testsuite/PackageTests/ExtraSources/extra-sources.cabal @@ -0,0 +1,15 @@ +cabal-version: 3.20 +name: extra-sources +version: 0 +build-type: Simple + +library + hs-source-dirs: src + build-depends: base + exposed-modules: MyLib + c-sources: cbits/test.c (-D DOIT=1) + -- Per-file options on C-- sources are plain GHC options, so an option for + -- the C-- preprocessor has to carry its own -optCmmP prefix. That prefix + -- only exists since GHC 9.12. + if impl(ghc >= 9.12) + cmm-sources: cbits/testcmm.cmm (-optCmmP-DDOIT=1) diff --git a/cabal-testsuite/PackageTests/ExtraSources/setup.out b/cabal-testsuite/PackageTests/ExtraSources/setup.out new file mode 100644 index 00000000000..43a3574bd1b --- /dev/null +++ b/cabal-testsuite/PackageTests/ExtraSources/setup.out @@ -0,0 +1,5 @@ +# Setup configure +Configuring extra-sources-0... +# Setup build +Preprocessing library for extra-sources-0... +Building library for extra-sources-0... diff --git a/cabal-testsuite/PackageTests/ExtraSources/setup.test.hs b/cabal-testsuite/PackageTests/ExtraSources/setup.test.hs new file mode 100644 index 00000000000..9e2abcb188b --- /dev/null +++ b/cabal-testsuite/PackageTests/ExtraSources/setup.test.hs @@ -0,0 +1,5 @@ +import Test.Cabal.Prelude + +main = setupTest $ do + setup "configure" [] + setup "build" [] diff --git a/cabal-testsuite/PackageTests/ExtraSources/src/MyLib.hs b/cabal-testsuite/PackageTests/ExtraSources/src/MyLib.hs new file mode 100644 index 00000000000..bcdf120b02c --- /dev/null +++ b/cabal-testsuite/PackageTests/ExtraSources/src/MyLib.hs @@ -0,0 +1,4 @@ +module MyLib where + +someFunc :: IO () +someFunc = mempty diff --git a/cabal-testsuite/PackageTests/SetupHooks/SetupHooksNonHs/SetupHooks.hs b/cabal-testsuite/PackageTests/SetupHooks/SetupHooksNonHs/SetupHooks.hs index c2cb9bf0e14..678cd594a98 100644 --- a/cabal-testsuite/PackageTests/SetupHooks/SetupHooksNonHs/SetupHooks.hs +++ b/cabal-testsuite/PackageTests/SetupHooks/SetupHooksNonHs/SetupHooks.hs @@ -64,7 +64,8 @@ pcc (PreConfComponentInputs _lbc pbd _comp) = -- That would work, but would mean we wouldn't benefit from -- recompilation checking. emptyBuildInfo - { cSources = [ autogenDir unsafeMakeSymbolicPath "Gen.c" + { cSources = map extraSourceFromPath + [ autogenDir unsafeMakeSymbolicPath "Gen.c" , autogenDir unsafeMakeSymbolicPath "Gen2.c" , autogenDir unsafeMakeSymbolicPath "DynDep.c"] } diff --git a/changelog.d/per-file-extra-source-options.md b/changelog.d/per-file-extra-source-options.md new file mode 100644 index 00000000000..4366f23d475 --- /dev/null +++ b/changelog.d/per-file-extra-source-options.md @@ -0,0 +1,40 @@ +--- +synopsis: Per-file options for extra source files +packages: [Cabal, Cabal-syntax, Cabal-hooks] +prs: 12288 +significance: significant +--- + +The extra-source fields (`c-sources`, `cxx-sources`, `asm-sources`, +`cmm-sources`, `js-sources`) now accept per-file options, written in +parentheses after each file: + +```cabal +c-sources: foo.c (-DFOO -O2) bar.c +cmm-sources: rts.cmm (-mavx2) +``` + +The options are passed to the compiler only when compiling that particular +file. This requires `cabal-version: 3.20` or later; using the syntax with an +earlier `cabal-version` is a parse error, so nothing changes for existing +packages. + +An option that contains whitespace, a `)` or a `"` has to be written as a +Haskell string literal, e.g. `c-sources: greet.c ("-DGREETING=\"hi there\"")`. + +The five fields now hold `ExtraSource` values rather than plain paths, so code +that reads or writes `cSources` and friends has to change. Use +`extraSourceFromPath` to make an `ExtraSource` with no options. +`Distribution.Simple.SetupHooks` exports both, so a `Hooks` package does not +need a direct dependency on `Cabal-syntax`. + +Options are accepted on all five kinds. For `c-sources`, `cxx-sources` and +`asm-sources` they are passed as compiler options (`-optc`/`-optcxx`/`-opta`). +GHC compiles C-- sources itself, so options on `cmm-sources` are passed to GHC, +next to the ones from the `cmm-options` field. JavaScript sources are only +preprocessed, so options on `js-sources` are passed to the JavaScript +preprocessor (`-optJSP`), which requires GHC 9.12 or later; with an older GHC +they are ignored and Cabal warns. The legacy GHCJS compiler does not +preprocess JavaScript sources at all — it only passes them to `-link-js-lib` +at link time — so options on `js-sources` are ignored there too, again with a +warning. diff --git a/doc/buildinfo-fields-reference.rst b/doc/buildinfo-fields-reference.rst index 21ad93188fc..de3f5b8e9f7 100644 --- a/doc/buildinfo-fields-reference.rst +++ b/doc/buildinfo-fields-reference.rst @@ -178,7 +178,7 @@ asm-sources * Documentation of :pkg-field:`library:asm-sources` .. math:: - \mathrm{commalist}\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\} + \mathrm{commalist}\left(\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\}{\left(\bullet\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}{\left({\left\{ {[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}]^c}\mid\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}[\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}] \right\}}^+_{}\right)}^\ast_{\bullet}\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\right)}^?\right) autogen-includes * Monoidal field @@ -231,7 +231,7 @@ c-sources * Documentation of :pkg-field:`library:c-sources` .. math:: - \mathrm{commalist}\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\} + \mathrm{commalist}\left(\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\}{\left(\bullet\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}{\left({\left\{ {[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}]^c}\mid\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}[\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}] \right\}}^+_{}\right)}^\ast_{\bullet}\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\right)}^?\right) cc-options * Monoidal field @@ -254,7 +254,7 @@ cmm-sources * Documentation of :pkg-field:`library:cmm-sources` .. math:: - \mathrm{commalist}\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\} + \mathrm{commalist}\left(\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\}{\left(\bullet\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}{\left({\left\{ {[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}]^c}\mid\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}[\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}] \right\}}^+_{}\right)}^\ast_{\bullet}\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\right)}^?\right) cpp-options * Monoidal field @@ -277,7 +277,7 @@ cxx-sources * Documentation of :pkg-field:`library:cxx-sources` .. math:: - \mathrm{commalist}\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\} + \mathrm{commalist}\left(\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\}{\left(\bullet\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}{\left({\left\{ {[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}]^c}\mid\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}[\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}] \right\}}^+_{}\right)}^\ast_{\bullet}\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\right)}^?\right) default-extensions * Monoidal field @@ -483,7 +483,7 @@ js-sources * Documentation of :pkg-field:`library:js-sources` .. math:: - \mathrm{commalist}\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\} + \mathrm{commalist}\left(\left\{ \mathop{\mathit{hs\text{-}string}}\mid{{[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{,}}\mathord{"}}]^c}}^+_{} \right\}{\left(\bullet\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}{\left({\left\{ {[\mathop{\mathord{``}\mathtt{\ }\mathord{"}}\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}]^c}\mid\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}[\mathop{\mathord{``}\mathtt{\text{(}}\mathord{"}}\cdots\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\mathop{\mathord{``}\mathtt{\text{\\}}\mathord{"}}] \right\}}^+_{}\right)}^\ast_{\bullet}\mathop{\mathord{``}\mathtt{\text{)}}\mathord{"}}\right)}^?\right) jspp-options * Monoidal field diff --git a/doc/cabal-package-description-file.rst b/doc/cabal-package-description-file.rst index 0607caec290..55664ada057 100644 --- a/doc/cabal-package-description-file.rst +++ b/doc/cabal-package-description-file.rst @@ -1893,10 +1893,62 @@ system-dependent values for these fields. Directories here will be passed as ``-I`` flags to GHC. +.. _per-file-source-options: + +Each of the five source-file fields below — :pkg-field:`c-sources`, +:pkg-field:`cxx-sources`, :pkg-field:`asm-sources`, :pkg-field:`cmm-sources` +and :pkg-field:`js-sources` — accepts, since ``cabal-version: 3.20``, per-file +options written in parentheses after a file name. The options apply only when +compiling that one file, on top of whatever the corresponding whole-component +field (:pkg-field:`cc-options` and friends) already supplies:: + + c-sources: + cbits/fast.c (-O3 -DFAST) + cbits/plain.c + cmm-sources: cbits/rts.cmm (-mavx2) + +The text between the parentheses is taken verbatim and split into individual +options in exactly the way ``--PROG-options`` is split on the command line: +options are separated by whitespace, and an option that itself contains +whitespace has to be quoted:: + + c-sources: cbits/wide.c ("-DMESSAGE=hello there") + +Within the parentheses only ``(`` and ``)`` have any meaning. They may nest as +long as they balance, so ``-DSIZE=f(1)`` needs no escaping; a parenthesis that +does not balance has to be escaped with a backslash. A backslash escapes ``(``, +``)`` and ``\``, and stands for itself everywhere else, so a Windows path such +as ``C:\foo\bar`` needs no doubling:: + + c-sources: cbits/paren.c (-DSMILEY=\) -DSIZE=f(1)) + +Quoting does not hide a parenthesis from this: the parentheses are matched +before the options are split, so ``("-DSMILEY=)")`` is an error rather than a +single option. A literal ``"`` inside an option is written ``\"`` within a +quoted option, again as on the command line:: + + c-sources: cbits/greet.c ("-DGREETING=\"hi there\"") + +A space is required between the file name and the opening parenthesis; +without one the parenthesis is taken to be part of the file name. Declaring an +earlier ``cabal-version`` and using this syntax is an error, not a silent +downgrade. + +For :pkg-field:`c-sources`, :pkg-field:`cxx-sources` and +:pkg-field:`asm-sources` the options are passed to the compiler +(``-optc``/``-optcxx``/``-opta``). GHC compiles C-- sources itself, so options +on :pkg-field:`cmm-sources` are passed to GHC, next to the ones from the +:pkg-field:`cmm-options` field; an option meant for the C-- preprocessor has to +be written with its own prefix, as in ``rts.cmm (-optCmmP-DUSE_FOO)``. +JavaScript sources are only preprocessed, so options on +:pkg-field:`js-sources` are passed to the JavaScript preprocessor +(``-optJSP``), which requires GHC 9.12 or later; with an older GHC they are +ignored and Cabal warns. + .. pkg-field:: c-sources: filename list A list of C source files to be compiled and linked with the Haskell - files. + files. Supports :ref:`per-file options `. .. pkg-field:: cxx-sources: filename list :since: 2.2 @@ -1907,24 +1959,25 @@ system-dependent values for these fields. and the :pkg-field:`cxx-options` fields. The files listed in the :pkg-field:`cxx-sources` can reference files listed in the :pkg-field:`c-sources` field and vice-versa. The object files will be linked - appropriately. + appropriately. Supports :ref:`per-file options `. .. pkg-field:: asm-sources: filename list :since: 3.0 A list of assembly source files to be compiled and linked with the - Haskell files. + Haskell files. Supports :ref:`per-file options `. .. pkg-field:: cmm-sources: filename list :since: 3.0 A list of C-- source files to be compiled and linked with the Haskell - files. + files. Supports :ref:`per-file options `. .. pkg-field:: js-sources: filename list A list of JavaScript source files to be linked with the Haskell - files (only for JavaScript targets). + files (only for JavaScript targets). Supports + :ref:`per-file options `. .. pkg-field:: extra-libraries: token list diff --git a/doc/file-format-changelog.rst b/doc/file-format-changelog.rst index 07d5c0a5420..07e5a909625 100644 --- a/doc/file-format-changelog.rst +++ b/doc/file-format-changelog.rst @@ -19,6 +19,15 @@ relative to the respective preceding *published* version. versions of the ``Cabal`` library denote unreleased development branches which have no stability guarantee. +``cabal-version: 3.20`` +----------------------- + +* The extra-source fields :pkg-field:`c-sources`, :pkg-field:`cxx-sources`, + :pkg-field:`asm-sources`, :pkg-field:`cmm-sources` and :pkg-field:`js-sources` + accept :ref:`per-file options `, given in + parentheses after each file, e.g. ``c-sources: foo.c (-DFOO) bar.c``. Options + are passed to the compiler only for that file. + ``cabal-version: 3.18`` -----------------------