mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 07:59:21 +08:00
Call the kotlin compiler from within a test case.
This is a fragile first step.
This commit is contained in:
@@ -25,20 +25,10 @@
|
||||
<artifactId>moshi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.moshi</groupId>
|
||||
<artifactId>moshi-kotlin-codegen-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.eugeniomarletti</groupId>
|
||||
<artifactId>kotlin-metadata</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
@@ -65,6 +55,32 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
The Kotlin compiler must be near the end of the list because its .jar file includes an
|
||||
obsolete version of Guava!
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler-embeddable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-annotation-processing-embeddable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.eugeniomarletti</groupId>
|
||||
<artifactId>kotlin-metadata</artifactId>
|
||||
</dependency>
|
||||
<!--
|
||||
Though we don't use compile-testing, including it is a convenient way to get tools.jar on the
|
||||
classpath. This dependency is required by kapt3.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.google.testing.compile</groupId>
|
||||
<artifactId>compile-testing</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -129,6 +145,18 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.21.0</version>
|
||||
<configuration>
|
||||
<!--
|
||||
Suppress the surefire classloader which prevents introspecting the classpath.
|
||||
http://maven.apache.org/surefire/maven-surefire-plugin/examples/class-loading.html
|
||||
-->
|
||||
<useManifestOnlyJar>false</useManifestOnlyJar>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@@ -168,7 +168,7 @@ internal class AdapterGenerator(
|
||||
if (property.differentiateAbsentFromNull) {
|
||||
result.beginControlFlow("%L -> ", index)
|
||||
result.addStatement("%N = %N.fromJson(%N)",
|
||||
property.localName, property.delegateName, readerParam);
|
||||
property.localName, property.delegateName, readerParam)
|
||||
result.addStatement("%N = true", property.localIsPresentName)
|
||||
result.endControlFlow()
|
||||
} else {
|
||||
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import javax.annotation.processing.Processor
|
||||
|
||||
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
||||
class CompilerTest {
|
||||
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodeGenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class ConstructorParameters(var a: Int, var b: Int)
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
"@JsonClass can't be applied to ConstructorParameters: must be a Kotlin data class")
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap
|
||||
import okio.Buffer
|
||||
import okio.Okio
|
||||
import org.jetbrains.kotlin.cli.common.CLITool
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.PrintStream
|
||||
import java.net.URLClassLoader
|
||||
import java.net.URLDecoder
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/** Prepares an invocation of the Kotlin compiler. */
|
||||
class KotlinCompilerCall(var scratchDir: File) {
|
||||
val sourcesDir = File(scratchDir, "sources")
|
||||
val classesDir = File(scratchDir, "classes")
|
||||
val servicesJar = File(scratchDir, "services.jar")
|
||||
|
||||
var inheritClasspath = false
|
||||
|
||||
val args = mutableListOf<String>()
|
||||
val classpath = mutableListOf<String>()
|
||||
val services = LinkedHashMultimap.create<KClass<*>, KClass<*>>()
|
||||
|
||||
/** Adds a source file to be compiled. */
|
||||
fun addKt(path: String, source: String) {
|
||||
val sourceFile = File(sourcesDir, path)
|
||||
sourceFile.parentFile.mkdirs()
|
||||
Okio.buffer(Okio.sink(sourceFile)).use {
|
||||
it.writeUtf8(source)
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a service like an annotation processor to make available to the compiler. */
|
||||
fun addService(serviceClass: KClass<*>, implementation: KClass<*>) {
|
||||
services.put(serviceClass, implementation)
|
||||
}
|
||||
|
||||
fun execute(): KotlinCompilerResult {
|
||||
val fullArgs = mutableListOf<String>()
|
||||
fullArgs.addAll(args)
|
||||
|
||||
fullArgs.add("-d")
|
||||
fullArgs.add(classesDir.toString())
|
||||
|
||||
val fullClasspath = fullClasspath()
|
||||
if (fullClasspath.isNotEmpty()) {
|
||||
fullArgs.add("-classpath")
|
||||
fullArgs.add(fullClasspath.joinToString(separator = ":"))
|
||||
}
|
||||
|
||||
for (source in sourcesDir.listFiles()) {
|
||||
fullArgs.add(source.toString())
|
||||
}
|
||||
|
||||
fullArgs.addAll(annotationProcessorArgs())
|
||||
|
||||
val systemErrBuffer = Buffer()
|
||||
val oldSystemErr = System.err
|
||||
System.setErr(PrintStream(systemErrBuffer.outputStream()))
|
||||
try {
|
||||
val exitCode = CLITool.doMainNoExit(K2JVMCompiler(), fullArgs.toTypedArray())
|
||||
val systemErr = systemErrBuffer.readUtf8()
|
||||
return KotlinCompilerResult(systemErr, exitCode)
|
||||
} finally {
|
||||
System.setErr(oldSystemErr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns arguments necessary to enable and configure kapt3. */
|
||||
private fun annotationProcessorArgs(): List<String> {
|
||||
val kaptSourceDir = File(scratchDir, "kapt/sources")
|
||||
val kaptStubsDir = File(scratchDir, "kapt/stubs")
|
||||
|
||||
return listOf(
|
||||
"-Xplugin=${kapt3Jar()}",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:sources=$kaptSourceDir",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:classes=$classesDir",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptStubsDir",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:apclasspath=$servicesJar",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns the classpath to use when compiling code. */
|
||||
private fun fullClasspath(): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
result.addAll(classpath)
|
||||
|
||||
// Copy over the classpath of the running application.
|
||||
if (inheritClasspath) {
|
||||
for (classpathFile in classpathFiles()) {
|
||||
result.add(classpathFile.toString())
|
||||
}
|
||||
}
|
||||
|
||||
if (!services.isEmpty) {
|
||||
writeServicesJar()
|
||||
result.add(servicesJar.toString())
|
||||
}
|
||||
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a .jar file that holds ServiceManager registrations. Necessary because AutoService's
|
||||
* results might not be visible to this test.
|
||||
*/
|
||||
private fun writeServicesJar() {
|
||||
ZipOutputStream(FileOutputStream(servicesJar)).use { zipOutputStream ->
|
||||
for (entry in services.asMap()) {
|
||||
zipOutputStream.putNextEntry(
|
||||
ZipEntry("META-INF/services/${entry.key.qualifiedName}"))
|
||||
val serviceFile = Okio.buffer(Okio.sink(zipOutputStream))
|
||||
for (implementation in entry.value) {
|
||||
serviceFile.writeUtf8(implementation.qualifiedName)
|
||||
serviceFile.writeUtf8("\n")
|
||||
}
|
||||
serviceFile.emit() // Don't close the entry; that closes the file.
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the files on the host process' classpath. */
|
||||
private fun classpathFiles(): List<File> {
|
||||
val classLoader = CompilerTest::class.java.classLoader
|
||||
if (classLoader !is URLClassLoader) {
|
||||
throw UnsupportedOperationException("unable to extract classpath from $classLoader")
|
||||
}
|
||||
|
||||
val result = mutableListOf<File>()
|
||||
for (url in classLoader.urLs) {
|
||||
if (url.protocol != "file") {
|
||||
throw UnsupportedOperationException("unable to handle classpath element $url")
|
||||
}
|
||||
result.add(File(URLDecoder.decode(url.path, "UTF-8")))
|
||||
}
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
/** Returns the path to the kotlin-annotation-processing .jar file. */
|
||||
private fun kapt3Jar(): File {
|
||||
for (file in classpathFiles()) {
|
||||
if (file.name.startsWith("kotlin-annotation-processing-embeddable")) return file
|
||||
}
|
||||
throw IllegalStateException("no kotlin-annotation-processing-embeddable jar on classpath:\n " +
|
||||
"${classpathFiles().joinToString(separator = "\n ")}}")
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
|
||||
class KotlinCompilerResult(
|
||||
val systemErr: String,
|
||||
var exitCode: ExitCode
|
||||
)
|
22
pom.xml
22
pom.xml
@@ -30,11 +30,13 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.7</java.version>
|
||||
<kotlin.version>1.2.21</kotlin.version>
|
||||
<kotlin-metadata.version>1.2.1</kotlin-metadata.version>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<okio.version>1.13.0</okio.version>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<compile-testing.version>0.8</compile-testing.version>
|
||||
<junit.version>4.12</junit.version>
|
||||
<assertj.version>1.7.0</assertj.version>
|
||||
</properties>
|
||||
@@ -97,6 +99,26 @@
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler-embeddable</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-annotation-processing-embeddable</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.eugeniomarletti</groupId>
|
||||
<artifactId>kotlin-metadata</artifactId>
|
||||
<version>${kotlin-metadata.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.testing.compile</groupId>
|
||||
<artifactId>compile-testing</artifactId>
|
||||
<version>${compile-testing.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
Reference in New Issue
Block a user