Switch main-kts to the updated resolvers infrastructure

This commit is contained in:
Ilya Chernikov
2019-11-15 16:31:11 +01:00
parent baec10fb40
commit b73625ad9e
7 changed files with 106 additions and 47 deletions
@@ -123,15 +123,30 @@ inline fun <R1, R2> ResultWithDiagnostics<R1>.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<T, R> Iterable<T>.mapSuccess(body: (T) -> ResultWithDiagnostics<R>): ResultWithDiagnostics<List<R>> {
inline fun<T, R> Iterable<T>.mapSuccess(body: (T) -> ResultWithDiagnostics<R>): ResultWithDiagnostics<List<R>> =
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<T, R> Iterable<T>.flatMapSuccess(body: (T) -> ResultWithDiagnostics<Collection<R>>): ResultWithDiagnostics<List<R>> =
mapSuccessImpl(body) { results, r ->
results.addAll(r)
}
inline fun<T, R1, R2> Iterable<T>.mapSuccessImpl(body: (T) -> ResultWithDiagnostics<R1>, updateResults: (MutableList<R2>, R1) -> Unit): ResultWithDiagnostics<List<R2>> {
val reports = ArrayList<ScriptDiagnostic>()
val results = ArrayList<R>()
val results = ArrayList<R2>()
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)
@@ -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))
}
@@ -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
@@ -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 }
@@ -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<File>? = 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<List<File>> {
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<File> {
): ResultWithDiagnostics<List<File>> {
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())
@@ -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<Annotation>): ResultWithDiagnostics<List<File>> {
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)
}
}
@@ -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<ScriptCompilationConfiguration> =
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<Annotation> = 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()
}
}
}