From 79bad24941029e0c44cc935af90e2778412e76ed Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Fri, 10 Aug 2018 11:19:18 +0200 Subject: [PATCH] Simple ivy resolver with test --- .../tools/kotlin-script-util/build.gradle.kts | 2 + .../kotlin-script-util/build.gradle.kts.173 | 2 + .../kotlin-script-util/build.gradle.kts.as32 | 2 + .../jetbrains/kotlin/script/util/resolve.kt | 20 ++-- .../kotlin/script/util/resolvers/basic.kt | 48 +++++++-- .../kotlin/script/util/resolvers/ivy.kt | 102 ++++++++++++++++++ .../kotlin/script/util/resolvers/maven.kt | 10 +- .../kotlin/script/util/templates/templates.kt | 4 + .../kotlin/script/util/ScriptUtilIT.kt | 15 +++ 9 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/ivy.kt diff --git a/libraries/tools/kotlin-script-util/build.gradle.kts b/libraries/tools/kotlin-script-util/build.gradle.kts index 72cb47c909d..bd216de5e51 100644 --- a/libraries/tools/kotlin-script-util/build.gradle.kts +++ b/libraries/tools/kotlin-script-util/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { compileOnly("com.jcabi:jcabi-aether:0.10.1") compileOnly("org.sonatype.aether:aether-api:1.13.1") compileOnly("org.apache.maven:maven-core:3.0.3") + compileOnly("org.apache.ivy:ivy:2.4.0") testCompileOnly(project(":compiler:cli")) testCompile(project(":kotlin-test:kotlin-test-junit")) testRuntime(project(":kotlin-reflect")) @@ -23,6 +24,7 @@ dependencies { testRuntime("com.jcabi:jcabi-aether:0.10.1") testRuntime("org.sonatype.aether:aether-api:1.13.1") testRuntime("org.apache.maven:maven-core:3.0.3") + testRuntime("org.apache.ivy:ivy:2.4.0") compileOnly(intellijDep()) { includeJars("openapi", "util") } testCompile(intellijDep()) { includeJars("openapi", "platform-api", "util") } } diff --git a/libraries/tools/kotlin-script-util/build.gradle.kts.173 b/libraries/tools/kotlin-script-util/build.gradle.kts.173 index 9eced250070..d299b523f36 100644 --- a/libraries/tools/kotlin-script-util/build.gradle.kts.173 +++ b/libraries/tools/kotlin-script-util/build.gradle.kts.173 @@ -15,6 +15,7 @@ dependencies { compileOnly("com.jcabi:jcabi-aether:0.10.1") compileOnly("org.sonatype.aether:aether-api:1.13.1") compileOnly("org.apache.maven:maven-core:3.0.3") + compileOnly("org.apache.ivy:ivy:2.4.0") testCompileOnly(project(":compiler:cli")) testCompile(project(":kotlin-test:kotlin-test-junit")) testRuntime(project(":kotlin-reflect")) @@ -23,6 +24,7 @@ dependencies { testRuntime("com.jcabi:jcabi-aether:0.10.1") testRuntime("org.sonatype.aether:aether-api:1.13.1") testRuntime("org.apache.maven:maven-core:3.0.3") + testRuntime("org.apache.ivy:ivy:2.4.0") compileOnly(intellijDep()) { includeJars("openapi", "util") } testCompile(intellijDep()) { includeJars("openapi", "util") } } diff --git a/libraries/tools/kotlin-script-util/build.gradle.kts.as32 b/libraries/tools/kotlin-script-util/build.gradle.kts.as32 index 72cb47c909d..bd216de5e51 100644 --- a/libraries/tools/kotlin-script-util/build.gradle.kts.as32 +++ b/libraries/tools/kotlin-script-util/build.gradle.kts.as32 @@ -15,6 +15,7 @@ dependencies { compileOnly("com.jcabi:jcabi-aether:0.10.1") compileOnly("org.sonatype.aether:aether-api:1.13.1") compileOnly("org.apache.maven:maven-core:3.0.3") + compileOnly("org.apache.ivy:ivy:2.4.0") testCompileOnly(project(":compiler:cli")) testCompile(project(":kotlin-test:kotlin-test-junit")) testRuntime(project(":kotlin-reflect")) @@ -23,6 +24,7 @@ dependencies { testRuntime("com.jcabi:jcabi-aether:0.10.1") testRuntime("org.sonatype.aether:aether-api:1.13.1") testRuntime("org.apache.maven:maven-core:3.0.3") + testRuntime("org.apache.ivy:ivy:2.4.0") compileOnly(intellijDep()) { includeJars("openapi", "util") } testCompile(intellijDep()) { includeJars("openapi", "platform-api", "util") } } diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt index 28304306935..fb8f26fc6f8 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt @@ -17,10 +17,8 @@ package org.jetbrains.kotlin.script.util import org.jetbrains.kotlin.script.InvalidScriptResolverAnnotation -import org.jetbrains.kotlin.script.util.resolvers.DirectResolver -import org.jetbrains.kotlin.script.util.resolvers.FlatLibDirectoryResolver -import org.jetbrains.kotlin.script.util.resolvers.MavenResolver -import org.jetbrains.kotlin.script.util.resolvers.Resolver +import org.jetbrains.kotlin.script.util.resolvers.* +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.io.File import java.util.concurrent.Future import kotlin.script.dependencies.KotlinScriptExternalDependencies @@ -54,9 +52,14 @@ open class KotlinAnnotatedScriptDependenciesResolver(val baseClassPath: List { script.annotations.forEach { annotation -> when (annotation) { - is Repository -> FlatLibDirectoryResolver.tryCreate(annotation)?.apply { resolvers.add(this) } - ?: resolvers.find { it is MavenResolver }?.takeIf { (it as MavenResolver).tryAddRepo(annotation) } - ?: throw IllegalArgumentException("Illegal argument for Repository annotation: $annotation") + is Repository -> { + val isFlat: Boolean = resolvers.firstIsInstanceOrNull()?.tryAddRepo(annotation) + ?: (FlatLibDirectoryResolver.tryCreate(annotation)?.also { resolvers.add(it) } != null) + if (!isFlat) { + resolvers.find { it !is FlatLibDirectoryResolver && it.tryAddRepo(annotation) } + ?: throw IllegalArgumentException("Illegal argument for Repository annotation: $annotation") + } + } is DependsOn -> {} is InvalidScriptResolverAnnotation -> throw Exception("Invalid annotation ${annotation.name}", annotation.error) else -> throw Exception("Unknown annotation ${annotation.javaClass}") @@ -74,3 +77,6 @@ class LocalFilesResolver : class FilesAndMavenResolver : KotlinAnnotatedScriptDependenciesResolver(emptyList(), arrayListOf(DirectResolver(), MavenResolver())) + +class FilesAndIvyResolver : + KotlinAnnotatedScriptDependenciesResolver(emptyList(), arrayListOf(DirectResolver(), IvyResolver())) diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt index 3a93d235bec..8e120ccd229 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/basic.kt @@ -19,28 +19,64 @@ package org.jetbrains.kotlin.script.util.resolvers import org.jetbrains.kotlin.script.util.DependsOn import org.jetbrains.kotlin.script.util.Repository import java.io.File +import java.net.MalformedURLException +import java.net.URL interface Resolver { fun tryResolve(dependsOn: DependsOn): Iterable? + fun tryAddRepo(annotation: Repository): Boolean } class DirectResolver : Resolver { override fun tryResolve(dependsOn: DependsOn): Iterable? = - dependsOn.value.takeUnless(String::isBlank)?.let(::File)?.takeIf { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) } + dependsOn.value.takeUnless(String::isBlank)?.let(::File)?.takeIf { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) } + + override fun tryAddRepo(annotation: Repository): Boolean = false } -class FlatLibDirectoryResolver(val path: File) : Resolver { +class FlatLibDirectoryResolver(vararg paths: File) : Resolver { + + private val localRepos = arrayListOf() init { - if (!path.exists() || !path.isDirectory) throw IllegalArgumentException("Invalid flat lib directory repository path '$path'") + for (path in paths) { + if (!path.exists() || !path.isDirectory) throw IllegalArgumentException("Invalid flat lib directory repository path '$path'") + } + localRepos.addAll(paths) } - override fun tryResolve(dependsOn: DependsOn): Iterable? = + override fun tryResolve(dependsOn: DependsOn): Iterable? { + for (path in localRepos) { // TODO: add coordinates and wildcard matching - dependsOn.value.takeUnless(String::isBlank)?.let { File(path, it) }?.takeIf { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) } + val res = dependsOn.value.takeUnless(String::isBlank) + ?.let { File(path, it) } + ?.takeIf { it.exists() && (it.isFile || it.isDirectory) } + if (res != null) return listOf(res) + } + return null + } + + override fun tryAddRepo(annotation: Repository): Boolean { + val urlStr = annotation.url.takeIf { it.isNotBlank() } ?: annotation.value.takeIf { it.isNotBlank() } ?: return false + val dirFromUrl = urlStr.toRepositoryUrlOrNull()?.takeIf { it.protocol == "file" }?.path + val repoDir = (dirFromUrl ?: urlStr).toRepositoryFileOrNull() ?: return false + localRepos.add(repoDir) + return true + } companion object { fun tryCreate(annotation: Repository): FlatLibDirectoryResolver? = - annotation.value.takeUnless(String::isBlank)?.let(::File)?.takeIf { it.exists() && it.isDirectory }?.let(::FlatLibDirectoryResolver) + annotation.value.takeUnless(String::isBlank)?.let(::File)?.takeIf { it.exists() && it.isDirectory } + ?.let { FlatLibDirectoryResolver(it) } } } + +internal fun String.toRepositoryUrlOrNull(): URL? = + try { + URL(this) + } catch (_: MalformedURLException) { + null + } + +internal fun String.toRepositoryFileOrNull(): File? = + File(this).takeIf { it.exists() && it.isDirectory } diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/ivy.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/ivy.kt new file mode 100644 index 00000000000..322eb758da1 --- /dev/null +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/ivy.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.script.util.resolvers + +import org.apache.ivy.Ivy +import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor +import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor +import org.apache.ivy.core.module.id.ModuleRevisionId +import org.apache.ivy.core.resolve.ResolveOptions +import org.apache.ivy.core.settings.IvySettings +import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter +import org.apache.ivy.plugins.resolver.URLResolver +import org.jetbrains.kotlin.script.util.DependsOn +import org.jetbrains.kotlin.script.util.Repository +import java.io.File + +class IvyResolver : Resolver { + + private fun String.isValidParam() = isNotBlank() + + override fun tryResolve(dependsOn: DependsOn): Iterable? { + val artifactId = when { + dependsOn.groupId.isValidParam() || dependsOn.artifactId.isValidParam() -> { + listOf(dependsOn.groupId, dependsOn.artifactId, dependsOn.version) + } + dependsOn.value.isValidParam() && dependsOn.value.count { it == ':' } == 2 -> { + dependsOn.value.split(':') + } + else -> { + error("Unknown set of arguments to maven resolver: ${dependsOn.value}") + } + } + return resolveArtifact(artifactId) + } + + private val ivyResolvers = arrayListOf() + + private fun resolveArtifact(artifactId: List): List { + + if (ivyResolvers.isEmpty()) { + ivyResolvers.add( + URLResolver().apply { + isM2compatible = true + name = "central" + addArtifactPattern("http://repo1.maven.org/maven2/$DEFAULT_ARTIFACT_PATTERN") + } + ) + } + val ivySettings = IvySettings().apply { + for (resolver in ivyResolvers) { + addResolver(resolver) + } + setDefaultResolver(ivyResolvers.first().name) + } + + val ivy = Ivy.newInstance(ivySettings) + + val ivyfile = File.createTempFile("ivy", ".xml") + ivyfile.deleteOnExit() + + val moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance( + ModuleRevisionId.newInstance(artifactId[0], artifactId[1] + "-caller", "working") + ) + + val depsDescriptor = DefaultDependencyDescriptor( + moduleDescriptor, + ModuleRevisionId.newInstance(artifactId[0], artifactId[1], artifactId[2]), + false, false, true + ) + moduleDescriptor.addDependency(depsDescriptor) + + //creates an ivy configuration file + XmlModuleDescriptorWriter.write(moduleDescriptor, ivyfile) + + val resolveOptions = ResolveOptions().setConfs(arrayOf("default")) + + //init resolve report + val report = ivy.resolve(ivyfile.toURI().toURL(), resolveOptions) + + return report.allArtifactsReports.map { it.localFile } + } + + override fun tryAddRepo(annotation: Repository): Boolean { + val urlStr = annotation.url.takeIf { it.isValidParam() } ?: annotation.value.takeIf { it.isValidParam() } ?: return false + val url = urlStr.toRepositoryUrlOrNull() ?: return false + ivyResolvers.add( + URLResolver().apply { + isM2compatible = true + name = annotation.id.takeIf { it.isValidParam() } ?: url.host + addArtifactPattern("${url.toString().let { if (it.endsWith('/')) it else "$it/" }}$DEFAULT_ARTIFACT_PATTERN") + } + ) + return true + } + + companion object { + const val DEFAULT_ARTIFACT_PATTERN = "[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]" + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt index 359c7703b10..4bf280df620 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolvers/maven.kt @@ -26,8 +26,6 @@ import org.sonatype.aether.resolution.DependencyResolutionException import org.sonatype.aether.util.artifact.DefaultArtifact import org.sonatype.aether.util.artifact.JavaScopes import java.io.File -import java.net.MalformedURLException -import java.net.URL import java.util.* val mavenCentral = RemoteRepository("maven-central", "default", "https://repo.maven.apache.org/maven2/") @@ -78,13 +76,9 @@ class MavenResolver(val reportError: ((String) -> Unit)? = null): Resolver { return null } - fun tryAddRepo(annotation: Repository): Boolean { + override fun tryAddRepo(annotation: Repository): Boolean { val urlStr = annotation.url.takeIf { it.isValidParam() } ?: annotation.value.takeIf { it.isValidParam() } ?: return false - try { - URL(urlStr) - } catch (_: MalformedURLException) { - return false - } + urlStr.toRepositoryUrlOrNull() ?: return false repos.add( RemoteRepository( if (annotation.id.isValidParam()) annotation.id else "central", diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt index 33982dd6309..a6e70e6941b 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.script.util.templates +import org.jetbrains.kotlin.script.util.FilesAndIvyResolver import org.jetbrains.kotlin.script.util.FilesAndMavenResolver import org.jetbrains.kotlin.script.util.LocalFilesResolver import kotlin.script.templates.ScriptTemplateDefinition @@ -28,6 +29,9 @@ abstract class StandardArgsScriptTemplateWithLocalResolving(val args: Array) +@ScriptTemplateDefinition(resolver = FilesAndIvyResolver::class, scriptFilePattern = ".*\\.kts") +abstract class StandardArgsScriptTemplateWithIvyResolving(val args: Array) + @ScriptTemplateDefinition(resolver = LocalFilesResolver::class, scriptFilePattern = ".*\\.kts") abstract class BindingsScriptTemplateWithLocalResolving(val bindings: Map) diff --git a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt index 8bf6c7537c4..78175ed3566 100644 --- a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt +++ b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.script.util.templates.BindingsScriptTemplateWithLocalResolving +import org.jetbrains.kotlin.script.util.templates.StandardArgsScriptTemplateWithIvyResolving import org.jetbrains.kotlin.script.util.templates.StandardArgsScriptTemplateWithLocalResolving import org.jetbrains.kotlin.script.util.templates.StandardArgsScriptTemplateWithMavenResolving import org.jetbrains.kotlin.utils.PathUtil.getResourcePathForClass @@ -108,6 +109,20 @@ done } } + @Test + fun testIvyResolveStdJUnitHelloWorld() { + val scriptClass = compileScript("args-junit-hello-world.kts", StandardArgsScriptTemplateWithIvyResolving::class) + if (scriptClass == null) { + System.err.println(classpathFromClassloader(Thread.currentThread().contextClassLoader)?.takeIfContainsAll(KOTLIN_JAVA_RUNTIME_JAR)?.joinToString()) + } + Assert.assertNotNull(scriptClass) + captureOut { + scriptClass!!.getConstructor(Array::class.java)!!.newInstance(arrayOf("a1")) + }.let { + Assert.assertEquals(argsHelloWorldOutput.linesSplitTrim(), it.linesSplitTrim()) + } + } + private fun compileScript( scriptFileName: String, scriptTemplate: KClass,