Skip to content

Commit 82ec542

Browse files
committed
Implement multiplatform common source flag #377
1 parent 32cfae2 commit 82ec542

File tree

6 files changed

+85
-34
lines changed

6 files changed

+85
-34
lines changed

core/src/main/kotlin/com/tschuchort/compiletesting/AbstractKotlinCompilation.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import java.io.OutputStream
2222
import java.io.PrintStream
2323
import java.net.URI
2424
import java.nio.file.Files
25+
import java.nio.file.Path
2526
import java.nio.file.Paths
27+
import kotlin.io.path.absolutePathString
2628

2729
/**
2830
* Base compilation class for sharing common compiler arguments and
@@ -141,6 +143,15 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
141143
// Directory for input source files
142144
protected val sourcesDir get() = workingDir.resolve("sources")
143145

146+
protected data class SourceWithPath(val path: Path, val isCommonSource: Boolean)
147+
148+
protected val sourcesWithPath: List<SourceWithPath> by lazy {
149+
sources.map {
150+
val path = Paths.get(it.writeIfNeeded(sourcesDir).absolutePath)
151+
SourceWithPath(path, it.isMultiplatformCommonSource)
152+
}
153+
}
154+
144155
protected inline fun <reified T> CommonCompilerArguments.trySetDeprecatedOption(optionSimpleName: String, value: T) {
145156
try {
146157
this.javaClass.getMethod(JvmAbi.setterName(optionSimpleName), T::class.java).invoke(this, value)
@@ -169,6 +180,8 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
169180
if (languageVersion != null)
170181
args.languageVersion = this.languageVersion
171182

183+
args.commonSources = sourcesWithPath.filter { it.isCommonSource }.map { it.path.toString() }.toTypedArray()
184+
172185
configuration(args)
173186

174187
/**
@@ -203,7 +216,7 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
203216
}
204217

205218
/** Performs the compilation step to compile Kotlin source files */
206-
protected fun compileKotlin(sources: List<File>, compiler: CLICompiler<A>, arguments: A): KotlinCompilation.ExitCode {
219+
protected fun compileKotlin(sources: List<Path>, compiler: CLICompiler<A>, arguments: A): KotlinCompilation.ExitCode {
207220

208221
/**
209222
* Here the list of compiler plugins is set
@@ -225,7 +238,7 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
225238
// in this step also include source files generated by kapt in the previous step
226239
val args = arguments.also { args ->
227240
args.freeArgs =
228-
sources.map(File::getAbsolutePath).distinct() + if (sources.none(File::hasKotlinFileExtension)) {
241+
sources.map(Path::absolutePathString).distinct() + if (sources.none(Path::hasKotlinFileExtension)) {
229242
/* __HACK__: The Kotlin compiler expects at least one Kotlin source file or it will crash,
230243
so we trick the compiler by just including an empty .kt-File. We need the compiler to run
231244
even if there are no Kotlin files because some compiler plugins may also process Java files. */

core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import java.net.URLClassLoader
3838
import java.nio.file.Path
3939
import javax.annotation.processing.Processor
4040
import javax.tools.*
41+
import kotlin.io.path.absolutePathString
4142

4243
data class PluginOption(val pluginId: PluginId, val optionName: OptionName, val optionValue: OptionValue)
4344

@@ -350,7 +351,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
350351
}
351352

352353
/** Performs the 1st and 2nd compilation step to generate stubs and run annotation processors */
353-
private fun stubsAndApt(sourceFiles: List<File>): ExitCode {
354+
private fun stubsAndApt(sourceFiles: List<Path>): ExitCode {
354355
if(annotationProcessors.isEmpty()) {
355356
log("No services were given. Not running kapt steps.")
356357
return ExitCode.OK
@@ -396,10 +397,10 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
396397
)
397398
)
398399

399-
val kotlinSources = sourceFiles.filter(File::hasKotlinFileExtension)
400-
val javaSources = sourceFiles.filter(File::hasJavaFileExtension)
400+
val kotlinSources = sourceFiles.filter(Path::hasKotlinFileExtension)
401+
val javaSources = sourceFiles.filter(Path::hasJavaFileExtension)
401402

402-
val sourcePaths = mutableListOf<File>().apply {
403+
val sourcePaths = mutableListOf<Path>().apply {
403404
addAll(javaSources)
404405

405406
if(kotlinSources.isNotEmpty()) {
@@ -414,9 +415,9 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
414415
Java files might generate Kotlin files which then need to be compiled in the
415416
compileKotlin step before the compileJava step). So instead we trick K2JVMCompiler
416417
by just including an empty .kt-File. */
417-
add(SourceFile.new("emptyKotlinFile.kt", "").writeIfNeeded(kaptBaseDir))
418+
add(SourceFile.new("emptyKotlinFile.kt", "").writeIfNeeded(kaptBaseDir).toPath())
418419
}
419-
}.map(File::getAbsolutePath).distinct()
420+
}.map(Path::absolutePathString).distinct()
420421

421422
if(!isJdk9OrLater()) {
422423
try {
@@ -446,10 +447,10 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
446447
}
447448

448449
/** Performs the 3rd compilation step to compile Kotlin source files */
449-
private fun compileJvmKotlin(sourceFiles: List<File>): ExitCode {
450-
val sources = sourceFiles +
451-
kaptKotlinGeneratedDir.listFilesRecursively() +
452-
kaptSourceDir.listFilesRecursively()
450+
private fun compileJvmKotlin(sourceFiles: List<Path>): ExitCode {
451+
val sources = sourcesWithPath.map { it.path } +
452+
kaptKotlinGeneratedDir.toPath().listFilesRecursively() +
453+
kaptSourceDir.toPath().listFilesRecursively()
453454

454455
return compileKotlin(sources, K2JVMCompiler(), commonK2JVMArgs())
455456
}
@@ -485,9 +486,9 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
485486
}
486487

487488
/** Performs the 4th compilation step to compile Java source files */
488-
private fun compileJava(sourceFiles: List<File>): ExitCode {
489-
val javaSources = (sourceFiles + kaptSourceDir.listFilesRecursively())
490-
.filterNot<File>(File::hasKotlinFileExtension)
489+
private fun compileJava(sourceFiles: List<Path>): ExitCode {
490+
val javaSources = (sourceFiles + kaptSourceDir.toPath().listFilesRecursively())
491+
.filterNot(Path::hasKotlinFileExtension)
491492

492493
if(javaSources.isEmpty())
493494
return ExitCode.OK
@@ -508,7 +509,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
508509
val isJavac9OrLater = isJavac9OrLater(getJavacVersionString(javacCommand))
509510
val javacArgs = baseJavacArgs(isJavac9OrLater)
510511

511-
val javacProc = ProcessBuilder(listOf(javacCommand) + javacArgs + javaSources.map(File::getAbsolutePath))
512+
val javacProc = ProcessBuilder(listOf(javacCommand) + javacArgs + javaSources.map(Path::absolutePathString))
512513
.directory(workingDir)
513514
.redirectErrorStream(true)
514515
.start()
@@ -558,7 +559,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
558559
OutputStreamWriter(internalMessageStream), javaFileManager,
559560
diagnosticCollector, javacArgs,
560561
/* classes to be annotation processed */ null,
561-
javaSources.map { FileJavaFileObject(it) }
562+
javaSources.map { FileJavaFileObject(it.toFile()) }
562563
.filter { it.kind == JavaFileObject.Kind.SOURCE }
563564
).call()
564565

@@ -591,9 +592,6 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
591592
kaptIncrementalDataDir.mkdirs()
592593
kaptKotlinGeneratedDir.mkdirs()
593594

594-
// write given sources to working directory
595-
val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }
596-
597595
pluginClasspaths.forEach { filepath ->
598596
if (!filepath.exists()) {
599597
error("Plugin $filepath not found")
@@ -618,7 +616,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
618616
withSystemProperty("idea.use.native.fs.for.win", "false") {
619617
// step 1 and 2: generate stubs and run annotation processors
620618
try {
621-
val exitCode = stubsAndApt(sourceFiles)
619+
val exitCode = stubsAndApt(sourcesWithPath.map { it.path })
622620
if (exitCode != ExitCode.OK) {
623621
return makeResult(exitCode)
624622
}
@@ -627,15 +625,15 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
627625
}
628626

629627
// step 3: compile Kotlin files
630-
compileJvmKotlin(sourceFiles).let { exitCode ->
628+
compileJvmKotlin(sourcesWithPath.map { it.path }).let { exitCode ->
631629
if(exitCode != ExitCode.OK) {
632630
return makeResult(exitCode)
633631
}
634632
}
635633
}
636634

637635
// step 4: compile Java files
638-
return makeResult(compileJava(sourceFiles))
636+
return makeResult(compileJava(sourcesWithPath.map { it.path }))
639637
}
640638

641639
private fun makeResult(exitCode: ExitCode): Result {

core/src/main/kotlin/com/tschuchort/compiletesting/KotlinJsCompilation.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,6 @@ class KotlinJsCompilation : AbstractKotlinCompilation<K2JSCompilerArguments>() {
100100
sourcesDir.mkdirs()
101101
outputDir.mkdirs()
102102

103-
// write given sources to working directory
104-
val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }
105-
106103
pluginClasspaths.forEach { filepath ->
107104
if (!filepath.exists()) {
108105
error("Plugin $filepath not found")
@@ -119,7 +116,7 @@ class KotlinJsCompilation : AbstractKotlinCompilation<K2JSCompilerArguments>() {
119116
*/
120117
withSystemProperty("idea.use.native.fs.for.win", "false") {
121118
// step 1: compile Kotlin files
122-
return makeResult(compileKotlin(sourceFiles, K2JSCompiler(), jsArgs()))
119+
return makeResult(compileKotlin(sourcesWithPath.map { it.path }, K2JSCompiler(), jsArgs()))
123120
}
124121
}
125122

core/src/main/kotlin/com/tschuchort/compiletesting/SourceFile.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,38 @@ import java.io.File
1111
abstract class SourceFile {
1212
internal abstract fun writeIfNeeded(dir: File): File
1313

14+
/** Marks this source file as a source of the common module in a multiplatform project */
15+
abstract val isMultiplatformCommonSource: Boolean
16+
1417
companion object {
1518
/**
1619
* Create a new Java source file for the compilation when the compilation is run
20+
*
21+
* @param isMultiplatformCommonSource marks this source file as a source of the common module in a multiplatform project
1722
*/
18-
fun java(name: String, @Language("java") contents: String, trimIndent: Boolean = true): SourceFile {
23+
fun java(name: String, @Language("java") contents: String, trimIndent: Boolean = true, isMultiplatformCommonSource: Boolean = false): SourceFile {
1924
require(File(name).hasJavaFileExtension())
2025
val finalContents = if (trimIndent) contents.trimIndent() else contents
21-
return new(name, finalContents)
26+
return new(name, finalContents, isMultiplatformCommonSource = isMultiplatformCommonSource)
2227
}
2328

2429
/**
2530
* Create a new Kotlin source file for the compilation when the compilation is run
31+
*
32+
* @param isMultiplatformCommonSource marks this source file as a source of the common module in a multiplatform project
2633
*/
27-
fun kotlin(name: String, @Language("kotlin") contents: String, trimIndent: Boolean = true): SourceFile {
34+
fun kotlin(name: String, @Language("kotlin") contents: String, trimIndent: Boolean = true, isMultiplatformCommonSource: Boolean = false): SourceFile {
2835
require(File(name).hasKotlinFileExtension())
2936
val finalContents = if (trimIndent) contents.trimIndent() else contents
30-
return new(name, finalContents)
37+
return new(name, finalContents, isMultiplatformCommonSource = isMultiplatformCommonSource)
3138
}
3239

3340
/**
3441
* Create a new source file for the compilation when the compilation is run
42+
*
43+
* @param isMultiplatformCommonSource marks this source file as a source of the common module in a multiplatform project
3544
*/
36-
fun new(name: String, contents: String) = object : SourceFile() {
45+
fun new(name: String, contents: String, isMultiplatformCommonSource: Boolean = false) = object : SourceFile() {
3746
override fun writeIfNeeded(dir: File): File {
3847
val file = dir.resolve(name)
3948
file.parentFile.mkdirs()
@@ -45,17 +54,23 @@ abstract class SourceFile {
4554

4655
return file
4756
}
57+
58+
override val isMultiplatformCommonSource: Boolean = isMultiplatformCommonSource
4859
}
4960

5061
/**
5162
* Compile an existing source file
63+
*
64+
* @param isMultiplatformCommonSource marks this source file as a source of the common module in a multiplatform project
5265
*/
53-
fun fromPath(path: File) = object : SourceFile() {
66+
fun fromPath(path: File, isMultiplatformCommonSource: Boolean = false) = object : SourceFile() {
5467
init {
5568
require(path.isFile)
5669
}
5770

5871
override fun writeIfNeeded(dir: File): File = path
72+
73+
override val isMultiplatformCommonSource: Boolean = isMultiplatformCommonSource
5974
}
6075
}
6176
}

core/src/main/kotlin/com/tschuchort/compiletesting/Utils.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ internal fun isJdk9OrLater(): Boolean
3131
= SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0
3232

3333
internal fun File.listFilesRecursively(): List<File> {
34-
return listFiles().flatMap { file ->
34+
return (listFiles() ?: throw RuntimeException("listFiles() was null. File is not a directory or I/O error occured"))
35+
.flatMap { file ->
3536
if(file.isDirectory)
3637
file.listFilesRecursively()
3738
else
@@ -52,6 +53,10 @@ internal fun Path.listFilesRecursively(): List<Path> {
5253
return files
5354
}
5455

56+
internal fun Path.hasKotlinFileExtension() = toFile().hasKotlinFileExtension()
57+
58+
internal fun Path.hasJavaFileExtension() = toFile().hasJavaFileExtension()
59+
5560
internal fun File.hasKotlinFileExtension() = hasFileExtension(listOf("kt", "kts"))
5661

5762
internal fun File.hasJavaFileExtension() = hasFileExtension(listOf("java"))

core/src/test/kotlin/com/tschuchort/compiletesting/KotlinCompilationTests.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,29 @@ class KotlinCompilationTests {
3535
assertClassLoadable(result, "KSource")
3636
}
3737

38+
@Test
39+
fun `can compile annotations that may only appear in multiplatform common module sources`() {
40+
val result = KotlinCompilation().apply {
41+
multiplatform = true
42+
sources = listOf(
43+
SourceFile.kotlin(
44+
"kSource.kt",
45+
"""
46+
import kotlin.js.JsExport
47+
48+
@JsExport
49+
fun add(a: Double, b: Double): Double {
50+
return a + b
51+
}
52+
""".trimIndent(),
53+
isMultiplatformCommonSource = true
54+
)
55+
)
56+
}.compile()
57+
58+
assertThat(ExitCode.OK).isEqualTo(result.exitCode)
59+
}
60+
3861
@Test
3962
fun `runs with only java sources`() {
4063
val result = defaultCompilerConfig().apply {

0 commit comments

Comments
 (0)