diff --git a/ChangeLog.md b/ChangeLog.md index 705214044a..4417b84371 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,14 +4,26 @@ Release notes: +* After an upgrade from an earlier version of Stack, on first use only, + Stack UNRELEASED may warn that it had trouble loading the CompilerPaths cache + and is ignoring the cache (code S-9841). That is because a field has been + added to the cache. + **Changes since v3.11.1:** Major changes: Behavior changes: +* On non-Windows operating systems, the Flag `--[no-]semaphore` to Stack's + `build` command has no effect if `ghc --info` does not report a semaphore + version. This is a limitation of the `semaphore-compat-2.0.0` package. + Other enhancements: +* Flag `--[no-]semaphore` to Stack's `build` command is no longer classified as + experimental in documentation. + Bug fixes: ## v3.11.1 - 2026-06-13 diff --git a/doc/commands/build_command.md b/doc/commands/build_command.md index 1546fe7019..1b5020ecc0 100644 --- a/doc/commands/build_command.md +++ b/doc/commands/build_command.md @@ -948,8 +948,6 @@ the [`stack path --local-install-root`](path_command.md) command. ### `--[no]-semaphore` flag -:octicons-beaker-24: Experimental - [:octicons-tag-24: 3.11.1](https://github.com/commercialhaskell/stack/releases/tag/v3.11.1) Default: Disabled @@ -959,18 +957,18 @@ parallel when possible. !!! info - GHC 9.8.1 and later can act as a jobserver client, which enables two or more + Some GHC versions 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. + Cabal 3.12.0.0 (a boot package of GHC 9.10.1) and later. On Windows, this is + supported by GHC 9.8.1 and later. On other operating systems, this is + supported by GHC if `ghc --info` reports a semaphore version. The flag is + ignored with a warning when the feature is unsupported. -!!! warning +!!! note - On Linux, musl and non-musl system semaphores are incompatible. That means - that a Stack executable built on Alpine Linux (such as the official Stack - for Linux) creates system semaphores that cannot be used by a GHC executable - built on non-musl Linux distributions. + On non-Windows operating systems, GHC 9.8.1 to 9.8.4, GHC 9.10.1 to 9.10.3 + and GHC 9.12.1 to 9.12.4 do not report a supported semaphore version. ### `--[no-]split-objs` flag diff --git a/doc/maintainers/stack_errors.md b/doc/maintainers/stack_errors.md index ac3086639c..20d3c6fa48 100644 --- a/doc/maintainers/stack_errors.md +++ b/doc/maintainers/stack_errors.md @@ -5,7 +5,7 @@ In connection with considering Stack's support of the [Haskell Error Index](https://errors.haskell.org/) initiative, this page seeks to take stock of the errors that Stack itself can raise, by reference to the -`master` branch of the Stack repository. Last updated: 2026-04-24. +`master` branch of the Stack repository. Last updated: 2026-06-25. * `Stack.main`: catches exceptions from action `commandLineHandler`. @@ -297,6 +297,7 @@ to take stock of the errors that Stack itself can raise, by reference to the [S-2965] | GHCInfoMissingGlobalPackageDB [S-5219] | GHCInfoMissingTargetPlatform [S-8299] | GHCInfoTargetPlatformInvalid String + [S-6307] | GHCInfoSemaphoreVersionUnknown String [S-2574] | CabalNotFound (Path Abs File) [S-8488] | GhcBootScriptNotFound [S-1128] | HadrianScriptNotFound @@ -331,6 +332,7 @@ to take stock of the errors that Stack itself can raise, by reference to the [S-5378] | GlobalPackageCacheFileMetadataMismatch [S-2673] | GlobalDumpParseFailure [S-8441] | CompilerCacheArchitectureInvalid Text + [S-9841] | GhcSemaphoreProtocolVersionUnknown ~~~ - `Stack.Templates.TemplatesPrettyException` diff --git a/src/Stack/Build/ExecuteEnv.hs b/src/Stack/Build/ExecuteEnv.hs index 5ae611b9bb..6b845a9ce8 100644 --- a/src/Stack/Build/ExecuteEnv.hs +++ b/src/Stack/Build/ExecuteEnv.hs @@ -72,6 +72,7 @@ import Stack.Constants , relDirSetupExeCache, relDirSetupExeSrc, relFileBuildLock , relFileSetupHs, relFileSetupLhs, relFileSetupLower , relFileSetupMacrosH, setupGhciShimCode, stackProgName + , osIsWindows ) import Stack.Constants.Config ( distDirFromDir, distRelativeDir ) import Stack.Package ( buildLogPath ) @@ -122,7 +123,9 @@ import System.Environment ( lookupEnv ) import System.FileLock ( SharedExclusive (..), withFileLock, withTryFileLock ) import System.Semaphore - ( Semaphore, destroySemaphore, freshSemaphore ) + ( ServerSemaphore, destroyServerSemaphore, freshSemaphore + , semaphoreVersion, versionsAreCompatible + ) -- | Type representing environments in which the @Setup.hs@ commands of Cabal -- (the library) can be executed. @@ -155,8 +158,9 @@ 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 + , serverSemaphore :: !(Maybe ServerSemaphore) + -- ^ The server semaphore that is used for job control, if --semaphore is + -- given } -- | Type representing setup executable circumstances. @@ -345,26 +349,51 @@ withExecuteEnv logFiles <- liftIO $ atomically newTChan let totalWanted = length $ filter (.wanted) locals pathEnvVar <- liftIO $ maybe mempty T.pack <$> lookupEnv "PATH" + ghcSemaphoreVersion <- view $ compilerPathsL . to (.semaphoreVersion) jobs <- view $ configL . to (.jobs) let semaphoreSupported = (cabalPkgVer >= mkVersion [3, 12, 0, 0]) - && (ghcVersion >= mkVersion [9, 8, 1]) + && maybe + False + (versionsAreCompatible semaphoreVersion) + ghcSemaphoreVersion 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" + , flow "flag was specified, which is supported by" + , ghcSemaphoreSupportMessage + , flow "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 + ghcSemaphoreSupportMessage = if osIsWindows + then + flow "GHC 9.8.1 or later (on Windows)" + else + -- Protocol version 1 was problematic on non-musl Linux + -- distributions only, because non-musl and musl semaphores are + -- incompatible and statically-linked binaries are musl. The GHC + -- project has decided that the semaphore-compat-2.0.0 package + -- will not be backwards compatible on all operating systems other + -- than Windows. + fillSep + [ flow "GHC if" + , style Shell "ghc --info" + , flow "reports a semaphore version (on operating systems \ + \other than Windows)" + ] + serverSemaphore <- if not buildOpts.semaphore then pure Nothing else if semaphoreSupported - then Just <$> liftIO (freshSemaphore semaphorePrefix jobs) + then do + result <- liftIO $ freshSemaphore semaphorePrefix jobs + case result of + Left err -> throwM err + Right serverSemaphore -> pure $ Just serverSemaphore else semaphoreUnsupportedWarning >> pure Nothing inner ExecuteEnv { buildOpts @@ -391,9 +420,9 @@ withExecuteEnv , customBuilt , largestPackageName , pathEnvVar - , semaphore + , serverSemaphore } `finally` do - liftIO (whenJust semaphore destroySemaphore) + liftIO (whenJust serverSemaphore destroyServerSemaphore) 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 233a9a56b9..71cb7e4139 100644 --- a/src/Stack/Build/ExecutePackage.hs +++ b/src/Stack/Build/ExecutePackage.hs @@ -153,7 +153,10 @@ import System.IO.Error ( isDoesNotExistError ) import System.PosixCompat.Files ( createLink, getFileStatus, modificationTime ) import System.Random ( randomIO ) -import System.Semaphore ( Semaphore (..), SemaphoreName (..) ) +import System.Semaphore + ( ServerSemaphore (..), clientSemaphoreName + , semaphoreIdentifier + ) -- | Generate the t'ConfigCache' value. getConfigCache :: @@ -576,7 +579,7 @@ realConfigAndBuild <> display actualCompiler ) config <- view configL - extraOpts <- extraBuildOptions wc ee.buildOpts ee.semaphore + extraOpts <- extraBuildOptions wc ee.buildOpts ee.serverSemaphore let stripTHLoading | config.hideTHLoading = ExcludeTHLoading | otherwise = KeepTHLoading @@ -1326,15 +1329,15 @@ extraBuildOptions :: (HasEnvConfig env, HasRunner env) => WhichCompiler -> BuildOpts - -> Maybe Semaphore + -> Maybe ServerSemaphore -> RIO env [String] -extraBuildOptions wc bopts semaphore = do +extraBuildOptions wc bopts mServerSemaphore = do colorOpt <- appropriateGhcColorFlag let optsFlag = compilerOptionsCabalFlag wc semaphoreFlag = maybe [] - (("--semaphore":) . L.singleton . getSemaphoreName . semaphoreName) - semaphore + (("--semaphore":) . L.singleton . semaphoreIdentifier . clientSemaphoreName . serverClientSemaphore) + mServerSemaphore baseOpts = maybe "" (" " ++) colorOpt if bopts.testOpts.coverage then do diff --git a/src/Stack/Setup.hs b/src/Stack/Setup.hs index 54ce267092..6b97231c4c 100644 --- a/src/Stack/Setup.hs +++ b/src/Stack/Setup.hs @@ -184,6 +184,7 @@ import System.IO.Error ( isPermissionError ) import System.FilePath ( searchPathSeparator ) import qualified System.FilePath as FP import System.Permissions ( setFileExecutable ) +import System.Semaphore ( SemaphoreProtocolVersion (..) ) import System.Uname ( getRelease ) -- | Type representing exceptions thrown by functions exported by the @@ -233,6 +234,7 @@ data SetupPrettyException | GHCInfoMissingGlobalPackageDB | GHCInfoMissingTargetPlatform | GHCInfoTargetPlatformInvalid !String + | GHCInfoSemaphoreVersionUnknown !String | CabalNotFound !(Path Abs File) | GhcBootScriptNotFound | HadrianScriptNotFound @@ -468,6 +470,13 @@ instance Pretty SetupPrettyException where [ flow "Invalid target platform in GHC info:" , fromString targetPlatform <> "." ] + pretty (GHCInfoSemaphoreVersionUnknown s) = + "[S-6307]" + <> line + <> fillSep + [ flow "Unknown semaphore version in GHC info:" + , fromString s <> "." + ] pretty (CabalNotFound compiler) = "[S-2574]" <> line @@ -1527,7 +1536,20 @@ pathsFromCompiler wc build sandboxed compiler = case Map.lookup cabalPackageName globalDump of Nothing -> prettyThrowIO $ CabalNotFound compiler Just dp -> pure $ pkgVersion dp.packageIdent - + let semaphoreSupported = + getGhcVersion compilerVersion >= mkVersion [9, 8, 1] + semaphoreVersion = case Map.lookup "Semaphore version" infoMap of + Nothing -> if osIsWindows && semaphoreSupported + then + Just $ SemaphoreProtocolVersion 1 + else + -- If GHC (via ghc --info) does not know what protocol version + -- for the semaphore it supports, we can be confident that it does + -- not support a protocol version >= 2: + Nothing + Just "1" -> Just $ SemaphoreProtocolVersion 1 + Just "2" -> Just $ SemaphoreProtocolVersion 2 + Just s -> prettyThrowM $ GHCInfoSemaphoreVersionUnknown s pure CompilerPaths { build , arch @@ -1540,6 +1562,7 @@ pathsFromCompiler wc build sandboxed compiler = , cabalVersion , globalDB , ghcInfo + , semaphoreVersion , globalDump } where diff --git a/src/Stack/Storage/User.hs b/src/Stack/Storage/User.hs index b5de0ca2a3..852127404e 100644 --- a/src/Stack/Storage/User.hs +++ b/src/Stack/Storage/User.hs @@ -52,7 +52,10 @@ import qualified RIO.FilePath as FP import Stack.Prelude import Stack.Storage.Util ( handleMigrationException, setUpdateDiff, updateCollection ) -import Stack.Types.Cache ( Action (..), PrecompiledCache (..) ) +import Stack.Types.Cache + ( Action (..), GhcSemaphoreProtocolVersion (..) + , PrecompiledCache (..) + ) import Stack.Types.Compiler ( ActualCompiler, compilerVersionText ) import Stack.Types.CompilerBuild ( CompilerBuild ) import Stack.Types.CompilerPaths @@ -70,6 +73,7 @@ data StorageUserException | GlobalPackageCacheFileMetadataMismatch | GlobalDumpParseFailure | CompilerCacheArchitectureInvalid Text + | GhcSemaphoreProtocolVersionUnknown deriving Show instance Exception StorageUserException where @@ -88,6 +92,9 @@ instance Exception StorageUserException where , "Invalid arch: " , show compilerCacheArch ] + displayException GhcSemaphoreProtocolVersionUnknown = + "Error: [S-9841]\n" + ++ "GHC semaphore protocol version unknown, ignoring cache." share [ mkPersist sqlSettings , mkMigrate "migrateAll" @@ -141,6 +148,7 @@ CompilerCache globalDb FilePath globalDbCacheSize Int64 globalDbCacheModified Int64 + semaphoreVersion GhcSemaphoreProtocolVersion Maybe info ByteString -- This is the ugliest part of this table, simply storing a Show/Read version of the @@ -366,6 +374,13 @@ loadCompilerPaths compiler build sandboxed = do Nothing -> throwIO $ CompilerCacheArchitectureInvalid compilerCache.compilerCacheArch Just arch -> pure arch + semaphoreVersion <- maybe + (throwIO GhcSemaphoreProtocolVersionUnknown) + ( \case + Unsupported -> pure Nothing + Supported spv -> pure $ Just spv + ) + compilerCache.compilerCacheSemaphoreVersion pure CompilerPaths { compiler , compilerVersion = compilerCache.compilerCacheActualVersion @@ -378,6 +393,7 @@ loadCompilerPaths compiler build sandboxed = do , cabalVersion , globalDB , ghcInfo = compilerCache.compilerCacheInfo + , semaphoreVersion , globalDump } @@ -405,6 +421,8 @@ saveCompilerPaths cp = withUserStorage $ do , compilerCacheGlobalDbCacheSize = sizeToInt64 $ fileSize globalDbStatus , compilerCacheGlobalDbCacheModified = timeToInt64 $ modificationTime globalDbStatus + , compilerCacheSemaphoreVersion = Just $ + maybe Unsupported Supported cp.semaphoreVersion , compilerCacheInfo = cp.ghcInfo , compilerCacheGlobalDump = tshow cp.globalDump , compilerCacheArch = T.pack $ Distribution.Text.display cp.arch diff --git a/src/Stack/Types/Cache.hs b/src/Stack/Types/Cache.hs index f114d51652..f8db3021b4 100644 --- a/src/Stack/Types/Cache.hs +++ b/src/Stack/Types/Cache.hs @@ -16,6 +16,7 @@ module Stack.Types.Cache , PrecompiledCache (..) , ConfigCacheType (..) , Action (..) + , GhcSemaphoreProtocolVersion (..) ) where import Data.Aeson @@ -30,6 +31,7 @@ import Database.Persist.Sql import Stack.Prelude import Stack.Types.ConfigureOpts ( ConfigureOpts ) import Stack.Types.GhcPkgId ( GhcPkgId ) +import System.Semaphore ( SemaphoreProtocolVersion (..) ) -- | Type representing types of cache in the Stack project SQLite database. data ConfigCacheType @@ -86,12 +88,38 @@ data Action instance PersistField Action where toPersistValue UpgradeCheck = PersistInt64 1 + fromPersistValue (PersistInt64 1) = Right UpgradeCheck fromPersistValue x = Left $ T.pack $ "Invalid Action: " ++ show x instance PersistFieldSql Action where sqlType _ = SqlInt64 +-- Type representing GHC-supported semaphore protocol versions. +data GhcSemaphoreProtocolVersion + = Unsupported + | Supported SemaphoreProtocolVersion + deriving (Eq, Ord, Show) + +instance PersistField GhcSemaphoreProtocolVersion where + toPersistValue Unsupported = + -- We can use 0 as a sentinel because we are confident that valid protocol + -- versions will always be >= 1: + PersistInt64 0 + toPersistValue (Supported spv) = + PersistInt64 $ fromIntegral $ getSemaphoreProtocolVersion spv + + fromPersistValue v + | PersistInt64 x <- v + , x == 0 = Right Unsupported + | PersistInt64 x <- v + , x > 0 = Right $ Supported $ SemaphoreProtocolVersion $ fromIntegral x + | otherwise = + Left $ T.pack $ "Invalid GhcSemaphoreProtocolVersion: " <> show v + +instance PersistFieldSql GhcSemaphoreProtocolVersion where + sqlType _ = SqlInt64 + -- | Type synonym representing caches of files and information about them -- sufficient to identify if they have changed subsequently. type FileCache = Map FilePath FileCacheInfo diff --git a/src/Stack/Types/CompilerPaths.hs b/src/Stack/Types/CompilerPaths.hs index b9a2b331e3..8c9a2c65cd 100644 --- a/src/Stack/Types/CompilerPaths.hs +++ b/src/Stack/Types/CompilerPaths.hs @@ -24,6 +24,7 @@ import Stack.Types.Compiler ( ActualCompiler, WhichCompiler, whichCompiler ) import Stack.Types.CompilerBuild ( CompilerBuild ) import Stack.Types.DumpPackage ( DumpPackage ) +import System.Semaphore ( SemaphoreProtocolVersion ) -- | Paths on the filesystem for the compiler we're using data CompilerPaths = CompilerPaths @@ -50,6 +51,8 @@ data CompilerPaths = CompilerPaths -- ^ Global package database , ghcInfo :: !ByteString -- ^ Output of @ghc --info@ + , semaphoreVersion :: !(Maybe SemaphoreProtocolVersion) + -- ^ The semaphore protocol version supported by the compiler or 'Nothing'. , globalDump :: !(Map PackageName DumpPackage) } deriving Show diff --git a/stack-ghc-9.12.5.yaml b/stack-ghc-9.12.5.yaml index a58cf33587..8ba23508a1 100644 --- a/stack-ghc-9.12.5.yaml +++ b/stack-ghc-9.12.5.yaml @@ -22,8 +22,6 @@ extra-deps: - open-browser-0.5.0.0@sha256:0df6c833fd8abcd8e819c43c618487844f21243a40078eb084f707cfb6e06bbf,1959 # nightly-2026-06-22 specifies tls-2.1.14 - tls-2.2.2@sha256:95f5acd4ce76cbd6bdc46b737370dcbd93c59cf1cd1934a30e55c61c1dc140e9,7283 -# ghc-9.12.4.20260614 specifies semaphore-compat-2.0.0 -- semaphore-compat-1.0.0@sha256:2dff81c2c0ec9bac9f8bae364db497188654d2e1e4330f4a0e2f12310149f3e9,1176 docker: enable: false diff --git a/stack-ghc-9.12.5.yaml.lock b/stack-ghc-9.12.5.yaml.lock index 87ccc05799..3327749c03 100644 --- a/stack-ghc-9.12.5.yaml.lock +++ b/stack-ghc-9.12.5.yaml.lock @@ -67,13 +67,6 @@ packages: size: 7056 original: hackage: tls-2.2.2@sha256:95f5acd4ce76cbd6bdc46b737370dcbd93c59cf1cd1934a30e55c61c1dc140e9,7283 -- completed: - hackage: semaphore-compat-1.0.0@sha256:2dff81c2c0ec9bac9f8bae364db497188654d2e1e4330f4a0e2f12310149f3e9,1176 - pantry-tree: - sha256: 73f47245a0a2804e907df243638bef51c8b815c3dd5e6a016dbb61628353197e - size: 277 - original: - hackage: semaphore-compat-1.0.0 snapshots: - completed: sha256: 26504b113016bff86ea8678fc3e477ee8a73ac066912c176a241e0f162c31f76