Mike Schilling said:
I've thought about this a bit, though not to the point of creating a design,
much less building prototypes. It seems to me that this approach is worth
investigating:
1. The interface of each class C in the system needs to be captured and
stored persistently, where "interface" means method signatures, field
definitions, and constant values. Superclass name too, to cover the changes
that can occur if what C inherits changes.
Yes, that's a good start.
2. Dependencies also need to be captured and stored persistently. This will
be information of the form:
. Class D depends on (some feature of) the interface of class C
While I originally thought into that direction, I now have the
impression that this is really too difficult. Afterall there
are a couple of java's features that would need to be taken care
for. (inheritence, compiletime-resolving of fields, static methods
and the choice among overloaded methods)
As long as none of the recently changed classes changes its interface
in an incompatible way(*), the current normal incremental build
suffices.
If any class (well, except for private nested or anonymous ones)
changes its interface such that it is possible for dependent classes
to notice the difference, then a full-rebuild is worth it.
My original point of avoiding full-builds is still fulfilled, because
the full rebuild wouldn't be necessary *everytime*! In the outset,
the problem was, that one either needs to *always* do full-rebuilds,
or risk inconsistencies. Now, it's about a reliable indicator, that
still shouldn't fire too often.
A. How granular should the dependency information be?
I'd be already happy, if any .class-file's change can be reliably
characterized as incompatible(*) or not.
It wouldn't be a question of which java-file changed, but rather:
Was any .class file changed incompatibly(*) during a normal incremental
build?
B. How to generate the dependency information. I presume it can be
calculated from .class file analysis
No, not possible (unless javac was modified), because usage of static
finals is not explicitly "mentioned" in the .class file.
Inheritance adds some wrinkles. If Sub overrides a method it inherits from
Super, that doesn't really change its interface. Classes which previously
called Sub.meth() don't have to be recompiled.
Unless it's a static method :-/ ... where dependent classes might continue
to call Super's version, even if they refer to Sub.meth().
The same goes with fields and overloaded (even non-static) methods.
(*): Chapter 13 of the JLS-3.0 (Java Language Specification 3rd Edition)
mentions (among other allowed changes):
* Adding new fields, methods, or constructors to an existing class or interface.
as a binary compatible change, but it isn't always compatible in our sense.
Our rules for compatibility are stricter, in that they demand
not only linkability, but also "equivalence in behaviour whether
or not a dependent class is recompiled as well".
I'm sure there are many more of these which further analysis would reveal.
I also fear so ... but once I leave out the problem to list all dependents,
I think that what remains should be possible and still useful.
One more note: this is an ideal open source project, since it could be
greatly useful to the development community and there is no money to be made
by solving it.
I wouldn't say so. Making builds reliable without resorting to always
doing full rebuilds might safe some costs. However, I'm a fan of not
only using open-source, but also contributing to it ...