Skip to content

Commit 99a5c2f

Browse files
committed
feat: piggyback off of JavacCompiler for better output handling
Signed-off-by: melodicore <datafox@datafox.me>
1 parent 8a59f50 commit 99a5c2f

3 files changed

Lines changed: 129 additions & 155 deletions

File tree

java-compiler/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,10 @@
121121
<artifactId>plexus-compiler-api</artifactId>
122122
<version>${plexusVersion}</version>
123123
</dependency>
124+
<dependency>
125+
<groupId>org.codehaus.plexus</groupId>
126+
<artifactId>plexus-compiler-javac</artifactId>
127+
<version>${plexusVersion}</version>
128+
</dependency>
124129
</dependencies>
125130
</project>

java-compiler/src/main/kotlin/dev/elide/maven/compiler/ElideJavacCompiler.kt

Lines changed: 116 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@
1212
*/
1313
package dev.elide.maven.compiler
1414

15-
import org.codehaus.plexus.compiler.*
15+
import org.codehaus.plexus.compiler.CompilerConfiguration
16+
import org.codehaus.plexus.compiler.CompilerException
17+
import org.codehaus.plexus.compiler.CompilerMessage
18+
import org.codehaus.plexus.compiler.CompilerResult
19+
import org.codehaus.plexus.compiler.javac.JavacCompiler
20+
import org.codehaus.plexus.util.StringUtils
1621
import org.codehaus.plexus.util.cli.CommandLineException
1722
import org.codehaus.plexus.util.cli.CommandLineUtils
1823
import org.codehaus.plexus.util.cli.Commandline
24+
import java.io.BufferedReader
1925
import java.io.File
2026
import java.io.IOException
21-
import java.util.*
27+
import java.io.StringReader
2228
import javax.inject.Named
2329
import javax.inject.Singleton
2430
import kotlin.io.path.absolutePathString
@@ -32,179 +38,139 @@ import kotlin.io.path.absolutePathString
3238
*/
3339
@Named(ELIDE_COMPILER)
3440
@Singleton
35-
open class ElideJavacCompiler :
36-
AbstractCompiler(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null) {
41+
open class ElideJavacCompiler : JavacCompiler() {
3742
override fun getCompilerId(): String = "elide"
3843

3944
override fun performCompile(config: CompilerConfiguration): CompilerResult {
40-
val dest = File(config.outputLocation)
41-
if (!dest.exists()) dest.mkdirs()
42-
val sources = getSourceFiles(config) ?: return CompilerResult()
43-
if (sources.isEmpty()) return CompilerResult()
44-
logCompiling(sources, config)
45+
config.isFork = true
46+
47+
val destinationDir = File(config.outputLocation)
48+
if (!destinationDir.exists()) destinationDir.mkdirs()
49+
50+
val sourceFiles = getSourceFiles(config)
51+
if (sourceFiles == null || sourceFiles.size == 0) return CompilerResult()
52+
53+
logCompiling(sourceFiles, config)
54+
4555
val executable = getElideExecutable(config)
46-
val args =
47-
buildList {
48-
addAll(buildElideArgs(config, sources))
49-
config.maxmem?.ifEmpty { null }?.let { add("-J-Xmx$this") }
50-
config.meminitial?.ifEmpty { null }?.let { add("-J-Xms$this") }
51-
52-
config.customCompilerArgumentsAsMap.entries
53-
.filter { it.value != null && it.key.startsWith("-J") }
54-
.ifEmpty { null }
55-
?.forEach { add("${it.key}=${it.value}") }
56-
}
56+
val javacVersion = getElideJavacVersion(executable)
57+
val args = buildCompilerArguments(config, sourceFiles, javacVersion)
5758

59+
return compileOutOfProcess(config, executable, args)
60+
}
61+
62+
override fun compileOutOfProcess(
63+
config: CompilerConfiguration,
64+
executable: String,
65+
args: Array<out String>,
66+
): CompilerResult {
5867
val cli = Commandline()
68+
5969
cli.setWorkingDirectory(config.workingDirectory.absolutePath)
60-
cli.executable = executable
61-
config.customCompilerArgumentsAsMap.keys
62-
.filter { it != null && it.startsWith("-J") }
63-
.apply { if (isNotEmpty()) cli.addArguments(toTypedArray()) }
70+
cli.setExecutable(executable)
71+
72+
try {
73+
val argumentsFile =
74+
JavacCompiler::class
75+
.java
76+
.getDeclaredMethod("createFileWithArguments", String::class.java.arrayType(), String::class.java)
77+
.run {
78+
isAccessible = true
79+
invoke(this@ElideJavacCompiler, args, config.buildDirectory.absolutePath) as File
80+
}
81+
cli.addArguments(
82+
arrayOf("javac", "--", "@" + argumentsFile.getCanonicalPath().replace(File.separatorChar, '/'))
83+
)
84+
85+
if (!StringUtils.isEmpty(config.maxmem)) cli.addArguments(arrayOf("-J-Xmx${config.maxmem}"))
86+
if (!StringUtils.isEmpty(config.meminitial)) cli.addArguments(arrayOf("-J-Xms${config.meminitial}"))
87+
88+
for (key in config.getCustomCompilerArgumentsAsMap().keys) {
89+
if (StringUtils.isNotEmpty(key) && key.startsWith("-J")) cli.addArguments(arrayOf<String>(key))
90+
}
91+
} catch (e: IOException) {
92+
throw CompilerException("Error creating file with javac arguments", e)
93+
}
6494

65-
cli.addArguments(args.toTypedArray())
6695
val out = CommandLineUtils.StringStreamConsumer()
67-
var returnCode: Int
96+
val returnCode: Int
6897
val messages: List<CompilerMessage>
98+
6999
try {
70100
returnCode = CommandLineUtils.executeCommandLine(cli, out, out)
71-
messages = parseOutput(returnCode, out.output.lines())
101+
102+
if (log.isDebugEnabled) log.debug("Compiler output:{}{}", EOL, out.output)
103+
104+
messages =
105+
JavacCompiler::class
106+
.java
107+
.getDeclaredMethod("parseModernStream", Int::class.java, BufferedReader::class.java)
108+
.run {
109+
isAccessible = true
110+
@Suppress("UNCHECKED_CAST")
111+
invoke(null, returnCode, BufferedReader(StringReader(out.output))) as MutableList<CompilerMessage>
112+
}
113+
val last = out.output.lines().reversed().let {
114+
for (line in it) {
115+
if (line.isNotBlank()) return@let line
116+
}
117+
""
118+
}
119+
if (last.isNotBlank()) {
120+
if (last.contains("")) messages.add(CompilerMessage(last, CompilerMessage.Kind.NOTE))
121+
else if (last.contains("")) messages.add(CompilerMessage(last, CompilerMessage.Kind.ERROR))
122+
}
72123
} catch (e: CommandLineException) {
73-
throw CompilerException("Error while executing Elide javac compiler.", e)
124+
throw CompilerException("Error while executing the Elide javac compiler.", e)
74125
} catch (e: IOException) {
75-
throw CompilerException("Error while executing Elide javac compiler.", e)
126+
throw CompilerException("Error while executing the Elide javac compiler.", e)
76127
}
77-
return CompilerResult(returnCode == 0, messages)
128+
129+
val success = returnCode == 0
130+
return CompilerResult(success, messages)
78131
}
79132

80133
private fun getElideExecutable(config: CompilerConfiguration): String =
81134
config.executable ?: ElideLocator.locate()?.absolutePathString() ?: ELIDE_EXECUTABLE
82135

83-
override fun createCommandLine(config: CompilerConfiguration): Array<String> {
84-
return buildElideArgs(config, getSourceFiles(config)).toTypedArray()
85-
}
86-
87-
private fun buildElideArgs(config: CompilerConfiguration, sources: Array<String>): MutableList<String> {
88-
val args: MutableList<String> = LinkedList<String>()
89-
args.add("javac")
90-
args.add("--")
91-
val destinationDir = File(config.outputLocation)
92-
args.add("-d")
93-
args.add(destinationDir.absolutePath)
94-
config.classpathEntries
95-
?.ifEmpty { null }
96-
?.let {
97-
args.add("-classpath")
98-
args.add(getPathString(it))
99-
}
100-
config.modulepathEntries
101-
?.ifEmpty { null }
102-
?.let {
103-
args.add("--module-path")
104-
args.add(getPathString(it))
105-
}
106-
config.sourceLocations
107-
?.ifEmpty { null }
108-
?.let {
109-
args.add("-sourcepath")
110-
args.add(getPathString(it))
111-
}
112-
args.addAll(listOf(*sources))
113-
114-
config.generatedSourcesDirectory.apply {
115-
mkdirs()
116-
args.add("-s")
117-
args.add(absolutePath)
118-
}
119-
config.proc?.apply { args.add("-proc:$this") }
120-
config.annotationProcessors?.apply {
121-
args.add("-processor")
122-
args.add(indices.joinToString(",") { this[it] })
123-
}
124-
config.processorPathEntries
125-
?.ifEmpty { null }
126-
?.let {
127-
args.add("-processorpath")
128-
args.add(getPathString(it))
129-
}
130-
config.processorModulePathEntries
131-
?.ifEmpty { null }
132-
?.let {
133-
args.add("--processor-module-path")
134-
args.add(getPathString(it))
135-
}
136-
if (config.isOptimize) args.add("-O")
137-
if (config.isDebug) {
138-
if (config.debugLevel?.isNotEmpty() == true) {
139-
args.add("-g:" + config.debugLevel)
140-
} else {
141-
args.add("-g")
142-
}
143-
}
144-
if (config.isVerbose) args.add("-verbose")
145-
if (config.isParameters) args.add("-parameters")
146-
if (config.isEnablePreview) args.add("--enable-preview")
147-
config.implicitOption?.apply { args.add("-implicit:$this") }
148-
if (config.isShowDeprecation) {
149-
args.add("-deprecation")
150-
config.isShowWarnings = true
151-
}
152-
if (!config.isShowWarnings) {
153-
args.add("-nowarn")
154-
} else {
155-
val warnings = config.warnings
156-
if (config.isShowLint) {
157-
if (warnings.isNotEmpty()) {
158-
args.add("-Xlint:$warnings")
159-
} else {
160-
args.add("-Xlint")
161-
}
162-
}
163-
}
164-
if (config.isFailOnWarning) args.add("-Werror")
165-
if (config.releaseVersion?.isNotEmpty() == true) {
166-
args.add("--release")
167-
args.add(config.releaseVersion)
168-
} else {
169-
if (config.targetVersion?.isEmpty() == true) {
170-
args.add("-target")
171-
args.add("1.1")
172-
} else {
173-
args.add("-target")
174-
args.add(config.targetVersion)
175-
}
176-
if (config.sourceVersion?.isEmpty() == true) {
177-
args.add("-source")
178-
args.add("1.3")
179-
} else {
180-
args.add("-source")
181-
args.add(config.sourceVersion)
136+
private fun getElideJavacVersion(executable: String): String? {
137+
val cli = Commandline()
138+
cli.setExecutable(executable)
139+
cli.addArguments(arrayOf("javac", "--", "-version"))
140+
val out = mutableListOf<String>()
141+
val err = mutableListOf<String>()
142+
try {
143+
val exitCode = CommandLineUtils.executeCommandLine(cli, out::add, err::add)
144+
if (exitCode != 0) {
145+
throw CompilerException(
146+
buildString {
147+
append("Could not retrieve version from ")
148+
append(executable)
149+
append(". Exit code ")
150+
append(exitCode)
151+
append(", Output: ")
152+
append(out.joinToString(System.lineSeparator()))
153+
append(", Error: ")
154+
append(err.joinToString(System.lineSeparator()))
155+
}
156+
)
182157
}
158+
} catch (e: CommandLineException) {
159+
throw CompilerException("Error while executing the external compiler $executable", e)
183160
}
184-
config.sourceEncoding
185-
?.ifEmpty { null }
186-
?.let {
187-
args.add("-encoding")
188-
args.add(it)
189-
}
190-
config.moduleVersion
191-
?.ifEmpty { null }
192-
?.let {
193-
args.add("--module-version")
194-
args.add(it)
161+
return tryParseVersion(out) ?: tryParseVersion(err)
162+
}
163+
164+
private fun tryParseVersion(versions: List<String>): String? {
165+
for (version in versions) {
166+
if (version.matches(VERSION_RE)) {
167+
return version.substringBefore('.')
195168
}
196-
config.customCompilerArgumentsEntries?.forEach { (key, value) ->
197-
if (key.isEmpty() || key.startsWith("-J")) return@forEach
198-
args.add(key)
199-
if (value.isNotEmpty()) args.add(value)
200169
}
201-
return args
170+
return null
202171
}
203172

204-
@Throws(IOException::class)
205-
private fun parseOutput(exitCode: Int, input: List<String>): List<CompilerMessage> {
206-
// very lazy for now
207-
val kind = if (exitCode == 0) CompilerMessage.Kind.NOTE else CompilerMessage.Kind.ERROR
208-
return input.map { CompilerMessage(it, kind) }
173+
companion object {
174+
val VERSION_RE = "\\d+\\.\\d+\\.\\d+".toRegex()
209175
}
210176
}

kotlin-plugin/src/main/kotlin/dev/elide/maven/plugin/kotlin/ElideKotlinLifecycleParticipant.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,19 @@ open class ElideKotlinLifecycleParticipant : KotlinLifecycleParticipant() {
6060
T::class
6161
.java
6262
.getDeclaredMethod(name, *args.map { it.first }.toTypedArray())
63-
.apply { isAccessible = true }
64-
.invoke(this, *args.map { it.second }.toTypedArray())
65-
.let { it as Boolean }
63+
.run {
64+
isAccessible = true
65+
invoke(this@ElideKotlinLifecycleParticipant, *args.map { it.second }.toTypedArray()) as Boolean
66+
}
6667

6768
inline fun <reified T> T.callPrivateFunc(name: String, vararg args: Pair<Class<out Any>, out Any?>) {
6869
T::class
6970
.java
7071
.getDeclaredMethod(name, *args.map { it.first }.toTypedArray())
71-
.apply { isAccessible = true }
72-
.invoke(this, *args.map { it.second }.toTypedArray())
72+
.apply {
73+
isAccessible = true
74+
invoke(this@ElideKotlinLifecycleParticipant, *args.map { it.second }.toTypedArray())
75+
}
7376
}
7477

7578
inline fun <reified T> T.callArg() = Pair(T::class.java, this)

0 commit comments

Comments
 (0)