Skip to content

Commit 04b9a40

Browse files
committed
Write services.jar to find services automatically, make lots of stuff configurable
1 parent 1340e49 commit 04b9a40

File tree

2 files changed

+113
-34
lines changed

2 files changed

+113
-34
lines changed

src/test/kotlin/KotlinCompilation.kt

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,48 +26,102 @@ import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
2626
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
2727
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
2828
import org.jetbrains.kotlin.config.Services
29+
import java.io.FileOutputStream
2930
import java.io.ObjectOutputStream
3031
import java.io.PrintStream
3132
import java.net.URLClassLoader
3233
import java.net.URLDecoder
34+
import java.util.zip.ZipEntry
35+
import java.util.zip.ZipOutputStream
3336

34-
private fun findJavaHome(): File {
37+
internal fun findJavaHome(): File {
3538
val path = System.getProperty("java.home")
3639
?: throw IllegalStateException("no java home found")
3740

3841
return File(path).also { check(it.isDirectory) }
3942
}
4043

41-
private fun findToolsJar(javaHome: File): File
42-
= File(javaHome.absolutePath + "/../lib/tools.jar").also { check(it.isFile) }
43-
44+
internal fun findToolsJar(jdkHome: File): File
45+
= File(jdkHome.absolutePath + "/../lib/tools.jar").also { check(it.isFile) }
4446

47+
@Suppress("unused", "MemberVisibilityCanBePrivate")
4548
class KotlinCompilation(
46-
val dir: File,
49+
/** Working directory for the compilation */
50+
val workingDir: File,
51+
/** Arbitrary arguments to be passed to kotlinc */
4752
val args: List<String> = emptyList(),
53+
/** Arbitrary arguments to be passed to kapt */
4854
val kaptArgs: Map<String, String> = emptyMap(),
49-
classpaths: List<String> = emptyList(),
55+
/**
56+
* Paths to directories or .jar files that contain classes
57+
* to be made available in the compilation (i.e. added to
58+
* the classpath)
59+
*/
60+
classpaths: List<File> = emptyList(),
61+
/** Source files to be compiled */
5062
val sources: List<SourceFile> = emptyList(),
51-
private val jdkHome: File = findJavaHome(),
52-
toolsJar: File = findToolsJar(jdkHome),
63+
/** Services to be passed to kapt */
64+
val services: List<Service<*,*>> = emptyList(),
65+
/**
66+
* Path to the JDK to be used
67+
*
68+
* null if no JDK is to be used (option -no-jdk)
69+
* */
70+
val jdkHome: File? = findJavaHome(),
71+
/** Search path for Kotlin runtime libraries */
72+
val kotlinHome: File? = null,
73+
/**
74+
* Don't look for kotlin-stdlib.jar, kotlin-script-runtime.jar
75+
* and kotlin-reflect.jar in the [kotlinHome] directory. They
76+
* need to be provided by [classpaths] or will be unavailable.
77+
* */
78+
val noStdLib: Boolean = kotlinHome == null,
79+
/**
80+
* Don't look for kotlin-reflect.jar in the [kotlinHome] directory.
81+
* It has to be provided by [classpaths] or will be unavailable.
82+
*
83+
* Setting it to false has no effect when [noStdLib] is true.
84+
*/
85+
val noReflect: Boolean = noStdLib,
86+
/** Path to the tools.jar file needed for kapt */
87+
toolsJar: File = findToolsJar(findJavaHome()),
88+
/** Inherit classpath from calling process */
5389
inheritClassPath: Boolean = false,
5490
correctErrorTypes: Boolean = false,
55-
private val skipRuntimeVersionCheck: Boolean = false
91+
val skipRuntimeVersionCheck: Boolean = false,
92+
val verbose: Boolean = false,
93+
val suppressWarnings: Boolean = false,
94+
val allWarningsAsErrors: Boolean = false,
95+
val reportOutputFiles: Boolean = false
5696
) {
57-
val sourcesDir = File(dir, "sources")
58-
val classesDir = File(dir, "classes")
97+
val sourcesDir = File(workingDir, "sources")
98+
val classesDir = File(workingDir, "classes")
5999

60-
private val services = Services.Builder()
100+
/**
101+
* Generate a .jar file that holds ServiceManager registrations. Necessary because AutoService's
102+
* results might not be visible to this test.
103+
*/
104+
val servicesJar = File(workingDir, "services.jar").apply {
105+
val servicesGroupedByClass = services.groupBy({ it.serviceClass }, { it.implementationClass })
61106

62-
fun <T : Any> addService(serviceClass: KClass<T>, implementation: T) {
63-
services.register(serviceClass.java, implementation)
64-
}
107+
ZipOutputStream(FileOutputStream(this)).use { zipOutputStream ->
108+
for (serviceEntry in servicesGroupedByClass) {
109+
zipOutputStream.putNextEntry(
110+
ZipEntry("META-INF/services/${serviceEntry.key.qualifiedName}")
111+
)
112+
val serviceFile = zipOutputStream.sink().buffer()
113+
for (implementation in serviceEntry.value) {
114+
serviceFile.writeUtf8(implementation.qualifiedName!!)
115+
serviceFile.writeUtf8("\n")
116+
}
117+
serviceFile.emit() // Don't close the entry; that closes the file.
118+
zipOutputStream.closeEntry()
119+
}
120+
}
121+
}
65122

66-
//private val servicesGroupedByClass = services.groupBy({ it.serviceClass }, { it.implementation })
67123

68-
/**
69-
* A Kotlin source file to be compiled
70-
*/
124+
/** A Kotlin source file to be compiled */
71125
data class SourceFile(val path: String, val contents: String) {
72126
/**
73127
* Writes the source file to the location and returns the
@@ -82,31 +136,36 @@ class KotlinCompilation(
82136
}
83137
}
84138

139+
/** Result of the compilation */
85140
data class Result(val messages: String, val exitCode: ExitCode)
86141

142+
/** A service that will be passed to kapt */
143+
data class Service<S : Any, T : S>(val serviceClass: KClass<S>, val implementationClass: KClass<T>)
144+
87145
val allClasspaths: List<String> = mutableListOf<String>().apply {
88-
addAll(classpaths)
146+
addAll(classpaths.map(File::getAbsolutePath))
147+
148+
add(servicesJar.absolutePath)
89149

90150
if(inheritClassPath)
91151
addAll(getHostProcessClasspathFiles().map(File::getAbsolutePath))
92152
}
93153

94154
/** Returns arguments necessary to enable and configure kapt3. */
95155
private val annotationProcessorArgs = object {
96-
private val kaptSourceDir = File(dir, "kapt/sources")
97-
private val kaptStubsDir = File(dir, "kapt/stubs")
156+
private val kaptSourceDir = File(workingDir, "kapt/sources")
157+
private val kaptStubsDir = File(workingDir, "kapt/stubs")
98158

99159
val pluginClassPaths = arrayOf(getKapt3Jar().absolutePath, toolsJar.absolutePath)
100160
val pluginOptions = arrayOf(
101161
"plugin:org.jetbrains.kotlin.kapt3:sources=$kaptSourceDir",
102162
"plugin:org.jetbrains.kotlin.kapt3:classes=$classesDir",
103163
"plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptStubsDir",
104-
"plugin:org.jetbrains.kotlin.kapt3:apclasspath=$sourcesDir",
164+
"plugin:org.jetbrains.kotlin.kapt3:apclasspath=$servicesJar",
105165
"plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=$correctErrorTypes",
106166
// Don't forget aptMode! Without it, the compiler will crash with an obscure error about
107167
// write unsafe context
108168
"plugin:org.jetbrains.kotlin.kapt3:aptMode=stubsAndApt",
109-
"plugin:org.jetbrains.kotlin.kapt3:processors=com.tschuchort.kotlinelements.TestProcessor",
110169
*if (kaptArgs.isNotEmpty())
111170
arrayOf("plugin:org.jetbrains.kotlin.kapt3:apoptions=${encodeOptions(kaptArgs)}")
112171
else
@@ -120,26 +179,44 @@ class KotlinCompilation(
120179
}
121180

122181
val k2jvmArgs = K2JVMCompilerArguments().apply {
123-
freeArgs = sourcesDir.listFiles().map { it.absolutePath }
182+
freeArgs = sourcesDir.listFiles().map { it.absolutePath } + args
124183
pluginOptions = annotationProcessorArgs.pluginOptions
125184
pluginClasspaths = annotationProcessorArgs.pluginClassPaths
126185
loadBuiltInsFromDependencies = true
127186
destination = classesDir.absolutePath
128187
classpath = allClasspaths.joinToString(separator = File.pathSeparator)
129-
noStdlib = true
130-
noReflect = true
188+
//noStdlib = true
189+
//noReflect = true
131190
skipRuntimeVersionCheck = this@KotlinCompilation.skipRuntimeVersionCheck
132191
reportPerf = false
133192
reportOutputFiles = true
134-
jdkHome = this@KotlinCompilation.jdkHome.absolutePath
193+
194+
if(this@KotlinCompilation.jdkHome != null) {
195+
jdkHome = this@KotlinCompilation.jdkHome.absolutePath
196+
}
197+
else {
198+
noJdk = true
199+
}
200+
201+
noStdlib = this@KotlinCompilation.noStdLib
202+
noReflect = this@KotlinCompilation.noReflect
203+
204+
this@KotlinCompilation.kotlinHome?.let {
205+
kotlinHome = it.absolutePath
206+
}
207+
208+
verbose = this@KotlinCompilation.verbose
209+
suppressWarnings = this@KotlinCompilation.suppressWarnings
210+
allWarningsAsErrors = this@KotlinCompilation.allWarningsAsErrors
211+
reportOutputFiles = this@KotlinCompilation.reportOutputFiles
135212
}
136213

137214
val compilerOutputbuffer = Buffer()
138215

139216
val exitCode = K2JVMCompiler().execImpl(
140217
PrintingMessageCollector(PrintStream(compilerOutputbuffer.outputStream()),
141218
MessageRenderer.WITHOUT_PATHS, true),
142-
Services.EMPTY,//services.build(),
219+
Services.EMPTY,
143220
k2jvmArgs
144221
)
145222

src/test/kotlin/SmokeTests.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.cli.common.ExitCode
55
import org.junit.Rule
66
import org.junit.Test
77
import org.junit.rules.TemporaryFolder
8+
import java.io.File
89
import javax.annotation.processing.Processor
910

1011
@InspectionRoot
@@ -24,13 +25,14 @@ class SmokeTests {
2425
)
2526

2627
val result = KotlinCompilation(
27-
dir = temporaryFolder.root,
28+
workingDir = temporaryFolder.root,
2829
sources = listOf(source),
30+
services = listOf(KotlinCompilation.Service(Processor::class, TestProcessor::class)),
31+
jdkHome = findJavaHome().parentFile,
2932
inheritClassPath = true,
30-
skipRuntimeVersionCheck = true
31-
).apply {
32-
addService(Processor::class, TestProcessor())
33-
}.run()
33+
skipRuntimeVersionCheck = true,
34+
verbose = true
35+
).run()
3436

3537
print(result.messages)
3638
assertThat(result.exitCode).isEqualTo(ExitCode.OK)

0 commit comments

Comments
 (0)