Remove dependency on embedded kotlin compiler

This is a large change that significantly simplifies the plugin build system by
having the kotlinc handle everything related to script compilation.  Since we
no longer use package name rewriting, the "packageName" option has been dropped
from the gradle script API and has been removed from existing plugins.

In addition to simplifying the build system, it should also significantly speed
up build times.  Previously we ran 2 build processes for every plugin and
didn't have fully working incremental compilation for scripts compared to 1
build process with built-in incremental compilation.

There were also some random pieces of code that referenced kotlinc directly, so
those have either been removed or refactored out.
This commit is contained in:
Gary Tierney
2018-03-27 21:12:44 +01:00
parent 97896a34a4
commit d8116478d4
35 changed files with 30 additions and 409 deletions
+1 -2
View File
@@ -20,7 +20,6 @@ repositories {
dependencies { dependencies {
compile gradleApi() compile gradleApi()
compile group: 'org.ow2.asm', name: 'asm-all', version: '5.0.3'
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: "$kotlinVersion" compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: "$kotlinVersion"
compile group: 'org.jetbrains.kotlin', name: 'kotlin-compiler-embeddable', version: "$kotlinVersion" compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: "$kotlinVersion"
} }
@@ -1,10 +1,6 @@
package org.apollo.build.plugin package org.apollo.build.plugin
import org.apollo.build.plugin.tasks.ApolloScriptCompileTask
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileTree
import org.gradle.api.tasks.testing.Test
class ApolloPluginExtension { class ApolloPluginExtension {
final Project project final Project project
@@ -14,11 +10,6 @@ class ApolloPluginExtension {
*/ */
String name String name
/**
* The package that plugin scripts (.kts files) will be packaged under for this plugin.
*/
String packageName = "org.apollo.game.plugins"
/** /**
* An optional description of this plugin. * An optional description of this plugin.
*/ */
@@ -51,21 +42,6 @@ class ApolloPluginExtension {
init() init()
} }
private def addDependencyOn(Project other, String configuration, boolean includeProject) {
def compileConfiguration = other.configurations.getByName("compile")
def sources = other.sourceSets.main
def deps = compileConfiguration.dependencies
deps.each {
project.dependencies.add(configuration, it)
}
project.dependencies.add(configuration, sources.output)
if (includeProject) {
project.dependencies.add(configuration, other)
}
}
/** /**
* Setup the {@link Project} with the correct dependencies and tasks required to build the plugin * Setup the {@link Project} with the correct dependencies and tasks required to build the plugin
* and its scripts. * and its scripts.
@@ -75,44 +51,26 @@ class ApolloPluginExtension {
def pluginTestingProject = project.findProject(':game:plugin-testing') def pluginTestingProject = project.findProject(':game:plugin-testing')
project.plugins.apply('kotlin') project.plugins.apply('kotlin')
project.dependencies {
def transitiveGameDeps = gameProject.configurations["compile"].dependencies
def gameSources = gameProject.sourceSets.main
transitiveGameDeps.each { dependency ->
compile dependency
}
compile gameSources.output
testCompile pluginTestingProject
}
project.sourceSets { project.sourceSets {
main { main.kotlin.srcDirs += this.srcDir
kotlin { test.kotlin.srcDirs += this.testDir
srcDir this.srcDir
exclude '*.kts'
}
}
test {
kotlin {
srcDir this.testDir
}
}
} }
def mainSources = project.sourceSets.main project.tasks["compileKotlin"].kotlinOptions.freeCompilerArgs += [
"-script-templates", "org.apollo.game.plugin.kotlin.KotlinPluginScript"
addDependencyOn(gameProject, "compile", false) ]
addDependencyOn(pluginTestingProject, "testCompile", true)
def buildTask = project.tasks['classes']
FileTree scripts = project.fileTree(srcDir).matching {
include '*.kts'
}
project.tasks.create('compileScripts', ApolloScriptCompileTask) {
def outputDir = mainSources.output.classesDir
inputs.files scripts.files
outputsDir = outputDir
compileClasspath = mainSources.compileClasspath + mainSources.runtimeClasspath + mainSources.output
scriptDefinitionClass = "org.apollo.game.plugin.kotlin.KotlinPluginScript"
mustRunAfter buildTask
}
buildTask.finalizedBy(project.tasks['compileScripts'])
} }
def getDependencies() { def getDependencies() {
@@ -126,5 +84,4 @@ class ApolloPluginExtension {
this.dependencies = dependencies this.dependencies = dependencies
} }
} }
@@ -1,59 +0,0 @@
package org.apollo.build.plugin.compiler
import kotlin.jvm.JvmClassMappingKt
import kotlin.reflect.KClass
import kotlin.reflect.full.KClasses
import kotlin.reflect.jvm.internal.KClassImpl
import kotlin.script.templates.ScriptTemplateDefinition
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import java.lang.management.ManagementFactory
class KotlinCompilerConfigurationFactory {
static CompilerConfiguration create(String scriptDefinitionClassName, Collection<File> classpath, MessageCollector messageCollector) {
def parentClassLoader = (URLClassLoader) Thread.currentThread().contextClassLoader
if (parentClassLoader == null) {
throw new RuntimeException("Unable to find current classloader")
}
URL[] classpathUrls = parentClassLoader.getURLs()
for (classpathUrl in classpathUrls) {
try {
classpath.add(new File(classpathUrl.toURI()))
} catch (ex) {
throw new RuntimeException("URL returned by ClassLoader is invalid", ex)
}
}
def runtimeBean = ManagementFactory.getRuntimeMXBean()
if (!runtimeBean.bootClassPathSupported) {
println("Warning! Boot class path is not supported, must be supplied on the command line")
} else {
def bootClasspath = runtimeBean.bootClassPath
classpath.addAll(bootClasspath.split(File.pathSeparatorChar.toString()).collect { new File(it) })
}
def classLoader = new URLClassLoader(classpath.collect { it.toURL() }.toArray(new URL[classpath.size()]))
def configuration = new CompilerConfiguration()
def scriptDefinitionClass = classLoader.loadClass(scriptDefinitionClassName)
def scriptDefinition = new KotlinScriptDefinitionFromAnnotatedTemplate(JvmClassMappingKt.getKotlinClass(scriptDefinitionClass),
null, null, null, classpath.collect())
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
configuration.put(JVMConfigurationKeys.CONTENT_ROOTS, classpath.collect { new JvmClasspathRoot(it) })
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, false)
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
configuration.copy()
return configuration
}
}
@@ -1,23 +0,0 @@
package org.apollo.build.plugin.compiler
import java.nio.file.Path
class KotlinScriptBinaryArtifact {
final String relativePath
final byte[] data
KotlinScriptBinaryArtifact(String relativePath, byte[] data) {
this.relativePath = relativePath
this.data = data
}
}
class KotlinScriptBinary {
final String mainClassName
final List<KotlinScriptBinaryArtifact> artifacts
KotlinScriptBinary(String mainClassName, List<KotlinScriptBinaryArtifact> artifacts) {
this.mainClassName = mainClassName
this.artifacts = artifacts
}
}
@@ -1,46 +0,0 @@
package org.apollo.build.plugin.compiler
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.commons.RemappingClassAdapter
import org.objectweb.asm.tree.ClassNode
class KotlinScriptBinaryArtifactRemapper {
final String originalSourceFileName
final String mainClassName
KotlinScriptBinaryArtifactRemapper(String originalSourceFileName, String mainClassName) {
this.originalSourceFileName = originalSourceFileName
this.mainClassName = mainClassName
}
KotlinScriptBinaryArtifact remapToPackage(KotlinScriptBinaryArtifact artifact, String packageName) {
def node = new ClassNode()
def writer = new ClassWriter(0)
def reader = new ClassReader(new ByteArrayInputStream(artifact.data))
reader.accept(node, ClassReader.EXPAND_FRAMES)
def normalizedPackageName = packageName.replace('.', '/')
def oldClassName = reader.getClassName()
def newClassName = artifact.relativePath.replace(oldClassName, "$normalizedPackageName/$oldClassName")
def remapper = new Remapper() {
@Override
String map(String typeName) {
if (typeName.equals(mainClassName) || typeName.startsWith("$mainClassName\$")) {
return "$normalizedPackageName/$typeName"
}
return super.map(typeName);
}
}
def remappingAdapter = new RemappingClassAdapter(writer, remapper)
node.accept(remappingAdapter)
writer.visitSource(originalSourceFileName, null)
writer.visitEnd()
return new KotlinScriptBinaryArtifact(newClassName, writer.toByteArray())
}
}
@@ -1,73 +0,0 @@
package org.apollo.build.plugin.compiler
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.java.PsiPackageStatementImpl
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.KotlinSourceRoot
import java.nio.file.Path
class KotlinScriptCompiler {
private String scriptDefinitionClass
private Collection<File> classpath
private MessageCollector messageCollector
KotlinScriptCompiler(String scriptDefinitionClass, Collection<File> classpath, MessageCollector messageCollector) {
this.scriptDefinitionClass = scriptDefinitionClass
this.classpath = classpath
this.messageCollector = messageCollector
}
KotlinScriptBinary compile(Path input) {
def compilerConfiguration = KotlinCompilerConfigurationFactory.create(
scriptDefinitionClass,
classpath,
messageCollector
)
def rootDisposable = Disposer.newDisposable()
def configuration = compilerConfiguration.copy()
configuration.put(CommonConfigurationKeys.MODULE_NAME, input.toString())
configuration.add(JVMConfigurationKeys.CONTENT_ROOTS, new KotlinSourceRoot(input.toAbsolutePath().toString()))
def configFiles = EnvironmentConfigFiles.JVM_CONFIG_FILES
def environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, configFiles)
try {
def generationState = KotlinToJVMBytecodeCompiler.INSTANCE.analyzeAndGenerate(environment)
if (generationState == null) {
throw new KotlinScriptCompilerException("Failed to generate bytecode for kotlin script")
}
def sourceFiles = environment.getSourceFiles()
def script = sourceFiles[0].script
if (script == null) {
throw new KotlinScriptCompilerException("Main source file is not a script")
}
def scriptFilePath = script.fqName.asString().replace('.', '/') + ".class"
def scriptFileClass = generationState.factory.get(scriptFilePath)
if (scriptFileClass == null) {
throw new KotlinScriptCompilerException("Unable to find compiled plugin class file $scriptFilePath")
}
def outputs = generationState.factory.asList()
def artifacts = outputs.collect { new KotlinScriptBinaryArtifact(it.relativePath, it.asByteArray()) }
return new KotlinScriptBinary(script.fqName.asString(), artifacts)
} catch (ex) {
throw new KotlinScriptCompilerException("Compilation failed", ex)
} finally {
Disposer.dispose(rootDisposable)
}
}
}
@@ -1,11 +0,0 @@
package org.apollo.build.plugin.compiler
class KotlinScriptCompilerException extends Exception {
KotlinScriptCompilerException(String message) {
super(message)
}
KotlinScriptCompilerException(String message, Throwable cause) {
super(message, cause)
}
}
@@ -1,84 +0,0 @@
package org.apollo.build.plugin.tasks
import org.apollo.build.plugin.ApolloPluginExtension
import org.apollo.build.plugin.compiler.KotlinScriptBinaryArtifactRemapper
import org.apollo.build.plugin.compiler.KotlinScriptCompiler
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import java.nio.file.Files
import java.nio.file.StandardOpenOption
class ApolloScriptCompileTask extends DefaultTask {
@OutputDirectory
File outputsDir
@Input
FileCollection compileClasspath
@Input
String scriptDefinitionClass
@TaskAction
def execute(IncrementalTaskInputs inputs) {
def extension = getProject().getExtensions().getByType(ApolloPluginExtension.class);
def packageName = extension.packageName
if (scriptDefinitionClass == null) {
throw new Exception("No script definition class given")
}
if (compileClasspath == null) {
throw new Exception("No compile classpath given")
}
def classpath = compileClasspath.files
def messageCollector = new PrintingMessageCollector(System.err, MessageRenderer.PLAIN_RELATIVE_PATHS, true);
def compiler = new KotlinScriptCompiler(scriptDefinitionClass, classpath, messageCollector)
outputsDir.mkdirs()
inputs.outOfDate {
removeBinariesFor(it.file)
def binary = compiler.compile(it.file.toPath())
def binaryArtifactRemapper = new KotlinScriptBinaryArtifactRemapper(it.file.name, binary.mainClassName)
def artifacts = binary.artifacts.collect { binaryArtifactRemapper.remapToPackage(it, packageName) }
artifacts.each {
def artifactOutput = outputsDir.toPath().resolve(it.relativePath)
Files.createDirectories(artifactOutput.getParent())
Files.write(artifactOutput, it.data,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING
)
}
}
inputs.removed {
removeBinariesFor(it.file)
}
}
def removeBinariesFor(File file) {
def normalizedFilename = file.name.replace("[^A-Z_]", "_")
def normalizedPrefix = normalizedFilename.subSequence(0, normalizedFilename.lastIndexOf('.'))
FileFilter filter = {
return it.name.startsWith(normalizedPrefix)
}
def binaries = outputsDir.listFiles(filter)
binaries.each {
it.delete()
}
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ dependencies {
compile group: 'io.github.lukehutch', name: 'fast-classpath-scanner', version: '2.0.21' compile group: 'io.github.lukehutch', name: 'fast-classpath-scanner', version: '2.0.21'
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: "$kotlinVersion" compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: "$kotlinVersion"
compile group: 'org.jetbrains.kotlin', name: 'kotlin-compiler-embeddable', version: "$kotlinVersion" compile group: 'org.jetbrains.kotlin', name: 'kotlin-script-runtime', version: "$kotlinVersion"
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-jdk8', version: '0.16' compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-jdk8', version: '0.16'
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.16' compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.16'
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "banking" name = "banking"
packageName = "org.apollo.game.plugin.banking"
authors = [ authors = [
"Major", "Major",
] ]
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "chat_commands" name = "chat_commands"
packageName = "org.apollo.game.plugin.cmd"
authors = [ authors = [
"Graham", "Graham",
"Major", "Major",
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "consumables" name = "consumables"
packageName = "org.apollo.game.plugin.consumables"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "training_dummy" name = "training_dummy"
packageName = "org.apollo.game.plugin.entity"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "emote_tab" name = "emote_tab"
packageName = "org.apollo.game.plugin.widget"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "following" name = "following"
packageName = "org.apollo.game.plugin.entity"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "player_action" name = "player_action"
packageName = "org.apollo.game.plugin.entity"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "spawning" name = "spawning"
packageName = "org.apollo.game.plugin.entity"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "walkto" name = "walkto"
packageName = "org.apollo.plugin.entity.walkto"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "al_kharid_npc_spawns" name = "al_kharid_npc_spawns"
packageName = "org.apollo.game.plugin.locations"
authors = [ authors = [
"Jesse W", "Jesse W",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "edgeville_npc_spawns" name = "edgeville_npc_spawns"
packageName = "org.apollo.game.plugin.locations"
authors = [ authors = [
"Jesse W", "Jesse W",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "falador_npc_spawns" name = "falador_npc_spawns"
packageName = "org.apollo.game.plugin.locations"
authors = [ authors = [
"Jesse W", "Jesse W",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "lumbridge_npc_spawns" name = "lumbridge_npc_spawns"
packageName = "org.apollo.game.plugin.locations"
authors = [ authors = [
"Gary Tierney", "Gary Tierney",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "tutorial_island_npc_spawns" name = "tutorial_island_npc_spawns"
packageName = "org.apollo.game.plugin.locations"
authors = [ authors = [
"Jesse W", "Jesse W",
] ]
@@ -1,6 +1,5 @@
plugin { plugin {
name = "varrock" name = "varrock"
packageName = "org.apollo.game.plugin.locations.varrock"
authors = [ authors = [
"Jesse W", "Jesse W",
"Major", "Major",
+1 -2
View File
@@ -1,7 +1,6 @@
plugin { plugin {
name = "door" name = "door"
packageName = "org.apollo.game.plugin.navigation.door" dependencies = ["api"]
dependencies = ["api"]
authors = [ authors = [
"Shiver", "Shiver",
"Arin" "Arin"
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "shops" name = "shops"
packageName = "org.apollo.game.plugin.shops"
authors = [ authors = [
"Stuart", "Stuart",
"Major", "Major",
+1 -2
View File
@@ -4,7 +4,6 @@ import org.apollo.cache.def.NpcDefinition
import org.apollo.game.plugin.shops.CategoryWrapper.Affix import org.apollo.game.plugin.shops.CategoryWrapper.Affix
import org.apollo.game.plugin.util.lookup.lookup_item import org.apollo.game.plugin.util.lookup.lookup_item
import org.apollo.game.plugin.util.lookup.lookup_npc import org.apollo.game.plugin.util.lookup.lookup_npc
import org.jetbrains.kotlin.utils.keysToMap
/** /**
* Creates a [Shop]. * Creates a [Shop].
@@ -16,7 +15,7 @@ fun shop(name: String, builder: ShopBuilder.() -> Unit) {
builder(shop) builder(shop)
val built = shop.build() val built = shop.build()
val operators = shop.operators().keysToMap { built } val operators = shop.operators().map { it to built }.toMap()
SHOPS.putAll(operators) SHOPS.putAll(operators)
} }
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "fishing_skill" name = "fishing_skill"
packageName = "org.apollo.game.plugin.skills.fishing"
authors = [ authors = [
"Linux", "Linux",
"Major", "Major",
-1
View File
@@ -1,6 +1,5 @@
plugin { plugin {
name = "mining-skill" name = "mining-skill"
packageName = "org.apollo.game.plugin.skills.mining"
authors = [ authors = [
"Graham", "Graham",
"Mikey`", "Mikey`",
@@ -1,6 +1,5 @@
plugin { plugin {
name = "runecrafting-skill" name = "runecrafting-skill"
packageName = "org.apollo.game.plugin.skills.runecrafting"
authors = [ authors = [
"Major", "Major",
"BugCrusher", "BugCrusher",
@@ -1,6 +1,5 @@
plugin { plugin {
name = "woodcutting_skill" name = "woodcutting_skill"
packageName = "org.apollo.game.plugin.skills.woodcutting"
authors = [ authors = [
"tlf30" "tlf30"
] ]
+1 -2
View File
@@ -1,4 +1,3 @@
plugin { plugin {
name = "command_utilities" name = "command_utilities"
packageName = "org.apollo.game.plugins.util" }
}
+1 -2
View File
@@ -1,4 +1,3 @@
plugin { plugin {
name = "entity_lookup" name = "entity_lookup"
packageName = "org.apollo.game.plugins.util" }
}
@@ -1,29 +1,14 @@
package org.apollo.game.plugin; package org.apollo.game.plugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner; import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import org.apollo.game.model.World; import org.apollo.game.model.World;
import org.apollo.game.plugin.kotlin.KotlinPluginScript; import org.apollo.game.plugin.kotlin.KotlinPluginScript;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation; import java.lang.reflect.Constructor;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity; import java.util.ArrayList;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector; import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
public class KotlinPluginEnvironment implements PluginEnvironment { public class KotlinPluginEnvironment implements PluginEnvironment {
+1 -1
View File
@@ -1,3 +1,3 @@
ext { ext {
kotlinVersion = '1.1.4-3' kotlinVersion = '1.2.31'
} }