diff --git a/.hlint.yaml b/.hlint.yaml index ca45b8956fb..0ec44ffcfd3 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -51,10 +51,11 @@ - --ignore-glob=Cabal-tests/tests/custom-setup/CabalDoctestSetup.hs - --ignore-glob=Cabal-tests/tests/custom-setup/IdrisSetup.hs - --ignore-glob=cabal-testsuite/PackageTests/BuildWays/q/app/Main.hs + - --ignore-glob=cabal-testsuite/PackageTests/CppOptions/CppOpts/Main.hs - --ignore-glob=cabal-testsuite/PackageTests/CMain/10168/src/Lib.hs - - --ignore-glob=cabal-testsuite/PackageTests/CmmSources/src/Demo.hs - - --ignore-glob=cabal-testsuite/PackageTests/CmmSourcesDyn/src/Demo.hs - - --ignore-glob=cabal-testsuite/PackageTests/CmmSourcesExe/src/Demo.hs + - --ignore-glob=cabal-testsuite/PackageTests/Cmm/CmmSources/src/Demo.hs + - --ignore-glob=cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/src/Demo.hs + - --ignore-glob=cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/src/Demo.hs - --ignore-glob=cabal-testsuite/PackageTests/NewBuild/CmdRun/Script/script.hs - --ignore-glob=cabal-testsuite/PackageTests/NewBuild/CmdRun/ScriptLiterate/script.lhs - --ignore-glob=cabal-testsuite/PackageTests/Regression/T5309/lib/Bio/Character/Exportable/Class.hs diff --git a/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs b/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs index d7ac41f9817..a625b17de0f 100644 --- a/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs +++ b/Cabal-hooks/src/Distribution/Simple/SetupHooks.hs @@ -16,7 +16,8 @@ a module @SetupHooks.hs@ which exports a value @setupHooks :: 'SetupHooks'@. This is a record that declares actions that should be hooked into the cabal build process. -See 'SetupHooks' for more details. +See also the introductory [how to guide](https://cabal.readthedocs.io/en/latest/how-to-use-setup-hooks.html) +in the Cabal user manual. -} module Distribution.Simple.SetupHooks ( -- * Hooks @@ -25,6 +26,9 @@ module Distribution.Simple.SetupHooks SetupHooks(..) , noSetupHooks + -- * Usage overview + -- $usage + -- * Configure hooks -- $configureHooks @@ -66,33 +70,43 @@ module Distribution.Simple.SetupHooks , Rules , rules , noRules + + -- *** Rules API + + -- $rulesAPI + , RulesM + , registerRule + , registerRule_ + + -- *** Rule construction , Rule - , Dependency (..) - , RuleOutput (..) - , RuleId , staticRule, dynamicRule + + -- **** Example usage of 'dynamicRule' + -- $dynamicRules + -- *** Rule inputs/outputs + -- **** Rule dependencies -- $rulesDemand + , Dependency (..) + , RuleOutput (..) + , RuleId + + -- **** Filesystem locations , Location(..) , location , autogenComponentModulesDir , componentBuildDir -- *** Actions - , RuleCommands(..) + , RuleCommands -- gnarly constructors not exposed; API is via 'staticRule' and 'dynamicRule' , Command , mkCommand , Dict(..) - -- *** Rules API - -- $rulesAPI - , RulesM - , registerRule - , registerRule_ - - -- **** File/directory monitoring + -- *** File/directory monitoring , addRuleMonitors , module Distribution.Simple.FileMonitor.Types @@ -279,6 +293,65 @@ Note that 'SetupHooks' can be monoidally combined, e.g.: > mySetupHooks = ... -} +{- $usage +'SetupHooks' allow hooks into the following phases: + + * The configure phase, via 'ConfigureHooks'. + + There are three hooks into the configure phase: + + * Package-wide pre-configure hook. + + This hook enables custom logic in the style of traditional @./configure@ + scripts, e.g. finding out information about the system and configuring + dependencies. + + * Package-wide post-configure hook. + + This hook is mostly used to write write custom package-wide information + to disk so that the next step can read it without repeating work. + + * Per-component pre-configure hooks. + + These hooks are used to modify individual components, e.g. declaring + new modules (such as autogenerated modules whose module names are not + statically known) or specifying per-component flags to be used when + building each component. + + You can think of this step as dynamically updating the stanza for a + single component in the .cabal file of a package. + + * The build phase, via 'BuildHooks'. + + There are two hooks into the build phase: + + * Per-component pre-build rules. + + A rule can be thought of an invocation of a code generator; each rule + specifies how to build a particular output. + + You can have rules that don't depend on any inputs (e.g. directly + generate a collection of modules programmatically, perhaps from some + kind of parsed schema), as well as preprocessor-like rules that take in + input files, e.g. the Happy parser generator that takes in a .y + file and generates a corresponding .hs file. + + This rather elaborate setup (compared to a one-shot program that + generates the required modules) allows proper recompilation checking. + See also + for some worked examples of pre-build rules. + + * Per-component post-build hooks. + + These can be thought of as "pre-linking hooks" and allow injecting + additional data into the final executable. + + * The install phase, via 'InstallHooks'. + + There is a single, per-component install hook. This allows copying over + additional files when installing a component (library/executable). +-} + {- $configureHooks Configure hooks can be used to augment the Cabal configure logic with package-specific logic. The main principle is that the configure hooks can @@ -314,18 +387,24 @@ Each t'Rule' consists of: - a specification of its static dependencies and outputs, - the commands that execute the rule. +You can think of a t'Rule' as describing a particular invocation of a code +generator or preprocessor, with the t'Command' specifying the particular +command that will be executed. + Rules are constructed using either one of the 'staticRule' or 'dynamicRule' smart constructors. Directly constructing a t'Rule' using the constructors of that data type is not advised, as this relies on internal implementation details -which are subject to change in between versions of the `Cabal-hooks` library. +which are subject to change in between versions of the @Cabal-hooks@ library. Note that: - To declare the dependency on the output of a rule, one must refer to the rule directly, and not to the path to the output executing that rule will eventually produce. - To do so, registering a t'Rule' with the API returns a unique identifier - for that rule, in the form of a t'RuleId'. + + This is achieved by using the t'RuleId' returned by 'registerRule', which + is the unique identifier for that rule. + - File dependencies and outputs are not specified directly by 'FilePath', but rather use the 'Location' type (which is more convenient when working with preprocessors). @@ -335,11 +414,97 @@ Note that: when to re-compute the entire set of rules. -} +{- $dynamicRules +The 'dynamicRule' smart constructor allows specifying rules that have dynamic +dependencies. This is the most complex part of the API, so let's work through +a representative example. + +Suppose for example that we have preprocessor for files that may depend on +each other via explicit import statements at the start of the file. To properly +preprocess these, we need two commands: + + 1. A "find dependencies" command that parses the header to find the dependencies. + 2. A "run preprocessor" command. This command might need to be passed the + dependencies as arguments, so the command in (1) should make that information + available to (2). + +This is exactly what 'dynamicRule' allows us to do. The first command is (1), +which returns dynamic dependencies and additional data passed to the second +command, (2). + +The rules for such a preprocessor would thus look something like (sketch): + +@ +ppDynPreBuildRules :: PreBuildComponentInputs -> RulesM () +ppDynPreBuildRules pbci = mdo -- NB: using RecursiveDo + + -- Scan filesystem for files to preprocess, e.g. with a monitored file glob. + inputModNms <- ... + + let + + -- (1): the "find dependencies" action + computeDepsAction + :: (Map ModuleName RuleId, FilePath) + -> IO ([Dependency], [ModuleName]) + computeDepsAction (modNmToRuleId, inFile) = do + src <- readFile inFile + let dynDeps = parseHeaderImports src + return + ( [ RuleDependency $ RuleOutput rId 1 + | dep <- dynDeps + , let rId = modNmToRuleId Map.! dep ] + , dynDeps ) + + -- (2): the "run preprocessor" action + runPPAction :: (FilePath, FilePath) -> [ModuleName] -> IO () + runPPAction (inFile, outFile) dynDeps = do + src <- readFile inFile + let ppResult = preprocessWithDependencies dynDeps src + writeFile outFile ppResult + + -- Construct a rule with dynamic dependencies using 'dynamicRule' + mkRule modNm = + dynamicRule (static Dict) + (mkCommand (static Dict) (static computeDepsAction) (allRuleIDs, inFile)) + (mkCommand (static Dict) (static runPPAction) (inFile, outFile)) + [ FileDependency $ Location sameDirectory (modPath <.> "myPp") ] + ( Location autogenDir (modPath <.> "hs" ) + NE.:| [ Location buildDir (unsafeCoerceSymbolicPath modPath <.> "myPpIface") ] ) + where + modPath = moduleNameSymbolicPath modNm + inFile = getSymbolicPath (sameDirectory modPath <.> "myPp") + outFile = getSymbolicPath (autogenDir modPath <.> "hs") + + autogenDir = ... -- derived from pbci + buildDir = ... -- derived from pbci + + registerOne :: ModuleName -> RulesM (ModuleName, RuleId) + registerOne modNm = do + rId <- registerRule ("MyPP: " <> show modNm) (mkRule modNm) + return (modNm, rId) + + -- Return a map from ModuleName to RuleId (used above via RecursiveDo). + allRuleIDs <- Map.fromList <$> traverse registerOne inputModNms + return () +@ + +Note the usage of @RecursiveDo@ to allow indexing into the set of all registered +rules in order to declare the dynamic dependencies of the rules. + +In this example we use tuples for the command arguments and @[ModuleName]@ for +the result of the dynamic dependency command; these have the required instances +needed for serialisation. If you use custom datatypes for these, you will need +to derive @Binary@, @Show@, @Eq@ to satisfy the API requirements (enforced by +the various calls to @static Dict@). + +-} + {- $rulesDemand Rules can declare various kinds of dependencies: - - 'staticDependencies': files or other rules that a rule statically depends on, - - extra dynamic dependencies, using the 'DynamicRuleCommands' constructor, + - static dependencies: files or other rules that a rule statically depends on, + - extra dynamic dependencies, using 'dynamicRule' smart constructor, - 'MonitorFilePath': additional files and directories to monitor. Rules are considered __out-of-date__ precisely when any of the following @@ -398,7 +563,8 @@ datatypes, respectively. We use 'addRuleMonitors' to declare a monitored directory that the collection of rules as a whole depends on. In this case, we declare that they depend on the contents of the "searchDir" directory. This means that the rules will be -computed anew whenever the contents of this directory change. +computed anew whenever the contents of this directory change. (This does not +mean all the rules will be re-run; only the out-of-date rules will be re-run.) -} {- Note [Not hiding SetupHooks constructors] diff --git a/Cabal/src/Distribution/Simple/GHC/Internal.hs b/Cabal/src/Distribution/Simple/GHC/Internal.hs index f32b0e34eb3..1cfef4c38d4 100644 --- a/Cabal/src/Distribution/Simple/GHC/Internal.hs +++ b/Cabal/src/Distribution/Simple/GHC/Internal.hs @@ -81,10 +81,10 @@ import Distribution.Types.GivenComponent import Distribution.Types.LocalBuildInfo import Distribution.Types.TargetInfo import Distribution.Types.UnitId +import Distribution.Types.Version import Distribution.Utils.NubList (NubListR, toNubListR) import Distribution.Utils.Path import Distribution.Verbosity -import Distribution.Version (Version) import Language.Haskell.Extension import System.Directory (listDirectory) import System.Environment (getEnv) @@ -482,6 +482,16 @@ componentJsGhcOptions verbosity lbi bi clbi odir filename = , ghcOptExtra = hcOptions GHC bi } +-- Applies options only if the GHC version is greater than or +-- equal to the given one. +ghcOptionsSince :: Monoid a => Version -> Compiler -> a -> a +ghcOptionsSince ver comp defOptions = + case compilerCompatVersion GHC comp of + Just v + | v >= ver -> defOptions + | otherwise -> mempty + Nothing -> mempty + componentGhcOptions :: VerbosityLevel -> LocalBuildInfo @@ -555,6 +565,16 @@ componentGhcOptions verbosity lbi bi clbi odir = , -- Unsupported extensions have already been checked by configure ghcOptExtensions = toNubListR $ usedExtensions bi , ghcOptExtensionMap = Map.fromList . compilerExtensions $ (compiler lbi) + , -- Use -pgmc to ensure that Cabal always passes cc-options, ld-options to GHC (#4435, #9801) + -- We can only do this on GHC >= 9.4, as we need https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6949 + -- Without that GHC MR, this change would cause GHC to never pass -no-pie when linking, + -- which can cause breakage depending on the C toolchain use. We would have to appropriately + -- pass -pgmc-supports-no-pie as appropriate to avoid this regression. + ghcOptCcProgram = + ghcOptionsSince + (mkVersion [9, 4]) + (compiler lbi) + (maybeToFlag $ programPath <$> lookupProgram gccProgram (withPrograms lbi)) } where exe_paths = diff --git a/Cabal/src/Distribution/Simple/SetupHooks/Rule.hs b/Cabal/src/Distribution/Simple/SetupHooks/Rule.hs index 1cee0fa14c8..b5dc929583c 100644 --- a/Cabal/src/Distribution/Simple/SetupHooks/Rule.hs +++ b/Cabal/src/Distribution/Simple/SetupHooks/Rule.hs @@ -289,12 +289,17 @@ instance Show RuleBinary where -- | A rule with static dependencies. -- -- Prefer using this smart constructor instead of v'Rule' whenever possible. +-- +-- See also 'dynamicRule' which adds support for dynamic dependencies. staticRule :: forall arg . Typeable arg => Command arg (IO ()) + -- ^ command to execute the rule -> [Dependency] + -- ^ static dependencies of the rule -> NE.NonEmpty Location + -- ^ rule results -> Rule staticRule cmd dep res = Rule @@ -307,17 +312,32 @@ staticRule cmd dep res = , results = res } --- | A rule with dynamic dependencies. +-- | A rule with dynamic dependencies, which consists of two parts: +-- +-- - a dynamic dependency computation, that returns additional edges to +-- be added to the build graph, together with an additional piece of data, +-- - the command to execute the rule itself, which receives the additional +-- piece of data returned by the dependency computation. -- -- Prefer using this smart constructor instead of v'Rule' whenever possible. +-- +-- Use 'staticRule' if you do not have any dynamic dependencies. dynamicRule :: forall depsArg depsRes arg . (Typeable depsArg, Typeable depsRes, Typeable arg) => StaticPtr (Dict (Binary depsRes, Show depsRes, Eq depsRes)) + -- ^ evidence that the result of the dynamic dependency command + -- is serialisable -> Command depsArg (IO ([Dependency], depsRes)) + -- ^ dynamic dependency computation, returning dynamic dependencies + -- and an additional piece of data to be consumed by the main rule command -> Command arg (depsRes -> IO ()) + -- ^ main rule command; takes in the piece of data returned by the dyn-deps + -- command -> [Dependency] + -- ^ static dependencies of the rule -> NE.NonEmpty Location + -- ^ rule results -> Rule dynamicRule dict depsCmd action dep res = Rule @@ -624,6 +644,9 @@ runCommand (Command{actionPtr = UserStatic ptr, actionArg = ScopedArgument arg}) -- - for a rule with static dependencies, a single command, -- - for a rule with dynamic dependencies, a command for computing dynamic -- dependencies, and a command for executing the rule. +-- +-- Prefer using 'staticRule' and 'dynamicRule' instead of the (internal) +-- constructors of 'RuleCommands'. data RuleCommands (scope :: Scope) diff --git a/cabal-testsuite/PackageTests/CmmSources/cabal.out b/cabal-testsuite/PackageTests/Cmm/CmmSources/cabal.out similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/cabal.out rename to cabal-testsuite/PackageTests/Cmm/CmmSources/cabal.out diff --git a/cabal-testsuite/PackageTests/CmmSources/cabal.project b/cabal-testsuite/PackageTests/Cmm/CmmSources/cabal.project similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/cabal.project rename to cabal-testsuite/PackageTests/Cmm/CmmSources/cabal.project diff --git a/cabal-testsuite/PackageTests/CmmSources/cabal.test.hs b/cabal-testsuite/PackageTests/Cmm/CmmSources/cabal.test.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/cabal.test.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSources/cabal.test.hs diff --git a/cabal-testsuite/PackageTests/CmmSources/cbits/HeapPrim.cmm b/cabal-testsuite/PackageTests/Cmm/CmmSources/cbits/HeapPrim.cmm similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/cbits/HeapPrim.cmm rename to cabal-testsuite/PackageTests/Cmm/CmmSources/cbits/HeapPrim.cmm diff --git a/cabal-testsuite/PackageTests/CmmSources/cmmexperiment.cabal b/cabal-testsuite/PackageTests/Cmm/CmmSources/cmmexperiment.cabal similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/cmmexperiment.cabal rename to cabal-testsuite/PackageTests/Cmm/CmmSources/cmmexperiment.cabal diff --git a/cabal-testsuite/PackageTests/CmmSources/demo/Main.hs b/cabal-testsuite/PackageTests/Cmm/CmmSources/demo/Main.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/demo/Main.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSources/demo/Main.hs diff --git a/cabal-testsuite/PackageTests/CmmSources/setup.out b/cabal-testsuite/PackageTests/Cmm/CmmSources/setup.out similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/setup.out rename to cabal-testsuite/PackageTests/Cmm/CmmSources/setup.out diff --git a/cabal-testsuite/PackageTests/CmmSources/setup.test.hs b/cabal-testsuite/PackageTests/Cmm/CmmSources/setup.test.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/setup.test.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSources/setup.test.hs diff --git a/cabal-testsuite/PackageTests/CmmSources/src/Demo.hs b/cabal-testsuite/PackageTests/Cmm/CmmSources/src/Demo.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSources/src/Demo.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSources/src/Demo.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/cabal.out b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cabal.out similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/cabal.out rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cabal.out diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/cabal.project b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cabal.project similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/cabal.project rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cabal.project diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/cabal.test.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cabal.test.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/cabal.test.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cabal.test.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/cbits/HeapPrim.cmm b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cbits/HeapPrim.cmm similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/cbits/HeapPrim.cmm rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cbits/HeapPrim.cmm diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/cmmexperiment.cabal b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cmmexperiment.cabal similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/cmmexperiment.cabal rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/cmmexperiment.cabal diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/demo/Main.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/demo/Main.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/demo/Main.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/demo/Main.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/setup.out b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/setup.out similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/setup.out rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/setup.out diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/setup.test.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/setup.test.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/setup.test.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/setup.test.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesDyn/src/Demo.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/src/Demo.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesDyn/src/Demo.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesDyn/src/Demo.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/cabal.out b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cabal.out similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/cabal.out rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cabal.out diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/cabal.project b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cabal.project similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/cabal.project rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cabal.project diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/cabal.test.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cabal.test.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/cabal.test.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cabal.test.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/cbits/HeapPrim.cmm b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cbits/HeapPrim.cmm similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/cbits/HeapPrim.cmm rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cbits/HeapPrim.cmm diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/cmmexperiment.cabal b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cmmexperiment.cabal similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/cmmexperiment.cabal rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/cmmexperiment.cabal diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/demo/Main.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/demo/Main.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/demo/Main.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/demo/Main.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/setup.out b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/setup.out similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/setup.out rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/setup.out diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/setup.test.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/setup.test.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/setup.test.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/setup.test.hs diff --git a/cabal-testsuite/PackageTests/CmmSourcesExe/src/Demo.hs b/cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/src/Demo.hs similarity index 100% rename from cabal-testsuite/PackageTests/CmmSourcesExe/src/Demo.hs rename to cabal-testsuite/PackageTests/Cmm/CmmSourcesExe/src/Demo.hs diff --git a/cabal-testsuite/PackageTests/CppOptions/CppOpts/Main.hs b/cabal-testsuite/PackageTests/CppOptions/CppOpts/Main.hs new file mode 100644 index 00000000000..cdd4f68347a --- /dev/null +++ b/cabal-testsuite/PackageTests/CppOptions/CppOpts/Main.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE CPP #-} + +module Main where + +#ifndef __TESTOPT_CPP__ +#error "Did not get required __TESTOPT_CPP__ from cpp-options" +#endif + +main :: IO () +main = do + -- The value 44 comes from __TESTOPT_CPP__ - see the cabal file. + let secret = __TESTOPT_CPP__ :: Int + if secret == 44 + then putStrLn ("The secret is " ++ show secret) + else error ("Expected value 44, got " ++ show secret) diff --git a/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.out b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.project b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.project new file mode 100644 index 00000000000..e6fdbadb439 --- /dev/null +++ b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.test.hs b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.test.hs new file mode 100644 index 00000000000..185491f22c9 --- /dev/null +++ b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cabal.test.hs @@ -0,0 +1,5 @@ +import Test.Cabal.Prelude + +main = cabalTest $ recordMode DoNotRecord $ do + cabal "v2-build" ["cpp-opts-exe"] + withPlan $ runPlanExe "cpp-opts" "cpp-opts-exe" [] diff --git a/cabal-testsuite/PackageTests/CppOptions/CppOpts/cpp-opts.cabal b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cpp-opts.cabal new file mode 100644 index 00000000000..9913b86d720 --- /dev/null +++ b/cabal-testsuite/PackageTests/CppOptions/CppOpts/cpp-opts.cabal @@ -0,0 +1,10 @@ +cabal-version: 2.2 +name: cpp-opts +version: 0.1 +build-type: Simple + +executable cpp-opts-exe + main-is: Main.hs + build-depends: base + default-language: Haskell2010 + cpp-options: -D__TESTOPT_CPP__=44 diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/Main.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/Main.hs new file mode 100644 index 00000000000..ec92f1779fa --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/Main.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Main where + +import Foreign.C (CInt (..)) + +foreign import ccall "asmlib.h meaning_of_life_asm" + meaning_of_life_asm :: IO CInt + +main :: IO () +main = do + secret <- meaning_of_life_asm + -- The value 33 comes from meaning_of_life_val - see asm-options in the cabal file. + if (secret == 33) + then putStrLn ("The secret is " ++ show secret) + else error ("Expected value 33, got " ++ show secret) diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib.h b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib.h new file mode 100644 index 00000000000..0b373105531 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib.h @@ -0,0 +1,6 @@ +#ifndef ASMLIB_H +#define ASMLIB_H + +int meaning_of_life_asm(); + +#endif diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_aarch64_darwin.S b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_aarch64_darwin.S new file mode 100644 index 00000000000..3a13064e4cc --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_aarch64_darwin.S @@ -0,0 +1,11 @@ + # macOS/AArch64 (Mach-O) + .macro func name + .globl _\name +_\name: + .endm + + .text + func meaning_of_life_asm + + mov w0, #MEANING_OF_LIFE_VAL + ret \ No newline at end of file diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_x86_64_linux.S b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_x86_64_linux.S new file mode 100644 index 00000000000..2c1dfb2f758 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_x86_64_linux.S @@ -0,0 +1,7 @@ + .text + .globl meaning_of_life_asm + .type meaning_of_life_asm, @function +meaning_of_life_asm: + movl $MEANING_OF_LIFE_VAL, %eax + ret + .size meaning_of_life_asm, .-meaning_of_life_asm \ No newline at end of file diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_x86_64_windows.S b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_x86_64_windows.S new file mode 100644 index 00000000000..0669d7acfd7 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/abits/asmlib_x86_64_windows.S @@ -0,0 +1,6 @@ + .text + .globl meaning_of_life_asm + +meaning_of_life_asm: + movl $MEANING_OF_LIFE_VAL, %eax + ret \ No newline at end of file diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.out b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.project b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.project new file mode 100644 index 00000000000..e6fdbadb439 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.test.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.test.hs new file mode 100644 index 00000000000..06e7f7b439d --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/cabal.test.hs @@ -0,0 +1,12 @@ +import Test.Cabal.Prelude +import Distribution.System (Arch (..), buildArch, OS (..), buildOS) + +main = do + skipUnlessIO "needs x86_64 or aarch64" + ( (buildArch == X86_64 && buildOS == Windows) + || (buildArch == X86_64 && buildOS == Linux) + || (buildArch == AArch64 && buildOS == OSX) + ) + cabalTest $ recordMode DoNotRecord $ do + cabal "v2-build" ["foreign-opts-asm-exe"] + withPlan $ runPlanExe "foreign-opts-asm" "foreign-opts-asm-exe" [] diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/foreign-opts-asm.cabal b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/foreign-opts-asm.cabal new file mode 100644 index 00000000000..2623c36aa3f --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsAsm/foreign-opts-asm.cabal @@ -0,0 +1,17 @@ +cabal-version: 3.0 +name: foreign-opts-asm +version: 0.1 +build-type: Simple + +executable foreign-opts-asm-exe + main-is: Main.hs + build-depends: base + default-language: Haskell2010 + include-dirs: abits + if os(windows) && arch(x86_64) + asm-sources: abits/asmlib_x86_64_windows.S + elif os(darwin) && arch(aarch64) + asm-sources: abits/asmlib_aarch64_darwin.S + elif os(linux) && arch(x86_64) + asm-sources: abits/asmlib_x86_64_linux.S + asm-options: -DMEANING_OF_LIFE_VAL=33 diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsCxx/Main.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsCxx/Main.hs index 2343305fc39..50e2d727d7e 100644 --- a/cabal-testsuite/PackageTests/FFI/ForeignOptsCxx/Main.hs +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsCxx/Main.hs @@ -11,6 +11,6 @@ main :: IO () main = do secret <- meaning_of_life_cxx -- The value 22 comes from __TESTOPT_CXX__ - see the cabal file. - if (secret == 22) + if secret == 22 then putStrLn ("The secret is " ++ show secret) else error ("Expected value 22, got " ++ show secret) diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/Main.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/Main.hs new file mode 100644 index 00000000000..33e31fe719c --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/Main.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Main where + +import Foreign.C (CInt (..)) + +-- With ld-options: -Wl,--wrap=meaning_of_life_ld_real, the linker redirects +-- this call to __wrap_meaning_of_life_ld_real, which returns 55. +foreign import ccall "ldlib.h meaning_of_life_ld_real" + meaning_of_life_ld_real :: IO CInt + +main :: IO () +main = do + secret <- meaning_of_life_ld_real + -- The value 55 comes from __wrap_meaning_of_life_ld_real - see ld-options in the cabal file. + if secret == 55 + then putStrLn ("The secret is " ++ show secret) + else error ("Expected value 55, got " ++ show secret) diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.out b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.project b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.project new file mode 100644 index 00000000000..e6fdbadb439 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.test.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.test.hs new file mode 100644 index 00000000000..395bebb60dc --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cabal.test.hs @@ -0,0 +1,8 @@ +import Test.Cabal.Prelude + +main = do + skipIfOSX "Apple linker does not support --wrap" + skipIfWindows "Windows linker does not support --wrap" + cabalTest $ recordMode DoNotRecord $ do + cabal "v2-build" ["foreign-opts-ld-exe"] + withPlan $ runPlanExe "foreign-opts-ld" "foreign-opts-ld-exe" [] diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cbits/ldlib.c b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cbits/ldlib.c new file mode 100644 index 00000000000..67e7972c6b2 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cbits/ldlib.c @@ -0,0 +1,14 @@ +#include "ldlib.h" + +/* The "real" implementation - returns 0, the wrong value. + * With ld-options: -Wl,--wrap=meaning_of_life_ld_real, the linker redirects + * all calls to this function to __wrap_meaning_of_life_ld_real below. */ +int meaning_of_life_ld_real() { + return 0; +} + +/* The wrapper that the linker substitutes in place of the real function. + * Returns 55 - see ld-options in the cabal file. */ +int __wrap_meaning_of_life_ld_real() { + return 55; +} diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cbits/ldlib.h b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cbits/ldlib.h new file mode 100644 index 00000000000..773c3dc6853 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/cbits/ldlib.h @@ -0,0 +1,10 @@ +#ifndef LDLIB_H +#define LDLIB_H + +/* The "real" function; with --wrap, calls to this are redirected by the linker. */ +int meaning_of_life_ld_real(); + +/* The wrapper that the linker calls instead of the real function. */ +int __wrap_meaning_of_life_ld_real(); + +#endif diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/foreign-opts-ld.cabal b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/foreign-opts-ld.cabal new file mode 100644 index 00000000000..741874d0a56 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsLd/foreign-opts-ld.cabal @@ -0,0 +1,15 @@ +cabal-version: 2.2 +name: foreign-opts-ld +version: 0.1 +build-type: Simple + +executable foreign-opts-ld-exe + main-is: Main.hs + build-depends: base + default-language: Haskell2010 + include-dirs: cbits + c-sources: cbits/ldlib.c + -- Redirect calls to meaning_of_life_ld_real to __wrap_meaning_of_life_ld_real. + -- If ld-options are not passed the real function (returning 0) is called instead + -- of the wrapper (returning 55), and the test fails at runtime. + ld-options: -Wl,--wrap=meaning_of_life_ld_real diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/Main.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/Main.hs new file mode 100644 index 00000000000..e3da361399f --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/Main.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Main where + +import Foreign.C (CInt (..)) + +foreign import ccall "pgmclib.h meaning_of_life_pgmc" + meaning_of_life_pgmc :: IO CInt + +main :: IO () +main = do + secret <- meaning_of_life_pgmc + -- The value 66 comes from __TESTOPT_PGMC__ - see cc-wrapper.sh. + if (secret == 66) + then putStrLn ("The secret is " ++ show secret) + else error ("Expected value 66, got " ++ show secret) diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/README.md b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/README.md new file mode 100644 index 00000000000..3f5209e81e6 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/README.md @@ -0,0 +1,7 @@ +# ForeignOptsPgmc + +This test case asserts that cabal passes the `-pgmc` GHC option to override the C compiler program. + +The cabal file sets `ghc-options: -pgmc scripts/cc-wrapper.sh`, pointing GHC at a shell script wrapper (`scripts/cc-wrapper.sh`) instead of the system C compiler. The wrapper adds `-D__TESTOPT_PGMC__=66` to every compilation and then delegates to the real `cc`. The C source requires `__TESTOPT_PGMC__` to be defined; if the wrapper is not used as the C compiler, the build fails with a `#error`. + +This test is skipped on Windows (no POSIX shell). diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.out b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.project b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.project new file mode 100644 index 00000000000..e6fdbadb439 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.test.hs b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.test.hs new file mode 100644 index 00000000000..8c1395ad636 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cabal.test.hs @@ -0,0 +1,13 @@ +import Test.Cabal.Prelude + +main = do + skipIfWindows "requires a POSIX shell script as the C compiler wrapper" + -- Use -pgmc to ensure that Cabal always passes cc-options, ld-options to GHC (#4435, #9801) + -- We can only do this on GHC >= 9.4, as we need https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6949 + -- Without that GHC MR, this change would cause GHC to never pass -no-pie when linking, + -- which can cause breakage depending on the C toolchain use. We would have to appropriately + -- pass -pgmc-supports-no-pie as appropriate to avoid this regression. + cabalTest $ recordMode DoNotRecord $ do + skipUnlessGhcVersion ">= 9.4" + cabal "v2-build" ["foreign-opts-pgmc-exe"] + withPlan $ runPlanExe "foreign-opts-pgmc" "foreign-opts-pgmc-exe" [] diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cbits/pgmclib.c b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cbits/pgmclib.c new file mode 100644 index 00000000000..a93cf4961e4 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cbits/pgmclib.c @@ -0,0 +1,9 @@ +#include "pgmclib.h" + +#ifndef __TESTOPT_PGMC__ +#error "Did not get required __TESTOPT_PGMC__ from the -pgmc wrapper" +#endif + +int meaning_of_life_pgmc() { + return __TESTOPT_PGMC__; +} diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cbits/pgmclib.h b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cbits/pgmclib.h new file mode 100644 index 00000000000..1e525fd32bf --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/cbits/pgmclib.h @@ -0,0 +1,6 @@ +#ifndef PGMCLIB_H +#define PGMCLIB_H + +int meaning_of_life_pgmc(); + +#endif diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/foreign-opts-pgmc.cabal b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/foreign-opts-pgmc.cabal new file mode 100644 index 00000000000..e8a119c3d36 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/foreign-opts-pgmc.cabal @@ -0,0 +1,12 @@ +cabal-version: 2.2 +name: foreign-opts-pgmc +version: 0.1 +build-type: Simple + +executable foreign-opts-pgmc-exe + main-is: Main.hs + build-depends: base + default-language: Haskell2010 + include-dirs: cbits + c-sources: cbits/pgmclib.c + ghc-options: -pgmc scripts/cc-wrapper.sh diff --git a/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/scripts/cc-wrapper.sh b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/scripts/cc-wrapper.sh new file mode 100755 index 00000000000..8ef2af63ec1 --- /dev/null +++ b/cabal-testsuite/PackageTests/FFI/ForeignOptsPgmc/scripts/cc-wrapper.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Wrapper around cc that adds -D__TESTOPT_PGMC__=66 to every compilation. +# Used by the ForeignOptsPgmc test to verify that -pgmc selects this wrapper. +exec cc -D__TESTOPT_PGMC__=66 "$@" diff --git a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single-2.out b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single-2.out new file mode 100644 index 00000000000..4360812ba2e --- /dev/null +++ b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single-2.out @@ -0,0 +1,67 @@ +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - Complex-0.1.0.0 (lib) (first run) + - Complex-0.1.0.0 (exe:Complex) (first run) +Configuring library for Complex-0.1.0.0... +Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a directory which does not exist. +Preprocessing library for Complex-0.1.0.0... +Building library for Complex-0.1.0.0... +Configuring executable 'Complex' for Complex-0.1.0.0... +Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a directory which does not exist. +Preprocessing executable 'Complex' for Complex-0.1.0.0... +Building executable 'Complex' for Complex-0.1.0.0... +# show-build-info Complex exe:Complex +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"exe","name":"exe:Complex","unit-id":"Complex-0.1.0.0-inplace-Complex","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-odir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hidir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hiedir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/extra-compilation-artifacts/hie","-stubdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-i","-iapp","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-optP-include","-optPsingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-Complex","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single-2.dist/home/.cabal/store/ghc-/package.db","-package-db","/single-2.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010","-threaded","-rtsopts","-with-rtsopts=-N -T","-Wredundant-constraints"],"modules":["Other","Paths_Complex"],"src-files":["Main.lhs"],"hs-src-dirs":["app"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +# cabal build +Up to date +# show-build-info Complex lib +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"lib","name":"lib","unit-id":"Complex-0.1.0.0-inplace","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-odir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hidir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hiedir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/extra-compilation-artifacts/hie","-stubdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-i","-isrc","-idoesnt-exist","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-optP-include","-optPsingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single-2.dist/home/.cabal/store/ghc-/package.db","-package-db","/single-2.dist/work/dist/packagedb/ghc-","-package-id","","-XHaskell2010","-Wall"],"modules":["A","B","C","D","Paths_Complex"],"src-files":[],"hs-src-dirs":["src","doesnt-exist"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +# cabal build +Build profile: -w ghc- -O1 +In order, the following will be built: + - criterion-1.1.4.0 (lib) (requires build) + - Complex-0.1.0.0 (bench:complex-benchmarks) (first run) +Configuring library for criterion-1.1.4.0... +Preprocessing library for criterion-1.1.4.0... +Building library for criterion-1.1.4.0... +Installing library in +Configuring benchmark 'complex-benchmarks' for Complex-0.1.0.0... +Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a directory which does not exist. +Preprocessing benchmark 'complex-benchmarks' for Complex-0.1.0.0... +Building benchmark 'complex-benchmarks' for Complex-0.1.0.0... +# show-build-info Complex bench:complex-benchmarks +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"bench","name":"bench:complex-benchmarks","unit-id":"Complex-0.1.0.0-inplace-complex-benchmarks","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-odir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hidir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hiedir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/extra-compilation-artifacts/hie","-stubdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-i","-ibenchmark","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-optP-include","-optPsingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-complex-benchmarks","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single-2.dist/home/.cabal/store/ghc-/package.db","-package-db","/single-2.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010","-Wall","-rtsopts","-threaded","-with-rtsopts=-N"],"modules":["Paths_Complex"],"src-files":["Main.hs"],"hs-src-dirs":["benchmark"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +# cabal build +Build profile: -w ghc- -O1 +In order, the following will be built: + - test-framework-0.8.1.1 (lib) (requires build) + - Complex-0.1.0.0 (test:func-test) (first run) +Configuring library for test-framework-0.8.1.1... +Preprocessing library for test-framework-0.8.1.1... +Building library for test-framework-0.8.1.1... +Installing library in +Configuring test suite 'func-test' for Complex-0.1.0.0... +Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a directory which does not exist. +Preprocessing test suite 'func-test' for Complex-0.1.0.0... +Building test suite 'func-test' for Complex-0.1.0.0... +# show-build-info Complex test:func-test +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:func-test","unit-id":"Complex-0.1.0.0-inplace-func-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-odir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hidir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hiedir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/extra-compilation-artifacts/hie","-stubdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-i","-itest","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-optP-include","-optPsingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-func-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single-2.dist/home/.cabal/store/ghc-/package.db","-package-db","/single-2.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["FuncMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +# cabal build +Build profile: -w ghc- -O1 +In order, the following will be built: + - another-framework-0.8.1.1 (lib) (requires build) + - Complex-0.1.0.0 (test:unit-test) (first run) +Configuring library for another-framework-0.8.1.1... +Preprocessing library for another-framework-0.8.1.1... +Building library for another-framework-0.8.1.1... +Installing library in +Configuring test suite 'unit-test' for Complex-0.1.0.0... +Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a directory which does not exist. +Preprocessing test suite 'unit-test' for Complex-0.1.0.0... +Building test suite 'unit-test' for Complex-0.1.0.0... +# show-build-info Complex test:unit-test +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:unit-test","unit-id":"Complex-0.1.0.0-inplace-unit-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-odir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hidir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hiedir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/extra-compilation-artifacts/hie","-stubdir","single-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-i","-itest","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-optP-include","-optPsingle-2.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-unit-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single-2.dist/home/.cabal/store/ghc-/package.db","-package-db","/single-2.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["UnitMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} \ No newline at end of file diff --git a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single-2.test.hs b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single-2.test.hs new file mode 100644 index 00000000000..aee4385bb54 --- /dev/null +++ b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single-2.test.hs @@ -0,0 +1,75 @@ +{-# LANGUAGE OverloadedStrings #-} + +import Test.Cabal.DecodeShowBuildInfo +import Test.Cabal.Prelude + +main = cabalTest $ do + -- the With GHC-9.2+ output contains -this-unit-id + skipUnlessGhcVersion "^>= 9.2" + withRepo "repo" $ do + runShowBuildInfo ["exe:Complex"] + >> withPlan + ( do + recordBuildInfo "Complex" (exe "Complex") + assertComponent + "Complex" + (exe "Complex") + defCompAssertion + { modules = ["Other", "Paths_Complex"] + , sourceFiles = ["Main.lhs"] + , sourceDirs = ["app"] + } + ) + + runShowBuildInfo ["lib:Complex"] + >> withPlan + ( do + recordBuildInfo "Complex" mainLib + assertComponent + "Complex" + mainLib + defCompAssertion + { modules = ["A", "B", "C", "D", "Paths_Complex"] + , sourceDirs = ["src", "doesnt-exist"] + } + ) + + runShowBuildInfo ["benchmark:complex-benchmarks"] + >> withPlan + ( do + recordBuildInfo "Complex" (bench "complex-benchmarks") + assertComponent + "Complex" + (bench "complex-benchmarks") + defCompAssertion + { modules = ["Paths_Complex"] + , sourceFiles = ["Main.hs"] + , sourceDirs = ["benchmark"] + } + ) + + runShowBuildInfo ["test:func-test"] + >> withPlan + ( do + recordBuildInfo "Complex" (test "func-test") + assertComponent + "Complex" + (test "func-test") + defCompAssertion + { sourceFiles = ["FuncMain.hs"] + , sourceDirs = ["test"] + } + ) + + runShowBuildInfo ["test:unit-test"] + >> withPlan + ( do + recordBuildInfo "Complex" (test "unit-test") + assertComponent + "Complex" + (test "unit-test") + defCompAssertion + { sourceFiles = ["UnitMain.hs"] + , sourceDirs = ["test"] + } + ) diff --git a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out index 3825d1f6cf8..fb8047cbd02 100644 --- a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out +++ b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.out @@ -15,11 +15,11 @@ Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a director Preprocessing executable 'Complex' for Complex-0.1.0.0... Building executable 'Complex' for Complex-0.1.0.0... # show-build-info Complex exe:Complex -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"exe","name":"exe:Complex","unit-id":"Complex-0.1.0.0-inplace-Complex","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-i","-iapp","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-Complex","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010","-threaded","-rtsopts","-with-rtsopts=-N -T","-Wredundant-constraints"],"modules":["Other","Paths_Complex"],"src-files":["Main.lhs"],"hs-src-dirs":["app"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"exe","name":"exe:Complex","unit-id":"Complex-0.1.0.0-inplace-Complex","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-i","-iapp","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/x/Complex/build/Complex/autogen/cabal_macros.h","-pgmc","","-this-unit-id","Complex-0.1.0.0-inplace-Complex","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010","-threaded","-rtsopts","-with-rtsopts=-N -T","-Wredundant-constraints"],"modules":["Other","Paths_Complex"],"src-files":["Main.lhs"],"hs-src-dirs":["app"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Up to date # show-build-info Complex lib -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"lib","name":"lib","unit-id":"Complex-0.1.0.0-inplace","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-i","-isrc","-idoesnt-exist","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-XHaskell2010","-Wall"],"modules":["A","B","C","D","Paths_Complex"],"src-files":[],"hs-src-dirs":["src","doesnt-exist"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"lib","name":"lib","unit-id":"Complex-0.1.0.0-inplace","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-i","-isrc","-idoesnt-exist","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/build/autogen/cabal_macros.h","-pgmc","","-this-unit-id","Complex-0.1.0.0-inplace","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-XHaskell2010","-Wall"],"modules":["A","B","C","D","Paths_Complex"],"src-files":[],"hs-src-dirs":["src","doesnt-exist"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Build profile: -w ghc- -O1 In order, the following will be built: @@ -34,7 +34,7 @@ Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a director Preprocessing benchmark 'complex-benchmarks' for Complex-0.1.0.0... Building benchmark 'complex-benchmarks' for Complex-0.1.0.0... # show-build-info Complex bench:complex-benchmarks -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"bench","name":"bench:complex-benchmarks","unit-id":"Complex-0.1.0.0-inplace-complex-benchmarks","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-i","-ibenchmark","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-complex-benchmarks","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010","-Wall","-rtsopts","-threaded","-with-rtsopts=-N"],"modules":["Paths_Complex"],"src-files":["Main.hs"],"hs-src-dirs":["benchmark"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"bench","name":"bench:complex-benchmarks","unit-id":"Complex-0.1.0.0-inplace-complex-benchmarks","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-i","-ibenchmark","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/b/complex-benchmarks/build/complex-benchmarks/autogen/cabal_macros.h","-pgmc","","-this-unit-id","Complex-0.1.0.0-inplace-complex-benchmarks","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010","-Wall","-rtsopts","-threaded","-with-rtsopts=-N"],"modules":["Paths_Complex"],"src-files":["Main.hs"],"hs-src-dirs":["benchmark"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Build profile: -w ghc- -O1 In order, the following will be built: @@ -49,7 +49,7 @@ Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a director Preprocessing test suite 'func-test' for Complex-0.1.0.0... Building test suite 'func-test' for Complex-0.1.0.0... # show-build-info Complex test:func-test -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:func-test","unit-id":"Complex-0.1.0.0-inplace-func-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-func-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["FuncMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:func-test","unit-id":"Complex-0.1.0.0-inplace-func-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/func-test/build/func-test/autogen/cabal_macros.h","-pgmc","","-this-unit-id","Complex-0.1.0.0-inplace-func-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["FuncMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} # cabal build Build profile: -w ghc- -O1 In order, the following will be built: @@ -64,4 +64,4 @@ Warning: [unknown-directory] 'hs-source-dirs: doesnt-exist' specifies a director Preprocessing test suite 'unit-test' for Complex-0.1.0.0... Building test suite 'unit-test' for Complex-0.1.0.0... # show-build-info Complex test:unit-test -{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:unit-test","unit-id":"Complex-0.1.0.0-inplace-unit-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen/cabal_macros.h","-this-unit-id","Complex-0.1.0.0-inplace-unit-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["UnitMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} +{"cabal-lib-version":"","compiler":{"flavour":"ghc","compiler-id":"ghc-","path":""},"components":[{"type":"test","name":"test:unit-test","unit-id":"Complex-0.1.0.0-inplace-unit-test","compiler-args":["-fbuilding-cabal-package","-O","-outputdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-odir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hidir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-hiedir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/extra-compilation-artifacts/hie","-stubdir","single.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-i","-itest","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/global-autogen","-Isingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build","-optP-include","-optPsingle.dist/work/./dist/build//ghc-/Complex-0.1.0.0/t/unit-test/build/unit-test/autogen/cabal_macros.h","-pgmc","","-this-unit-id","Complex-0.1.0.0-inplace-unit-test","-hide-all-packages","-Wmissing-home-modules","-no-user-package-db","-package-db","/single.dist/home/.cabal/store/ghc-/package.db","-package-db","/single.dist/work/dist/packagedb/ghc-","-package-id","","-package-id","","-XHaskell2010"],"modules":[],"src-files":["UnitMain.hs"],"hs-src-dirs":["test"],"src-dir":"/","cabal-file":"Complex.cabal"}]} \ No newline at end of file diff --git a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.test.hs b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.test.hs index b4bdc16f0cd..8869461c2e7 100644 --- a/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.test.hs +++ b/cabal-testsuite/PackageTests/ShowBuildInfo/Complex/single.test.hs @@ -1,44 +1,76 @@ {-# LANGUAGE OverloadedStrings #-} -import Test.Cabal.Prelude -import Test.Cabal.DecodeShowBuildInfo + +import Test.Cabal.DecodeShowBuildInfo +import Test.Cabal.Prelude main = cabalTest $ do -- the With GHC-9.2+ output contains -this-unit-id - skipUnlessGhcVersion ">= 9.2" + -- the With GHC-9.4+ output contains -pgmc by default + skipUnlessGhcVersion ">= 9.4" withRepo "repo" $ do - runShowBuildInfo ["exe:Complex"] >> withPlan (do - recordBuildInfo "Complex" (exe "Complex") - assertComponent "Complex" (exe "Complex") defCompAssertion - { modules = ["Other", "Paths_Complex"] - , sourceFiles = ["Main.lhs"] - , sourceDirs = ["app"] - }) + runShowBuildInfo ["exe:Complex"] + >> withPlan + ( do + recordBuildInfo "Complex" (exe "Complex") + assertComponent + "Complex" + (exe "Complex") + defCompAssertion + { modules = ["Other", "Paths_Complex"] + , sourceFiles = ["Main.lhs"] + , sourceDirs = ["app"] + } + ) - runShowBuildInfo ["lib:Complex"] >> withPlan (do - recordBuildInfo "Complex" mainLib - assertComponent "Complex" mainLib defCompAssertion - { modules = ["A", "B", "C", "D", "Paths_Complex"] - , sourceDirs = ["src", "doesnt-exist"] - }) + runShowBuildInfo ["lib:Complex"] + >> withPlan + ( do + recordBuildInfo "Complex" mainLib + assertComponent + "Complex" + mainLib + defCompAssertion + { modules = ["A", "B", "C", "D", "Paths_Complex"] + , sourceDirs = ["src", "doesnt-exist"] + } + ) - runShowBuildInfo ["benchmark:complex-benchmarks"] >> withPlan (do - recordBuildInfo "Complex" (bench "complex-benchmarks") - assertComponent "Complex" (bench "complex-benchmarks") defCompAssertion - { modules = ["Paths_Complex"] - , sourceFiles = ["Main.hs"] - , sourceDirs = ["benchmark"] - }) + runShowBuildInfo ["benchmark:complex-benchmarks"] + >> withPlan + ( do + recordBuildInfo "Complex" (bench "complex-benchmarks") + assertComponent + "Complex" + (bench "complex-benchmarks") + defCompAssertion + { modules = ["Paths_Complex"] + , sourceFiles = ["Main.hs"] + , sourceDirs = ["benchmark"] + } + ) - runShowBuildInfo ["test:func-test"] >> withPlan (do - recordBuildInfo "Complex" (test "func-test") - assertComponent "Complex" (test "func-test") defCompAssertion - { sourceFiles = ["FuncMain.hs"] - , sourceDirs = ["test"] - }) + runShowBuildInfo ["test:func-test"] + >> withPlan + ( do + recordBuildInfo "Complex" (test "func-test") + assertComponent + "Complex" + (test "func-test") + defCompAssertion + { sourceFiles = ["FuncMain.hs"] + , sourceDirs = ["test"] + } + ) - runShowBuildInfo ["test:unit-test"] >> withPlan (do - recordBuildInfo "Complex" (test "unit-test") - assertComponent "Complex" (test "unit-test") defCompAssertion - { sourceFiles = ["UnitMain.hs"] - , sourceDirs = ["test"] - }) + runShowBuildInfo ["test:unit-test"] + >> withPlan + ( do + recordBuildInfo "Complex" (test "unit-test") + assertComponent + "Complex" + (test "unit-test") + defCompAssertion + { sourceFiles = ["UnitMain.hs"] + , sourceDirs = ["test"] + } + ) diff --git a/cabal-testsuite/PackageTests/ShowBuildInfo/Custom/custom.test.hs b/cabal-testsuite/PackageTests/ShowBuildInfo/Custom/custom.test.hs index f8c413c7c5b..182a0338347 100644 --- a/cabal-testsuite/PackageTests/ShowBuildInfo/Custom/custom.test.hs +++ b/cabal-testsuite/PackageTests/ShowBuildInfo/Custom/custom.test.hs @@ -1,7 +1,8 @@ {-# LANGUAGE OverloadedStrings #-} -import Test.Cabal.Prelude -import Test.Cabal.DecodeShowBuildInfo -import Control.Monad.Trans.Reader + +import Control.Monad.Trans.Reader +import Test.Cabal.DecodeShowBuildInfo +import Test.Cabal.Prelude main = setupTest $ do -- No cabal test because per-component is broken with it @@ -14,15 +15,18 @@ main = setupTest $ do assertCommonBuildInfo buildInfo let [libBI, exeBI] = components buildInfo - assertComponentPure libBI defCompAssertion - { modules = ["MyLib"] - , compType = "lib" - , sourceDirs = ["src"] - } - - assertComponentPure exeBI defCompAssertion - { sourceFiles = ["Main.hs"] - , compType = "exe" - , sourceDirs = ["app"] - } + assertComponentPure + libBI + defCompAssertion + { modules = ["MyLib"] + , compType = "lib" + , sourceDirs = ["src"] + } + assertComponentPure + exeBI + defCompAssertion + { sourceFiles = ["Main.hs"] + , compType = "exe" + , sourceDirs = ["app"] + } diff --git a/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs b/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs index c4b45c6f161..57bcb5dbb79 100644 --- a/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs +++ b/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs @@ -125,6 +125,10 @@ normalizeOutput nenv = resub ("\"path\":\"" <> posixRegexEscape (normalizerGhcPath nenv) <> "\"") "\"path\":\"\"" + -- Normalize the C compiler path embedded in -pgmc. + . resub + ("\"-pgmc\",\"[^\"]+\"") + "\"-pgmc\",\"\"" -- Remove cabal version output from show-build-info output . resub ("{\"cabal-lib-version\":\"" ++ posixRegexEscape (display (normalizerCabalVersion nenv)) ++ "\"") diff --git a/changelog.d/11713.md b/changelog.d/11713.md new file mode 100644 index 00000000000..04669a05c75 --- /dev/null +++ b/changelog.d/11713.md @@ -0,0 +1,9 @@ +--- +synopsis: Adding an explicit pgmc flag for interaction with GHC +packages: [Cabal] +prs: 11713 +--- + +Added an explicit `pgmc` flag to Cabal's GHC invocation logic. This allows for +reliable and consistent passing of `cc-options`, `ld-options`, and `cpp-options` +to GHC by removing conditional filtering of these arguments. diff --git a/changelog.d/UnexposeRuleCommands.md b/changelog.d/UnexposeRuleCommands.md new file mode 100644 index 00000000000..eab08b3c6bb --- /dev/null +++ b/changelog.d/UnexposeRuleCommands.md @@ -0,0 +1,10 @@ +--- +synopsis: Stop exposing constructors of RuleCommands +packages: [Cabal-hooks] +prs: 11771 +issues: 11461 +--- + +The constructors of the `SetupHooks` `RuleCommands` are no longer exposed from +`Distribution.Simple.SetupHooks`. These were rather gnarly internal constructors; +the intended public API is via `staticRule` and `dynamicRule`. diff --git a/doc/cabal-package-description-file.rst b/doc/cabal-package-description-file.rst index 9b73463a516..184c2abae56 100644 --- a/doc/cabal-package-description-file.rst +++ b/doc/cabal-package-description-file.rst @@ -2893,9 +2893,11 @@ while a basic ``SetupHooks.hs`` file might look like the following: -- ... +For a detailed guide on using the ``Hooks`` build type, see +:ref:`How to use the Hooks build type `. Refer to the `Hackage documentation for the Distribution.Simple.SetupHooks module `__ -for an overview of the ``Hooks`` API. Further motivation and a technical overview -of the design is available in `Haskell Tech Proposal #60 `__ . +for a full reference of the ``Hooks`` API. Further motivation and a technical overview +of the design is available in `Haskell Tech Proposal #60 `__. .. _custom-setup: diff --git a/doc/how-to-use-setup-hooks.rst b/doc/how-to-use-setup-hooks.rst new file mode 100644 index 00000000000..917c495025f --- /dev/null +++ b/doc/how-to-use-setup-hooks.rst @@ -0,0 +1,275 @@ +.. _setup-hooks-guide: + +How to use the Hooks build type +================================ + +The ``Hooks`` build type allows customising the configuration and building +of a package using a collection of **hooks** into the build system. +It was introduced in Cabal 3.14 as a replacement for the +:ref:`Custom build type `. + +See also: + + - `Hackage documentation for the Cabal-hooks package + `__ + - `Haskell Tech Proposal introducing the feature + `__ + - `Introductory blog post + `__ + +.. _setup-hooks-motivation: + +Why use ``build-type: Hooks``? +------------------------------- + +The main problem with ``build-type: Custom`` is that it is a wholesale replacement +of the entire build system, which means it doesn't integrate with other tooling, +such as the Haskell Language Server. + +``build-type: Hooks`` aims to remedy this by guaranteeing that the main phases +for configuring/building a package are still the ``Cabal`` ``configure`` and +``build`` phases, while allowing custom hooks to run in between. + +Another advantage of ``build-type: Hooks`` is that it is defined as a Haskell +library interface, instead of the command-line interface of ``Setup.hs``. + + +.. _setup-hooks-usage: + +How to use ``build-type: Hooks`` +---------------------------------- + +To define a package with ``build-type: Hooks``, you will need the following: + +Update your ``.cabal`` file to set ``build-type: Hooks``, using a ``custom-setup`` +stanza to declare the dependencies of your ``SetupHooks``: + +.. code-block:: cabal + + cabal-version: 3.14 + build-type: Hooks + + custom-setup + setup-depends: + base >= 4.18 && < 5, + Cabal-hooks >= 3.16 && < 3.18 + +Then, define a Haskell module called ``SetupHooks.hs`` next to your ``.cabal`` +file. For example: + +.. code-block:: haskell + + module SetupHooks where + import Distribution.Simple.SetupHooks ( SetupHooks, noSetupHooks ) + + setupHooks :: SetupHooks + setupHooks = + noSetupHooks + { configureHooks = myConfigureHooks + , buildHooks = myBuildHooks } + +This ``SetupHooks.hs`` module is where you define all of the hooks into the +build system. The following hooks exist: + + * Hooks into the configure phase: + + * Package-wide pre-configure hook. Used for custom ``./configure``-style logic. + * Package-wide post-configure hook. Used mostly to write information to disk + for the per-component configure hooks. + * Per-component pre-configure hook. Used to modify individual components in + the package description, e.g. specifying per-component build flags or + declaring autogenerated modules. + + * Hooks into the build phase (per component): + + * Pre-build rules. Used to define code generators or custom preprocessors, + i.e. rules to generate source files. + * Post-build hook. Used before linking to inject data into build artifacts. + + * Hooks into the install phase (per component). These are used to copy over + extra files when installing an executable. + +.. _setup-hooks-basic-hooks: + +Basic hooks +----------- + +With the exception of pre-build rules, all hooks take the form: + +.. code-block:: haskell + + HookInput -> IO HookOutput + +Specifying a hook thus amounts to providing a Haskell function of that type. + +For the package-wide pre-configure, we have: + +.. code-block:: haskell + + type PreConfPackageHook = PreConfPackageInputs -> IO PreConfPackageOutputs + +and a typical hook would look like: + +.. code-block:: haskell + + myPreConfPackageHook :: PreConfPackageHook + myPreConfPackageHook inputs@(PreConfPackageInputs {..}) = do + ... -- custom logic goes here (e.g. querying system properties) + let myNewBuildOptions = ... + newConfiguredProgs = ... + return $ + (noPreConfPackageOutputs inputs) + { buildOptions = myNewBuildOptions + , extraConfiguredProgs = newConfiguredProgs + } + +For per-component pre-configure hooks, we have: + +.. code-block:: haskell + + type PreConfComponentHook = PreConfComponentInputs -> IO PreConfComponentOutputs + +and a typical hook would look like: + +.. code-block:: haskell + + myPreConfComponentHook :: PreConfComponentHook + myPreConfComponentHook inputs@(PreConfComponentInputs { component = comp, .. }) = + case componentName comp of + CLibName LMainLibName -> do + ... -- custom logic goes here (e.g. parsing an XML schema) + let newModules = ... + myLdOptions = ... + return $ + (noPreConfComponentOutputs inputs) + { componentDiff = + ComponentDiff $ CLib $ + emptyLibrary + { exposedModules = newModules + , libBuildInfo = + emptyBuildInfo + { autogenModules = newModules + , ldOptions = myLdOptions + } + } + } + _ -> return (noPreConfComponentOutputs inputs) + +Post-build hooks and install hooks are similar. + +Once you have defined all the hooks you need, finish by populating the +``SetupHooks`` record: + +.. code-block:: haskell + + module SetupHooks ( setupHooks ) where + + import Distribution.Simple.SetupHooks + + setupHooks :: SetupHooks + setupHooks = + noSetupHooks + { configureHooks = noConfigureHooks + { preConfPackageHook = Just myPreConfPackageHook + , preConfComponentHook = Just myPreConfComponentHook + } + } + +.. _setup-hooks-pre-build-rules: + +Pre-build rules +--------------- + +A pre-build rule is a specification of what command to run in order to generate +a collection of outputs (most commonly Haskell source modules). Each rule is +registered separately, and rules can depend on each other. This allows for +fine-grained recompilation logic, where only outdated rules are re-run. + +A good starting point is the `Hackage documentation for pre-build rules +`__. + +Let's work through example usage of the API. First off, we define some code +generators. These must be Haskell functions that take a single argument and +return `IO ()`. + +.. code-block:: haskell + + {-# LANGUAGE DeriveAnyClass, DeriveGeneric, DerivingStrategies #-} + + -- An example generator (a Haskell function). + runMyPP :: MyPPInput -> IO () + runMyPP (MyPPInput {..}) = ... + + -- Custom datatype for the argument to our single code generator. + data MyPPInput + = MyPPInput + { ppVerbFlags :: VerbosityFlags + , ppSrcDir :: SymbolicPath Pkg (Dir Source) + , ppOutDir :: SymbolicPath Pkg (Dir Source) + , ppBaseName :: String + } + deriving stock ( Show, Eq, Generic ) + deriving anyclass Binary + +Here we've defined a single generator, ``runMyPP``. The idea is that it takes +an input file with extension ``.myPpExt`` in the source directory and output a Haskell +source file in the output directory. + +We defined a custom argument type ``MyPPInput`` for our code generator. This +argument type must be serialisable and have equality, so we derive +``Eq`` and ``Binary``. It would be fine to just use a tuple as well, but +a datatype is a bit more readable. + +Next, we should search for all input files that need to be preprocessed, and +register one pre-build rule for each such file: + +.. code-block:: haskell + + {-# LANGUAGE StaticPointers #-} + + preBuildRules :: PreBuildComponentInputs -> RulesM () + preBuildRules pbci@(PreBuildComponentInputs { buildingWhat = what, localBuildInfo = lbi, targetInfo = tgt }) = do + let clbi = targetCLBI tgt + autogenDir = autogenComponentModulesDir lbi clbi + + -- Scan the source directories for .ppExt files, registering one rule each. + inputFiles <- findAndMonitorSourceDirsFileExt pbci "ppExt" + for_ inputFiles $ \loc@(Location srcDir relPath) -> do + let baseName = dropExtension (getSymbolicPath relPath) + registerRule_ (fromString $ "myPP:" ++ baseName) $ + staticRule + (mkCommand (static Dict) (static runMyPP) $ + MyPPInput { ppVerbFlags = buildingWhatVerbosity what + , ppSrcDir = srcDir + , ppOutDir = autogenDir + , ppBaseName = baseName }) + -- Inputs of the rule: the ".ppExt" file in the source tree. + [ FileDependency loc ] + -- Outputs of the rule: a corresponding ".hs" file, in the + -- directory for autogenerated modules. + ( Location autogenDir (makeRelativePathEx (baseName <.> "hs")) NE.:| [] ) + +Here, each rule has a single dependency (on the input ``.ppExt`` file), and +produces a single output (the ``.hs`` file). It is perfectly possible to define +rules that don't depend on any files on disk, or that only depend on other rules. + +Note that a rule should never depend on a file generated by another rule; instead +you should declare a dependency on the rule directly (using ``RuleDependency`` +instead of ``FileDependency``, with the ``RuleId`` returned by ``registerRule``). + +The ``StaticPointers`` extension is used to ensure that code generators are +static (and that the instances they need, such as ``Binary MyPPInput``, are also +static). This is explained in `the Haskell Tech Proposal `__, +but you don't really need to know the justification to use the API. +The essential restriction is that any code enclosed by ``static`` cannot refer +to locally-bound variables. It is thus crucial that ``runMyPP`` is defined as a +top-level function, and any arguments it needs must be passed via its argument +type (in this case, the ``MyPPInput`` type). +Refer to the `GHC user guide entry on Static Pointers `__ +for more information. + +In this example, all rule dependencies are static, so we used ``staticRule``. +For rules with dynamic dependencies (e.g. parsing dependencies from the +input files), you can use ``dynamicRule``. The API for this is more complex; +refer to `the Hackage documentation `__ +for details. diff --git a/doc/index.rst b/doc/index.rst index 4bd13c65d7a..d14e29674a0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,6 +19,7 @@ Welcome to the Cabal User Guide how-to-build-like-nix how-to-run-in-windows how-to-use-backpack + how-to-use-setup-hooks how-to-report-bugs .. toctree::