
In all this discussion on configurations I think I'm not getting over my main point about why we can't go for the 'easy' option of allowing package 'available' tests everywhere. Let me list a few reasons and then expand on them. 1. it often doesn't mean what you want 2. it allows auto-use deps 3. it makes life hard for distros and cabal-get 4. it makes the install order significant So, 1: it often doesn't mean what you want configuration: available(foo >= 2) cpp-options: -Denable_cool_feature There's no necessary relation between what packages are available in the environment and the one(s) that the package is going to be built against. So the above will fail if we actually build with foo-1 but foo-2 is available. What's worse is that it will likely work on the developers machine but fail on the users machine. 2: it allows auto-use deps An auto-use dep is one where we add an extra dependency on a package just because it's available. This is bad for a number of reasons. It means you cannot build a package on one machine and distribute it on another if that other doesn't have the optional dependency (the only way to do it would be to delete the optional package on the build machine). Distros do not support this kind of dep. It mostly makes no sense in binary-based distros and is actively banned in source distros like gentoo: http://www.gentoo.org/proj/en/qa/automagic.xml Indeed this kind of dependency cannot even be expressed in gentoo's dependency syntax. 3: it makes life hard for distros and cabal-get So how is cabal-get supposed to decide before hand all the dependencies that are needed to install a set of packages. Suppose we want to install A and B, but B checks if A is available and if so depends on C. So cabal-get needs to figure out the order it's going to install things and forward-propagate the set of installed packages at that time to figure out if an extra dep on C will be needed or not. As I mentioned already, many distros cannot express these kinds of automatic optional dependencies and so they will have to be turned into hard dependencies or eliminated by the distro package authors. This is a lot more work for them than if the translation is straightforward and can be automated. 4: it makes the install order significant in the above example, we only need C if we install A then B. If we install B then A, then we do not need C. So the order that we install becomes significant. We might be able to make cabal-get always choose one order (at the expense of preventing parallel builds) but that's not necessarily the same order that a distro package manager will pick. This isn't that bad but it's not exactly nice or intuitive. Duncan

