@@ -52,6 +52,7 @@ import Distribution.Parsec.ParseResult
52
52
import Distribution.Simple.Utils (die' , fromUTF8BS , warn )
53
53
import Distribution.Text (display )
54
54
import Distribution.Types.CondTree
55
+ import Distribution.Types.Dependency (Dependency )
55
56
import Distribution.Types.ForeignLib
56
57
import Distribution.Types.UnqualComponentName
57
58
(UnqualComponentName , mkUnqualComponentName )
@@ -62,6 +63,7 @@ import Distribution.Version
62
63
import System.Directory (doesFileExist )
63
64
64
65
import Distribution.Compat.Lens
66
+ import qualified Distribution.Types.BuildInfo.Lens as L
65
67
import qualified Distribution.Types.GenericPackageDescription.Lens as L
66
68
import qualified Distribution.Types.PackageDescription.Lens as L
67
69
@@ -149,7 +151,15 @@ parseGenericPackageDescription' lexWarnings fs = do
149
151
150
152
-- elif conditional is accepted if spec version is >= 2.1
151
153
let hasElif = if specVersion pd >= mkVersion [2 ,1 ] then HasElif else NoElif
152
- execStateT (goSections hasElif sectionFields) gpd
154
+
155
+ -- Common stanzas
156
+ (sectionFields', commonStanzas) <-
157
+ if specVersion pd >= mkVersion [2 , 1 ]
158
+ then partitionCommonStanzas hasElif sectionFields
159
+ else pure (sectionFields, Map. empty)
160
+
161
+ -- parse secitons
162
+ execStateT (goSections hasElif commonStanzas sectionFields') gpd
153
163
where
154
164
emptyGpd :: GenericPackageDescription
155
165
emptyGpd = GenericPackageDescription emptyPackageDescription [] Nothing [] [] [] [] []
@@ -180,8 +190,8 @@ parseGenericPackageDescription' lexWarnings fs = do
180
190
maybeWarnCabalVersion _ _ = return ()
181
191
182
192
-- Sections
183
- goSections :: HasElif -> [Field Position ] -> SectionParser ()
184
- goSections hasElif = traverse_ process
193
+ goSections :: HasElif -> Map String CondTreeBuildInfo -> [Field Position ] -> SectionParser ()
194
+ goSections hasElif commonStanzas = traverse_ process
185
195
where
186
196
process (Field (Name pos name) _) =
187
197
lift $ parseWarning pos PWTTrailingFields $
@@ -194,40 +204,40 @@ goSections hasElif = traverse_ process
194
204
parseSection :: Name Position -> [SectionArg Position ] -> [Field Position ] -> SectionParser ()
195
205
parseSection (Name pos name) args fields
196
206
| name == " library" && null args = do
197
- lib <- lift $ parseCondTree hasElif (libraryFieldGrammar Nothing ) (targetBuildDepends . libBuildInfo) fields
207
+ lib <- lift $ parseCondTreeWithCommonStanzas hasElif (libraryFieldGrammar Nothing ) commonStanzas fields
198
208
-- TODO: check that library is defined once
199
209
L. condLibrary ?= lib
200
210
201
211
-- Sublibraries
202
212
| name == " library" = do
203
213
-- TODO: check cabal-version
204
214
name' <- parseUnqualComponentName pos args
205
- lib <- lift $ parseCondTree hasElif (libraryFieldGrammar $ Just name') (targetBuildDepends . libBuildInfo) fields
215
+ lib <- lift $ parseCondTreeWithCommonStanzas hasElif (libraryFieldGrammar $ Just name') commonStanzas fields
206
216
-- TODO check duplicate name here?
207
217
L. condSubLibraries %= snoc (name', lib)
208
218
209
219
| name == " foreign-library" = do
210
220
name' <- parseUnqualComponentName pos args
211
- flib <- lift $ parseCondTree hasElif (foreignLibFieldGrammar name') (targetBuildDepends . foreignLibBuildInfo) fields
221
+ flib <- lift $ parseCondTreeWithCommonStanzas hasElif (foreignLibFieldGrammar name') commonStanzas fields
212
222
-- TODO check duplicate name here?
213
223
L. condForeignLibs %= snoc (name', flib)
214
224
215
225
| name == " executable" = do
216
226
name' <- parseUnqualComponentName pos args
217
- exe <- lift $ parseCondTree hasElif (executableFieldGrammar name') (targetBuildDepends . buildInfo) fields
227
+ exe <- lift $ parseCondTreeWithCommonStanzas hasElif (executableFieldGrammar name') commonStanzas fields
218
228
-- TODO check duplicate name here?
219
229
L. condExecutables %= snoc (name', exe)
220
230
221
231
| name == " test-suite" = do
222
232
name' <- parseUnqualComponentName pos args
223
- testStanza <- lift $ parseCondTree hasElif testSuiteFieldGrammar (targetBuildDepends . _testStanzaBuildInfo) fields
233
+ testStanza <- lift $ parseCondTreeWithCommonStanzas hasElif testSuiteFieldGrammar commonStanzas fields
224
234
testSuite <- lift $ traverse (validateTestSuite pos) testStanza
225
235
-- TODO check duplicate name here?
226
236
L. condTestSuites %= snoc (name', testSuite)
227
237
228
238
| name == " benchmark" = do
229
239
name' <- parseUnqualComponentName pos args
230
- benchStanza <- lift $ parseCondTree hasElif benchmarkFieldGrammar (targetBuildDepends . _benchmarkStanzaBuildInfo) fields
240
+ benchStanza <- lift $ parseCondTreeWithCommonStanzas hasElif benchmarkFieldGrammar commonStanzas fields
231
241
bench <- lift $ traverse (validateBenchmark pos) benchStanza
232
242
-- TODO check duplicate name here?
233
243
L. condBenchmarks %= snoc (name', bench)
@@ -261,6 +271,7 @@ goSections hasElif = traverse_ process
261
271
parseWarning pos PWTUnknownSection $ " Ignoring section: " ++ show name
262
272
263
273
parseName :: Position -> [SectionArg Position ] -> SectionParser String
274
+ -- TODO: use strict parser
264
275
parseName pos args = case args of
265
276
[SecArgName _pos secName] ->
266
277
pure $ fromUTF8BS secName
@@ -274,6 +285,20 @@ parseName pos args = case args of
274
285
lift $ parseFailure pos $ " Invalid name " ++ show args
275
286
pure " "
276
287
288
+ parseCommonName :: Position -> [SectionArg Position ] -> ParseResult String
289
+ parseCommonName pos args = case args of
290
+ [SecArgName _pos secName] ->
291
+ pure $ fromUTF8BS secName
292
+ [SecArgStr _pos secName] ->
293
+ pure $ fromUTF8BS secName
294
+ [] -> do
295
+ parseFailure pos $ " name required"
296
+ pure " "
297
+ _ -> do
298
+ -- TODO: pretty print args
299
+ parseFailure pos $ " Invalid name " ++ show args
300
+ pure " "
301
+
277
302
parseUnqualComponentName :: Position -> [SectionArg Position ] -> SectionParser UnqualComponentName
278
303
parseUnqualComponentName pos args = mkUnqualComponentName <$> parseName pos args
279
304
@@ -291,10 +316,10 @@ warnInvalidSubsection :: Section Position -> ParseResult ()
291
316
warnInvalidSubsection (MkSection (Name pos name) _ _) =
292
317
void (parseFailure pos $ " invalid subsection " ++ show name)
293
318
294
-
295
319
data HasElif = HasElif | NoElif
296
320
deriving (Eq , Show )
297
321
322
+ -- TODO: add warning about include section
298
323
parseCondTree
299
324
:: forall a c .
300
325
HasElif -- ^ accept @elif@
@@ -366,6 +391,119 @@ When/if we re-implement the parser to support formatting preservging roundtrip
366
391
with new AST, this all need to be rewritten.
367
392
-}
368
393
394
+ -------------------------------------------------------------------------------
395
+ -- Common stanzas
396
+ -------------------------------------------------------------------------------
397
+
398
+ -- $commonStanzas
399
+ --
400
+ -- [Note: Common stanzas]
401
+ --
402
+ -- In Cabal 2.2 we support simple common stanzas:
403
+ --
404
+ -- * Commons stanzas define 'BuildInfo'
405
+ --
406
+ -- * Include statements can only occur at top of other stanzas (think: imports)
407
+ --
408
+ -- In particular __there aren't__
409
+ --
410
+ -- * implicit stanzas
411
+ --
412
+ -- * More specific common stanzas (executable, test-suite).
413
+ --
414
+ --
415
+ -- The approach uses the fact that 'BuildInfo' is a 'Monoid':
416
+ --
417
+ -- @
418
+ -- mergeCommonStanza' :: HasBuildInfo comp => BuildInfo -> comp -> comp
419
+ -- mergeCommonStanza' bi = over L.BuildInfo (bi <>)
420
+ -- @
421
+ --
422
+ -- Real 'mergeCommonStanza' is more complicated as we have to deal with
423
+ -- conditional trees.
424
+ --
425
+ -- The approach is simple, and have good properties:
426
+ --
427
+ -- * Common stanzas are parsed exactly once, even if not-used. Thus we report errors in them.
428
+ --
429
+ type CondTreeBuildInfo = CondTree ConfVar [Dependency ] BuildInfo
430
+
431
+ -- | Create @a@ from 'BuildInfo'.
432
+ --
433
+ -- Law: @view buildInfo . fromBuildInfo = id@
434
+ class L. HasBuildInfo a => FromBuildInfo a where
435
+ fromBuildInfo :: BuildInfo -> a
436
+
437
+ instance FromBuildInfo Library where fromBuildInfo bi = set L. buildInfo bi emptyLibrary
438
+ instance FromBuildInfo ForeignLib where fromBuildInfo bi = set L. buildInfo bi emptyForeignLib
439
+ instance FromBuildInfo Executable where fromBuildInfo bi = set L. buildInfo bi emptyExecutable
440
+
441
+ instance FromBuildInfo TestSuiteStanza where
442
+ fromBuildInfo = TestSuiteStanza Nothing Nothing Nothing
443
+
444
+ instance FromBuildInfo BenchmarkStanza where
445
+ fromBuildInfo = BenchmarkStanza Nothing Nothing Nothing
446
+
447
+ partitionCommonStanzas :: HasElif -> [Field Position ] -> ParseResult ([Field Position ], Map String CondTreeBuildInfo )
448
+ partitionCommonStanzas _hasElif [] = pure ([] , Map. empty)
449
+ partitionCommonStanzas hasElif (Section (Name pos name) args secFields : fields) | name == " common" = do
450
+ commonName <- parseCommonName pos args
451
+ biTree <- parseCondTree hasElif buildInfoFieldGrammar targetBuildDepends secFields
452
+
453
+ (fs, m) <- partitionCommonStanzas hasElif fields
454
+
455
+ -- TODO: check duplicate name
456
+ pure (fs, Map. insert commonName biTree m)
457
+
458
+ -- | Other fields fall through:
459
+ partitionCommonStanzas hasElif (field : fields) = do
460
+ (fs, m) <- partitionCommonStanzas hasElif fields
461
+ pure (field : fs, m)
462
+
463
+ parseCondTreeWithCommonStanzas
464
+ :: forall a . FromBuildInfo a
465
+ => HasElif -- ^ accept @elif@
466
+ -> ParsecFieldGrammar' a -- ^ grammar
467
+ -> Map String CondTreeBuildInfo -- ^ common stanzas
468
+ -> [Field Position ]
469
+ -> ParseResult (CondTree ConfVar [Dependency ] a )
470
+ parseCondTreeWithCommonStanzas hasElif grammar commonStanzas = goIncludes []
471
+ where
472
+ -- parse leading includes
473
+ goIncludes acc (Section (Name pos name) args secFields : fields) | name == " include" = do
474
+ unless (null secFields) $
475
+ parseFailure pos " Non-empty include stanza"
476
+ commonName <- parseCommonName pos args
477
+ case Map. lookup commonName commonStanzas of
478
+ Nothing -> do
479
+ parseFailure pos $ " Undefined common stanza included: " ++ commonName
480
+ goIncludes acc fields
481
+ Just commonTree ->
482
+ goIncludes (acc ++ [commonTree]) fields
483
+
484
+ -- Go to parsing condTree after first non-include 'Field'.
485
+ goIncludes acc fields = go acc fields
486
+
487
+ -- parse actual CondTree
488
+ go :: [CondTreeBuildInfo ] -> [Field Position ] -> ParseResult (CondTree ConfVar [Dependency ] a )
489
+ go bis fields = do
490
+ x <- parseCondTree hasElif grammar (view L. targetBuildDepends) fields
491
+ pure $ foldr mergeCommonStanza x bis
492
+
493
+ mergeCommonStanza
494
+ :: forall a . FromBuildInfo a
495
+ => CondTree ConfVar [Dependency ] BuildInfo
496
+ -> CondTree ConfVar [Dependency ] a
497
+ -> CondTree ConfVar [Dependency ] a
498
+ mergeCommonStanza (CondNode bi _ bis) (CondNode x _ cs) =
499
+ CondNode x' (x' ^. L. targetBuildDepends) cs'
500
+ where
501
+ -- new value is old value with buildInfo field _prepended_.
502
+ x' = x & L. buildInfo %~ (bi <> )
503
+
504
+ -- tree components are appended together.
505
+ cs' = map (fmap fromBuildInfo) bis ++ cs
506
+
369
507
-------------------------------------------------------------------------------
370
508
-- Old syntax
371
509
-------------------------------------------------------------------------------
0 commit comments