Skip to content

Commit

Permalink
Fix filling Main-Class attribute in manifest file of output jar
Browse files Browse the repository at this point in the history
Before 5b7cee6 JVM CLI compiler
was calling `KotlinToJVMBytecodeCompiler.compileBunchOfSources`.
`compileBunchOfSources` detected possible main classes,
and filled the Main-Class attribute in output jar
if if there was only one candidate.

After the change JVM CLI began calling
`KotlinToJVMBytecodeCompiler.compileModules`, which was not searching for a main class.

This change adds searching for main classes to `compileModules`.
We search for a main class only when one module is compiled,
and an output is written a jar file (so the change only affects JVM CLI compilation).

    #KT-32272 Fixed
  • Loading branch information
AlexeyTsvetkov committed Jul 1, 2019
1 parent ffc4096 commit 270219d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ object KotlinToJVMBytecodeCompiler {
private fun writeOutput(
configuration: CompilerConfiguration,
outputFiles: OutputFileCollection,
mainClass: FqName?
mainClassProvider: MainClassProvider?
) {
val reportOutputFiles = configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES)
val jarPath = configuration.get(JVMConfigurationKeys.OUTPUT_JAR)
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
if (jarPath != null) {
val includeRuntime = configuration.get(JVMConfigurationKeys.INCLUDE_RUNTIME, false)
CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, mainClass, outputFiles)
CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, mainClassProvider?.mainClassFqName, outputFiles)
if (reportOutputFiles) {
val message = OutputMessageUtil.formatOutputMessage(outputFiles.asList().flatMap { it.sourceFiles }.distinct(), jarPath)
messageCollector.report(OUTPUT, message)
Expand All @@ -91,7 +91,7 @@ object KotlinToJVMBytecodeCompiler {
}
return GenerationStateEventCallback { state ->
val currentOutput = SimpleOutputFileCollection(state.factory.currentOutput)
writeOutput(configuration, currentOutput, mainClass = null)
writeOutput(configuration, currentOutput, null)
if (!configuration.get(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, false)) {
state.factory.releaseGeneratedOutput()
}
Expand Down Expand Up @@ -167,7 +167,8 @@ object KotlinToJVMBytecodeCompiler {
try {
for ((_, state) in outputs) {
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
writeOutput(state.configuration, state.factory, null)
val mainClassProvider = if (outputs.size == 1) MainClassProvider(state, environment) else null
writeOutput(state.configuration, state.factory, mainClassProvider)
}

if (projectConfiguration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)) {
Expand Down Expand Up @@ -244,16 +245,20 @@ object KotlinToJVMBytecodeCompiler {
(File(path).takeIf(File::isAbsolute) ?: buildFile.resolveSibling(path)).absolutePath
}

private fun findMainClass(generationState: GenerationState, files: List<KtFile>): FqName? {
val mainFunctionDetector = MainFunctionDetector(generationState.bindingContext, generationState.languageVersionSettings)
return files.asSequence()
.map { file ->
if (mainFunctionDetector.hasMain(file.declarations))
JvmFileClassUtil.getFileClassInfoNoResolve(file).facadeClassFqName
else
null
}
.singleOrNull { it != null }
private class MainClassProvider(generationState: GenerationState, environment: KotlinCoreEnvironment) {
val mainClassFqName: FqName? by lazy { findMainClass(generationState, environment.getSourceFiles()) }

private fun findMainClass(generationState: GenerationState, files: List<KtFile>): FqName? {
val mainFunctionDetector = MainFunctionDetector(generationState.bindingContext, generationState.languageVersionSettings)
return files.asSequence()
.map { file ->
if (mainFunctionDetector.hasMain(file.declarations))
JvmFileClassUtil.getFileClassInfoNoResolve(file).facadeClassFqName
else
null
}
.singleOrNull { it != null }
}
}

fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
Expand All @@ -268,10 +273,8 @@ object KotlinToJVMBytecodeCompiler {

val generationState = analyzeAndGenerate(environment) ?: return false

val mainClass = findMainClass(generationState, environment.getSourceFiles())

try {
writeOutput(environment.configuration, generationState.factory, mainClass)
writeOutput(environment.configuration, generationState.factory, MainClassProvider(generationState, environment))
return true
} finally {
generationState.destroy()
Expand Down
33 changes: 33 additions & 0 deletions compiler/tests/org/jetbrains/kotlin/cli/CustomCliTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,48 @@

package org.jetbrains.kotlin.cli

import junit.framework.Assert
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.test.CompilerTestUtil
import org.jetbrains.kotlin.test.TestCaseWithTmpdir
import java.io.File
import java.util.jar.JarFile

private const val EMPTY_MAIN_FUN = "fun main() {}"

class CustomCliTest : TestCaseWithTmpdir() {
fun testArgfileWithNonTrivialWhitespaces() {
val text = "-include-runtime\r\n\t\t-language-version\n\t1.2\r\n-version"
val argfile = File(tmpdir, "argfile").apply { writeText(text, Charsets.UTF_8) }
CompilerTestUtil.executeCompilerAssertSuccessful(K2JVMCompiler(), listOf("@" + argfile.absolutePath))
}

fun testMainClass() {
val mainKt = tmpdir.resolve("main.kt").apply {
writeText(EMPTY_MAIN_FUN)
}
compileAndCheckMainClass(listOf(mainKt), expectedMainClass = "MainKt")
}

fun testMultipleMainClasses() {
val main1Kt = tmpdir.resolve("main1.kt").apply {
writeText(EMPTY_MAIN_FUN)
}
val main2Kt = tmpdir.resolve("main2.kt").apply {
writeText(EMPTY_MAIN_FUN)
}

compileAndCheckMainClass(listOf(main1Kt, main2Kt), expectedMainClass = null)
}

private fun compileAndCheckMainClass(sourceFiles: List<File>, expectedMainClass: String?) {
val jarFile = tmpdir.resolve("output.jar")
val args = listOf("-include-runtime", "-d", jarFile.absolutePath) + sourceFiles.map { it.absolutePath }
CompilerTestUtil.executeCompilerAssertSuccessful(K2JVMCompiler(), args)

JarFile(jarFile).use {
val mainClassAttr = it.manifest.mainAttributes.getValue("Main-Class")
Assert.assertEquals(expectedMainClass, mainClassAttr)
}
}
}

0 comments on commit 270219d

Please sign in to comment.