desruisseaux opened a new pull request, #271: URL: https://github.com/apache/maven-compiler-plugin/pull/271
This is a proposed rewriting of the Maven compiler plugin. This work is for Maven 4 only, the plugin for Maven 3 will stay unaffected. A major goal of this work is to improve the support of Java Module System in Maven. # Incompatible changes This section describes behaviors of the Maven compiler plugin 4 that changed in incompatibles way compared to versions 3. Some projects building successfully with version 3 of `maven-compiler-plugin` may fail with version 4 because of those changes. This section describes corrections that can be applied to projects for fixing those build failures. ## Cannot use both `--release` and `--source` As a general rule, this new Maven plugin does not modify the compiler options specified in the configuration. This policy may break projects that specify the `<release>` option together with `<source>` and `<target>`. The previous version of the plugin applied some heuristic rules for deciding which options to pass to the Java compiler. The new version passes all options verbatim, which may result in the following message from the `javac` compiler: > option --source cannot be used together with --release ### Correction if the build fails Removes the `<source>` and `<target>` configuration parameters. They may be specified indirectly through the `maven.compiler.source` and `maven.compiler.target` properties. If those configuration parameters or properties are inherited from a super-POM that cannot be modified, clear the `<source>` and `<target>` parameters as below: ```xml <project> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source/> <target/> <release>17</release> </configuration> </plugin> </plugins> </build> </project> ``` ## Use of dependencies with automatic names If a dependency does not declare explicitly whether the JAR file should be placed on the class-path or on the module-path, the Maven plugin uses heuristic rules for making its own decision. Those rules are not obvious, and the selected option is not always appropriate for the project. The results may sometime be different between this version and the previous version of `maven-compiler-plugin`. Some JAR files may placed on the class-path when they were previously on the module-path, or conversely. By default, a dependency is placed on `--module-path` if the project is itself modularized (i.e., contains a `module-info.java`) and one of the following conditions is true: * The JAR contains a `module-info.class` entry. * The JAR contains a `META-INF/MANIFEST.MF` entry with an `Automatic-Module-Name` attribute. In all other cases, the dependency is placed on `--class-path`. This default behavior may be adjusted in future Maven plugin versions depending on experience. ### Correction if the build fails Developers are strongly encouraged to specify explicitly where they want their non-modular JAR files to appear in a modular project. It can be done by setting the type of a dependency to `modular-jar` or `classpath-jar`. Example: ```xml <project> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-utils</artifactId> <version>3.0.24</version> <type>modular-jar</type> </dependency> </dependencies> </project> ``` Note that specifying the dependency type is usually not needed in other cases (e.g., non-modular project, or dependencies that are already modular). It should not be needed neither for dependencies with the runtime scope. However, it may be a good practice to be explicit anyway for more guarantees. ### Guideline for choosing the dependency type If a non-modular dependency is forced to the `modular-jar` type, Java will automatically infer a module name from the file name. That name can be shown by the `jar --describe-module` command. For example, the following command shows the name of above `plexus-utils` dependency on a Unix system: ```bash jar --describe-module --file ~/.m2/repository/org/codehaus/plexus/plexus-utils/3.0.24/plexus-utils-3.0.24.jar ``` The command output contains `plexus.utils@3.0.24 automatic`. That module name must be declared in the `module-info.java` file. ```java module my.module { requires plexus.utils; } ``` Conversely, if a dependency is forced to the `classpath-jar` type, then the `module-info.java` is not modified. Instead, the `pom.xml` file should have the following configuration (replace `my.module` by the actual project module name): ```xml <project> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> <arg>--add-reads</arg> <arg>my.module=ALL-UNNAMED</arg> </compilerArgs> </configuration> </plugin> </plugins> </build> </project> ``` ## `jpms.args` generation The `META-INF/jpms.args` file is no longer generated. # Changes in compiler parameters In its current version, the new plugin does not remove any parameters that exist in version 3 of `maven-compiler-plugin`. However, the behavior of some parameters has been modified and some parameters are deprecated. This section lists the changes. ## Deprecated but still working parameters The following parameters are deprecated but are still working: * `<compilerArgument>`: already replaced by `<compilerArgs>` since Maven 3.1. The replacement uses a list of strings instead of a single string. * `<testCompilerArgument>`: replaced by `<testCompilerArgs>` for the same reason as above. Note that the latter is a new parameter introduced in Maven 4, see next point. * `<testCompilerArguments>`: replaced by `<testCompilerArgs>` for consistency with the main plugin. The former parameter was inconsistent in the name ("Arguments" suffix instead of "Args"), but also in the value type (`Map<String,String>` instead of `List<String>`). * `<annotationProcessorPaths>`: replaced by ordinary dependencies with `<type>proc</type>`. The latter is a new artifact type introduced in Maven 4-alpha13. * `<useModulePath>`: replaced by `<type>classpath-jar</type>` declarations in dependencies. ## Deprecated parameters that are no-op The following parameters are marked as deprecated for removal. They all became no-op in the new plugin: * `<forceJavacCompilerUse>`: the documentation is not really explicit, but this parameters seems to be about forcing the use of `java.lang.Compiler` API instead of `javax.tools.JavaCompiler`. The former class was deprecated since Java 9 and no longer exists in Java 21. * `<compilerVersion>`: this parameter was passed to `org.codehaus.plexus.compiler.CompilerConfiguration`, but it is unclear how the Plexus compiler used it. We see no obvious mapping in `javax.tools` or `java.lang.Process` API. * `<compilerReuseStrategy>`: does not apply well to the `javax.tools.JavaCompiler` API that the new plugin is using. * `<skipMultiThreadWarning>`: deprecated as a consequence of `<compilerReuseStrategy>` deprecation. * `<optimize>`: was already deprecated in Maven 3, reported here for completness. * `<outputFileName>`: producing the final JAR file can be considered as the task of separated plugins, for example the JAR plugin. Furthermore, this parameter does not work well in the context of Module Source Hierarchy, because the output is not a single JAR file. * `<outputTimestamp>`: not really applicable to the Maven compiler plugin. It was used only as an heuristic rules for guessing if the developer intended to have a reproducible build. ## Changes in default values The Maven compiler plugin has numerous default values, which sometime differ from the standard (JDK) default values. For example, the default value of `-source` and `-target` options was hard-coded to "1.8" in Maven 3, while the standard (JDK) default is the version of the JDK used for compiling the code. The following parameters had their default value modified in the new plugin: * `<source>` and `<target>`: removed the default value and added a Javadoc saying "As of Java 9, the --release option is preferred." * `<release>`: no default value. Therefor, the standard (JDK) default applies. * If both `<release>` and `<target>` are unspecified, a warning is logged. * `<debugLevel>`: omitting this parameter now means "JDK default debug info" instead of "all debug info". * A new `all` level (Maven-specific) has been added for meaning "all debug info". * `<createMissingPackageInfoClass>`: default changed from `true` to `false`, because this workaround is no longer necessary for the new incremental build system. * `<showWarnings>`: default to `true` for consistency with the JDK default value. ### Default values not yet changed The following changes are in consideration, but not yet applied. The intend for those changes would be to have the same default values as the `javac` tool: * `<meminitial>` and `<maxmem>`: if no unit is specified, change the default from 'M' to bytes as interpreted by the `javac` tool. ## Changes in parameters validation * Include/exclude filters accept also glob and regex syntax (see [implementation changes](Implementation-changes)). * All parameters for memory settings accept also 'K' and 'G' suffix in addition of 'M'. * `<debugLevel>` values unknown to the plugin but known by the compiler are now accepted. # Changes in implementation This section lists some noticeable implementation changes. ## Plexus dependencies removed 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. ## ASM no longer used for generating package-info 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. ## Heuristic removed 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. ## Include/exclude filters 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. ## Incremental compilation 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](https://issues.apache.org/jira/browse/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. ### Detection of changes in dependencies 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_. ## Overwriting `module-info` in tests 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: 1. Compile the test `module-info.java` file alone. 2. Temporarily rename the main `module-info.class` file as `module-info.class.bak`. 3. Temporarily move the test `module-info.class` file (compiled in step 1) in place of the main one (saved in step 2). 4. Temporarily rename the test `module-info.java` file as `module-info.java.bak` (otherwise the compiler seems to get confused). 5. Compile all test classes except the test `module-info.java` file which has been compiled at step 1. 6. 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. ## Compiler options validation 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. ## Miscellaneous 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`. # Changes in tests This section lists some noticeable changes in the tests. ## Changes in POM parameters Moved or added the following configuration parameters in the `pom.xml` files of some tests: * The `<outputDirectory>` and `<testOutputDirectory>` parameters declared under `<configuration>` moved to `<build>`, because those properties are read-only in the configuration. * Many `<source>` and `<target>` parameters have been either removed or replaced by `<release>`. * For some tests using a non-modular JAR is a modular project, `<type>modular-jar</type>` has been added in the dependency declaration. ## Changes to met new assumptions The plugin incremental compilation algorithm depends on the convention that Java source files are located in directories of the same name as their package names, with the `.` separator replaced by path separator (`/` or `\`). This is a very common convention, but not strictly required by the Java compiler. For example, if the `src/main/java/MyCode.java` file contains the `package foo` statement, the compiled class will be located in `target/classes/foo/MyCode.class` — note the `foo` additional directory. In such case, the incremental build algorithm will not track correctly the changes. The following tests have been made compliant with the convention for allowing the algorithm to work: * `mcompiler-182` in integration tests. Note that due to [MCOMPILER-209](https://jira.codehaus.org/browse/MCOMPILER-209), the old algorithm was compiling everything without really detecting change. So this issue is maybe not really a regression. To reproduce the old behavior, users can just disable the incremental compilation. ## Removed tests ### JUnit tests Removed the following directories and associated test methods: * `compiler-one-output-file-test2` because it was redundant with `compiler-one-output-file-test`. The only difference was the addition of include/exclude filters, but that difference had no effect because the compiler mock used in this test was ignoring all sources anyway. This test has been replaced by `compiler-modular-project`. ### Integration tests The tests in the following directories were already disabled and have been removed: * `MCOMPILER-197` because it ran only on Java 8 while the build now requires Java 17. * `groovy-project-with-new-plexus-compiler` because it ran only on Java 8 and the plexus compiler has been removed. The tests in the following directores are not supported anymore and have been removed: * `release-without-profile` because the plugin no longer try to chose automatically which parameters to use between `--source` and `--release`. This is justified by the fact that the plugin cannot run on Java 8. * `release-without-profile-fork` for the same reason as above. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@maven.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org