Description
When a forking JavacCompiler
receives an argument it translates all canonical platform path separators to /
. This behaviour is invisible on most contemporary JVM capable platforms but it trivially manifests on Windows where the canonical path separator is \
.
The practical effect of this behaviour is that Maven cannot pass multiline arguments to a forked javac
on Windows. Multiline arguments rely on \
to escape line breaks, and javac
does accept these on Windows, but JavacCompiler
corrupts such arguments by erroneously POSIX-path'ifying them.
Besides the unfortunate translation just described, JavacCompiler
additionally fails to replicate that same translation when merely reporting the command line. The practical effect of that behaviour is that mvn -X
will report multiline compiler arguments verbatim, then actually pass another set of arguments to javac
.
This translation behaviour was introduced in commit d3fee7f ([PLX-314] When forking javac, receive "The input line is too long" error from Windows, 2007-01-11), first released in plexus-compiler-1.5.3
, and has existed since. There is no apparent motivation for the translation part of that change.
It is evident that JavacCompiler
's path separator handling is defective but it is not obvious what the defect is.
Arguably the path separator translation may now be considered canonical and therefore unchangeable. Hypothetically, a configuration may have passed path arguments on some non-/
path separator platform using native path separators and since come to rely on the hidden translation for compilation on /
path separator platforms. Such a configuration would break on /
path separator platforms were the translation to be removed. I would personally argue that a translation such as this should never be automatically performed and that the functionality is in every sense an undesirable bug, however, I can understand erring on the side of caution here. If simply removing the translation is unacceptable, as a compromise, JavacCompiler
might learn to preserve any \
immediately followed by /[\r\n]+/
, i.e. a line break escape.
The relevant implementations are
JavacCompiler::createCommandLine
JavacCompiler::createFileWithArguments
The behaviour can be trivially reproduced with any forking maven-compiler-plugin
on Windows. For example, the POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example.multi-line-fork-crash</groupId>
<artifactId>minimally-reproducible</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<fork>true</fork>
<compilerArgs>
<arg>
-Xplugin:ErrorProne \
-Xep:DeadException:WARN</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
generates a target/javac.bat
that contains
"-Xplugin:ErrorProne /
-Xep:DeadException:WARN"
whose execution fails with
error: invalid flag: -Xep:DeadException
where it should fail with
plug-in not found: ErrorProne
For an authentic example see google/error-prone#4256.
For comparison, InProcessCompiler
supports multiline arguments in Linux and Windows whether or not the line break is escaped, i.e. the following is accepted:
<arg>
-Xplugin:ErrorProne \
-Xep:DeadException:WARN
-Xep:DeadException:WARN
</arg>
javac
does not support the unescaped form; arguably, JavacCompiler
might reasonably learn to perform a translation from unescaped to escaped.