Catch and report exceptions from DependenciesResolver
This commit is contained in:
@@ -25,8 +25,10 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
|||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.script.dependencies.DependenciesResolver.ResolveResult.Failure
|
||||||
import kotlin.script.dependencies.ScriptContents
|
import kotlin.script.dependencies.ScriptContents
|
||||||
import kotlin.script.dependencies.ScriptDependencies
|
import kotlin.script.dependencies.ScriptDependencies
|
||||||
|
import kotlin.script.dependencies.ScriptReport
|
||||||
|
|
||||||
class ScriptContentLoader(private val project: Project) {
|
class ScriptContentLoader(private val project: Project) {
|
||||||
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile)
|
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile)
|
||||||
@@ -62,10 +64,16 @@ class ScriptContentLoader(private val project: Project) {
|
|||||||
file: VirtualFile
|
file: VirtualFile
|
||||||
): ScriptDependencies? {
|
): ScriptDependencies? {
|
||||||
val scriptContents = getScriptContents(scriptDef, file)
|
val scriptContents = getScriptContents(scriptDef, file)
|
||||||
val result = scriptDef.dependencyResolver.resolve(
|
val environment = getEnvironment(scriptDef)
|
||||||
scriptContents,
|
val result = try {
|
||||||
getEnvironment(scriptDef)
|
scriptDef.dependencyResolver.resolve(
|
||||||
)
|
scriptContents,
|
||||||
|
environment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
e.asResolveFailure(scriptDef)
|
||||||
|
}
|
||||||
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
||||||
return result.dependencies?.adjustByDefinition(scriptDef)
|
return result.dependencies?.adjustByDefinition(scriptDef)
|
||||||
}
|
}
|
||||||
@@ -82,3 +90,7 @@ fun ScriptDependencies.adjustByDefinition(
|
|||||||
return copy(classpath = additionalClasspath + classpath)
|
return copy(classpath = additionalClasspath + classpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Throwable.asResolveFailure(scriptDef: KotlinScriptDefinition): Failure {
|
||||||
|
val prefix = "${scriptDef.dependencyResolver::class.simpleName} threw exception ${this::class.simpleName}:\n "
|
||||||
|
return Failure(ScriptReport(prefix + (message ?: "<no message>")))
|
||||||
|
}
|
||||||
@@ -290,6 +290,14 @@ class ScriptTemplateTest {
|
|||||||
Assert.assertNotNull(aClass)
|
Assert.assertNotNull(aClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThrowing() {
|
||||||
|
val messageCollector = TestMessageCollector()
|
||||||
|
compileScript("fib.kts", ScriptWithThrowingResolver::class, null, messageCollector = messageCollector)
|
||||||
|
|
||||||
|
messageCollector.assertHasMessage("Exception from resolver", desiredSeverity = CompilerMessageSeverity.ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSmokeScriptException() {
|
fun testSmokeScriptException() {
|
||||||
val aClass = compileScript("smoke_exception.kts", ScriptWithArrayParam::class)
|
val aClass = compileScript("smoke_exception.kts", ScriptWithArrayParam::class)
|
||||||
@@ -511,6 +519,12 @@ class SeveralConstructorsResolver(val c: Int): TestKotlinScriptDependenciesResol
|
|||||||
}
|
}
|
||||||
class DefaultArgsConstructorResolver(val c: Int = 0): TestKotlinScriptDependenciesResolver()
|
class DefaultArgsConstructorResolver(val c: Int = 0): TestKotlinScriptDependenciesResolver()
|
||||||
|
|
||||||
|
class ThrowingResolver: DependenciesResolver {
|
||||||
|
override fun resolve(scriptContents: ScriptContents, environment: Environment): ResolveResult {
|
||||||
|
throw IllegalStateException("Exception from resolver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ScriptTemplateDefinition(
|
@ScriptTemplateDefinition(
|
||||||
scriptFilePattern =".*\\.kts",
|
scriptFilePattern =".*\\.kts",
|
||||||
resolver = TestKotlinScriptDummyDependenciesResolver::class)
|
resolver = TestKotlinScriptDummyDependenciesResolver::class)
|
||||||
@@ -583,6 +597,9 @@ abstract class ScriptWithSeveralConstructorsResolver(val num: Int)
|
|||||||
@ScriptTemplateDefinition(resolver = DefaultArgsConstructorResolver::class)
|
@ScriptTemplateDefinition(resolver = DefaultArgsConstructorResolver::class)
|
||||||
abstract class ScriptWithDefaultArgsResolver(val num: Int)
|
abstract class ScriptWithDefaultArgsResolver(val num: Int)
|
||||||
|
|
||||||
|
@ScriptTemplateDefinition(resolver = ThrowingResolver::class)
|
||||||
|
abstract class ScriptWithThrowingResolver(val num: Int)
|
||||||
|
|
||||||
@Target(AnnotationTarget.FILE)
|
@Target(AnnotationTarget.FILE)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class DependsOn(val path: String)
|
annotation class DependsOn(val path: String)
|
||||||
|
|||||||
+45
-21
@@ -28,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||||
|
import kotlinx.coroutines.experimental.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.experimental.Job
|
import kotlinx.coroutines.experimental.Job
|
||||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.experimental.launch
|
import kotlinx.coroutines.experimental.launch
|
||||||
@@ -135,40 +136,63 @@ internal class ScriptDependenciesUpdater(
|
|||||||
): TimeStampedJob {
|
): TimeStampedJob {
|
||||||
val currentTimeStamp = TimeStamps.next()
|
val currentTimeStamp = TimeStamps.next()
|
||||||
|
|
||||||
fun process(result: DependenciesResolver.ResolveResult) {
|
|
||||||
val lastRequest = requests[file.path]
|
|
||||||
val lastTimeStamp = lastRequest?.job?.timeStamp
|
|
||||||
val isLastSentRequest = lastTimeStamp == null || lastTimeStamp == currentTimeStamp
|
|
||||||
if (isLastSentRequest) {
|
|
||||||
if (lastRequest != null) {
|
|
||||||
// no job running atm unless there is a job started while we process this result
|
|
||||||
requests.replace(file.path, lastRequest, ModStampedRequest(lastRequest.modificationStamp, job = null))
|
|
||||||
}
|
|
||||||
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
|
||||||
val resultingDependencies = (result.dependencies ?: ScriptDependencies.Empty).adjustByDefinition(scriptDef)
|
|
||||||
if (cache(resultingDependencies, file)) {
|
|
||||||
onChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val dependenciesResolver = scriptDef.dependencyResolver
|
val dependenciesResolver = scriptDef.dependencyResolver
|
||||||
val scriptContents = contentLoader.getScriptContents(scriptDef, file)
|
val scriptContents = contentLoader.getScriptContents(scriptDef, file)
|
||||||
val environment = contentLoader.getEnvironment(scriptDef)
|
val environment = contentLoader.getEnvironment(scriptDef)
|
||||||
val newJob = if (dependenciesResolver is AsyncDependenciesResolver) {
|
val newJob = if (dependenciesResolver is AsyncDependenciesResolver) {
|
||||||
launch(asyncUpdatesDispatcher) {
|
launchAsyncUpdate(asyncUpdatesDispatcher, file, currentTimeStamp, scriptDef) {
|
||||||
process(dependenciesResolver.resolveAsync(scriptContents, environment))
|
dependenciesResolver.resolveAsync(scriptContents, environment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert(dependenciesResolver is LegacyResolverWrapper)
|
assert(dependenciesResolver is LegacyResolverWrapper)
|
||||||
launch(legacyUpdatesDispatcher) {
|
launchAsyncUpdate(legacyUpdatesDispatcher, file, currentTimeStamp, scriptDef) {
|
||||||
process(dependenciesResolver.resolve(scriptContents, environment))
|
dependenciesResolver.resolve(scriptContents, environment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TimeStampedJob(newJob, currentTimeStamp)
|
return TimeStampedJob(newJob, currentTimeStamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchAsyncUpdate(
|
||||||
|
dispatcher: CoroutineDispatcher,
|
||||||
|
file: VirtualFile,
|
||||||
|
currentTimeStamp: TimeStamp,
|
||||||
|
scriptDef: KotlinScriptDefinition,
|
||||||
|
doResolve: suspend () -> DependenciesResolver.ResolveResult
|
||||||
|
) = launch(dispatcher) {
|
||||||
|
val result = try {
|
||||||
|
doResolve()
|
||||||
|
}
|
||||||
|
catch (t: Throwable) {
|
||||||
|
t.asResolveFailure(scriptDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
processResult(file, currentTimeStamp, result, scriptDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResult(
|
||||||
|
file: VirtualFile,
|
||||||
|
currentTimeStamp: TimeStamp,
|
||||||
|
result: DependenciesResolver.ResolveResult,
|
||||||
|
scriptDef: KotlinScriptDefinition
|
||||||
|
) {
|
||||||
|
val lastRequest = requests[file.path]
|
||||||
|
val lastTimeStamp = lastRequest?.job?.timeStamp
|
||||||
|
val isLastSentRequest = lastTimeStamp == null || lastTimeStamp == currentTimeStamp
|
||||||
|
if (isLastSentRequest) {
|
||||||
|
if (lastRequest != null) {
|
||||||
|
// no job running atm unless there is a job started while we process this result
|
||||||
|
requests.replace(file.path, lastRequest, ModStampedRequest(lastRequest.modificationStamp, job = null))
|
||||||
|
}
|
||||||
|
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
||||||
|
val resultingDependencies = (result.dependencies ?: ScriptDependencies.Empty).adjustByDefinition(scriptDef)
|
||||||
|
if (cache(resultingDependencies, file)) {
|
||||||
|
onChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateSync(file: VirtualFile, scriptDef: KotlinScriptDefinition): Boolean {
|
fun updateSync(file: VirtualFile, scriptDef: KotlinScriptDefinition): Boolean {
|
||||||
val newDeps = contentLoader.loadContentsAndResolveDependencies(scriptDef, file) ?: ScriptDependencies.Empty
|
val newDeps = contentLoader.loadContentsAndResolveDependencies(scriptDef, file) ?: ScriptDependencies.Empty
|
||||||
return cache(newDeps, file)
|
return cache(newDeps, file)
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<error descr="TestDependenciesResolver threw exception IllegalStateException:
|
||||||
|
Exception from resolver">
|
||||||
|
</error>
|
||||||
|
val s = 3
|
||||||
|
val g = 4
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package custom.scriptDefinition
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.script.dependencies.*
|
||||||
|
import kotlin.script.dependencies.experimental.*
|
||||||
|
import kotlin.script.templates.ScriptTemplateDefinition
|
||||||
|
|
||||||
|
class TestDependenciesResolver : DependenciesResolver {
|
||||||
|
override fun resolve(
|
||||||
|
scriptContents: ScriptContents,
|
||||||
|
environment: Environment
|
||||||
|
): DependenciesResolver.ResolveResult {
|
||||||
|
error("Exception from resolver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ScriptTemplateDefinition(TestDependenciesResolver::class, scriptFilePattern = "script.kts")
|
||||||
|
open class Template
|
||||||
+6
@@ -95,4 +95,10 @@ public class ScriptConfigurationHighlightingTestGenerated extends AbstractScript
|
|||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/simple/");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/simple/");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("throwingResolver")
|
||||||
|
public void testThrowingResolver() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/throwingResolver/");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user