Working on a fix for ticket #89

Stephen Blackheath [to cabal-devel] rubbernecking.trumpet.stephen at blacksapphire.com
Mon May 25 09:28:56 EDT 2009


Duncan,

Thanks kindly.  See below...

Duncan Coutts wrote:
> On Sat, 2009-05-09 at 16:41 +0000, Duncan Coutts wrote:
>> On Wed, 2009-05-06 at 11:50 +1200, Stephen Blackheath [to cabal-devel]
>> wrote:
> 
>>> http://upcycle.it/~blackh/cabal/cabal-ticket-89-v3.patch
>> Ok, reviewing the v5 version now...
> 
> Right, so I've got quite a few comments, but it's quite a big patch! I
> hope I'm not discouraging you.

We need to get this right.  I expected this task to be a pain in the
butt, so I am not the least bit discouraged.  :)

> So adding targetBuildDepends into the BuildInfo is certainly the way to
> go. I'm a bit confused by the changes in the Configuration module and
> that there are no changes to the parser.
>
> Here's how I'd expect it to work: adjust the parser so that
> build-depends goes into the targetBuildDepends in the BuildInfo. After
> parsing we can transform the GenericPackageDescription so that the top
> level buildDepends contains the union of the targetBuildDepends. 

The way the parser works took a bit of figuring out.

Distribution.PackageDescription.Parse.parsePackageDescription returns a
GenericPackageDescription structure, which contains 'condLibrary' and
'condExecutables'.  These are tree structures of some kind, and they
contain all the build-depends information, along with conditionals based
on flags defined in the .cabal file.  It hasn't been stuffed into the
packageDescription yet - that is, buildDepends has not actually been
populated yet.  This actually happens in
Distribution.PackageDescription.Configuration.finalizePackageDescription,
right at the top (called from Distribution.Simple.Configure.configure)
after some processing on the conditionals.

In actual fact, buildDepends probably does get populated by the parser,
but its contents are not used before that field gets overwritten in
finalizePackageDescription.

So, the 'build-depends' field values that the parser returns (in
condLibrary and condExecutables fields) are already divided into the
different targets (exes & lib).  The parser already outputs the needed
information and doesn't need to be modified.

As currently implemented, the resolution of conditionals - which must
take place before buildDepends (and the new targetBuildDepends) can be
populated - depends on a "dependency test function" which has knowledge
about installed packages, and therefore doesn't belong in the parser.
So, there doesn't seem to be much point in modifying the parser.

> Optionally (depending on the cabal-version), we would then change each
> targetBuildDepends to contain the top level buildDepends union, rather
> than the original. This is the backwards compat hack. Doing it this way,
> by changing the data means that we should not need the "if
> newPackageDepsBehaviour" stuff when building the package. We can just
> follow the data and we get the old behaviour when each component uses
> the union of all deps.

OK - that's a good idea. I will change it to work that way.

> I don't see that we need to change anything in the configurations stuff
> when resolving GenericPackageDescription to a PackageDescription, though
> perhaps I'm missing something.

The configuration code currently takes the tree structure with
conditionals in it (from GenericPackageDescription), resolves the
conditionals against available packages, and uses that to populate
buildDepends.  In doing so, it discards any association of
'build-depends' constraints with targets - this being the information we
now want to keep.  So one way or another, the configuration code does
need to be modified.

> Why do we have to filter out the internal package deps when saving the
> packageDeps in the LocalBuildInfo? Isn't it ok to have them since we'll
> really refer to them as packages when we build things.

My reasoning was this:  There is an "external view" of the package,
which is the view used by cabal's "register" functionality.  In this
view, we don't want to see internal dependencies (because it'll look
like a cycle) and we want to see the dependencies for the package as a
whole - the union of the individual targets (because we don't care about
the package's internal structure).

So, I used 'packageDeps' to represent this view, since this is exactly
the same view of the package as 'packageDeps' currently has.  An
alternative would be to get rid of 'packageDeps', and replace it with a
function that calculates the "external view" of the package from the
targetPackageDeps data.  We could give it a more suitable name with a
comment that explains this concept of an internal / external view of
dependencies.  I think that's a much better way.

'packageDeps' is currently used by

1. Haddock
2. cabal's "register" function
3. Makefile generation

Part of my motivation in doing it the way I did was to avoid having to
modify these.

> The need to identify the target all the time when constructing command
> lines for ghc etc is a bit unpleasant though perhaps it's essential.
> Hmm, yes building one component needs both the original info from
> the .cabal file (ie the BuildInfo and PackageDescription) but also the
> local post-configure stuff LocalBuildInfo and something similar that's
> per-component. It used to be that the only per-component info we needed
> was the original BuildInfo but now we need resolved dependencies too.
> This'll want refactoring at some point.

Perhaps the ideal way would be to separate the targets completely, and
have separate buildLib and buildExe functions in the compiler-specific
code which only see a target-specific LocalBuildInfo structure.  Then we
could eliminate the code I added to identify targets.

> Could we make the registering of the local packages more generic. I mean
> instead of having each compiler's build function do it, have it done
> from the generic build function by calling register (or possibly calling
> per-compiler things).

That would be good.  It seems to fits in well with my suggestion above
of splitting 'build' into 'buildLib' and 'buildExe'.

> Do you suppose it's possible for us to split these changes into multiple
> patches. I'd hope that we can have:
>       * package type and parser changes (setting the targetBuildDepends
>         and the buildDepends to their union)
>       * changes to use the per-component targetBuildDepends (including
>         changes in the active build parts)
>       * configure changes to switch the behaviour on the cabal version
>         (ie using the union vs using correct per-component deps)
> Note that so far none of this is to do with internal deps, just allowing
> the deps of each component to be different.
>       * check for internal deps, disallow for older cabal-version
>       * use internal deps during build, register internal packages
> 
> Does that look like the dependencies between patches works out? Does it
> cover everything?

That looks good, but like I said before, as things currently stand, no
change is needed in the parser.  So I want to thrash that one out with
you first so I know exactly what I'm doing.

> Perhaps we should try and merge this incrementally. That'd make each bit
> easier to review and I could add some refactoring patches as we go
> along.

OK - that's a very good way.  Since the first step is the addition of
targetBuildDepends, let's first get to the bottom of how that's going to
be implemented.  Then I can work on that first patch.

Do you see what I mean that the code needs to be changed in the
configuration code, not in the parser?  (Except to tidy it up.)

----------
Here is another idea for re-factoring:

Currently configure has two steps:

1. (GenericPackageDescription, installed packages) ->
finalizePackageDescription -> PackageDescription (with buildDepends now
populated). It resolves conditionals and dependencies.  (In fact
finalizePackageDescription only needs a 'check dependency' function, not
the whole list of dependencies.)

2. 'configure': (PackageDescription, installed packages) -> Resolve
specific versions of dependencies -> PackageDescription

The suggestion is this:

- Move the new targetBuildDepends into a new data structure that gets
returned by finalizePackageDescription.  Get rid of buildDepends and
calculate it as necessary (in fact it is only needed inside configure).
 Now finalizePackageDescription returns additional information instead
of a modified PackageDescription.

- Move the fields from GenericPackageDescription into
PackageDescription.  Get rid of GenericPackageDescription.

This means that PackageDescription now represents just the output of the
parser.  The concept of a "finalized" PackageDescription is replaced by
a separate data structure.  This extra data structure acts as a stepping
stone between PackageDescription and LocalBuildInfo.  It is only used
inside 'configure'.


Steve



More information about the cabal-devel mailing list