1212 */
1313package 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
1621import org.codehaus.plexus.util.cli.CommandLineException
1722import org.codehaus.plexus.util.cli.CommandLineUtils
1823import org.codehaus.plexus.util.cli.Commandline
24+ import java.io.BufferedReader
1925import java.io.File
2026import java.io.IOException
21- import java.util.*
27+ import java.io.StringReader
2228import javax.inject.Named
2329import javax.inject.Singleton
2430import 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}
0 commit comments