Java Incremental Compilation

At very high level, a java compiler takes project sources, classpath and configuration options as its inputs and produces class files and compile messages as the output. Java sources are never compiled in isolation. At minimum the java compiler needs to access java.lang.Object and several other java standard library classes, but most sources reference other classes from the same module and/or other reactor build modules and/or classpath dependencies. Only some aspects of the referenced classes affect compilation results. For example, if a referenced class is marked as @deprecated, java compiler needs to produce corresponding warning message. Method bodies and private members, on the other hand, have no impact on compilation results. Term âœstructure❠will be used to refer class aspects that affect compilation results.

A java source needs to be compiled during incremental build when any of the following is true - compiler configuration has changed since previous build - the java source itself has changed (or was introduced) since previous build - any of the packages referenced by on-demand declaration becomes available or disappears - any of the types referenced by the java source is added, removed or is changed structurally

Incremental compilation

Javac does not provide information about dependencies among classes. When a class is changed (or introduced or removed) it is impossible to tell what other classes are affected by the change. If there is a change, the only guaranteed way to produce correct compiler output is to recompile everything.

Eclipse java compiler, on the other hand, provides information about type and package names referenced by each source file. This allows fine-grained incremental compilation, where only changed sources and sourced affected by the changes are recompiled. Only structural class changes affect other classes, which further reduces amount of work performed during incremental build

Detecting classpath dependency changes

The most precise way to detect classpath changes is to calculate class structure digest for all classes on classpath and use this classpath digest to determine all types that changed structurally since previous build and all introduced/removed packages. This precise classpath change detection is required to take advantage of incremental compilation provided by Eclipse java compiler but comes at the cost of CPU and I/O resources needed to calculate and persist the digest.

Less precise but faster classpath change detection can be used with javac java compiler where only list of classpath entries and their length and timestamp are used to detect if there is a change.

Stale output class file cleanup

A java source can be compiled to zero, one or more .class files. When a source file is removed, either physically from source tree or using includes/excludes compiler configuration, all corresponding .class file(s) must be removed. Likewise, if nested/local/secondary type is removed from a java source, corresponding .class file must be removed.

Trying Takari incremental java compiler

Our longer-term goal is to develop set of new packaging types (tentatively “takari-jar”, “takari-maven-plugin”, etc), which will use incremental java compiler by default. To use incremental java compiler with existing packaging types, like “jar”, add the following xml snippet to parent pom.xml file.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>io.takari.maven.plugins</groupId>
        <artifactId>takari-lifecycle-plugin</artifactId>
      </plugin>
    </plugins>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>io.takari.maven.plugins</groupId>
          <artifactId>takari-lifecycle-plugin</artifactId>
          <version>1.8.3</version>
          <executions>
            <execution>
              <id>compile</id>
              <goals>
                <goal>compile</goal>
              </goals>
              <phase>compile</phase>
              <configuration>
                <!-- or "javac" to use javac java compiler -->
                <compilerId>jdt</compilerId>
              </configuration>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.1</version>
          <configuration>
            <!-- disable maven-compiler-plugin -->
            <skipMain>true</skipMain>
            <skip>true</skip>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Comparison to maven-compiler-plugin

Starting with version 3.0, maven-compiler-plugin (“m-c-p” for short) provides some limited incremental build support. Here is a summary that shows differences between takari-lifecycle-plugin and maven-compiler-plugin:

Takari lifecycle plugin (javac)

Takari lifecycle plugin (jdt)

Maven compiler plugin (javac)

Configuration change

Rebuild all

Rebuild all

Do nothing

Source change

Rebuild all

Rebuild affected

Rebuild all

Reactor dependency change

Rebuild all

Rebuild affected

Do nothing (a bug?)

Thirdpary dependency change

Rebuild all

Rebuild affected

Do nothing

Stale output

Delete specific

Delete specific

Delete all

Compiler message

Replayed on rebuild

Replayed on rebuild

Omitted on rebuild

 

Comments

Maven Training

To use Maven correctly you'll need to understand the fundamentals. This class is designed to deliver just that.

Introduction to Maven
 

Stay Connected

 

Newsletter

Subscribe to our newsletter and stay up to date with the latest news and events!