-
Notifications
You must be signed in to change notification settings - Fork 0
Implementation changes
This page lists some noticeable implementation changes.
The major change is the replacement of Plexus compiler API by the standard javax.tools.JavaCompiler
API introduced in Java 6.
This changes required an almost full rewrite of the compiler plugin.
Other Plexus dependencies such as StringUtils
could also be removed because they now have replacements in the standard Java API.
The previous compiler plugin used ASM for generating the package-info.class
files that were omitted by the Java compiler because empty.
This issue is moot because the package-info.class
files were generated
as a workaround for the way that Maven incremental compilation worked.
The new plugin no longer generate those classes by default
(i.e., the <createMissingPackageInfoClass>
default value has been changed to false
).
If empty package-info.class
files are nevertheless desired,
they are generated by the Java compiler itself with the -Xpkginfo:always
option.
If that extra option is not supported, then a warning is logged and no option is added.
The previous plugin tried to be clever by intercepting and modifying the values of user-supplied --patch-module
compiler arguments,
by adding --path-module
and --add-reads <module>=ALL-UNNAMED
arguments on its own initiative, etc.
The result was saved in a META-INF/jpms.args
file added to the JAR file.
Above-cited heuristics have been removed from the new compiler plugin,
as this plugin aims to give more configuration control to the users.
For example, developers are encouraged to set a dependency type to modular-jar
or classpath-jar
instead of jar
.
Some heuristics have been kept for compatibility with current widespread practice,
in particular when the dependency type is only jar
.
But most heuristics should not be applied when developers use the most explicit ways to configure Maven.
The Plexus scranners used for include/exclude filters have been replaced by java.nio.file.PathMatcher
.
The set of allowed syntax contains at least "glob" and "regex".
See FileSystem.getPathMatcher(String)
Javadoc for a description of the "glob" syntax.
If no syntax is specified, then the default syntax is "glob" with the following modification:
- Unless escaped by
\
, all occurrences of the/
character are replaced by the platform-specific separator.
The list of files to process is built by applying the path matcher on each regular (non directory) files. The walk in file trees has the following characteristics:
- Symbolic links are followed.
- Hidden files and hidden directories are ignored.
The code for detecting which files to recompile has been rewritten. The previous code had an issue with list of files generated and compared in one case as absolute files, and in other case as relative files. Consequently, the plugin always considered that all files changed, which may explain the performance issue reported in MCOMPILER-209.
The new implementation saves the list of source files, together with their last modification times,
in a binary file with the .cache
extension instead of a text file with the .lst
extension.
This custom binary encoding is more compact than the previous text files (because of less redundancies) and has more information.
The incremental compilation behavior is modified as below:
- Changes in compiler options cause a recompilation of all files. Those changes are detected on a "best effort" basis only.
- By default, addition of new source files does not cause the recompilation of all files, because this is usually not necessary.
- The modification times of source files are compared with the modification times of the same source files during the previous build (saved in above-cited binary file), instead of compared with the modification times of the class files.
The last point avoids the problem that the <createMissingPackageInfoClass>
parameter tried to solve.
Because the new algorithm does not depend on .class
timestamps, there is no need to force their creation.
If the <useIncrementalCompilation>
parameter is set to false
, the plugin does not use the binary cache file
and compare modification times of source files with modification times of class files.
This approach reproduces the previous behavior.
When checking if a dependency changed, the new implementation compares the JAR timestamps against the start time of
the previous build saved in the binary file instead of comparing against the value given by session.getStartTime()
.
The reason for this change is as below:
- Consider a project with 3 modules named A, B and C where modules B and C depend on module A.
- Module A is modified and
mvn install
is executed on the command-line. -
A.jar
get a modification time which is after the build start time. Module B and C detect that fact and perform a "clean and build all". This is the current behavior of Maven 3 incremental compilation.
But let assume that the compilation failed in module B because of the change in A.
The developer fixes the compilarion error in B, then executes mvn install
a second time:
- Compilation of A is skipped because nothing changed. Consequently, the timestamp of
A.jar
is unchanged. - With the Maven 3 implementation, module C will not detect that module A changed because the modification time
of
A.jar
is before the second build start time.
By using the timestamp saved in the binary file instead, the new algorithm will detect that A.jar
has been
updated since the last build of C.
For modular projects having a module-info.java
file in their main sources,
Maven 3 recommended to declare the test dependencies in another module-info.java
file located in the test sources.
The latter was basically a copy of the former with the addition of, for example, requires org.junit.jupiter.api
statements.
This approach is deprecated in Maven 4.
Java does not let us overwrite module-info
easily, maybe for security reasons.
Maven 3 supported that approach by putting the project upside-down:
the test classes were considered the project's main code
(so that the test's module-info
takes precedence),
and the main classes were considered the patch added on top of the tests.
This new compiler plugin keeps the main classes as main and the test classes as patch,
and instead executes the following steps:
- Compile the test
module-info.java
file alone. - Temporarily rename the main
module-info.class
file asmodule-info.class.bak
. - Temporarily move the test
module-info.class
file (compiled in step 1) in place of the main one (saved in step 2). - Temporarily rename the test
module-info.java
file asmodule-info.java.bak
(otherwise the compiler seems to get confused). - Compile all test classes except the test
module-info.java
file which has been compiled at step 1. - Restore all
*.bak
files to their original location.
A shutdown hook is registered for executing step 6 even if the user interrupted the compilation with [Ctrl-C].
Above hack works, but is obviously fragile and should be used in last resort only.
The preferred approach is to use options formally supported by the JDK such as --add-reads
.
Their use should be easier with this new plugin than with Maven 3,
since Maven 4 automatizes some of those options.
The way to handle compiler options has been modified.
Previously, the Maven plugin validated some options before to pass them to the compiler.
For example, if the <debuglevel>
value contains anything else than lines
, vars
or source
, the plugin raised an error.
The intend was to provide more informative message.
But in the javax.tools.JavaCompiler
interface, there is an API telling us whether an option is supported.
Therefor, the new plugin version first asks to the compiler whether the option is supported,
and only if the compiler said "no", the validation is performed for producing the error message.
Consequently, if the compiler claims to support the -g:foo
option,
then the plugin will no longer block the use of the foo
value in <debuglevel>
even if the plugin does not know that value.
when the plugin proposes a code snippet (e.g. for specifying the target Java release),
the Maven compiler plugin version shown in the snippet is fetched from META-INF/MANIFEST.MF
instead of META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties
.