
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