Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions .stan.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]]
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Behavior changes:

Other enhancements:

* 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:

## v3.9.3 - 2026-02-19
Expand Down
30 changes: 24 additions & 6 deletions doc/commands/build_command.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1140,7 +1158,7 @@ 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.

## Examples

Expand Down
5 changes: 5 additions & 0 deletions doc/configure/yaml/non-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ 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. Ignored with a warning when
# unsupported.
semaphore: false

library-profiling: false
executable-profiling: false
library-stripping: true
Expand Down
1 change: 1 addition & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 38 additions & 2 deletions src/Stack/Build/ExecuteEnv.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -115,6 +118,8 @@ import qualified System.Directory as D
import System.Environment ( lookupEnv )
import System.FileLock
( SharedExclusive (..), withFileLock, withTryFileLock )
import System.Semaphore
( Semaphore, destroySemaphore, freshSemaphore )

-- | Type representing environments in which the @Setup.hs@ commands of Cabal
-- (the library) can be executed.
Expand Down Expand Up @@ -147,6 +152,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)
-- ^ The semaphore that is used for job control, if --semaphore is given
}

-- | Type representing setup executable circumstances.
Expand Down Expand Up @@ -256,6 +263,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
Expand Down Expand Up @@ -320,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
Expand All @@ -330,6 +342,27 @@ withExecuteEnv
logFiles <- liftIO $ atomically newTChan
let totalWanted = length $ filter (.wanted) locals
pathEnvVar <- liftIO $ maybe mempty T.pack <$> lookupEnv "PATH"
jobs <- view $ configL . to (.jobs)
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
Expand All @@ -355,7 +388,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))

Expand Down
14 changes: 10 additions & 4 deletions src/Stack/Build/ExecutePackage.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ::
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1294,17 +1295,22 @@ 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 = maybe
[]
(("--semaphore":) . L.singleton . getSemaphoreName . semaphoreName)
semaphore
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]
Expand Down
1 change: 1 addition & 0 deletions src/Stack/BuildOpts.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ defaultBuildOpts = BuildOpts
, interleavedOutput = defaultFirstTrue buildMonoid.interleavedOutput
, progressBar = CappedBar
, ddumpDir = Nothing
, semaphore = defaultFirstFalse buildMonoid.semaphore
}
where
buildMonoid = undefined :: BuildOptsMonoid
Expand Down
1 change: 1 addition & 0 deletions src/Stack/Config/Build.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading