Skip to content

Commit

Permalink
introduce restx-kotlin-compiler
Browse files Browse the repository at this point in the history
will be used to provide auto compile on kotlin code
  • Loading branch information
xhanin committed Feb 9, 2018
1 parent 06d157e commit 182c944
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Expand Up @@ -596,6 +596,7 @@
<module>restx-jongo-java8</module>
<module>restx-specs-tests-java8</module>
<module>restx-samplest-java8</module>
<module>restx-kotlin-compiler</module>
<module>restx-samplest-kotlin</module>
</modules>
</profile>
Expand Down
118 changes: 118 additions & 0 deletions restx-kotlin-compiler/pom.xml
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.restx</groupId>
<artifactId>restx-parent</artifactId>
<version>0.36-SNAPSHOT</version>
</parent>

<artifactId>restx-kotlin-compiler</artifactId>
<name>restx-kotlin-compiler</name>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<kotlin.version>1.2.21</kotlin.version>
<kotlin.compiler.incremental>false</kotlin.compiler.incremental>
</properties>

<dependencies>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-common</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-compiler-embeddable</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals> <goal>compile</goal> </goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals> <goal>test-compile</goal> </goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals> <goal>compile</goal> </goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals> <goal>testCompile</goal> </goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
@@ -0,0 +1,35 @@
package restx.kt.compiler;

import javax.tools.Diagnostic;
import java.net.MalformedURLException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;

/**
* Created by xhanin on 09/02/2018.
*/
public class KotlinCompiler {
private final Path destination;

public KotlinCompiler(Path destination) {
this.destination = destination;
}

public Collection<Diagnostic<?>> compile(Collection<Path> sources) {
for (Path source : sources) {
try {
KotlinCompilerHelper.INSTANCE.compileKotlinScript(
Thread.currentThread().getContextClassLoader(),
source.toUri().toURL(),
destination,
(state, d) -> true
);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("can't convert path to URL: " + e);
}
}

return Collections.emptyList();
}
}
@@ -0,0 +1,112 @@
package restx.kt.compiler

import org.jetbrains.kotlin.com.intellij.openapi.*
import org.jetbrains.kotlin.cli.common.*
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.jvm.compiler.*
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.state.*
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.*
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.resolve.*
import java.io.*
import java.net.*
import java.nio.file.*
import java.util.*
import java.util.jar.*

/**
* Author: Sergey Mashkov
* Mostly borrowed from vertx koltin compiler
*/
object KotlinCompilerHelper {
fun compileKotlinScript(classLoader: ClassLoader,
url: URL,
destination: Path,
predicate: (GenerationState, ClassDescriptor) -> Boolean
): Map<Class<*>, ClassDescriptor> {

val configuration = CompilerConfiguration()
val printingMessageCollector = PrintingMessageCollector(System.err, MessageRenderer.WITHOUT_PATHS, false)
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, printingMessageCollector)
configuration.put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, false)
configuration.put(CLIConfigurationKeys.REPORT_PERF, false)

configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, LanguageVersionSettingsImpl.DEFAULT)

configuration.put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, destination.toFile())
configuration.put(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK, true)
configuration.put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME)

val classPath = (
classLoader.classPath()
+ ClassLoader.getSystemClassLoader().classPath()
+ (Thread.currentThread().contextClassLoader?.classPath() ?: emptyList())
+ propertyClassPath("java.class.path")
+ propertyClassPath("sun.boot.class.path")
).distinct().filter { Files.exists(it) }

for (item in classPath) {
configuration.add(JVMConfigurationKeys.CONTENT_ROOTS, JvmClasspathRoot(item.toFile()))
}
configuration.add(JVMConfigurationKeys.CONTENT_ROOTS, KotlinSourceRoot(Paths.get(url.toURI()).toString()))

val collected = HashMap<String, ClassDescriptor>()

val environment = KotlinCoreEnvironment.createForProduction(Disposable { }, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
val finalState = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(environment)

if (printingMessageCollector.hasErrors()) {
throw CompilationException("Compilation failed", null, null)
}
if (finalState == null) {
return emptyMap()
}

val compilerClassLoader = GeneratedClassLoader(finalState.factory, classLoader)

return finalState.factory.getClassFiles().toList()
.map { it.relativePath.removeSuffix(".class").replace("/", ".") }
.filter { it !in collected }
.mapNotNull { finalState.bindingContext.get(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, FqNameUnsafe(it.replace("$", "."))) }
.filter { predicate(finalState, it) }
.associateBy { finalState.typeMapper.mapClass(it).className }
.mapKeys { compilerClassLoader.loadClass(it.key) }
}

private fun ClassLoader.classPath() = (classPathImpl() + manifestClassPath()).distinct()

private fun ClassLoader.classPathImpl(): List<Path> {
val parentUrls = parent?.classPathImpl() ?: emptyList()

return when {
this is URLClassLoader -> urLs.filterNotNull().map(URL::toURI).mapNotNull { ifFailed(null) { Paths.get(it) } } + parentUrls
else -> parentUrls
}
}

private fun ClassLoader.manifestClassPath() =
getResources("META-INF/MANIFEST.MF")
.asSequence()
.mapNotNull { ifFailed(null) { it.openStream().use { Manifest().apply { read(it) } } } }
.flatMap { it.mainAttributes?.getValue("Class-Path")?.splitToSequence(" ")?.filter(String::isNotBlank) ?: emptySequence() }
.mapNotNull { ifFailed(null) { Paths.get(URI.create(it)) } }
.toList()

private fun propertyClassPath(key: String) = System.getProperty(key)
?.split(File.pathSeparator)
?.filter(String::isNotEmpty)
?.map { Paths.get(it) }
?: emptyList()

private inline fun <R> ifFailed(default: R, block: () -> R) = try {
block()
} catch (t: Throwable) {
default
}

}
@@ -0,0 +1,38 @@
package restx.kt.compiler;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import javax.tools.Diagnostic;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;

/**
* Created by xhanin on 09/02/2018.
*/
public class KotlinCompilerTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();

@Test
public void should_compile_simple_class() throws Exception {
File target = folder.newFolder("target");

KotlinCompiler compiler = new KotlinCompiler(target.toPath());

Collection<Path> sources = asList(Paths.get("src/test/resources/MyClass.kt"));
Collection<Diagnostic<?>> diagnostics = compiler.compile(sources);

assertThat(diagnostics)
.isNotNull()
.isEmpty();

assertThat(new File(target, "MyClass.class")).exists();
}
}
1 change: 1 addition & 0 deletions restx-kotlin-compiler/src/test/resources/MyClass.kt
@@ -0,0 +1 @@
class MyClass

0 comments on commit 182c944

Please sign in to comment.