From 3737f0e2b2632e71ebf061938a0f45026cff9460 Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Thu, 19 Feb 2026 16:51:59 +0100 Subject: [PATCH 1/8] Add flags for semaphore and pass to Cabal --- src/Stack/Build/ExecutePackage.hs | 5 +++-- src/Stack/BuildOpts.hs | 1 + src/Stack/Config/Build.hs | 1 + src/Stack/Options/BuildMonoidParser.hs | 6 ++++++ src/Stack/Types/BuildOpts.hs | 8 ++++++++ src/Stack/Types/BuildOptsMonoid.hs | 12 ++++++++++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Stack/Build/ExecutePackage.hs b/src/Stack/Build/ExecutePackage.hs index 33d95f05d2..ff0e38cfdb 100644 --- a/src/Stack/Build/ExecutePackage.hs +++ b/src/Stack/Build/ExecutePackage.hs @@ -1298,13 +1298,14 @@ extraBuildOptions :: extraBuildOptions wc bopts = do colorOpt <- appropriateGhcColorFlag let optsFlag = compilerOptionsCabalFlag wc + semaphoreFlag = if bopts.semaphore then [ "--semaphore", "/tmp/temp" ] else [] baseOpts = maybe "" (" " ++) colorOpt if bopts.testOpts.coverage then do hpcIndexDir <- toFilePathNoTrailingSep <$> hpcRelativeDir - pure [optsFlag, "-hpcdir " ++ hpcIndexDir ++ baseOpts] + pure $ semaphoreFlag ++ [optsFlag, "-hpcdir " ++ hpcIndexDir ++ baseOpts] else - pure [optsFlag, baseOpts] + pure $ semaphoreFlag ++ [optsFlag, baseOpts] -- Library, sub-library, foreign library and executable build components. primaryComponentOptions :: LocalPackage -> [String] diff --git a/src/Stack/BuildOpts.hs b/src/Stack/BuildOpts.hs index 86d27dd834..649c26250e 100644 --- a/src/Stack/BuildOpts.hs +++ b/src/Stack/BuildOpts.hs @@ -62,6 +62,7 @@ defaultBuildOpts = BuildOpts , interleavedOutput = defaultFirstTrue buildMonoid.interleavedOutput , progressBar = CappedBar , ddumpDir = Nothing + , semaphore = defaultFirstFalse buildMonoid.semaphore } where buildMonoid = undefined :: BuildOptsMonoid diff --git a/src/Stack/Config/Build.hs b/src/Stack/Config/Build.hs index 8a43b8df21..afd020a9f8 100644 --- a/src/Stack/Config/Build.hs +++ b/src/Stack/Config/Build.hs @@ -95,6 +95,7 @@ buildOptsFromMonoid buildMonoid = BuildOpts , interleavedOutput = fromFirstTrue buildMonoid.interleavedOutput , progressBar = fromFirst CappedBar buildMonoid.progressBar , ddumpDir = getFirst buildMonoid.ddumpDir + , semaphore = fromFirstFalse buildMonoid.semaphore } where isHaddockFromHackage = fromFirstFalse buildMonoid.haddockForHackage diff --git a/src/Stack/Options/BuildMonoidParser.hs b/src/Stack/Options/BuildMonoidParser.hs index 640836e7c1..718dba30c0 100644 --- a/src/Stack/Options/BuildMonoidParser.hs +++ b/src/Stack/Options/BuildMonoidParser.hs @@ -70,6 +70,7 @@ buildOptsMonoidParser hide0 = BuildOptsMonoid <*> interleavedOutput <*> progressBar <*> ddumpDir + <*> semaphore where hideBool = hide0 /= BuildCmdGlobalOpts hide :: Mod f a @@ -244,6 +245,11 @@ buildOptsMonoidParser hide0 = BuildOptsMonoid <> help "Specify output directory for ddump-files." <> hide )) + semaphore = firstBoolFlagsFalse + "semaphore" + "Use Cabal's --semaphore feature to build modules of the same package in \ + \parallel." + hide -- | Parser for Cabal verbosity options cabalVerbosityOptsParser :: Bool -> Parser (First CabalVerbosity) diff --git a/src/Stack/Types/BuildOpts.hs b/src/Stack/Types/BuildOpts.hs index 1a7da7eddd..4c4f83fcbb 100644 --- a/src/Stack/Types/BuildOpts.hs +++ b/src/Stack/Types/BuildOpts.hs @@ -18,6 +18,7 @@ module Stack.Types.BuildOpts , BenchmarkOpts (..) , buildOptsHaddockL , buildOptsInstallExesL + , buildOptsSemaphoreL ) where import Stack.Prelude @@ -95,6 +96,9 @@ data BuildOpts = BuildOpts , progressBar :: !ProgressBarFormat -- ^ Format of the progress bar , ddumpDir :: !(Maybe Text) + , semaphore :: !Bool + -- ^ Use Cabal's --semaphore feature to build modules of the same package + -- in parallel. } deriving Show @@ -131,3 +135,7 @@ buildOptsInstallExesL = buildOptsHaddockL :: Lens' BuildOpts Bool buildOptsHaddockL = lens (.buildHaddocks) (\bopts t -> bopts {buildHaddocks = t}) + +buildOptsSemaphoreL :: Lens' BuildOpts Bool +buildOptsSemaphoreL = + lens (.semaphore) (\bopts t -> bopts {semaphore = t}) diff --git a/src/Stack/Types/BuildOptsMonoid.hs b/src/Stack/Types/BuildOptsMonoid.hs index 8d02a106e3..e8477f43b2 100644 --- a/src/Stack/Types/BuildOptsMonoid.hs +++ b/src/Stack/Types/BuildOptsMonoid.hs @@ -24,6 +24,7 @@ module Stack.Types.BuildOptsMonoid , buildOptsMonoidTestsL , buildOptsMonoidBenchmarksL , buildOptsMonoidInstallExesL + , buildOptsMonoidSemaphoreL , toFirstCabalVerbosity , readProgressBarFormat ) where @@ -80,6 +81,7 @@ data BuildOptsMonoid = BuildOptsMonoid , interleavedOutput :: !FirstTrue , progressBar :: !(First ProgressBarFormat) , ddumpDir :: !(First Text) + , semaphore :: !FirstFalse } deriving (Generic, Show) @@ -121,6 +123,7 @@ instance FromJSON (WithJSONWarnings BuildOptsMonoid) where interleavedOutput <- FirstTrue <$> o ..:? interleavedOutputName progressBar <- First <$> o ..:? progressBarName ddumpDir <- o ..:? ddumpDirName ..!= mempty + semaphore <- FirstFalse <$> o ..:? semaphoreArgName pure BuildOptsMonoid { trace , profile @@ -156,6 +159,7 @@ instance FromJSON (WithJSONWarnings BuildOptsMonoid) where , interleavedOutput , progressBar , ddumpDir + , semaphore } libProfileArgName :: Text @@ -254,6 +258,9 @@ progressBarName = "progress-bar" ddumpDirName :: Text ddumpDirName = "ddump-dir" +semaphoreArgName :: Text +semaphoreArgName = "semaphore" + instance Semigroup BuildOptsMonoid where (<>) = mappenddefault @@ -403,6 +410,11 @@ buildOptsMonoidInstallExesL = lens (.installExes.firstFalse) (\buildMonoid t -> buildMonoid {installExes = FirstFalse t}) +buildOptsMonoidSemaphoreL :: Lens' BuildOptsMonoid (Maybe Bool) +buildOptsMonoidSemaphoreL = + lens (.semaphore.firstFalse) + (\buildMonoid t -> buildMonoid {semaphore = FirstFalse t}) + -- Type representing formats of Stack's progress bar when building. data ProgressBarFormat = NoBar -- No progress bar at all. From 58ed210e9bd496966059b1816b124f2450751b2b Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Thu, 19 Feb 2026 22:34:48 +0100 Subject: [PATCH 2/8] Create and pass semaphore to Cabal --- package.yaml | 1 + src/Stack/Build/ExecuteEnv.hs | 15 ++++++++++++++- src/Stack/Build/ExecutePackage.hs | 11 ++++++++--- stack.cabal | 6 +++++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/package.yaml b/package.yaml index 52cda3e04a..42f8aa4354 100644 --- a/package.yaml +++ b/package.yaml @@ -120,6 +120,7 @@ dependencies: - random - rio >= 0.1.22.0 && ( < 0.1.23.0 || > 0.1.23.0 ) - rio-prettyprint >= 0.1.8.0 +- semaphore-compat - split - stm - tar >= 0.6.2.0 diff --git a/src/Stack/Build/ExecuteEnv.hs b/src/Stack/Build/ExecuteEnv.hs index 11f4837b88..63caa37887 100644 --- a/src/Stack/Build/ExecuteEnv.hs +++ b/src/Stack/Build/ExecuteEnv.hs @@ -115,6 +115,7 @@ import qualified System.Directory as D import System.Environment ( lookupEnv ) import System.FileLock ( SharedExclusive (..), withFileLock, withTryFileLock ) +import System.Semaphore ( Semaphore, freshSemaphore, destroySemaphore ) -- | Type representing environments in which the @Setup.hs@ commands of Cabal -- (the library) can be executed. @@ -147,6 +148,8 @@ data ExecuteEnv = ExecuteEnv -- ^ For nicer interleaved output: track the largest package name size , pathEnvVar :: !Text -- ^ Value of the PATH environment variable + , semaphore :: !(Maybe Semaphore) + -- ^ Semaphore used for job control, if --semaphore is given } -- | Type representing setup executable circumstances. @@ -256,6 +259,9 @@ getSetupExe setupHs setupShimHs tmpdir = do renameFile tmpExePath exePath pure $ Just exePath +semaphorePrefix :: String +semaphorePrefix = "stack" + -- | Execute a function that takes an t'ExecuteEnv'. withExecuteEnv :: forall env a. HasEnvConfig env @@ -330,6 +336,10 @@ withExecuteEnv logFiles <- liftIO $ atomically newTChan let totalWanted = length $ filter (.wanted) locals pathEnvVar <- liftIO $ maybe mempty T.pack <$> lookupEnv "PATH" + jobs <- view $ configL . to (.jobs) + semaphore <- if buildOpts.semaphore + then Just <$> liftIO (freshSemaphore semaphorePrefix jobs) + else pure Nothing inner ExecuteEnv { buildOpts , buildOptsCLI @@ -355,7 +365,10 @@ withExecuteEnv , customBuilt , largestPackageName , pathEnvVar - } `finally` dumpLogs logFiles totalWanted + , semaphore + } `finally` do + liftIO (whenJust semaphore destroySemaphore) + dumpLogs logFiles totalWanted where toDumpPackagesByGhcPkgId = Map.fromList . map (\dp -> (dp.ghcPkgId, dp)) diff --git a/src/Stack/Build/ExecutePackage.hs b/src/Stack/Build/ExecutePackage.hs index ff0e38cfdb..b03d1d2b02 100644 --- a/src/Stack/Build/ExecutePackage.hs +++ b/src/Stack/Build/ExecutePackage.hs @@ -151,6 +151,7 @@ import System.IO.Error ( isDoesNotExistError ) import System.PosixCompat.Files ( createLink, getFileStatus, modificationTime ) import System.Random ( randomIO ) +import System.Semaphore ( Semaphore (..), SemaphoreName (..) ) -- | Generate the t'ConfigCache' value. getConfigCache :: @@ -569,7 +570,7 @@ realConfigAndBuild <> display actualCompiler ) config <- view configL - extraOpts <- extraBuildOptions wc ee.buildOpts + extraOpts <- extraBuildOptions wc ee.buildOpts ee.semaphore let stripTHLoading | config.hideTHLoading = ExcludeTHLoading | otherwise = KeepTHLoading @@ -1294,11 +1295,15 @@ extraBuildOptions :: (HasEnvConfig env, HasRunner env) => WhichCompiler -> BuildOpts + -> Maybe Semaphore -> RIO env [String] -extraBuildOptions wc bopts = do +extraBuildOptions wc bopts semaphore = do colorOpt <- appropriateGhcColorFlag let optsFlag = compilerOptionsCabalFlag wc - semaphoreFlag = if bopts.semaphore then [ "--semaphore", "/tmp/temp" ] else [] + semaphoreFlag = maybe + [] + (("--semaphore":) . L.singleton . getSemaphoreName . semaphoreName) + semaphore baseOpts = maybe "" (" " ++) colorOpt if bopts.testOpts.coverage then do diff --git a/stack.cabal b/stack.cabal index 0a35d9cecb..58e9c912c0 100644 --- a/stack.cabal +++ b/stack.cabal @@ -1,6 +1,6 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.39.1. +-- This file has been generated from package.yaml by hpack version 0.38.1. -- -- see: https://github.com/sol/hpack @@ -473,6 +473,7 @@ library , random , rio >=0.1.22.0 && (<0.1.23.0 || >0.1.23.0) , rio-prettyprint >=0.1.8.0 + , semaphore-compat , split , stm , tar >=0.6.2.0 @@ -596,6 +597,7 @@ executable stack , random , rio >=0.1.22.0 && (<0.1.23.0 || >0.1.23.0) , rio-prettyprint >=0.1.8.0 + , semaphore-compat , split , stack , stm @@ -701,6 +703,7 @@ executable stack-integration-test , random , rio >=0.1.22.0 && (<0.1.23.0 || >0.1.23.0) , rio-prettyprint >=0.1.8.0 + , semaphore-compat , split , stm , tar >=0.6.2.0 @@ -819,6 +822,7 @@ test-suite stack-unit-test , raw-strings-qq , rio >=0.1.22.0 && (<0.1.23.0 || >0.1.23.0) , rio-prettyprint >=0.1.8.0 + , semaphore-compat , split , stack , stm From 1e52319f63037ae50de12475e7192793a66b2c2f Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Thu, 19 Feb 2026 22:59:30 +0100 Subject: [PATCH 3/8] Add changelog entry --- ChangeLog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 7bc39a1831..9390cbb439 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,9 @@ Behavior changes: Other enhancements: +* Added `--semaphore`, which allows modules within the same package to be built + in parallel. + Bug fixes: ## v3.9.3 - 2026-02-19 From 83027b8bbd313cfd426d3eb6f0e4a7d9ae77fc48 Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Fri, 20 Feb 2026 01:05:03 +0100 Subject: [PATCH 4/8] Add documentation for --semaphore --- doc/commands/build_command.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/commands/build_command.md b/doc/commands/build_command.md index 3515acd25d..94ee4dc366 100644 --- a/doc/commands/build_command.md +++ b/doc/commands/build_command.md @@ -1140,7 +1140,14 @@ where the test suite takes the form of an executable and the executable takes nothing on the standard input stream (`stdin`). Pass this flag to override that specification and allow the executable to receive input on that stream. If you pass `--no-tests-allow-stdin` and the executable seeks input on the standard -input stream, an exception will be thown. +input stream, an exception will be thrown. + +### `--[no]-semaphore` flag + +Default: Disabled + +Uses the semaphore feature in Cabal/GHC to build multiple modules from the same +package in parallel. (see GHC's documentation on `-jsem`) ## Examples From 9ea5ae06dccb2b8571ca30f47c70e6ef1e004070 Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Fri, 20 Feb 2026 23:49:42 +0000 Subject: [PATCH 5/8] Extend documentation and conform to other docs Also moves `--[no-]semaphore` higher up the list of `stack build --help` options. --- ChangeLog.md | 5 ++-- doc/commands/build_command.md | 35 +++++++++++++++++--------- doc/configure/yaml/non-project.md | 4 +++ src/Stack/Build/ExecuteEnv.hs | 5 ++-- src/Stack/Options/BuildMonoidParser.hs | 8 +++--- src/Stack/Types/BuildOpts.hs | 4 +-- src/Stack/Types/BuildOptsMonoid.hs | 8 +++--- 7 files changed, 43 insertions(+), 26 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9390cbb439..779d5e7aa3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,8 +12,9 @@ Behavior changes: Other enhancements: -* Added `--semaphore`, which allows modules within the same package to be built - in parallel. +* Add flag `--[no-]semaphore` (default: disabled) to Stack's `build` command, + to allow GHC to use a system semaphore to perform compilation in parallel when + possible. Supported, by default, by GHC 9.10.1 or later. Bug fixes: diff --git a/doc/commands/build_command.md b/doc/commands/build_command.md index 94ee4dc366..f9245105e5 100644 --- a/doc/commands/build_command.md +++ b/doc/commands/build_command.md @@ -7,11 +7,12 @@ stack build [TARGET] [--dry-run] [--pedantic] [--fast] [--ghc-options OPTIONS] [--flag PACKAGE:[-]FLAG] [--dependencies-only | --only-snapshot | --only-dependencies | --only-locals] [--file-watch | --file-watch-poll] [--watch-all] [--exec COMMAND [ARGUMENT(S)]] - [--only-configure] [--trace] [--profile] [--no-strip] - [--[no-]library-profiling] [--[no-]executable-profiling] - [--[no-]library-stripping] [--[no-]executable-stripping] - [--[no-]haddock] [--haddock-arguments HADDOCK_ARGS] - [--[no-]open] [--[no-]haddock-deps] [--[no-]haddock-internal] + [--only-configure] [--[no-]semaphore] [--trace] [--profile] + [--no-strip] [--[no-]library-profiling] + [--[no-]executable-profiling] [--[no-]library-stripping] + [--[no-]executable-stripping] [--[no-]haddock] + [--haddock-arguments HADDOCK_ARGS] [--[no-]open] + [--[no-]haddock-deps] [--[no-]haddock-internal] [--[no-]haddock-hyperlink-source] [--[no-]haddock-for-hackage] [--[no-]copy-bins] [--[no-]copy-compiler-tool] [--[no-]prefetch] [--[no-]keep-going] [--[no-]keep-tmp-files] [--[no-]force-dirty] @@ -916,6 +917,23 @@ expressions, and generate a profiling report in tests or benchmarks. The flag affects the location of the local project installation directory. See the [`stack path --local-install-root`](path_command.md) command. +### `--[no]-semaphore` flag + +:octicons-tag-24: UNRELEASED + +Default: Disabled + +This flag allows GHC to use a system semaphore to perform compilation in +parallel when possible. + +!!! info + + GHC 9.8.1 and later can act as a jobserver client, which enables two or more + GHC processes running at once to share system resources with each other, + communicating via a system semaphore. This GHC feature is supported by + Cabal 3.12.0.0 (a boot package of GHC 9.10.1) and later. The flag is ignored + with a warning when the feature is unsupported. + ### `--[no-]split-objs` flag :octicons-beaker-24: Experimental @@ -1142,13 +1160,6 @@ specification and allow the executable to receive input on that stream. If you pass `--no-tests-allow-stdin` and the executable seeks input on the standard input stream, an exception will be thrown. -### `--[no]-semaphore` flag - -Default: Disabled - -Uses the semaphore feature in Cabal/GHC to build multiple modules from the same -package in parallel. (see GHC's documentation on `-jsem`) - ## Examples All the following examples assume that: diff --git a/doc/configure/yaml/non-project.md b/doc/configure/yaml/non-project.md index e05ccd93a7..29c1944350 100644 --- a/doc/configure/yaml/non-project.md +++ b/doc/configure/yaml/non-project.md @@ -157,6 +157,10 @@ Default: ~~~yaml build: + # Since Stack UNRELEASED. Supported by GHC 9.8.1 or later with Cabal 3.12.0.0 + # (a boot package of GHC 9.10.1) or later. + semaphore: false + library-profiling: false executable-profiling: false library-stripping: true diff --git a/src/Stack/Build/ExecuteEnv.hs b/src/Stack/Build/ExecuteEnv.hs index 63caa37887..0b0d8df60a 100644 --- a/src/Stack/Build/ExecuteEnv.hs +++ b/src/Stack/Build/ExecuteEnv.hs @@ -115,7 +115,8 @@ import qualified System.Directory as D import System.Environment ( lookupEnv ) import System.FileLock ( SharedExclusive (..), withFileLock, withTryFileLock ) -import System.Semaphore ( Semaphore, freshSemaphore, destroySemaphore ) +import System.Semaphore + ( Semaphore, destroySemaphore, freshSemaphore ) -- | Type representing environments in which the @Setup.hs@ commands of Cabal -- (the library) can be executed. @@ -149,7 +150,7 @@ data ExecuteEnv = ExecuteEnv , pathEnvVar :: !Text -- ^ Value of the PATH environment variable , semaphore :: !(Maybe Semaphore) - -- ^ Semaphore used for job control, if --semaphore is given + -- ^ The semaphore that is used for job control, if --semaphore is given } -- | Type representing setup executable circumstances. diff --git a/src/Stack/Options/BuildMonoidParser.hs b/src/Stack/Options/BuildMonoidParser.hs index 718dba30c0..7de4eeaa78 100644 --- a/src/Stack/Options/BuildMonoidParser.hs +++ b/src/Stack/Options/BuildMonoidParser.hs @@ -36,7 +36,8 @@ import Stack.Types.ComponentUtils ( unqualCompFromString ) -- | Parse command line arguments for build configuration. buildOptsMonoidParser :: GlobalOptsContext -> Parser BuildOptsMonoid buildOptsMonoidParser hide0 = BuildOptsMonoid - <$> trace' + <$> semaphore + <*> trace' <*> profile <*> noStrip <*> libProfiling @@ -70,7 +71,6 @@ buildOptsMonoidParser hide0 = BuildOptsMonoid <*> interleavedOutput <*> progressBar <*> ddumpDir - <*> semaphore where hideBool = hide0 /= BuildCmdGlobalOpts hide :: Mod f a @@ -247,8 +247,8 @@ buildOptsMonoidParser hide0 = BuildOptsMonoid )) semaphore = firstBoolFlagsFalse "semaphore" - "Use Cabal's --semaphore feature to build modules of the same package in \ - \parallel." + "the use of a system semaphore to perform compilation in parallel when \ + \possible. Supported, by default, by GHC 9.10.1 or later." hide -- | Parser for Cabal verbosity options diff --git a/src/Stack/Types/BuildOpts.hs b/src/Stack/Types/BuildOpts.hs index 4c4f83fcbb..bdff5a886a 100644 --- a/src/Stack/Types/BuildOpts.hs +++ b/src/Stack/Types/BuildOpts.hs @@ -97,8 +97,8 @@ data BuildOpts = BuildOpts -- ^ Format of the progress bar , ddumpDir :: !(Maybe Text) , semaphore :: !Bool - -- ^ Use Cabal's --semaphore feature to build modules of the same package - -- in parallel. + -- ^ Use Cabal's --semaphore=SEMAPHORE option to build modules of the same + -- package in parallel. } deriving Show diff --git a/src/Stack/Types/BuildOptsMonoid.hs b/src/Stack/Types/BuildOptsMonoid.hs index e8477f43b2..a6f378f60c 100644 --- a/src/Stack/Types/BuildOptsMonoid.hs +++ b/src/Stack/Types/BuildOptsMonoid.hs @@ -44,7 +44,8 @@ import Stack.Types.ComponentUtils ( StackUnqualCompName ) -- | Build options that may be specified as non-project specific configuration -- options under the build key (with certain exceptions) or from the CLI. data BuildOptsMonoid = BuildOptsMonoid - { trace :: !Any + { semaphore :: !FirstFalse + , trace :: !Any -- ^ Cannot be specified under the build key , profile :: !Any -- ^ Cannot be specified under the build key @@ -81,7 +82,6 @@ data BuildOptsMonoid = BuildOptsMonoid , interleavedOutput :: !FirstTrue , progressBar :: !(First ProgressBarFormat) , ddumpDir :: !(First Text) - , semaphore :: !FirstFalse } deriving (Generic, Show) @@ -125,7 +125,8 @@ instance FromJSON (WithJSONWarnings BuildOptsMonoid) where ddumpDir <- o ..:? ddumpDirName ..!= mempty semaphore <- FirstFalse <$> o ..:? semaphoreArgName pure BuildOptsMonoid - { trace + { semaphore + , trace , profile , noStrip , libProfile @@ -159,7 +160,6 @@ instance FromJSON (WithJSONWarnings BuildOptsMonoid) where , interleavedOutput , progressBar , ddumpDir - , semaphore } libProfileArgName :: Text From 6dc43743192062c264bba2e62312420886f81b3c Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Sat, 21 Feb 2026 00:53:38 +0000 Subject: [PATCH 6/8] Ignore unsupported `--semaphore` with a warning --- doc/configure/yaml/non-project.md | 3 ++- src/Stack/Build/ExecuteEnv.hs | 30 ++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/doc/configure/yaml/non-project.md b/doc/configure/yaml/non-project.md index 29c1944350..8607963bdb 100644 --- a/doc/configure/yaml/non-project.md +++ b/doc/configure/yaml/non-project.md @@ -158,7 +158,8 @@ Default: ~~~yaml build: # Since Stack UNRELEASED. Supported by GHC 9.8.1 or later with Cabal 3.12.0.0 - # (a boot package of GHC 9.10.1) or later. + # (a boot package of GHC 9.10.1) or later. Ignored with a warning when + # unsupported. semaphore: false library-profiling: false diff --git a/src/Stack/Build/ExecuteEnv.hs b/src/Stack/Build/ExecuteEnv.hs index 0b0d8df60a..dfee2c548e 100644 --- a/src/Stack/Build/ExecuteEnv.hs +++ b/src/Stack/Build/ExecuteEnv.hs @@ -54,6 +54,7 @@ import qualified Distribution.Simple.Build.Macros as C import Distribution.System ( OS (..), Platform (..) ) import Distribution.Types.PackageName ( mkPackageName ) import Distribution.Verbosity ( showForCabal ) +import Distribution.Version ( mkVersion ) import Path ( PathException, (), parent, parseRelDir, parseRelFile ) import Path.Extra ( forgivingResolveFile, toFilePathNoTrailingSep ) @@ -86,7 +87,9 @@ import Stack.Types.BuildOpts ( BuildOpts (..) ) import Stack.Types.BuildOptsCLI ( BuildOptsCLI (..) ) import Stack.Types.BuildOptsMonoid ( CabalVerbosity (..) ) import Stack.Types.Compiler - ( WhichCompiler (..), compilerVersionString, whichCompilerL ) + ( WhichCompiler (..), compilerVersionString + , getGhcVersion, whichCompilerL + ) import Stack.Types.CompilerPaths ( CompilerPaths (..), HasCompiler (..), cabalVersionL , getCompilerPath @@ -327,6 +330,8 @@ withExecuteEnv ignoringAbsence (removeFile setupO) ignoringAbsence (removeFile setupShimHi) ignoringAbsence (removeFile setupShimO) + compilerVersion <- view actualCompilerVersionL + let ghcVersion = getGhcVersion compilerVersion cabalPkgVer <- view cabalVersionL globalDB <- view $ compilerPathsL . to (.globalDB) let globalDumpPkgs = toDumpPackagesByGhcPkgId globalPackages @@ -338,9 +343,26 @@ withExecuteEnv let totalWanted = length $ filter (.wanted) locals pathEnvVar <- liftIO $ maybe mempty T.pack <$> lookupEnv "PATH" jobs <- view $ configL . to (.jobs) - semaphore <- if buildOpts.semaphore - then Just <$> liftIO (freshSemaphore semaphorePrefix jobs) - else pure Nothing + let semaphoreSupported = + (cabalPkgVer >= mkVersion [3, 12, 0, 0]) + && (ghcVersion >= mkVersion [9, 8, 1]) + semaphoreUnsupportedWarning = + prettyWarnL + [ "The" + , style Shell "--semaphore" + , flow "flag was specified, which is supported by GHC 9.8.1 or \ + \later with Cabal 3.12.0.0 (a boot package of GHC 9.10.1) \ + \or later. GHC version" + , fromString (versionString ghcVersion) + , flow "and Cabal version" + , fromString (versionString cabalPkgVer) + , flow "was found. The flag will be ignored." + ] + semaphore <- if not buildOpts.semaphore + then pure Nothing + else if semaphoreSupported + then Just <$> liftIO (freshSemaphore semaphorePrefix jobs) + else semaphoreUnsupportedWarning >> pure Nothing inner ExecuteEnv { buildOpts , buildOptsCLI From fa0f84025651522bd83098585b81954c7b692cfa Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Sat, 21 Feb 2026 01:19:19 +0000 Subject: [PATCH 7/8] Update unit test --- tests/unit/Stack/ConfigSpec.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/Stack/ConfigSpec.hs b/tests/unit/Stack/ConfigSpec.hs index cbabbede89..0f66d79f45 100644 --- a/tests/unit/Stack/ConfigSpec.hs +++ b/tests/unit/Stack/ConfigSpec.hs @@ -61,6 +61,7 @@ buildOptsConfig = "snapshot: lts-24.24\n" ++ "packages: ['.']\n" ++ "build:\n" ++ + " semaphore: true\n" ++ " library-profiling: true\n" ++ " executable-profiling: true\n" ++ " library-stripping: false\n" ++ @@ -228,6 +229,7 @@ spec = beforeAll setup $ do writeFile (toFilePath stackDotYaml) buildOptsConfig loadConfig' $ \config -> liftIO $ do let bopts = config.build + bopts.semaphore `shouldBe` True bopts.libProfile `shouldBe` True bopts.exeProfile `shouldBe` True bopts.libStrip `shouldBe` False From 4eff6501213c30b4daaae8db49d77a749e38e12b Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Sat, 21 Feb 2026 16:16:20 +0000 Subject: [PATCH 8/8] Update STAN exceptions --- .stan.toml | 80 +++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/.stan.toml b/.stan.toml index 586019edb7..f5108a97ce 100644 --- a/.stan.toml +++ b/.stan.toml @@ -67,57 +67,57 @@ # Infinite: base/isSuffixOf # Usage of the 'isSuffixOf' function that hangs on infinite lists [[ignore]] - id = "OBS-STAN-0102-luLR/n-533:30" + id = "OBS-STAN-0102-luLR/n-540:30" # ✦ Category: #Infinite #List # ✦ File: src\Stack\New.hs # -# 532 ┃ -# 533 ┃ let isPkgSpec f = ".cabal" `L.isSuffixOf` f || "package.yaml" `L.isSuffixOf` f -# 534 ┃ ^^^^^^^^^^^^^^ +# 539 ┃ +# 540 ┃ let isPkgSpec f = ".cabal" `L.isSuffixOf` f || "package.yaml" `L.isSuffixOf` f +# 541 ┃ ^^^^^^^^^^^^^^ # Infinite: base/isSuffixOf # Usage of the 'isSuffixOf' function that hangs on infinite lists [[ignore]] - id = "OBS-STAN-0102-luLR/n-533:65" + id = "OBS-STAN-0102-luLR/n-540:65" # ✦ Category: #Infinite #List # ✦ File: src\Stack\New.hs # -# 532 ┃ -# 533 ┃ let isPkgSpec f = ".cabal" `L.isSuffixOf` f || "package.yaml" `L.isSuffixOf` f -# 534 ┃ ^^^^^^^^^^^^^^ +# 539 ┃ +# 540 ┃ let isPkgSpec f = ".cabal" `L.isSuffixOf` f || "package.yaml" `L.isSuffixOf` f +# 541 ┃ ^^^^^^^^^^^^^^ # Infinite: ghc-internal/isSuffixOf # Usage of the 'isSuffixOf' function that hangs on infinite lists [[ignore]] - id = "OBS-STAN-0102-8cspI6-400:41" + id = "OBS-STAN-0102-8cspI6-404:41" # ✦ Category: #Infinite #List # ✦ File: src\Stack\Coverage.hs # -# 399 ┃ -# 400 ┃ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) -# 401 ┃ ^^^^^^^^^^^^^^ +# 403 ┃ +# 404 ┃ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) +# 405 ┃ ^^^^^^^^^^^^^^ # Infinite: ghc-internal/isSuffixOf # Usage of the 'isSuffixOf' function that hangs on infinite lists [[ignore]] - id = "OBS-STAN-0102-8cspI6-433:31" + id = "OBS-STAN-0102-8cspI6-437:31" # ✦ Category: #Infinite #List # ✦ File: src\Stack\Coverage.hs # -# 432 ┃ -# 433 ┃ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) -# 434 ┃ ^^^^^^^^^^^^^^ +# 436 ┃ +# 437 ┃ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) +# 438 ┃ ^^^^^^^^^^^^^^ # Infinite: ghc-internal/isSuffixOf # Usage of the 'isSuffixOf' function that hangs on infinite lists [[ignore]] - id = "OBS-STAN-0102-8cspI6-664:30" + id = "OBS-STAN-0102-8cspI6-668:30" # ✦ Category: #Infinite #List # ✦ File: src\Stack\Coverage.hs # -# 663 ┃ -# 664 ┃ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) -# 665 ┃ ^^^^^^^^^^^^^^ +# 667 ┃ +# 668 ┃ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files) +# 669 ┃ ^^^^^^^^^^^^^^ # Infinite: ghc-internal/isSuffixOf # Usage of the 'isSuffixOf' function that hangs on infinite lists @@ -140,25 +140,25 @@ # Anti-pattern: Data.ByteString.Char8.pack [[ignore]] - id = "OBS-STAN-0203-erw24B-1043:3" + id = "OBS-STAN-0203-erw24B-1079:3" # ✦ Description: Usage of 'pack' function that doesn't handle Unicode characters # ✦ Category: #AntiPattern # ✦ File: src\Stack\Build\ExecuteEnv.hs # -# 1042 ┃ -# 1043 ┃ S8.pack . formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S%6Q" -# 1044 ┃ ^^^^^^^ +# 1078 ┃ +# 1079 ┃ S8.pack . formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S%6Q" +# 1080 ┃ ^^^^^^^ # Anti-pattern: Data.ByteString.Char8.pack [[ignore]] - id = "OBS-STAN-0203-tuE+RG-249:24" + id = "OBS-STAN-0203-tuE+RG-250:24" # ✦ Description: Usage of 'pack' function that doesn't handle Unicode characters # ✦ Category: #AntiPattern # ✦ File: src\Stack\Build\ExecutePackage.hs # -# 248 ┃ -# 249 ┃ newConfigFileRoot <- S8.pack . toFilePath <$> view configFileRootL -# 250 ┃ ^^^^^^^ +# 249 ┃ +# 250 ┃ newConfigFileRoot <- S8.pack . toFilePath <$> view configFileRootL +# 251 ┃ ^^^^^^^ # Anti-pattern: Data.ByteString.Char8.pack [[ignore]] @@ -258,36 +258,36 @@ # Anti-pattern: unsafe functions [[ignore]] - id = "OBS-STAN-0212-FNS1cF-67:17" + id = "OBS-STAN-0212-FNS1cF-68:17" # ✦ Description: Usage of unsafe functions breaks referential transparency # ✦ Category: #Unsafe #AntiPattern # ✦ File: src\Stack\BuildOpts.hs # -# 66 ┃ -# 67 ┃ buildMonoid = undefined :: BuildOptsMonoid -# 68 ┃ ^^^^^^^^^ +# 67 ┃ +# 68 ┃ buildMonoid = undefined :: BuildOptsMonoid +# 69 ┃ ^^^^^^^^^ # Anti-pattern: unsafe functions [[ignore]] - id = "OBS-STAN-0212-FNS1cF-79:14" + id = "OBS-STAN-0212-FNS1cF-80:14" # ✦ Description: Usage of unsafe functions breaks referential transparency # ✦ Category: #Unsafe #AntiPattern # ✦ File: src\Stack\BuildOpts.hs # -# 78 ┃ -# 79 ┃ toMonoid = undefined :: TestOptsMonoid -# 80 ┃ ^^^^^^^^^ +# 79 ┃ +# 80 ┃ toMonoid = undefined :: TestOptsMonoid +# 81 ┃ ^^^^^^^^^ # Anti-pattern: unsafe functions [[ignore]] - id = "OBS-STAN-0212-FNS1cF-90:15" + id = "OBS-STAN-0212-FNS1cF-91:15" # ✦ Description: Usage of unsafe functions breaks referential transparency # ✦ Category: #Unsafe #AntiPattern # ✦ File: src/Stack/BuildOpts.hs # -# 89 ┃ -# 90 ┃ beoMonoid = undefined :: BenchmarkOptsMonoid -# 91 ┃ ^^^^^^^^^ +# 90 ┃ +# 91 ┃ beoMonoid = undefined :: BenchmarkOptsMonoid +# 92 ┃ ^^^^^^^^^ # Anti-pattern: Pattern matching on '_' # Pattern matching on '_' for sum types can create maintainability issues