From ae450e4cbc103afb77c8a349c991b05f974b7605 Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Wed, 1 Feb 2017 12:58:59 -0800 Subject: [PATCH] Fix optional dependencies problem. --- kobalt/src/Build.kt | 9 +- .../kotlin/com/beust/kobalt/JarGenerator.kt | 2 +- .../beust/kobalt/api/IDependencyManager.kt | 5 +- .../beust/kobalt/maven/DependencyManager.kt | 32 +++-- .../beust/kobalt/maven/DependencyManager2.kt | 32 +++-- .../com/beust/kobalt/maven/aether/Aether.kt | 133 +++++++++++------- .../com/beust/kobalt/maven/aether/Booter.kt | 15 ++ .../com/beust/kobalt/maven/aether/Filters.kt | 15 ++ .../com/beust/kobalt/app/BuildFileCompiler.kt | 14 +- .../com/beust/kobalt/app/BuildScriptUtil.kt | 15 +- .../com/beust/kobalt/app/ParsedBuildFile.kt | 4 +- .../plugin/application/ApplicationPlugin.kt | 2 +- .../kobalt/maven/DependencyManagerTest.kt | 115 ++++++++++----- .../com/beust/kobalt/maven/DownloadTest.kt | 2 +- .../com/beust/kobalt/misc/AetherTest.kt | 55 ++++++++ 15 files changed, 323 insertions(+), 127 deletions(-) create mode 100644 modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Filters.kt create mode 100644 src/test/kotlin/com/beust/kobalt/misc/AetherTest.kt diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt index b8d2d8d9..3563f46c 100644 --- a/kobalt/src/Build.kt +++ b/kobalt/src/Build.kt @@ -26,7 +26,7 @@ object Versions { val okio = "1.6.0" val retrofit = "2.1.0" val gson = "2.6.2" - val aether = "1.1.0" + val aether = "1.0.0.v20140518" val sonatypeAether = "1.13.1" val maven = "3.3.9" } @@ -102,6 +102,7 @@ val kobaltPluginApi = project { "org.slf4j:slf4j-nop:1.6.0", "org.eclipse.aether:aether-spi:${Versions.aether}", + "org.eclipse.aether:aether-util:${Versions.aether}", "org.eclipse.aether:aether-impl:${Versions.aether}", "org.eclipse.aether:aether-connector-basic:${Versions.aether}", "org.eclipse.aether:aether-transport-file:${Versions.aether}", @@ -158,6 +159,7 @@ val kobaltApp = project(kobaltPluginApi, wrapper) { "com.squareup.retrofit2:converter-gson:${Versions.retrofit}", "com.squareup.okhttp3:okhttp-ws:${Versions.okhttp}", "biz.aQute.bnd:bndlib:2.4.0", + "org.sonatype.aether:aether-api:${Versions.sonatypeAether}", "com.squareup.okhttp3:logging-interceptor:3.2.0", @@ -176,7 +178,10 @@ val kobaltApp = project(kobaltPluginApi, wrapper) { dependenciesTest { compile("org.testng:testng:6.9.11", - "org.assertj:assertj-core:3.4.1") + "org.assertj:assertj-core:3.4.1", + "org.eclipse.aether:aether-spi:${Versions.aether}", + "org.eclipse.aether:aether-util:${Versions.aether}" + ) } assemble { diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt index 0edc4ffc..aeef361c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt @@ -104,7 +104,7 @@ class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) context.variant.productFlavor.compileDependencies + context.variant.productFlavor.compileRuntimeDependencies val transitiveDependencies = dependencyManager.calculateDependencies(project, context, - listOf(Scope.COMPILE), allDependencies) + scopes = listOf(Scope.COMPILE), passedDependencies = allDependencies) transitiveDependencies.map { it.jarFile.get() }.forEach { file : File -> diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt index ab80cd3f..ce1778aa 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt @@ -1,6 +1,8 @@ package com.beust.kobalt.api +import com.beust.kobalt.maven.aether.Filters import com.beust.kobalt.maven.aether.Scope +import org.eclipse.aether.graph.DependencyFilter /** * Manage the creation of dependencies and also provide dependencies for projects. @@ -36,6 +38,7 @@ interface IDependencyManager { * allDependencies is typically either compileDependencies or testDependencies */ fun calculateDependencies(project: Project?, context: KobaltContext, - scopeFilters: Collection = listOf(Scope.COMPILE), + dependencyFilter: DependencyFilter = Filters.EXCLUDE_OPTIONAL_FILTER, + scopes: List = listOf(Scope.COMPILE), vararg passedDependencies: List): List } \ No newline at end of file diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt index 7792455d..4b11a42b 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt @@ -2,12 +2,15 @@ package com.beust.kobalt.maven import com.beust.kobalt.KobaltException import com.beust.kobalt.api.* +import com.beust.kobalt.maven.aether.Filters import com.beust.kobalt.maven.aether.KobaltAether import com.beust.kobalt.maven.aether.Scope import com.beust.kobalt.maven.dependency.FileDependency import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.KobaltExecutors import com.google.common.collect.ArrayListMultimap +import org.eclipse.aether.graph.DependencyFilter +import org.eclipse.aether.util.filter.OrDependencyFilter import java.io.File import java.util.* import javax.inject.Inject @@ -92,7 +95,8 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val * are passed, they are calculated from the scope filters. */ override fun calculateDependencies(project: Project?, context: KobaltContext, - scopeFilters: Collection, + dependencyFilter: DependencyFilter, + scopes: List, vararg passedDependencies: List): List { val result = arrayListOf() @@ -100,7 +104,7 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val * Extract the correct dependencies from the project based on the scope filters. */ fun filtersToDependencies(project: Project, scopes: Collection): List { - return arrayListOf().apply { + val result = arrayListOf().apply { if (scopes.contains(Scope.COMPILE)) { addAll(project.compileDependencies) } @@ -111,17 +115,18 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val addAll(project.testDependencies) } } + return result } val allDependencies : Array> = if (project == null || passedDependencies.any()) passedDependencies - else arrayOf(filtersToDependencies(project, scopeFilters)) + else arrayOf(filtersToDependencies(project, scopes)) allDependencies.forEach { dependencies -> - result.addAll(transitiveClosure(dependencies, scopeFilters, project?.name)) + result.addAll(transitiveClosure(dependencies, dependencyFilter, project?.name)) } result.addAll(runClasspathContributors(project, context)) - result.addAll(dependentProjectDependencies(project, context, scopeFilters)) + result.addAll(dependentProjectDependencies(project, context, dependencyFilter, scopes.contains(Scope.TEST))) // Dependencies get reordered by transitiveClosure() but since we just added a bunch of new ones, // we need to reorder them again in case we're adding dependencies that are already present @@ -144,13 +149,13 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val * TODO: This should be private, everyone should be calling calculateDependencies(). */ fun transitiveClosure(dependencies : List, - scopeFilter: Collection = emptyList(), + dependencyFilter: DependencyFilter? = null, requiredBy: String? = null): List { val result = arrayListOf() dependencies.forEach { result.add(it) if (it.isMaven) { - val resolved = aether.resolveAll(it.id, null, scopeFilter).map { it.toString() } + val resolved = aether.resolveAll(it.id, null, dependencyFilter) result.addAll(resolved.map { create(it) }) } } @@ -183,7 +188,7 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val * their own dependencies */ private fun dependentProjectDependencies(project: Project?, context: KobaltContext, - scopeFilters: Collection): List { + dependencyFilter: DependencyFilter, isTest: Boolean): List { if (project == null) { return emptyList() } else { @@ -199,8 +204,8 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val project.dependsOn.forEach { p -> maybeAddClassDir(KFiles.joinDir(p.directory, p.classesDir(context))) - if (scopeFilters.contains(Scope.TEST)) maybeAddClassDir(KFiles.makeOutputTestDir(project).path) - val otherDependencies = calculateDependencies(p, context, scopeFilters) + if (isTest) maybeAddClassDir(KFiles.makeOutputTestDir(project).path) + val otherDependencies = calculateDependencies(p, context, dependencyFilter, Scope.toScopes(isTest)) result.addAll(otherDependencies) } @@ -225,8 +230,13 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val deps.add(testProvidedDependencies) scopeFilters.add(Scope.TEST) } + val filter = + if (isTest) OrDependencyFilter(Filters.COMPILE_FILTER, Filters.TEST_FILTER) + else Filters.COMPILE_FILTER deps.filter { it.any() }.forEach { - transitive.addAll(calculateDependencies(project, context, scopeFilters, it)) + transitive.addAll(calculateDependencies(project, context, filter, + scopes = Scope.toScopes(isTest), + passedDependencies = it)) } } } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager2.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager2.kt index bba0a85a..839f4e91 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager2.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager2.kt @@ -9,6 +9,7 @@ import com.beust.kobalt.maven.dependency.FileDependency import com.beust.kobalt.misc.KFiles import com.google.common.collect.ArrayListMultimap import com.google.inject.Inject +import org.eclipse.aether.graph.DependencyFilter import java.io.File import java.util.* @@ -59,18 +60,18 @@ class DependencyManager2 @Inject constructor(val aether: KobaltAether) { * Resolve the dependencies for the give project based on the scope filters. */ fun resolve(project: Project, context: KobaltContext, isTest: Boolean, - passedScopeFilters : List = emptyList(), + passedScopes : List = emptyList(), passedIds: List = emptyList()): List { val result = hashSetOf() val nonMavenDependencies = hashSetOf() - val scopeFilters = - if (passedScopeFilters.isEmpty()) - if (isTest) listOf(Scope.TEST) + val actualScopes = + if (passedScopes.isEmpty()) + if (isTest) listOf(Scope.TEST, Scope.COMPILE) else listOf(Scope.COMPILE) - else passedScopeFilters + else passedScopes - val toDependencies = Scope.toDependencyLambda(scopeFilters) + val toDependencies = Scope.toDependencyLambda(actualScopes) // Make sure that classes/ and test-classes/ are always at the top of this classpath, // so that older versions of that project on the classpath don't shadow them @@ -82,7 +83,7 @@ class DependencyManager2 @Inject constructor(val aether: KobaltAether) { // Passed and direct ids val ids = hashSetOf().apply { addAll(passedIds) - addAll(toDependencies(project)) + addAll(toDependencies(project).filter { ! it.optional }) } // Contributed id's @@ -105,8 +106,9 @@ class DependencyManager2 @Inject constructor(val aether: KobaltAether) { var i = 0 ids.forEach { if (it.isMaven) { - val resolved = aether.resolveAll(it.id, filterScopes = scopeFilters) - .map { create(it.toString(), false, project.directory) } + result.add(it) + val resolved = aether.resolveAll(it.id, dependencyFilter = scopesToDependencyFilter(actualScopes)) + .map { create(it, false, project.directory) } i++ result.addAll(resolved) } else { @@ -119,6 +121,18 @@ class DependencyManager2 @Inject constructor(val aether: KobaltAether) { return reorderDependencies(result) } + private fun scopesToDependencyFilter(scopes: List, includeOptional: Boolean = false): DependencyFilter { + return DependencyFilter { p0, p1 -> + if (p0.dependency.optional && ! includeOptional) return@DependencyFilter false + + val result = scopes.any { + p0.dependency.scope == "" && scopes.contains(Scope.COMPILE) || + p0.dependency.scope == it.scope + } + result + } + } + /** * Reorder dependencies so that if an artifact appears several times, only the one with the higest version * is included. diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Aether.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Aether.kt index b3d2352e..8986d3b2 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Aether.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Aether.kt @@ -5,7 +5,6 @@ import com.beust.kobalt.api.IClasspathDependency import com.beust.kobalt.api.Kobalt import com.beust.kobalt.api.Project import com.beust.kobalt.internal.KobaltSettings -import com.beust.kobalt.internal.KobaltSettingsXml import com.beust.kobalt.internal.getProxy import com.beust.kobalt.maven.CompletedFuture import com.beust.kobalt.maven.LocalDep @@ -23,7 +22,6 @@ import org.eclipse.aether.collection.CollectRequest import org.eclipse.aether.collection.CollectResult import org.eclipse.aether.graph.Dependency import org.eclipse.aether.graph.DependencyFilter -import org.eclipse.aether.graph.DependencyNode import org.eclipse.aether.repository.ArtifactRepository import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.resolution.DependencyRequest @@ -32,10 +30,7 @@ import org.eclipse.aether.resolution.VersionRangeRequest import org.eclipse.aether.resolution.VersionRangeResult import org.eclipse.aether.transfer.ArtifactNotFoundException import org.eclipse.aether.util.artifact.JavaScopes -import org.eclipse.aether.util.filter.AndDependencyFilter -import org.eclipse.aether.util.filter.DependencyFilterUtils import java.io.File -import java.util.* import java.util.concurrent.Future enum class Scope(val scope: String, val dependencyLambda: (Project) -> List) { @@ -47,26 +42,22 @@ enum class Scope(val scope: String, val dependencyLambda: (Project) -> List): DependencyFilter { - val javaScopes = scopes.map { DependencyFilterUtils.classpathFilter(it.scope) }.toTypedArray() - return AndDependencyFilter(KobaltAether.ExcludeOptionalDependencyFilter(), *javaScopes) - } + fun toScopes(isTest: Boolean) = if (isTest) listOf(Scope.TEST, Scope.COMPILE) else listOf(Scope.COMPILE) /** * @return a lambda that extracts the correct dependencies from a project based on the scope - * filters passed. + * filters passed (excludes optional dependencies). */ fun toDependencyLambda(scopes: Collection) : (Project) -> List { val result = { project : Project -> - scopes.fold(arrayListOf(), + val deps = scopes.fold(arrayListOf(), { list: ArrayList, scope: Scope -> - list.addAll(scope.dependencyLambda(project)) + list.addAll(scope.dependencyLambda(project).filter { ! it.optional }) list }) - } + deps + } + return result } } @@ -94,16 +85,16 @@ class KobaltAether @Inject constructor (val settings: KobaltSettings, val aether DependencyResult(AetherDependency(it.artifact), it.repository.toString()) } - fun resolveAll(id: String, artifactScope: Scope? = null, filterScopes: Collection = emptyList()) + fun resolveAll(id: String, artifactScope: Scope? = null, dependencyFilter: DependencyFilter?) : List { - val results = aether.resolve(DefaultArtifact(id), artifactScope, filterScopes) + val results = aether.resolve(DefaultArtifact(id), artifactScope, dependencyFilter) return results.map { it.artifact.toString() } } - fun resolve(id: String, artifactScope: Scope? = null, filterScopes: Collection = emptyList()) + fun resolve(id: String, artifactScope: Scope? = null, dependencyFilter: DependencyFilter = Filters.COMPILE_FILTER) : DependencyResult { kobaltLog(ConsoleRepositoryListener.LOG_LEVEL, "Resolving $id") - val result = resolveToArtifact(id, artifactScope, filterScopes) + val result = resolveToArtifact(id, artifactScope, dependencyFilter) if (result != null) { return DependencyResult(AetherDependency(result.artifact), result.repository.toString()) } else { @@ -111,34 +102,23 @@ class KobaltAether @Inject constructor (val settings: KobaltSettings, val aether } } - fun resolveToArtifact(id: String, artifactScope: Scope? = null, filterScopes: Collection = emptyList()) + fun resolveToArtifact(id: String, artifactScope: Scope? = null, + dependencyFilter: DependencyFilter? = null) : AetherResult? { kobaltLog(ConsoleRepositoryListener.LOG_LEVEL, "Resolving $id") - val results = aether.resolve(DefaultArtifact(MavenId.toKobaltId(id)), artifactScope, filterScopes) + val results = aether.resolve(DefaultArtifact(MavenId.toKobaltId(id)), artifactScope, dependencyFilter) if (results.size > 0) { return results[0] } else { return null } } - - class ExcludeOptionalDependencyFilter : DependencyFilter { - override fun accept(node: DependencyNode?, p1: MutableList?): Boolean { -// val result = node != null && ! node.dependency.isOptional - val accept1 = node == null || node.artifact.artifactId != "srczip" - val accept2 = node != null && !node.dependency.isOptional - val result = accept1 && accept2 - return result - } - } } @Singleton class Aether(localRepo: File, val settings: KobaltSettings, eventBus: EventBus) { private val system = Booter.newRepositorySystem() private val session = Booter.newRepositorySystemSession(system, localRepo, settings, eventBus) -// private val classpathFilter = Scopes.toFilter(Scopes.COMPILE, Scopes.TEST) -// private val testClasspathFilter = Scopes.toFilter(Scopes.TEST) private val kobaltRepositories: List get() = Kobalt.repos.map { @@ -170,7 +150,7 @@ class Aether(localRepo: File, val settings: KobaltSettings, eventBus: EventBus) if (resolved != null) { val newArtifact = DefaultArtifact(artifact.groupId, artifact.artifactId, artifact.extension, resolved.highestVersion.toString()) - val artifactResult = resolve(newArtifact, null, emptyList()) + val artifactResult = resolve(newArtifact, null) if (artifactResult.any()) { return artifactResult[0] } else { @@ -187,7 +167,9 @@ class Aether(localRepo: File, val settings: KobaltSettings, eventBus: EventBus) return result } - fun resolve(artifact: Artifact, artifactScope: Scope?, filterScopes: Collection): List { + fun resolve(artifact: Artifact, artifactScope: Scope?, + dependencyFilter: DependencyFilter? = null) + : List { fun manageException(ex: Exception, artifact: Artifact): List { if (artifact.extension == "pom") { // Only display a warning for .pom files. Not resolving a .jar or other artifact @@ -198,7 +180,6 @@ class Aether(localRepo: File, val settings: KobaltSettings, eventBus: EventBus) } try { - val scopeFilter = Scope.toFilter(filterScopes) val result = if (KobaltAether.isRangeVersion(artifact.version)) { val request = rangeRequest(artifact) @@ -212,7 +193,8 @@ class Aether(localRepo: File, val settings: KobaltSettings, eventBus: EventBus) throw KobaltException("Couldn't resolve range artifact " + artifact) } } else { - val dependencyRequest = DependencyRequest(collectRequest(artifact, artifactScope), scopeFilter) + val dependencyRequest = DependencyRequest(collectRequest(artifact, artifactScope), dependencyFilter) + try { system.resolveDependencies(session, dependencyRequest).artifactResults.map { AetherResult(it.artifact, it.repository) @@ -256,7 +238,7 @@ class AetherDependency(val artifact: Artifact, override val optional: Boolean = if (file.exists()) { CompletedFuture(file) } else { - val td = aether.resolve(artifact, null, emptyList()) + val td = aether.resolve(artifact, null) if (td.any()) { val newFile = td[0].artifact.file if (newFile != null) { @@ -318,21 +300,38 @@ class AetherDependency(val artifact: Artifact, override val optional: Boolean = override fun toString() = id } -fun main(argv: Array) { - val request = CollectRequest().apply { - root = Dependency(DefaultArtifact("org.testng:testng:6.9.11"), JavaScopes.COMPILE) +fun f(argv: Array) { + val collectRequest = CollectRequest().apply { + root = Dependency(DefaultArtifact("com.squareup.retrofit2:converter-jackson:jar:2.1.0"), JavaScopes.COMPILE) repositories = listOf( - RemoteRepository.Builder("Maven", "default", "http://repo1.maven.org/maven2/").build(), - RemoteRepository.Builder("JCenter", "default", "https://jcenter.bintray.com").build()) - } - val dependencyRequest = DependencyRequest().apply { - collectRequest = request +// RemoteRepository.Builder("Maven", "default", "http://repo1.maven.org/maven2/").build() + RemoteRepository.Builder("JCenter", "default", "https://jcenter.bintray.com").build() + ) } +// val dependencyRequest = DependencyRequest().apply { +// collectRequest = request +// filter = object: DependencyFilter { +// override fun accept(p0: DependencyNode, p1: MutableList?): Boolean { +// if (p0.artifact.artifactId.contains("android")) { +// println("ANDROID") +// } +// return p0.dependency.scope == JavaScopes.COMPILE +// } +// +// } +// } + val dr2 = DependencyRequest(collectRequest, null).apply {} + + +// val system = ManualRepositorySystemFactory.newRepositorySystem() +// val session = DefaultRepositorySystemSession() +// val localRepo = LocalRepository(File("/Users/cedricbeust/t/localAether").absolutePath) +// session.localRepositoryManager = system.newLocalRepositoryManager(session, localRepo) + val system = Booter.newRepositorySystem() - val session = Booter.newRepositorySystemSession(system, File("/tmp"), KobaltSettings(KobaltSettingsXml()), - EventBus()) -// val session = MavenRepositorySystemUtils.newSession(KobaltSettings(KobaltSettingsXml())) - val result = system.resolveDependencies(session, dependencyRequest).artifactResults + val session = Booter.newRepositorySystemSession(system) + + val result = system.resolveDependencies(session, dr2).artifactResults println("RESULT: " + result) // KobaltLogger.LOG_LEVEL = 1 @@ -346,4 +345,36 @@ fun main(argv: Array) { // println("Artifact: " + d) } +fun f2() { + val system = Booter.newRepositorySystem() + + val session = Booter.newRepositorySystemSession(system) + + val artifact = DefaultArtifact("com.squareup.retrofit2:converter-jackson:jar:2.1.0") + +// DependencyFilter classpathFlter = DependencyFilterUtils.classpathFilter( JavaScopes.COMPILE ); + val f2 = DependencyFilter { dependencyNode, list -> + println("ACCEPTING " + dependencyNode) + true + } + + val collectRequest = CollectRequest() + collectRequest.root = Dependency(artifact, JavaScopes.COMPILE) + collectRequest.repositories = listOf( + RemoteRepository.Builder("Maven", "default", "http://repo1.maven.org/maven2/").build() + ) + + val dependencyRequest = DependencyRequest(collectRequest, null) + + val artifactResults = system.resolveDependencies(session, dependencyRequest).artifactResults + + for (artifactResult in artifactResults) { + println(artifactResult.artifact.toString() + " resolved to " + artifactResult.artifact.file) + } +} + + +fun main(args: Array) { + f2() +} diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Booter.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Booter.kt index 0190eee6..f963ff4c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Booter.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Booter.kt @@ -16,6 +16,21 @@ object Booter { // return org.eclipse.aether.examples.plexus.PlexusRepositorySystemFactory.newRepositorySystem(); } + fun newRepositorySystemSession(system: RepositorySystem): DefaultRepositorySystemSession { + val session = org.apache.maven.repository.internal.MavenRepositorySystemUtils.newSession() + + val localRepo = LocalRepository("target/local-repo") + session.localRepositoryManager = system.newLocalRepositoryManager(session, localRepo) + + session.transferListener = ConsoleTransferListener() + session.repositoryListener = ConsoleRepositoryListener(System.out, EventBus()) + + // uncomment to generate dirty trees + // session.setDependencyGraphTransformer( null ); + + return session + } + fun newRepositorySystemSession(system: RepositorySystem, repo: File, settings: KobaltSettings, eventBus: EventBus): DefaultRepositorySystemSession { val session = MavenRepositorySystemUtils.newSession(settings) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Filters.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Filters.kt new file mode 100644 index 00000000..78911d3f --- /dev/null +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/aether/Filters.kt @@ -0,0 +1,15 @@ +package com.beust.kobalt.maven.aether + +import org.eclipse.aether.graph.DependencyFilter +import org.eclipse.aether.util.artifact.JavaScopes + +object Filters { + val COMPILE_FILTER = DependencyFilter { p0, p1 -> + p0.dependency.scope == "" || p0.dependency.scope == JavaScopes.COMPILE + } + val TEST_FILTER = DependencyFilter { p0, p1 -> p0.dependency.scope == JavaScopes.TEST } + + val EXCLUDE_OPTIONAL_FILTER = DependencyFilter { p0, p1 -> + ! p0.dependency.optional + } +} diff --git a/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt b/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt index 3ff43428..5172c842 100644 --- a/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt @@ -46,7 +46,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil private val SCRIPT_JAR = "buildScript.jar" - fun compileBuildFiles(args: Args): FindProjectResult { + fun compileBuildFiles(args: Args, forceRecompile: Boolean = false): FindProjectResult { // // Create the KobaltContext // Note: can't use apply{} here or each field will refer to itself instead of the constructor field @@ -66,7 +66,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil // // Find all the projects in the build file, possibly compiling them // - val projectResult = findProjects(context) + val projectResult = findProjects(context, forceRecompile) return projectResult } @@ -76,7 +76,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil class FindProjectResult(val context: KobaltContext, val projects: List, val pluginUrls: List, val taskResult: TaskResult) - private fun findProjects(context: KobaltContext): FindProjectResult { + private fun findProjects(context: KobaltContext, forceRecompile: Boolean): FindProjectResult { var errorTaskResult: TaskResult? = null val projects = arrayListOf() buildFiles.forEach { buildFile -> @@ -105,7 +105,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil KFiles.saveFile(modifiedBuildFile, parsedBuildFile.buildScriptCode) val taskResult = maybeCompileBuildFile(context, BuildFile(Paths.get(modifiedBuildFile.path), "Modified ${Constants.BUILD_FILE_NAME}", buildFile.realPath), - buildScriptJarFile, pluginUrls) + buildScriptJarFile, pluginUrls, forceRecompile) if (taskResult.success) { projects.addAll(buildScriptUtil.runBuildScriptJarFile(buildScriptJarFile, pluginUrls, context)) } else { @@ -124,7 +124,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil } private fun maybeCompileBuildFile(context: KobaltContext, buildFile: BuildFile, buildScriptJarFile: File, - pluginUrls: List) : TaskResult { + pluginUrls: List, forceRecompile: Boolean) : TaskResult { kobaltLog(2, "Running build file ${buildFile.name} jar: $buildScriptJarFile") // If the user specifed --profiles, always recompile the build file since we don't know if @@ -135,8 +135,8 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil // compiled with. val bs = BuildScriptJarFile(buildScriptJarFile) val same = bs.sameProfiles(args.profiles) - if (same && buildScriptUtil.isUpToDate(buildFile, buildScriptJarFile)) { - kobaltLog(2, " Build file is up to date") + if (same && ! forceRecompile && buildScriptUtil.isUpToDate(buildFile, buildScriptJarFile)) { + kobaltLog(2, " Build file $buildScriptJarFile is up to date") return TaskResult() } else { kobaltLog(2, " Need to recompile ${buildFile.name}") diff --git a/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt b/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt index 0e519d27..d7386161 100644 --- a/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt +++ b/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt @@ -13,6 +13,7 @@ import com.beust.kobalt.internal.build.BuildFile import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.Topological import com.beust.kobalt.misc.kobaltLog +import com.beust.kobalt.misc.warn import com.beust.kobalt.plugin.KobaltPlugin import com.google.inject.Inject import java.io.File @@ -63,11 +64,15 @@ class BuildScriptUtil @Inject constructor(val plugins: Plugins, val files: KFile val name = entry.name; if (name.endsWith(".class")) { val className = name.substring(0, name.length - 6).replace("/", ".") - val cl : Class<*>? = classLoader.loadClass(className) - if (cl != null) { - classes.add(cl) - } else { - throw KobaltException("Couldn't instantiate $className") + try { + val cl: Class<*>? = classLoader.loadClass(className) + if (cl != null) { + classes.add(cl) + } else { + throw KobaltException("Couldn't instantiate $className") + } + } catch(ex: ClassNotFoundException) { + warn("Couldn't find class $className") } } entry = stream.nextJarEntry; diff --git a/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt b/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt index e7777b46..85fb983f 100644 --- a/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt +++ b/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt @@ -162,8 +162,8 @@ class ParsedBuildFile(val buildFile: BuildFile, val context: KobaltContext, val // // Compile the jar file // - val kotlintDeps = dependencyManager.calculateDependencies(null, context) - val deps: List = kotlintDeps.map { it.jarFile.get().absolutePath } + val kotlinDeps = dependencyManager.calculateDependencies(null, context) + val deps: List = kotlinDeps.map { it.jarFile.get().absolutePath } val outputJar = File(buildScriptJarFile.absolutePath) val result = kotlinCompilePrivate { classpath(files.kobaltJar) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt index edd6be7b..4d89daed 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt @@ -112,7 +112,7 @@ class ApplicationPlugin @Inject constructor(val configActor: ConfigActor, vararg ids: String) { ids.forEach { id -> - assertThat(dependencies.any { it.id.contains(id) }).isTrue() + if (! dependencies.any { it.id.contains(id) }) { + throw AssertionError("Couldn't find $id in $dependencies") + } + } + } + + private fun assertDoesNotContain(dependencies: List, vararg ids: String) { + ids.forEach { id -> + if (dependencies.any { it.id.contains(id) }) { + throw AssertionError("$id should not be found in $dependencies") + } } } @@ -27,87 +39,118 @@ class DependencyManagerTest @Inject constructor(val dependencyManager: Dependenc fun testScopeDependenciesShouldBeDownloaded() { val testDeps = listOf(dependencyManager.create("org.testng:testng:6.9.11")) + val filter = AndDependencyFilter(Filters.EXCLUDE_OPTIONAL_FILTER, Filters.COMPILE_FILTER) + // Should only resolve to TestNG - dependencyManager.transitiveClosure(testDeps, listOf(Scope.COMPILE)).let { dependencies -> + dependencyManager.transitiveClosure(testDeps, filter).let { dependencies -> assertThat(dependencies.any { it.id.contains(":jcommander:") }).isFalse() assertContains(dependencies, ":testng:") } // Should resolve to TestNG and its dependencies - dependencyManager.transitiveClosure(testDeps, listOf(Scope.TEST)).let { dependencies -> + dependencyManager.transitiveClosure(testDeps).let { dependencies -> assertContains(dependencies, ":jcommander:") assertContains(dependencies, ":bsh:") assertContains(dependencies, ":ant:") assertContains(dependencies, ":ant-launcher:") assertContains(dependencies, ":testng:") } - } @Test fun honorRuntimeDependenciesBetweenProjects() { + Kobalt.context = null val buildFileString = """ import com.beust.kobalt.* - val lib = project { - name = "lib" + val lib1 = project { + name = "lib1" dependencies { - compile("org.testng:testng:6.9.11") - runtime("com.beust:jcommander:1.48") + compile("com.beust:klaxon:0.26", + "com.beust:jcommander:1.48") } } - val p = project(lib) { - name = "transitive" + val p = project(lib1) { + name = "transitive1" } """ - val compileResult = compileBuildFile(buildFileString, Args(), compilerFactory) + val compileResult = compileBuildFile(sharedBuildFile, Args(), compilerFactory) val project2 = compileResult.projects[1] - val dependencies = dependencyManager.calculateDependencies(project2, Kobalt.context!!, - listOf(Scope.COMPILE, Scope.RUNTIME)) - assertContains(dependencies, ":testng:") - assertContains(dependencies, ":jcommander:") + val dependencies = dependencyManager.calculateDependencies(project2, Kobalt.context!!, Filters.COMPILE_FILTER) + assertContains(dependencies, ":klaxon:") + assertContains(dependencies, ":guice:") + assertDoesNotContain(dependencies, ":guave:") } + val sharedBuildFile = """ + import com.beust.kobalt.* + + val lib2 = project { + name = "lib2" + dependencies { + // pick dependencies that don't have dependencies themselves, to avoid interferences + compile("com.beust:klaxon:0.27", + "com.google.inject:guice:4.0") + runtime("com.beust:jcommander:1.48") + compileOptional("junit:junit:4.12") + } + } + + val p = project(lib2) { + name = "transitive2" + } + """ + @Test fun honorRuntimeDependenciesBetweenProjects2() { val buildFileString = """ import com.beust.kobalt.* - val lib = project { - name = "lib" + val lib2 = project { + name = "lib2" dependencies { - compile("org.testng:testng:6.9.11") + // pick dependencies that don't have dependencies themselves, to avoid interferences + compile("com.beust:klaxon:0.27", + "com.google.inject:guice:4.0) runtime("com.beust:jcommander:1.48") } } - val p = project(lib) { - name = "transitive" + val p = project(lib2) { + name = "transitive2" } """ - val compileResult = compileBuildFile(buildFileString, Args(), compilerFactory) + val compileResult = compileBuildFile(sharedBuildFile, Args(), compilerFactory) val project2 = compileResult.projects[1] - dependencyManager2.resolve(project2, Kobalt.context!!, isTest = false, - passedScopeFilters = listOf(Scope.COMPILE, Scope.RUNTIME)).let { dependencies -> - assertThat(dependencies.size).isEqualTo(4) - assertContains(dependencies, ":testng:") - assertContains(dependencies, ":jcommander:") - } + Kobalt.context!!.let { context -> + dependencyManager2.resolve(project2, context, isTest = false, + passedScopes = listOf(Scope.COMPILE)).let { dependencies -> + assertContains(dependencies, ":klaxon:jar:0.27") + assertContains(dependencies, ":guice:") + assertDoesNotContain(dependencies, ":jcommander:") + assertDoesNotContain(dependencies, ":junit:") + } - dependencyManager2.resolve(project2, Kobalt.context!!, isTest = false, - passedScopeFilters = listOf(Scope.COMPILE)).let { dependencies -> - assertThat(dependencies.size).isEqualTo(3) - assertContains(dependencies, ":testng:") - } + dependencyManager2.resolve(project2, context, isTest = false, + passedScopes = listOf(Scope.RUNTIME)).let { dependencies -> + assertContains(dependencies, ":jcommander:") + assertDoesNotContain(dependencies, ":klaxon:jar:0.27") + assertDoesNotContain(dependencies, ":guice:") + assertDoesNotContain(dependencies, ":junit:") + } + + dependencyManager2.resolve(project2, context, isTest = false, + passedScopes = listOf(Scope.COMPILE, Scope.RUNTIME)).let { dependencies -> + assertContains(dependencies, ":klaxon:") + assertContains(dependencies, ":jcommander:") + assertContains(dependencies, ":guice:") + assertDoesNotContain(dependencies, ":junit:") + } - dependencyManager2.resolve(project2, Kobalt.context!!, isTest = false, - passedScopeFilters = listOf(Scope.RUNTIME)).let { dependencies -> - assertThat(dependencies.size).isEqualTo(3) - assertContains(dependencies, ":jcommander:") } } diff --git a/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt b/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt index c7d09bda..71fc32ad 100644 --- a/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt +++ b/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt @@ -147,7 +147,7 @@ class DownloadTest @Inject constructor( @Test fun parentPomTest() { - // Resolve com.squareup.retrofit2:converter-moshi:2.0.0 + // Resolve com.squareup.retrofit2:converter-moshi:1.1.0 // This id has a parent pom which defines moshi version to be 1.1.0. Make sure that this // version is being fetched instead of moshi:1.2.0-SNAPSHOT (which gets discarded anyway // since snapshots are not allowed to be returned when looking up a versionless id) diff --git a/src/test/kotlin/com/beust/kobalt/misc/AetherTest.kt b/src/test/kotlin/com/beust/kobalt/misc/AetherTest.kt new file mode 100644 index 00000000..ede0890b --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/misc/AetherTest.kt @@ -0,0 +1,55 @@ +package com.beust.kobalt.misc + +import com.beust.kobalt.TestModule +import com.beust.kobalt.maven.DependencyManager +import com.beust.kobalt.maven.aether.Booter +import com.beust.kobalt.maven.aether.KobaltAether +import com.google.inject.Inject +import org.assertj.core.api.Assertions.assertThat +import org.eclipse.aether.artifact.DefaultArtifact +import org.eclipse.aether.collection.CollectRequest +import org.eclipse.aether.graph.Dependency +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.resolution.DependencyRequest +import org.eclipse.aether.util.artifact.JavaScopes +import org.testng.annotations.Guice +import org.testng.annotations.Test + +@Guice(modules = arrayOf(TestModule::class)) +class AetherTest { + @Inject + lateinit var kobaltAether: KobaltAether + + @Inject + lateinit var dependencyManager: DependencyManager + + @Test + fun aetherShouldNotIncludeOptionalDependencies() { + val system = Booter.newRepositorySystem() + val session = Booter.newRepositorySystemSession(system) + val artifact = DefaultArtifact("com.squareup.retrofit2:converter-jackson:jar:2.1.0") + + val collectRequest = CollectRequest().apply { + root = Dependency(artifact, JavaScopes.COMPILE) + repositories = listOf( + RemoteRepository.Builder("Maven", "default", "http://repo1.maven.org/maven2/").build() + ) + } + + val dependencyRequest = DependencyRequest(collectRequest, null) + + val artifactResults = system.resolveDependencies(session, dependencyRequest).artifactResults + + // Make sure that com.google.android is not included (it's an optional dependency of retrofit2) + assertThat(artifactResults.none { it.toString().contains("android") }) + } + + @Test + fun kobaltAetherShouldNotIncludeOptionalDependencies() { + val dep = kobaltAether.create("com.squareup.retrofit2:converter-jackson:jar:2.1.0", optional = false) + val closure = dependencyManager.transitiveClosure(listOf(dep)) + + // Make sure that com.google.android is not included (it's an optional dependency of retrofit2) + assertThat(closure.none { it.toString().contains("android") }) + } +}