Thomas Schilling wrote:
Hello cabal-devel,
the following is a summary of the changes to Cabal to enable Cabal
Configurations.
As we are about to integrate Cabal configurations into the main
branch in order to get more testing feedback, I think it is a good
time to give an overview about my changes.
** Parsing
In order to allow the new syntactic categories of sections and if-else
blocks, I added those categories to the low-level parsing primitives.
Possible section names are "library", "executable", or "flag" of
which the latter two require the name of the executable/flag name as a
parameter. Conditional blocks may have an optional else-branch.
The current implementation is rather restrictive in its permitted
style; namely, the opening "{" has to be on the same line as the
section name or "if" keyword and the closing "}" has to stand alone on
its line unless it is followed by an "else {" branch.
At a higher level we then parse this lower-level structure into a
'GenericPackageDescription' structure. This is defined as
data GenericPackageDescription =
GenericPackageDescription {
packageDescription :: PackageDescription,
packageFlags :: [Flag],
condLibrary :: Maybe (CondTree ConfVar [Dependency]
Library),
condExecutables :: [(String, CondTree ConfVar [Dependency]
Executable)]
}
The 'packageDescription' field contains a conventional package
description with only part of the fields filled in, namely all global
settings like version number or package name. However, it does *not*
contain a valid "build-depends" field. This and all other
library-related parts now go into the library section. The old Cabal
format can easily be translated into this format so backwards
compatibility is no problem.
The 'CondTree' structure is an internal representation of a block of
field descriptions and possible conditional blocks.
data CondTree v c a = CondNode
{ condTreeData :: a
, condTreeConstraints :: c
, condTreeComponents :: [( Condition v
, CondTree v c a
, Maybe (CondTree v c a))]
}
A 'CondTree' node contains both data and constraints/dependencies.
Conditions are parameterized over the variable type, which in our case
will presumably be only one type.
data ConfVar = OS String | Arch String | Flag String
** Resolving Conditions/Dependencies
'CondTree's can be resolved to a flat 'a' (the data part) given a
function to determine which constraints are satisfied and which
aren't.
resolveWithFlags :: Monoid a =>
[(String,[Bool])]
-- ^ Domain for each flag name, will be tested in order.
-> String -- ^ OS name, as returned by System.Info.os
-> String -- ^ arch name, as returned by System.Info.arch
-> [CondTree ConfVar [d] a] -- ^ All CondTrees must satisfy the deps
-> ([d] -> DepTestRslt [d]) -- ^ Dependency test function.
-> (Either [d] -- missing dependencies
( [a] -- result data
, [d] -- overall chosen dependencies
, [(String, Bool)])) -- chosen flag assignments
This function is the heart of the flag resolution process and is
implemented rather naively by trying all possible flag assignments and
choosing the first that satisfies the dependencies. As long as we
don't use too many flags (or set some explicitly) this shouldn't be a
problem.
Note that the data has to be a Monoid. The problem is, that due to
lack of first-class record fields[1] we have to store a partially
filled-in 'a' (in our use case a 'Library' or 'Executable') and then
rely on the Monoid instance to do the right thing. This strategy also
required changing the way defaults are set; before, this was done by
setting it in the empty value and just (blindly) overwriting it
whenever it was set explicitly in the .cabal file. Now, however, we
have to set it after the package has been resolved, and find out which
fields haven't been set explicitly. This is a rather hackish
("hacky"?) approach but seems to work fine so far.
[1] Implementing approximations would likely require extensions to
Haskell98 thus wouldn't be a good idea for Cabal which tries to be
as portable as possible.
The function that does the actual flag-assignment resolution on
packages is
finalizePackageDescription ::
[(String,Bool)] -- ^ Explicitly specified flag assignments
-> Maybe [PackageIdentifier] -- ^ Available dependencies
-> String -- ^ OS-name
-> String -- ^ Arch-name
-> GenericPackageDescription
-> Either [Dependency] -- ^ Missing dependencies
( PackageDescription -- ^ Resolved package description
, [(String,Bool)]) -- ^ Flag assignments chosen
Using this function the 'configure' command determines a resolved
package description (or fails) and stores it in the 'LocalBuildInfo'
structure. From there it is available to all the other setup
commands. Unfortunately, this is not sufficient for all build
commands.
** Breaking the Interface to Setup.(l)hs
Due to the new staged package resolution process, the 'confHook'
arguments had to be changed, and most hooks could be changed to drop
the 'PackageDescription' argument.
Regarding hooks, I made a proposal for a radical overhaul of hooks a couple
of months ago:
http://www.haskell.org/pipermail/cabal-devel/2007-April/000512.html
I think there was general agreement, see the followup thread
http://www.haskell.org/pipermail/cabal-devel/2007-May/000528.html
It might be worth doing this at the same time - lump all the breaking
changes together to avoid repeated pain.
Cheers,
Simon
_______________________________________________
cabal-devel mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/cabal-devel