Configurations proposal

Duncan Coutts duncan.coutts at worc.ox.ac.uk
Tue Oct 24 20:36:05 EDT 2006


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



More information about the cabal-devel mailing list