Hi All, Before the 6.6 release we had a longish discussion on how to do configurations and their semantics and implementation complexity etc.
I would like to re-propose the last scheme that I cam up with. I'll try to make it a concrete proposal as well as giving some motivating examples to give the intuition of the meaning. Key points: It's crucial that the user / packaging system have control over optional aspects of configuration. For example it is verboten for a package to just inspect the current environment and decide to use a package just because it's available without the user getting any say in that decision. (Eg a user might want to build a package to depoly on another system where that optional package is not available). What it certainly allowed is for a package to state conditional dependencies, that is a package may say: "if you, the user, force me to build using base-1.0 then I absolutely require fps-0.8". So the user / packager must have complete control over the environment that the package sees. On the other hand, for convenience it's ok for the default decision about these optional things to depend on what's available, the os or whatever. It's just important that any defaults be overrideable. So that motivates a design that separates decisions based on the environment from conditional requirements that are intrinsic and independent of the environment. So I propose two kinds of stanza: flags and configurations. flag ::= "flag:" name "default:" fexp fexp ::= "available"( dependency ) | "os"( STRING ) | "arch"( STRING ) | fexp || fexp | fexp && fexp | !fexp | True | False conf ::= "configuration:" cexp fields cexp ::= "flag"( STRING ) | "using"( dependency ) | "os"( STRING ) | "arch"( STRING ) | cexp || cexp | cexp && cexp | !cexp | True | False So flag default values can depend on what packages are available in the environment. There's no restriction on the packages that are named. The default value may also depend on the OS and architecture the package is being configured for. (We should consider splitting this into host and target arch). The idea is that these named flags turn into user tweakable settings but with sensible default values. For example they might turn into check buttons in an IDE or --enable-foo flags to the cabal configure command line. We could certainly allow and optional description of the flag. Here are some examples: flag: debug default: False synopsis: Whether to turn on debug checks or not. description: Enabling debugging turns on blah blah blah .... flag: gui default: os(windows) || (available(gtk>=0.9.10) && available(cairo>=0.9.10)) As for the meaning, it's not clear to me if it should be that each test is independent of if all constraints must be satisfied on a single package simultaneously. That is if we have a expression like: available ( P > 1) && available ( P < 1) does there have to be a single package P with versions satisfying both constraints (ie impossible) or if the tests are independent and so may be satisfied by there being both P-0.9 and P-1.1 available. I'm not sure it really matters, so we should go for the simple independent test meaning. Opinions? Now for the configurations. The point of view here is that we already know exactly the versions of packages that the builder has chosen for us to use and all we can do is adjust our build settings on that basis or complain that we need more packages to be able to build under these conditions. If we need more packages then the build agent may let us have them or not. (in which case configuration fails). So let's look at a few examples: configuration: flag(debug) cpp-options: -DDEBUG other-modules: Thing.DebugUtils build-depends: SuperDebugLogger >= 1.1 configuration: flag(gui) && os(windows) build-depends: Win32 >= 2.0 configuration: flag(gui) && !os(windows) build-depends: gtk >= 0.9.10, cairo >= 0.9.10 configuration: using(base < 2) build-depends: fps >= 0.8 Note how in the last example, there was no flag involved. This conditional dependency has nothing to do with aspects of the 'outer' environment, it depends only on what packages we've been told to build with. So some fields in the stanza just affect internal aspects of the build like exposing more modules or using some cpp defines or whatever. The more tricky ones are the ones that ask for more deps. There are a few corner cases here. Remember that at the point that we are evaluating the configuration's guard expression have decided on the exact versions of packages that are going to be used. The easy case is adding a new dep, then it's up to the builder (ie Cabal) to figure out if that dep can be satisfied. If it can then the version that Cabal decides on is added to the set of packages we're using and we carry on. If it's not available then the configuration fails. The harder cases are: * adding a build dep on a package that we already depend on, but with a version constraint that is not satisfied by the version that was already selected. * adding a dep which causes the guard of another configuration to change. In the first case I would suggest that this configuration fails. However it may not be total failure, the build agent may re-try the configuration with a different set of package versions (if it has any flexibility in that area - depending on what's available and on any user policy). In the second case I would suggest that we allow configuration guards to change from false to true but not the other way around (ie it would be a configuration failure and an error on the part of the package author). This allows us to do a simple fixpoint and make the result independent of the order of the evaluation of the configuration guards. So the restriction on a guard changing from true to false would ban examples like: configuration: !using(foo) build-depends: foo Since supposing that foo is not in the top level build-depends then ! using(foo) will be true in which case we'll add foo. But now that changes the guard. The alternative to banning this kind of case is to simply ignore any changes from true to false, but then I think we'd get a bit of oddness and the evaluation order would matter in some strange cases. Can anyone think of any sensible examples that this rule would get in the way for? I mentioned earlier that the build agent might need/decide to backtrack if it found that the versions of packages it chose did not satisfy the extra build-deps introduced by some configuration stanza. How about we start with True as the Can anyone see if it's possible to find out a priori if there is a set of packages that can satisfy the conditional constraints. Is this a standard constraint solving problem? Does it have a solution? Here's a nasty example: configuration: using(P = 1.1) build-depends: P = 1.0 configuration: using(P = 1.0) build-depends: P = 1.1 Duncan _______________________________________________ cabal-devel mailing list cabal-devel@haskell.org http://www.haskell.org/mailman/listinfo/cabal-devel