forked from haskell/cabal
/
PackageEnvironment.hs
284 lines (260 loc) · 11.7 KB
/
PackageEnvironment.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Client.PackageEnvironment
-- Maintainer : cabal-devel@haskell.org
-- Portability : portable
--
-- Utilities for working with the package environment file. Patterned after
-- Distribution.Client.Config.
-----------------------------------------------------------------------------
module Distribution.Client.PackageEnvironment (
PackageEnvironment(..),
loadPackageEnvironment,
dumpPackageEnvironment
) where
import Distribution.Client.Config ( SavedConfig(..), baseSavedConfig,
commentSavedConfig, initialSavedConfig,
loadConfig, configFieldDescriptions,
installDirsFields )
import Distribution.Client.ParseUtils ( parseFields, ppFields, ppSection )
import Distribution.Client.Setup ( GlobalFlags(..), InstallFlags(..),
SandboxFlags(..) )
import Distribution.Simple.Compiler ( PackageDB(..) )
import Distribution.Simple.InstallDirs ( InstallDirs(..), PathTemplate,
toPathTemplate )
import Distribution.Simple.Setup ( Flag(..), ConfigFlags(..),
fromFlagOrDefault, toFlag )
import Distribution.Simple.Utils ( notice, warn, lowercase )
import Distribution.ParseUtils ( FieldDescr(..), ParseResult(..),
liftField, lineNo, locatedErrorMsg,
parseFilePathQ, readFields,
showPWarning, simpleField, warning )
import Distribution.Verbosity ( Verbosity )
import Control.Monad ( foldM, when )
import Data.List ( partition )
import Data.Monoid ( Monoid(..) )
import Distribution.Compat.Exception ( catchIO )
import System.Directory ( canonicalizePath,
createDirectoryIfMissing, renameFile )
import System.FilePath ( (<.>), (</>), takeDirectory )
import System.IO.Error ( isDoesNotExistError )
import Text.PrettyPrint ( ($+$) )
import qualified Text.PrettyPrint as Disp
import qualified Distribution.Compat.ReadP as Parse
import qualified Distribution.ParseUtils as ParseUtils ( Field(..) )
--
-- * Configuration saved in the package environment file
--
-- TODO: add a 'constraints' field (really needed? there is already
-- 'constraint'), remove duplication between D.C.PackageEnvironment and
-- D.C.Config
data PackageEnvironment = PackageEnvironment {
pkgEnvInherit :: Flag FilePath,
pkgEnvSavedConfig :: SavedConfig
}
instance Monoid PackageEnvironment where
mempty = PackageEnvironment {
pkgEnvInherit = mempty,
pkgEnvSavedConfig = mempty
}
mappend a b = PackageEnvironment {
pkgEnvInherit = combine pkgEnvInherit,
pkgEnvSavedConfig = combine pkgEnvSavedConfig
}
where
combine f = f a `mappend` f b
-- | Values that *must* be initialised.
basePackageEnvironment :: FilePath -> IO PackageEnvironment
basePackageEnvironment pkgEnvDir = do
baseConf <- baseSavedConfig
return $ mempty {
pkgEnvSavedConfig = baseConf {
savedConfigureFlags = (savedConfigureFlags baseConf) {
configUserInstall = toFlag False
},
savedUserInstallDirs = (savedUserInstallDirs baseConf) {
prefix = toFlag (toPathTemplate pkgEnvDir)
},
savedGlobalInstallDirs = (savedGlobalInstallDirs baseConf) {
prefix = toFlag (toPathTemplate pkgEnvDir)
},
savedGlobalFlags = (savedGlobalFlags baseConf) {
globalLogsDir = toFlag $ pkgEnvDir </> "logs",
-- TODO: cabal-dev uses the global world file: is this right?
globalWorldFile = toFlag $ pkgEnvDir </> "world"
}
}
}
-- | Initial configuration that we write out to the package environment file if
-- it does not exist. When the package environment gets loaded it gets layered
-- on top of 'basePackageEnvironment'.
initialPackageEnvironment :: FilePath -> IO PackageEnvironment
initialPackageEnvironment pkgEnvDir = do
initialConf <- initialSavedConfig
return $ mempty {
pkgEnvSavedConfig = initialConf {
savedUserInstallDirs = (savedUserInstallDirs initialConf) {
prefix = toFlag (toPathTemplate pkgEnvDir)
},
savedGlobalInstallDirs = (savedGlobalInstallDirs initialConf) {
prefix = toFlag (toPathTemplate pkgEnvDir)
},
savedGlobalFlags = (savedGlobalFlags initialConf) {
globalLocalRepos = [pkgEnvDir </> "packages"],
-- TODO: cabal-dev uses the global world file: is this right?
globalWorldFile = toFlag $ pkgEnvDir </> "world"
},
savedConfigureFlags = (savedConfigureFlags initialConf) {
configUserInstall = toFlag False,
-- TODO: This should include comp. flavor and version
configPackageDBs = [Just (SpecificPackageDB $ pkgEnvDir
</> "packages.conf.d")]
},
savedInstallFlags = (savedInstallFlags initialConf) {
installSummaryFile = [toPathTemplate (pkgEnvDir </>
"logs" </> "build.log")]
}
}
}
-- | Default values that get used if no value is given. Used here to include in
-- comments when we write out the initial package environment.
commentPackageEnvironment :: FilePath -> IO PackageEnvironment
commentPackageEnvironment pkgEnvDir = do
commentConf <- commentSavedConfig
return $ mempty { pkgEnvSavedConfig = commentConf }
-- | Entry point for the 'cabal dump-pkgenv' command.
dumpPackageEnvironment :: Verbosity -> SandboxFlags -> FilePath -> IO ()
dumpPackageEnvironment verbosity sandboxFlags path = do
pkgEnv <- loadPackageEnvironment verbosity path
putStrLn . showPackageEnvironment $ pkgEnv
-- | Load the package environment file, creating it if doesn't exist.
loadPackageEnvironment :: Verbosity -> FilePath -> IO PackageEnvironment
loadPackageEnvironment verbosity path = do
pkgEnvDir <- canonicalizePath . takeDirectory $ path
addBasePkgEnv pkgEnvDir $ do
minp <- readPackageEnvironmentFile mempty path
case minp of
Nothing -> do
notice verbosity $ "Writing default package environment to " ++ path
commentPkgEnv <- commentPackageEnvironment pkgEnvDir
initialPkgEnv <- initialPackageEnvironment pkgEnvDir
writePackageEnvironmentFile path commentPkgEnv initialPkgEnv
return initialPkgEnv
Just (ParseOk warns pkgEnv) -> do
when (not $ null warns) $ warn verbosity $
unlines (map (showPWarning path) warns)
return pkgEnv
Just (ParseFailed err) -> do
let (line, msg) = locatedErrorMsg err
warn verbosity $
"Error parsing package environment file " ++ path
++ maybe "" (\n -> ":" ++ show n) line ++ ":\n" ++ msg
warn verbosity $ "Using default package environment."
initialPackageEnvironment pkgEnvDir
where
addBasePkgEnv :: FilePath -> IO PackageEnvironment -> IO PackageEnvironment
addBasePkgEnv pkgEnvDir body = do
base <- basePackageEnvironment pkgEnvDir
extra <- body
case pkgEnvInherit extra of
NoFlag ->
return $ base `mappend` extra
(Flag confPath) -> do
conf <- loadConfig verbosity (Flag confPath) (Flag False)
let conf' = base `mappend` conf `mappend` (pkgEnvSavedConfig extra)
return $ extra { pkgEnvSavedConfig = conf' }
-- | Descriptions of all fields in the package environment file.
pkgEnvFieldDescrs :: [FieldDescr PackageEnvironment]
pkgEnvFieldDescrs = [
simpleField "inherit"
(fromFlagOrDefault Disp.empty . fmap Disp.text) (optional parseFilePathQ)
pkgEnvInherit (\v pkgEnv -> pkgEnv { pkgEnvInherit = v })
]
++ map toPkgEnv configFieldDescriptions
where
optional = Parse.option mempty . fmap toFlag
toPkgEnv :: FieldDescr SavedConfig -> FieldDescr PackageEnvironment
toPkgEnv fieldDescr =
liftField pkgEnvSavedConfig
(\savedConfig pkgEnv -> pkgEnv { pkgEnvSavedConfig = savedConfig})
fieldDescr
-- | Read the package environment file.
readPackageEnvironmentFile :: PackageEnvironment -> FilePath
-> IO (Maybe (ParseResult PackageEnvironment))
readPackageEnvironmentFile initial file =
handleNotExists $
fmap (Just . parsePackageEnvironment initial) (readFile file)
where
handleNotExists action = catchIO action $ \ioe ->
if isDoesNotExistError ioe
then return Nothing
else ioError ioe
-- | Parse the package environment file.
parsePackageEnvironment :: PackageEnvironment -> String
-> ParseResult PackageEnvironment
parsePackageEnvironment initial str = do
fields <- readFields str
let (knownSections, others) = partition isKnownSection fields
pkgEnv <- parse others
let config = pkgEnvSavedConfig pkgEnv
installDirs0 = savedUserInstallDirs config
-- 'install-dirs' is the only section that we care about.
installDirs <- foldM parseSection installDirs0 knownSections
return pkgEnv {
pkgEnvSavedConfig = config {
savedUserInstallDirs = installDirs,
savedGlobalInstallDirs = installDirs
}
}
where
isKnownSection :: ParseUtils.Field -> Bool
isKnownSection (ParseUtils.Section _ "install-dirs" _ _) = True
isKnownSection _ = False
parse :: [ParseUtils.Field] -> ParseResult PackageEnvironment
parse = parseFields pkgEnvFieldDescrs initial
parseSection :: InstallDirs (Flag PathTemplate)
-> ParseUtils.Field
-> ParseResult (InstallDirs (Flag PathTemplate))
parseSection accum (ParseUtils.Section _ "install-dirs" name fs)
| name' == "" = do accum' <- parseFields installDirsFields accum fs
return accum'
| otherwise = do warning "The install-dirs section should be unnamed"
return accum
where name' = lowercase name
parseSection accum f = do
warning $ "Unrecognized stanza on line " ++ show (lineNo f)
return accum
-- | Write out the package environment file.
writePackageEnvironmentFile :: FilePath -> PackageEnvironment
-> PackageEnvironment -> IO ()
writePackageEnvironmentFile path comments pkgEnv = do
let tmpPath = (path <.> "tmp")
createDirectoryIfMissing True (takeDirectory path)
writeFile tmpPath $ explanation
++ showPackageEnvironmentWithComments comments pkgEnv ++ "\n"
renameFile tmpPath path
where
-- TODO: Better explanation
explanation = unlines
["-- This is a Cabal package environment file."
,""
,"-- The available configuration options are listed below."
,"-- Some of them have default values listed."
,""
,"-- Lines (like this one) beginning with '--' are comments."
,"-- Be careful with spaces and indentation because they are"
,"-- used to indicate layout for nested sections."
,"",""
]
-- | Pretty-print the package environment data.
showPackageEnvironment :: PackageEnvironment -> String
showPackageEnvironment = showPackageEnvironmentWithComments mempty
showPackageEnvironmentWithComments :: PackageEnvironment -> PackageEnvironment
-> String
showPackageEnvironmentWithComments defPkgEnv pkgEnv = Disp.render $
ppFields pkgEnvFieldDescrs defPkgEnv pkgEnv
$+$ Disp.text ""
$+$ ppSection "install-dirs" "" installDirsFields
(field defPkgEnv) (field pkgEnv)
where
field = savedUserInstallDirs . pkgEnvSavedConfig