On 10/31/06, Duncan Coutts
In all this discussion on configurations I think I'm not getting over my main point about why we can't go for the 'easy' option of allowing package 'available' tests everywhere.
<snip> configuration: available(foo >= 2)
cpp-options: -Denable_cool_feature
I think that everybody already agrees that available(x) should not be allowed in cexp's. You've always been clear about the disadvantages of allowing that. My point is that using(x) should not be allowed in cexp's either. I suggestion the following rules, which would eliminate the need for using(x), by allowing a configuration like this: configuration: using(foo >= x) .... to always be written as: flag: blah default: avalable(foo >= x) configuration: blah 1. If Cabal is not given an explicit package version number to use, then it will always choose the latest already-installed version of that package that satisfies the effective Build-depends constraints. This is already how it works right now. 2. If Cabal is told to use a specific version of a package (e.g. on the command line), then available(x) will return false for all versions of that package except for the version that the user requested. If that specifically-requested version fails to satisfy any effective configurations' Build-Depends for that package, then the build stops with an error. 3. If Cabal-get can satisfy a dependency without downloading a different (usually newer) version of a package, then it will do so. Otherwise, it will download and installed the latest available version of the package that satisfies the effective Build-depends in the package description. 4. The effective Build-depends in the intersection of the main stanza's Build-depends with the Build-depends of all enabled configurations. Effectively, a configuration cannot loosen or remove the dependencies given in the main stanza--a configuration can only restrict the dependencies. Example 1: Installed versions of foo: 1.0, 2.0, 3.0 Build-depends: foo >= 2.0 foo 3.0 is selected because it is the latest available version >= 2.0 Example 2: Installed versions of foo: 1.0, 2.0, 3.0 Build-depends: foo < 2.0 foo 1.0 is used to build the package, because it is the latest version < 2.0 Example 3: Installed versions of foo: 1.0, 2.0, 3.0 Build-depends: foo >= 2.0 Flag: bleeding-edge-features Default: available(foo >= 3.0) configuration: bleeding-edge-features Build-depends: foo >= 3.0 cpp-options: -DBLEED (a) Additional user input: use foo-2.0 The user did not give the bleeding-edge-features flag, so its default is used. Since the user selected foo-2.0 explicitly, then available(foo >= 3.0) is false (when a specific version is selected, all other versions are made unavailable). Thus, the bleeding-edge-features flag is not enabled automatically, so the corresponding configuration is not used, (no -DBLEED). (b) Additional user input: --bleeding-edge-features, use foo-2.0 The user forced the bleeding-edge-features flag, so the corresponding configuration is used. But, that configuration requires foo >= 3.0. The build stops with an error because foo >= 3.0 is not available due to the "use foo-2.0". Example 4: Installed versions of foo: 1.0, 2.0, 3.0 Build-depends: foo >= 2.0 Additional user input: none Available for download: foo-3.0, foo-4.0 foo-3.0 will be selected (the latest installed version, >= 2.0) Nothing will be downloaded. Example 5: Installed versions of foo: 1.0, 2.0 Build-depends: foo >= 3.0 Available for download: foo-3.0, foo-4.0 Addtional user input: none No installed versions of foo satisfy foo >= 3.0 If we are using Cabal (not cabal-get), then the build fails with an error. If we are using cabal-get (not just Cabal) then there are two versions of foo available for download that satisfy it. Cabal-get will download and register foo-4.0 (the latest version that satisfies the constraint) Then, Cabal will build the package using foo-4.0. Example 6: Installed versions of foo: 1.0, 2.0, 3.0 Build-depends: foo >= 2.0 Available for download: foo-3.0, foo-4.0 Addtional user input: use foo-4.0 Although foo-2.0 and foo-3.0 are installed neither one will satisfy foo >= 3.0 because "use foo-4.0" effectively hides all versions except 4.0. Since foo-4.0 is available for download, Cabal-get will download and register it. Then, Cabal will build the package using foo-4.0. Example 7: Build-depends: foo >= 2.0 Flag: use-old-version Default: !available(foo >= 2.0) Configuration: use-old-version Build-depends: foo < 2.0 cpp-options: -DCOMPAT Here, the user is trying to loosen the dependencies from the main stanza using a flag. But, as I stated in rule 4 above, the build-depends are always taken as intersections. That is, we have (foo >= 2.0) && (foo < 2.0) = { }, so the build fails. Notice that Cabal can (and should) determine statically that the Build-depends of the configuration is mutually exclusive with the build-depends of the main stanza and thus issue an error/warning. Example 8: Build-depends: foo >= 1.0 Flag: use-new-version Default: available(foo >= 2.0) #Notice that this stanza is not needed: if availble(foo >= 2.0), #then we are guaranteed to use such a version #Configuration: use-new-version #Build-depends: foo >= 2.0 Configuratoin: !use-new-version cpp-options: -DCOMPAT This is a reformulation of Example 7 that actually works as intended: If foo
= 2.0 is not availabe, then we define COMPAT, otherwise, we don't.
Example 9: Build-depends: foo >= 2.0 Flag: use-old-version Default: !available(foo >= 2.0) Configuration: use-old-version Build-depends: foo >= 1.0 cpp-options: -DCOMPAT Here is another reformulation of Example 7. Notice that the Build-depends of the main stanza is more restrictive than the build-depends of the configuration. This is a good indication that the user does not understand how Build-depends are combined, so a warning/error should be issued. Regards, Brian

