incremental javac, make
I've been looking into incremental javac compilation again. I had most of the code for one approach done weeks ago but it never really got to the point of doing anything useful.
The goal is to simplify a GNU make based Java build system while ensuring consitent and complete builds.
javac -m <module>
comes very close to what I
want but the main problem is that it doesn't remove stale files.
These come about for the same reasons that might occur with C
development, for example the .java file is renamed or deleted.
But there are many more cases that occur regularly in Java, for
example an inner class or anonymous inner class is removed or
renamed. And in C these aren't such an issue since a link line or
whatever is just going to ignore any stale files anyway but with
Java you can't easily calculate all the possible .class files
(without recompiling the source) so you just grab all the files in
the directory when creating a jar or module, so you don't want
stale ones lying about.
So far i've created a tool called ijavac
that uses
the --module-source-path
only to automatically find
all source files that need recompiling. It optionally supports
per-module mode where it restricts processing to in-module
classes. It also automatically removes all stale files before
they are recompiled. It works by parsing all the existing .class
files, matching them up with their source based
on --module-source-path
and checking timestamps. The
parsed .class files are used to create the full set of down-stream
dependent classes, then match them to the corresponding set of
.java files, and then invoke javac with this set.
In per-module mode it isn't quite as fast as using javac -m, but it's close and it ensures stale files are removed. Because it's only performing file-level dependencies it can recompile more than is necessary. In whole-project mode it depends on what was changed and how many files could need recompiling. However i'm not sure I can fit this in with my build system as I want to support generated files which may require a per-module build order.
There are also cases where module mode fails, regardless of whether the stale files are removed or not. For example:
// module a public class Bob { int x; } // module b public class Foo { Bob bob; int baz() { return bob.x; } }
If x is renamed in class Bob then a per-module rebuild will only rebuild Bob.class. Subsequently running a dependency-aware module build on module b will not recompile Foo.
The whole-project mode will catch this case succesfully assuming a per-module build hasn't already updated Bob.class independently. Although if you have a deeply dependent object (that is used widely in a project) it's about the same speed just to delete all the classes and rebuild them all together.
The main reason is that the per-module mode restricts it's view to only in-module classes and sources. I guess it should be able to handle cross-module checks with some additional work.
Another idea i'm toying with is creating a cleanup routine that is
run as a post-process after javac -m
. Becasue this
only needs to match each .class with a .java it doesn't have to
worry about building the whole dependency graph and can get away
with only parsing the Source attribute. I'm not sure why javac -m
doesn't expunge stale files but alas it does not.
I also have the code to generate the module-level dependency lists which is what would go into a makefile. The makefile wouldn't track .class files as one would with C.
But for now i'm not sure I really got anywhere so I guess it'll just go on the backburner again.
Apparently 'best practice' using maven is just to delete and rebuild every time which is nonsense.