diff --git a/kotlin-codegen/compiler/pom.xml b/kotlin-codegen/compiler/pom.xml index d6aa8dd..092d18e 100644 --- a/kotlin-codegen/compiler/pom.xml +++ b/kotlin-codegen/compiler/pom.xml @@ -25,20 +25,10 @@ moshi ${project.version} - - com.squareup.moshi - moshi-kotlin-codegen-runtime - ${project.version} - org.jetbrains.kotlin kotlin-stdlib - - me.eugeniomarletti - kotlin-metadata - 1.2.1 - com.google.auto auto-common @@ -65,6 +55,32 @@ assertj-core test + + + + org.jetbrains.kotlin + kotlin-compiler-embeddable + + + org.jetbrains.kotlin + kotlin-annotation-processing-embeddable + + + me.eugeniomarletti + kotlin-metadata + + + + com.google.testing.compile + compile-testing + test + @@ -129,6 +145,18 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.21.0 + + + false + + diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt index 399a651..78b7428 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt @@ -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 { diff --git a/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt new file mode 100644 index 0000000..21ad81f --- /dev/null +++ b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt @@ -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") + } + +} \ No newline at end of file diff --git a/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/KotlinCompilerCall.kt b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/KotlinCompilerCall.kt new file mode 100644 index 0000000..3abb126 --- /dev/null +++ b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/KotlinCompilerCall.kt @@ -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() + val classpath = mutableListOf() + val services = LinkedHashMultimap.create, 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() + 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 { + 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 { + val result = mutableListOf() + 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 { + val classLoader = CompilerTest::class.java.classLoader + if (classLoader !is URLClassLoader) { + throw UnsupportedOperationException("unable to extract classpath from $classLoader") + } + + val result = mutableListOf() + 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 ")}}") + } +} \ No newline at end of file diff --git a/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/KotlinCompilerResult.kt b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/KotlinCompilerResult.kt new file mode 100644 index 0000000..27a65a3 --- /dev/null +++ b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/KotlinCompilerResult.kt @@ -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 +) \ No newline at end of file diff --git a/pom.xml b/pom.xml index d1d2a10..eebd3a4 100644 --- a/pom.xml +++ b/pom.xml @@ -30,11 +30,13 @@ UTF-8 1.7 1.2.21 + 1.2.1 1.13.0 + 0.8 4.12 1.7.0 @@ -97,6 +99,26 @@ ${kotlin.version} test + + org.jetbrains.kotlin + kotlin-compiler-embeddable + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-annotation-processing-embeddable + ${kotlin.version} + + + me.eugeniomarletti + kotlin-metadata + ${kotlin-metadata.version} + + + com.google.testing.compile + compile-testing + ${compile-testing.version} +