Skip to content

Commit

Permalink
Split up user and project storage (fixes #4893)
Browse files Browse the repository at this point in the history
  • Loading branch information
snoyberg committed Jun 23, 2019
1 parent ca12d57 commit c360543
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 216 deletions.
4 changes: 4 additions & 0 deletions ChangeLog.md
Expand Up @@ -13,6 +13,10 @@ Behavior changes:

Other enhancements:

* Move configure information for local packages back to .stack-work to
improve caching. See
[#4893](https://github.com/commercialhaskell/stack/issues/4893).

Bug fixes:
* Fix to allow dependencies on specific versions of local git repositories. See
[#4862](https://github.com/commercialhaskell/stack/pull/4862)
Expand Down
3 changes: 2 additions & 1 deletion package.yaml
Expand Up @@ -229,7 +229,8 @@ library:
- Stack.Setup.Installed
- Stack.SetupCmd
- Stack.SourceMap
- Stack.Storage
- Stack.Storage.Project
- Stack.Storage.User
- Stack.Types.Build
- Stack.Types.CompilerBuild
- Stack.Types.Compiler
Expand Down
3 changes: 2 additions & 1 deletion src/Stack/Build/Cache.hs
Expand Up @@ -41,7 +41,8 @@ import Path
import Path.IO
import Stack.Constants
import Stack.Constants.Config
import Stack.Storage
import Stack.Storage.Project
import Stack.Storage.User
import Stack.Types.Build
import Stack.Types.Cache
import Stack.Types.Config
Expand Down
36 changes: 23 additions & 13 deletions src/Stack/Config.hs
Expand Up @@ -29,7 +29,7 @@ module Stack.Config
,getInNixShell
,defaultConfigYaml
,getProjectConfig
,loadBuildConfig
,withBuildConfig
) where

import Control.Monad.Extra (firstJustM)
Expand Down Expand Up @@ -64,7 +64,8 @@ import Stack.Config.Nix
import Stack.Constants
import Stack.Build.Haddock (shouldHaddockDeps)
import Stack.Lock (lockCachedWanted)
import Stack.Storage (initStorage)
import Stack.Storage.Project (initProjectStorage)
import Stack.Storage.User (initUserStorage)
import Stack.SourceMap
import Stack.Types.Build
import Stack.Types.Compiler
Expand Down Expand Up @@ -373,9 +374,9 @@ configFromConfigMonoid
hsc
(maybe HpackBundled HpackCommand $ getFirst configMonoidOverrideHpack)
clConnectionCount
(\configPantryConfig -> initStorage
(\configPantryConfig -> initUserStorage
(configStackRoot </> relFileStorage)
(\configStorage -> inner Config {..}))
(\configUserStorage -> inner Config {..}))

-- | Get the default location of the local programs directory.
getDefaultLocalProgramsBase :: MonadThrow m
Expand Down Expand Up @@ -448,8 +449,10 @@ loadConfig inner = do

-- | Load the build configuration, adds build-specific values to config loaded by @loadConfig@.
-- values.
loadBuildConfig :: RIO Config BuildConfig
loadBuildConfig = do
withBuildConfig
:: RIO BuildConfig a
-> RIO Config a
withBuildConfig inner = do
config <- ask

-- If provided, turn the AbstractResolver from the command line
Expand Down Expand Up @@ -526,13 +529,20 @@ loadBuildConfig = do
wanted <- lockCachedWanted stackYamlFP (projectResolver project) $
fillProjectWanted stackYamlFP config project

return BuildConfig
{ bcConfig = config
, bcSMWanted = wanted
, bcExtraPackageDBs = extraPackageDBs
, bcStackYaml = stackYamlFP
, bcCurator = projectCurator project
}
-- Unfortunately redoes getProjectWorkDir, since we don't have a BuildConfig yet
workDir <- view workDirL
let projectStorageFile = parent stackYamlFP </> workDir </> relFileStorage

initProjectStorage projectStorageFile $ \projectStorage -> do
let bc = BuildConfig
{ bcConfig = config
, bcSMWanted = wanted
, bcExtraPackageDBs = extraPackageDBs
, bcStackYaml = stackYamlFP
, bcCurator = projectCurator project
, bcProjectStorage = projectStorage
}
runRIO bc inner
where
getEmptyProject :: Maybe RawSnapshotLocation -> [PackageIdentifierRevision] -> RIO Config Project
getEmptyProject mresolver extraDeps = do
Expand Down
2 changes: 1 addition & 1 deletion src/Stack/Docker.hs
Expand Up @@ -49,7 +49,7 @@ import Stack.Config (getInContainer)
import Stack.Constants
import Stack.Constants.Config
import Stack.Setup (ensureDockerStackExe)
import Stack.Storage (loadDockerImageExeCache,saveDockerImageExeCache)
import Stack.Storage.User (loadDockerImageExeCache,saveDockerImageExeCache)
import Stack.Types.Version
import Stack.Types.Config
import Stack.Types.Docker
Expand Down
4 changes: 2 additions & 2 deletions src/Stack/Nix.hs
Expand Up @@ -18,7 +18,7 @@ import Data.Version (showVersion)
import Lens.Micro (set)
import Path.IO
import qualified Paths_stack as Meta
import Stack.Config (getInContainer, loadBuildConfig)
import Stack.Config (getInContainer, withBuildConfig)
import Stack.Config.Nix (nixCompiler)
import Stack.Constants (platformVariantEnvVar,inNixShellEnvVar,inContainerEnvVar)
import Stack.Types.Config
Expand Down Expand Up @@ -54,7 +54,7 @@ runShellAndExit = do
--
-- 2. This function ends up exiting before running other code
-- (thus the void return type)
compilerVersion <- view wantedCompilerVersionL <$> loadBuildConfig
compilerVersion <- withBuildConfig $ view wantedCompilerVersionL

ghc <- either throwIO return $ nixCompiler compilerVersion
let pkgsInConfig = nixPackages (configNix config)
Expand Down
10 changes: 1 addition & 9 deletions src/Stack/Runners.hs
Expand Up @@ -29,7 +29,7 @@ import Stack.DefaultColorWhen (defaultColorWhen)
import qualified Stack.Docker as Docker
import qualified Stack.Nix as Nix
import Stack.Setup
import Stack.Storage (upgradeChecksSince, logUpgradeCheck)
import Stack.Storage.User (upgradeChecksSince, logUpgradeCheck)
import Stack.Types.Config
import Stack.Types.Docker (dockerEnable)
import Stack.Types.Nix (nixEnable)
Expand All @@ -55,14 +55,6 @@ withDefaultEnvConfig
-> RIO Config a
withDefaultEnvConfig = withEnvConfig AllowNoTargets defaultBuildOptsCLI

-- | Upgrade a 'Config' environment to a 'BuildConfig' environment by
-- performing further parsing of project-specific configuration. This
-- is intended to be run inside a call to 'withConfig'.
withBuildConfig :: RIO BuildConfig a -> RIO Config a
withBuildConfig inner = do
bconfig <- loadBuildConfig
runRIO bconfig inner

-- | Upgrade a 'Config' environment to an 'EnvConfig' environment by
-- performing further parsing of project-specific configuration (like
-- 'withBuildConfig') and then setting up a build environment
Expand Down
2 changes: 1 addition & 1 deletion src/Stack/Setup.hs
Expand Up @@ -81,7 +81,7 @@ import Stack.GhcPkg (createDatabase, getGlobalDB, mkGhcPackagePath,
import Stack.Prelude hiding (Display (..))
import Stack.SourceMap
import Stack.Setup.Installed
import Stack.Storage (loadCompilerPaths, saveCompilerPaths)
import Stack.Storage.User (loadCompilerPaths, saveCompilerPaths)
import Stack.Types.Build
import Stack.Types.Compiler
import Stack.Types.CompilerBuild
Expand Down
215 changes: 215 additions & 0 deletions src/Stack/Storage/Project.hs
@@ -0,0 +1,215 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-unused-top-binds -Wno-identities #-}

-- | Work with SQLite database used for caches across a single project.
module Stack.Storage.Project
( initProjectStorage
, ConfigCacheKey
, configCacheKey
, loadConfigCache
, saveConfigCache
, deactiveConfigCache
) where

import qualified Data.ByteString as S
import qualified Data.Set as Set
import Database.Persist.Sql (SqlBackend)
import Database.Persist.Sqlite
import Database.Persist.TH
import qualified Pantry.Internal as SQLite
import Path
import Stack.Prelude hiding (MigrationFailure)
import Stack.Storage.User (updateList, updateSet)
import Stack.Types.Build
import Stack.Types.Cache
import Stack.Types.Config (HasBuildConfig, buildConfigL, bcProjectStorage, ProjectStorage (..))
import Stack.Types.GhcPkgId

share [ mkPersist sqlSettings
, mkDeleteCascade sqlSettings
, mkMigrate "migrateAll"
]
[persistLowerCase|
ConfigCacheParent sql="config_cache"
directory FilePath "default=(hex(randomblob(16)))"
type ConfigCacheType
pkgSrc CachePkgSrc
active Bool
pathEnvVar Text
haddock Bool default=0
UniqueConfigCacheParent directory type sql="unique_config_cache"
deriving Show

ConfigCacheDirOption
parent ConfigCacheParentId sql="config_cache_id"
index Int
value String sql="option"
UniqueConfigCacheDirOption parent index
deriving Show

ConfigCacheNoDirOption
parent ConfigCacheParentId sql="config_cache_id"
index Int
value String sql="option"
UniqueConfigCacheNoDirOption parent index
deriving Show

ConfigCacheDep
parent ConfigCacheParentId sql="config_cache_id"
value GhcPkgId sql="ghc_pkg_id"
UniqueConfigCacheDep parent value
deriving Show

ConfigCacheComponent
parent ConfigCacheParentId sql="config_cache_id"
value S.ByteString sql="component"
UniqueConfigCacheComponent parent value
deriving Show
|]

-- | Initialize the database.
initProjectStorage ::
HasLogFunc env
=> Path Abs File -- ^ storage file
-> (ProjectStorage -> RIO env a)
-> RIO env a
initProjectStorage fp f = SQLite.initStorage "Stack" migrateAll fp $ f . ProjectStorage

-- | Run an action in a database transaction
withProjectStorage ::
(HasBuildConfig env, HasLogFunc env)
=> ReaderT SqlBackend (RIO env) a
-> RIO env a
withProjectStorage inner =
flip SQLite.withStorage_ inner =<< view (buildConfigL . to bcProjectStorage . to unProjectStorage)

-- | Key used to retrieve configuration or flag cache
type ConfigCacheKey = Unique ConfigCacheParent

-- | Build key used to retrieve configuration or flag cache
configCacheKey :: Path Abs Dir -> ConfigCacheType -> ConfigCacheKey
configCacheKey dir = UniqueConfigCacheParent (toFilePath dir)

-- | Internal helper to read the 'ConfigCache'
readConfigCache ::
(HasBuildConfig env, HasLogFunc env)
=> Entity ConfigCacheParent
-> ReaderT SqlBackend (RIO env) ConfigCache
readConfigCache (Entity parentId ConfigCacheParent {..}) = do
let configCachePkgSrc = configCacheParentPkgSrc
coDirs <-
map (configCacheDirOptionValue . entityVal) <$>
selectList
[ConfigCacheDirOptionParent ==. parentId]
[Asc ConfigCacheDirOptionIndex]
coNoDirs <-
map (configCacheNoDirOptionValue . entityVal) <$>
selectList
[ConfigCacheNoDirOptionParent ==. parentId]
[Asc ConfigCacheNoDirOptionIndex]
let configCacheOpts = ConfigureOpts {..}
configCacheDeps <-
Set.fromList . map (configCacheDepValue . entityVal) <$>
selectList [ConfigCacheDepParent ==. parentId] []
configCacheComponents <-
Set.fromList . map (configCacheComponentValue . entityVal) <$>
selectList [ConfigCacheComponentParent ==. parentId] []
let configCachePathEnvVar = configCacheParentPathEnvVar
let configCacheHaddock = configCacheParentHaddock
return ConfigCache {..}

-- | Load 'ConfigCache' from the database.
loadConfigCache ::
(HasBuildConfig env, HasLogFunc env)
=> ConfigCacheKey
-> RIO env (Maybe ConfigCache)
loadConfigCache key =
withProjectStorage $ do
mparent <- getBy key
case mparent of
Nothing -> return Nothing
Just parentEntity@(Entity _ ConfigCacheParent {..})
| configCacheParentActive ->
Just <$> readConfigCache parentEntity
| otherwise -> return Nothing

-- | Insert or update 'ConfigCache' to the database.
saveConfigCache ::
(HasBuildConfig env, HasLogFunc env)
=> ConfigCacheKey
-> ConfigCache
-> RIO env ()
saveConfigCache key@(UniqueConfigCacheParent dir type_) new =
withProjectStorage $ do
mparent <- getBy key
(parentId, mold) <-
case mparent of
Nothing ->
(, Nothing) <$>
insert
ConfigCacheParent
{ configCacheParentDirectory = dir
, configCacheParentType = type_
, configCacheParentPkgSrc = configCachePkgSrc new
, configCacheParentActive = True
, configCacheParentPathEnvVar = configCachePathEnvVar new
, configCacheParentHaddock = configCacheHaddock new
}
Just parentEntity@(Entity parentId _) -> do
old <- readConfigCache parentEntity
update
parentId
[ ConfigCacheParentPkgSrc =. configCachePkgSrc new
, ConfigCacheParentActive =. True
, ConfigCacheParentPathEnvVar =. configCachePathEnvVar new
]
return (parentId, Just old)
updateList
ConfigCacheDirOption
ConfigCacheDirOptionParent
parentId
ConfigCacheDirOptionIndex
(maybe [] (coDirs . configCacheOpts) mold)
(coDirs $ configCacheOpts new)
updateList
ConfigCacheNoDirOption
ConfigCacheNoDirOptionParent
parentId
ConfigCacheNoDirOptionIndex
(maybe [] (coNoDirs . configCacheOpts) mold)
(coNoDirs $ configCacheOpts new)
updateSet
ConfigCacheDep
ConfigCacheDepParent
parentId
ConfigCacheDepValue
(maybe Set.empty configCacheDeps mold)
(configCacheDeps new)
updateSet
ConfigCacheComponent
ConfigCacheComponentParent
parentId
ConfigCacheComponentValue
(maybe Set.empty configCacheComponents mold)
(configCacheComponents new)

-- | Mark 'ConfigCache' as inactive in the database.
-- We use a flag instead of deleting the records since, in most cases, the same
-- cache will be written again within in a few seconds (after
-- `cabal configure`), so this avoids unnecessary database churn.
deactiveConfigCache :: HasBuildConfig env => ConfigCacheKey -> RIO env ()
deactiveConfigCache (UniqueConfigCacheParent dir type_) =
withProjectStorage $
updateWhere
[ConfigCacheParentDirectory ==. dir, ConfigCacheParentType ==. type_]
[ConfigCacheParentActive =. False]

0 comments on commit c360543

Please sign in to comment.