From b73625ad9e0e89ddedbcf1a78281d7c85b4e2048 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Fri, 15 Nov 2019 16:31:11 +0100 Subject: [PATCH] Switch main-kts to the updated resolvers infrastructure --- .../script/experimental/api/errorHandling.kt | 21 ++++++- .../FileSystemDependenciesResolver.kt | 2 +- .../kotlin/mainKts/test/mainKtsTest.kt | 2 +- .../tools/kotlin-main-kts/build.gradle.kts | 2 + .../org/jetbrains/kotlin/mainKts/impl/ivy.kt | 58 ++++++++++--------- .../jetbrains/kotlin/mainKts/impl/resolve.kt | 36 ++++++++++++ .../org/jetbrains/kotlin/mainKts/scriptDef.kt | 32 +++++----- 7 files changed, 106 insertions(+), 47 deletions(-) create mode 100644 libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/resolve.kt diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt index 32499ff1e7a..e3562cd75c4 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt @@ -123,15 +123,30 @@ inline fun ResultWithDiagnostics.onSuccess(body: (R1) -> ResultWith * return failure with merged diagnostics after first failed transformation * and success with merged diagnostics and list of results if all transformations succeeded */ -inline fun Iterable.mapSuccess(body: (T) -> ResultWithDiagnostics): ResultWithDiagnostics> { +inline fun Iterable.mapSuccess(body: (T) -> ResultWithDiagnostics): ResultWithDiagnostics> = + mapSuccessImpl(body) { results, r -> + results.add(r) + } + +/** + * maps transformation ([body]) over iterable merging diagnostics and flatten the results + * return failure with merged diagnostics after first failed transformation + * and success with merged diagnostics and list of results if all transformations succeeded + */ +inline fun Iterable.flatMapSuccess(body: (T) -> ResultWithDiagnostics>): ResultWithDiagnostics> = + mapSuccessImpl(body) { results, r -> + results.addAll(r) + } + +inline fun Iterable.mapSuccessImpl(body: (T) -> ResultWithDiagnostics, updateResults: (MutableList, R1) -> Unit): ResultWithDiagnostics> { val reports = ArrayList() - val results = ArrayList() + val results = ArrayList() for (it in this) { val result = body(it) reports.addAll(result.reports) when (result) { is ResultWithDiagnostics.Success -> { - results.add(result.value) + updateResults(results, result.value) } else -> { return ResultWithDiagnostics.Failure(reports) diff --git a/libraries/scripting/dependencies/src/kotlin/script/experimental/dependencies/FileSystemDependenciesResolver.kt b/libraries/scripting/dependencies/src/kotlin/script/experimental/dependencies/FileSystemDependenciesResolver.kt index a0d56ce56f3..69e9c543a4d 100644 --- a/libraries/scripting/dependencies/src/kotlin/script/experimental/dependencies/FileSystemDependenciesResolver.kt +++ b/libraries/scripting/dependencies/src/kotlin/script/experimental/dependencies/FileSystemDependenciesResolver.kt @@ -33,7 +33,7 @@ class FileSystemDependenciesResolver(vararg paths: File) : ExternalDependenciesR // TODO: add coordinates and wildcard matching val file = if (repo == null) File(artifactCoordinates) else File(repo, artifactCoordinates) when { - !file.exists() -> messages.add("File '$file' does not exists") + !file.exists() -> messages.add("File '$file' not found") !file.isFile && !file.isDirectory -> messages.add("Path '$file' is neither file nor directory") else -> return ResultWithDiagnostics.Success(listOf(file)) } diff --git a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt index 62ff3110e0f..c9b9465aa4c 100644 --- a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt +++ b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt @@ -68,7 +68,7 @@ class MainKtsTest { @Test fun testResolveError() { val res = evalFile(File("$TEST_DATA_ROOT/hello-resolve-error.main.kts")) - assertFailed("Unrecognized set of arguments to ivy resolver: abracadabra", res) + assertFailed("File 'abracadabra' not found", res) } @Test diff --git a/libraries/tools/kotlin-main-kts/build.gradle.kts b/libraries/tools/kotlin-main-kts/build.gradle.kts index 15678d38fc2..b39372208ee 100644 --- a/libraries/tools/kotlin-main-kts/build.gradle.kts +++ b/libraries/tools/kotlin-main-kts/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { compileOnly("org.apache.ivy:ivy:2.5.0") compileOnly(project(":compiler:cli-common")) compileOnly(project(":kotlin-scripting-jvm-host")) + compileOnly(project(":kotlin-scripting-dependencies")) compileOnly(project(":kotlin-script-util")) testCompile(project(":kotlin-scripting-jvm-host")) testCompile(project(":kotlin-script-util")) @@ -26,6 +27,7 @@ dependencies { embedded(project(":kotlin-scripting-common")) { isTransitive = false } embedded(project(":kotlin-scripting-jvm")) { isTransitive = false } embedded(project(":kotlin-scripting-jvm-host")) { isTransitive = false } + embedded(project(":kotlin-scripting-dependencies")) { isTransitive = false } embedded(project(":kotlin-script-util")) { isTransitive = false } embedded("org.apache.ivy:ivy:2.5.0") embedded(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false } diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/ivy.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/ivy.kt index 5d7dddb5b8f..08e08217a06 100644 --- a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/ivy.kt +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/ivy.kt @@ -19,34 +19,40 @@ import org.apache.ivy.plugins.resolver.IBiblioResolver import org.apache.ivy.plugins.resolver.URLResolver import org.apache.ivy.util.DefaultMessageLogger import org.apache.ivy.util.Message -import org.jetbrains.kotlin.script.util.KotlinAnnotatedScriptDependenciesResolver -import org.jetbrains.kotlin.script.util.resolvers.DirectResolver -import org.jetbrains.kotlin.script.util.resolvers.experimental.GenericArtifactCoordinates -import org.jetbrains.kotlin.script.util.resolvers.experimental.GenericRepositoryCoordinates -import org.jetbrains.kotlin.script.util.resolvers.experimental.GenericRepositoryWithBridge -import org.jetbrains.kotlin.script.util.resolvers.experimental.MavenArtifactCoordinates import java.io.File +import kotlin.script.experimental.api.* +import kotlin.script.experimental.dependencies.ExternalDependenciesResolver +import kotlin.script.experimental.dependencies.RepositoryCoordinates +import kotlin.script.experimental.dependencies.impl.toRepositoryUrlOrNull -class IvyResolver : GenericRepositoryWithBridge { +class IvyResolver : ExternalDependenciesResolver { private fun String?.isValidParam() = this?.isNotBlank() ?: false - override fun tryResolve(artifactCoordinates: GenericArtifactCoordinates): Iterable? = with (artifactCoordinates) { - if (this is MavenArtifactCoordinates && (groupId.isValidParam() || artifactId.isValidParam())) { - resolveArtifact(groupId.orEmpty(), artifactId.orEmpty(), version.orEmpty()) - } else { - val artifactType = string.substringAfterLast('@', "").trim() - val stringCoordinates = if (artifactType.isNotEmpty()) string.removeSuffix("@$artifactType") else string - if (stringCoordinates.isValidParam() && stringCoordinates.count { it == ':' }.let { it == 2 || it == 3 }) { - val artifactId = stringCoordinates.split(':') + override fun acceptsArtifact(artifactCoordinates: String): Boolean = with(artifactCoordinates) { + isValidParam() && count { it == ':' }.let { it == 2 || it == 3 } + } + + override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean = + repositoryCoordinates.toRepositoryUrlOrNull() != null + + override suspend fun resolve(artifactCoordinates: String): ResultWithDiagnostics> { + + val artifactType = artifactCoordinates.substringAfterLast('@', "").trim() + val stringCoordinates = if (artifactType.isNotEmpty()) artifactCoordinates.removeSuffix("@$artifactType") else artifactCoordinates + return if (acceptsArtifact(stringCoordinates)) { + val artifactId = stringCoordinates.split(':') + try { resolveArtifact( artifactId[0], artifactId[1], artifactId[2], if (artifactId.size > 3) artifactId[3] else null, if (artifactType.isNotEmpty()) artifactType else null ) - } else { - error("Unrecognized set of arguments to ivy resolver: $stringCoordinates") + } catch (e: Exception) { + makeFailureResult(e.asDiagnostics()) } + } else { + makeFailureResult("Unrecognized set of arguments to ivy resolver: $stringCoordinates") } } @@ -54,7 +60,7 @@ class IvyResolver : GenericRepositoryWithBridge { private fun resolveArtifact( groupId: String, artifactName: String, revision: String, conf: String? = null, type: String? = null - ): List { + ): ResultWithDiagnostics> { if (ivyResolvers.isEmpty() || ivyResolvers.none { it.name == "central" }) { ivyResolvers.add( @@ -111,22 +117,23 @@ class IvyResolver : GenericRepositoryWithBridge { XmlModuleDescriptorWriter.write(moduleDescriptor, ivyFile) val report = ivy.resolve(ivyFile.toURI().toURL(), resolveOptions) - return report.allArtifactsReports.map { it.localFile } + val diagnostics = report.allProblemMessages.map { it.asErrorDiagnostics() } + + return if (report.hasError()) makeFailureResult(diagnostics) + else report.allArtifactsReports.map { it.localFile }.asSuccess(diagnostics) } - override fun tryAddRepository(repositoryCoordinates: GenericRepositoryCoordinates): Boolean { - val url = repositoryCoordinates.url + override fun addRepository(repositoryCoordinates: RepositoryCoordinates) { + val url = repositoryCoordinates.toRepositoryUrlOrNull() if (url != null) { ivyResolvers.add( IBiblioResolver().apply { isM2compatible = true - name = repositoryCoordinates.name.takeIf { it.isValidParam() } ?: url.host + name = url.host root = url.toExternalForm() } ) - return true } - return false } companion object { @@ -135,6 +142,3 @@ class IvyResolver : GenericRepositoryWithBridge { } } } - -class FilesAndIvyResolver : - KotlinAnnotatedScriptDependenciesResolver(emptyList(), arrayListOf(DirectResolver(), IvyResolver()).asIterable()) diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/resolve.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/resolve.kt new file mode 100644 index 00000000000..a3d2e4964ad --- /dev/null +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/resolve.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.mainKts.impl + +import org.jetbrains.kotlin.script.util.DependsOn +import org.jetbrains.kotlin.script.util.Repository +import java.io.File +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.flatMapSuccess +import kotlin.script.experimental.api.makeFailureResult +import kotlin.script.experimental.dependencies.ExternalDependenciesResolver +import kotlin.script.experimental.dependencies.tryAddRepository + +suspend fun resolveFromAnnotations(resolver: ExternalDependenciesResolver, annotations: Iterable): ResultWithDiagnostics> { + annotations.forEach { annotation -> + when (annotation) { + is Repository -> { + val repositoryCoordinates = with(annotation) { value.takeIf { it.isNotBlank() } ?: url } + if (!resolver.tryAddRepository(repositoryCoordinates)) + return makeFailureResult("Unrecognized repository coordinates: $repositoryCoordinates") + } + is DependsOn -> {} + else -> return makeFailureResult("Unknown annotation ${annotation.javaClass}") + } + } + return annotations.filterIsInstance(DependsOn::class.java).flatMapSuccess { dep -> + val artifactCoordinates = + if (dep.value.isNotBlank()) dep.value + else listOf(dep.groupId, dep.artifactId, dep.version).filter { it?.isNotBlank() ?: false }.joinToString(":") + resolver.resolve(artifactCoordinates) + } +} + diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt index 81b8dcd0b65..62c4efb8862 100644 --- a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt @@ -5,7 +5,9 @@ package org.jetbrains.kotlin.mainKts -import org.jetbrains.kotlin.mainKts.impl.FilesAndIvyResolver +import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.mainKts.impl.IvyResolver +import org.jetbrains.kotlin.mainKts.impl.resolveFromAnnotations import org.jetbrains.kotlin.script.util.CompilerOptions import org.jetbrains.kotlin.script.util.DependsOn import org.jetbrains.kotlin.script.util.Import @@ -15,6 +17,8 @@ import kotlin.script.dependencies.ScriptContents import kotlin.script.dependencies.ScriptDependenciesResolver import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.* +import kotlin.script.experimental.dependencies.CompoundDependenciesResolver +import kotlin.script.experimental.dependencies.FileSystemDependenciesResolver import kotlin.script.experimental.host.FileBasedScriptSource import kotlin.script.experimental.host.FileScriptSource import kotlin.script.experimental.jvm.compat.mapLegacyDiagnosticSeverity @@ -60,7 +64,7 @@ object MainKtsEvaluationConfiguration : ScriptEvaluationConfiguration( ) class MainKtsConfigurator : RefineScriptCompilationConfigurationHandler { - private val resolver = FilesAndIvyResolver() + private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), IvyResolver()) override operator fun invoke(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics = processAnnotations(context) @@ -92,23 +96,21 @@ class MainKtsConfigurator : RefineScriptCompilationConfigurationHandler { (it as? CompilerOptions)?.options?.toList() ?: emptyList() } - val resolvedClassPath = try { - val scriptContents = object : ScriptContents { - override val annotations: Iterable = annotations.filter { it is DependsOn || it is Repository } - override val file: File? = null - override val text: CharSequence? = null + val resolveResult = try { + runBlocking { + resolveFromAnnotations(resolver, annotations.filter { it is DependsOn || it is Repository }) } - resolver.resolve(scriptContents, emptyMap(), ::report, null).get()?.classpath?.toList() - // TODO: add diagnostics } catch (e: Throwable) { - return ResultWithDiagnostics.Failure(*diagnostics.toTypedArray(), e.asDiagnostics(path = context.script.locationId)) + ResultWithDiagnostics.Failure(*diagnostics.toTypedArray(), e.asDiagnostics(path = context.script.locationId)) } - return ScriptCompilationConfiguration(context.compilationConfiguration) { - if (resolvedClassPath != null) updateClasspath(resolvedClassPath) - if (importedSources.isNotEmpty()) importScripts.append(importedSources) - if (compileOptions.isNotEmpty()) compilerOptions.append(compileOptions) - }.asSuccess(diagnostics) + return resolveResult.onSuccess { resolvedClassPath -> + ScriptCompilationConfiguration(context.compilationConfiguration) { + if (resolvedClassPath != null) updateClasspath(resolvedClassPath) + if (importedSources.isNotEmpty()) importScripts.append(importedSources) + if (compileOptions.isNotEmpty()) compilerOptions.append(compileOptions) + }.asSuccess() + } } }