On Wed, Nov 01, 2006 at 02:04:29AM +0000, Duncan Coutts wrote:
In all this discussion on configurations I think I'm not getting over my main point about why we can't go for the 'easy' option of allowing package 'available' tests everywhere.
I don't think anyone is arguing that we should do.
So, 1: it often doesn't mean what you want
configuration: available(foo >= 2) cpp-options: -Denable_cool_feature
There's no necessary relation between what packages are available in the environment and the one(s) that the package is going to be built against. So the above will fail if we actually build with foo-1 but foo-2 is available.
What's worse is that it will likely work on the developers machine but fail on the users machine.
Am I right in thinking that your objection to removing "using" and rewriting build-depends: foo configuration: using (foo >= 2) cc-options: -DENABLE_NEW_WAY as flag: new_way default: available(foo >= 2) configuration: new_way build-depends: foo >= 2 cc-options: -DENABLE_NEW_WAY configuration: !new_way build-depends: foo < 2 is that people might incorrectly write something like flag: new_way default: available(foo >= 2) build-depends: foo configuration: new_way cc-options: -DENABLE_NEW_WAY and not realise that it can break in some circumstances? If so then I don't think we should worry about this. They could equally well write build-depends: foo cc-options: -DENABLE_NEW_WAY and not be aware that it will break for some people (e.g. those with foo 1) (OK, in this case they'd probably realise as they've made a #define for it, but in the need-fps-if-base=1 they could easily not realise). I don't think "using" is worth the pain just to protect people from shooting themselves in certain feet. I especially don't think this should be in the first implementation of configurations as: * it'll make the implementation take longer to write, as things are much more complex with it * it can be added in the future without breaking any cabal files that have been written, while it can't be removed without doing so
2: it allows auto-use deps
An auto-use dep is one where we add an extra dependency on a package just because it's available.
Removing "using" doesn't enable auto-use deps.
3: it makes life hard for distros and cabal-get
So cabal-get needs to figure out the order it's going to install things and forward-propagate the set of installed packages at that time to figure out if an extra dep on C will be needed or not.
"available" in flags has this problem: flag: foo default: available(bar) configuration: foo build-depends: baz configuration: !foo build-depends: quux There will be different deps depending on whether bar will be installed when this package is compiled. I don't see this as a problem as cabal-get needs to work out what it's going to do before it does it anyway, or it might compile a dozen packages before hitting a dependency it can't satisfy and failing. Whether cabal-get should try alternate orders if the first one doesn't work is an interesting question, but not one I think we need to worry about right now.
4: it makes the install order significant
Again, having "available" in flags already makes install order significant, e.g. flag: foo default: available(bar) configuration: foo build-depends: baz needs baz only if bar is installed first. If you specify all flags it won't matter, of course. Thanks Ian

Ian Lynagh wrote:
On Wed, Nov 01, 2006 at 02:04:29AM +0000, Duncan Coutts wrote:
In all this discussion on configurations I think I'm not getting over my main point about why we can't go for the 'easy' option of allowing package 'available' tests everywhere.
I don't think anyone is arguing that we should do.
So, 1: it often doesn't mean what you want
configuration: available(foo >= 2) cpp-options: -Denable_cool_feature
There's no necessary relation between what packages are available in the environment and the one(s) that the package is going to be built against. So the above will fail if we actually build with foo-1 but foo-2 is available.
What's worse is that it will likely work on the developers machine but fail on the users machine.
Am I right in thinking that your objection to removing "using" and rewriting
build-depends: foo
configuration: using (foo >= 2) cc-options: -DENABLE_NEW_WAY
as
flag: new_way default: available(foo >= 2)
configuration: new_way build-depends: foo >= 2 cc-options: -DENABLE_NEW_WAY
configuration: !new_way build-depends: foo < 2
Yes, I'm with Ian (and Brian?) here. What was missing from Duncan's original proposal was the exact way in which the user is supposed to specify which version of foo they want when there's a choice. The version with a flag and no 'using()' makes this clear: you use a flag to choose, no extra mechanism is required. Also, using() is a weakened form of available(), and has some of the same disadvantages when allowed in the conditional of a configuration. The syntax ends up a bit long-winded, but this feels like the right design to me. The *only* place you can make a choice based on what's in the local package databse is in a 'default' field. Cheers, Simon
participants (4)
-
Brian Smith
-
Duncan Coutts
-
Ian Lynagh
-
Simon Marlow