From 8cf9f908058e20266f601541d95bc80d4f85d4ce Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 7 Aug 2020 15:58:30 +0200 Subject: [PATCH] Added dependency scanning, added default runtime configurations --- src/dorkbox/gradle/DependencyScanner.kt | 102 ++++++++++++++++++++ src/dorkbox/gradle/GradleUtils.kt | 1 + src/dorkbox/gradle/StaticMethodsAndTools.kt | 98 +++++++++++++------ 3 files changed, 174 insertions(+), 27 deletions(-) create mode 100644 src/dorkbox/gradle/DependencyScanner.kt diff --git a/src/dorkbox/gradle/DependencyScanner.kt b/src/dorkbox/gradle/DependencyScanner.kt new file mode 100644 index 0000000..cefc3cf --- /dev/null +++ b/src/dorkbox/gradle/DependencyScanner.kt @@ -0,0 +1,102 @@ +package dorkbox.gradle + +import org.gradle.api.Project +import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.artifacts.ResolvedDependency +import java.io.File + +object DependencyScanner { + + /** + * THIS MUST BE IN "afterEvaluate" or run from a specific task. + */ + fun scan(project: Project, + configurationName: String, + projectDependencies: MutableList, + existingNames: MutableSet): MutableList { + + project.configurations.getByName(configurationName).resolvedConfiguration.firstLevelModuleDependencies.forEach { dep -> + // we know the FIRST series will exist + val makeDepTree = makeDepTree(dep, existingNames) + if (makeDepTree != null) { + // it's only null if we've ALREADY scanned it + projectDependencies.add(makeDepTree) + } + } + + return projectDependencies + } + + + + // how to resolve dependencies + // NOTE: it is possible, when we have a project DEPEND on an older version of that project (ie: bootstrapped from an older version) + // we can have infinite recursion. + // This is a problem, so we limit how much a dependency can show up the the tree + private fun makeDepTree(dep: ResolvedDependency, existingNames: MutableSet): Dependency? { + val module = dep.module.id + val group = module.group + val name = module.name + val version = module.version + + if (!existingNames.contains("$group:$name")) { + // println("Searching: $group:$name:$version") + val artifacts: List = dep.moduleArtifacts.map { artifact: ResolvedArtifact -> + val artifactModule = artifact.moduleVersion.id + DependencyInfo(artifactModule.group, artifactModule.name, artifactModule.version, artifact.file.absoluteFile) + } + + val children = mutableListOf() + dep.children.forEach { + existingNames.add("$group:$name") + val makeDep = makeDepTree(it, existingNames) + if (makeDep != null) { + children.add(makeDep) + } + } + + return Dependency(group, name, version, artifacts, children.toList()) + } + + // we already have this dependency in our chain. + return null + } + + /** + * Flatten the dependency children + */ + fun flattenDeps(dep: Dependency): List { + val flatDeps = mutableSetOf() + flattenDep(dep, flatDeps) + return flatDeps.toList() + } + + private fun flattenDep(dep: Dependency, flatDeps: MutableSet) { + flatDeps.add(dep) + dep.children.forEach { + flattenDep(it, flatDeps) + } + } + + data class Dependency(val group: String, + val name: String, + val version: String, + val artifacts: List, + val children: List) { + + fun mavenId(): String { + return "$group:$name:$version" + } + + override fun toString(): String { + return mavenId() + } + } + + data class DependencyInfo(val group: String, val name: String, val version: String, val file: File) { + val id: String + get() { + return "$group:$name:$version" + } + } +} diff --git a/src/dorkbox/gradle/GradleUtils.kt b/src/dorkbox/gradle/GradleUtils.kt index c6872f6..176c95d 100644 --- a/src/dorkbox/gradle/GradleUtils.kt +++ b/src/dorkbox/gradle/GradleUtils.kt @@ -21,6 +21,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.compile.AbstractCompile import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import java.util.* diff --git a/src/dorkbox/gradle/StaticMethodsAndTools.kt b/src/dorkbox/gradle/StaticMethodsAndTools.kt index b595b0a..743e4ef 100644 --- a/src/dorkbox/gradle/StaticMethodsAndTools.kt +++ b/src/dorkbox/gradle/StaticMethodsAndTools.kt @@ -3,9 +3,7 @@ package dorkbox.gradle import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.plugins.JavaPluginConvention import java.io.File import java.util.* import kotlin.reflect.KMutableProperty @@ -13,6 +11,7 @@ import kotlin.reflect.KProperty import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.full.declaredMemberProperties + open class StaticMethodsAndTools(private val project: Project) { /** * Maps the property (key/value) pairs of a property file onto the specified target object. Also maps fields in the targetObject to the @@ -113,40 +112,45 @@ open class StaticMethodsAndTools(private val project: Project) { /** * Resolves all child dependencies of the project + * + * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ - fun resolveDependencies(): List { - val configuration = project.configurations.getByName("default") as Configuration + fun resolveDependencies(): List { + val projectDependencies = mutableListOf() + val existingNames = mutableSetOf() - val includedDeps = mutableSetOf() - val depsToSearch = LinkedList() - depsToSearch.addAll(configuration.resolvedConfiguration.firstLevelModuleDependencies) + DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames) + DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames) - return includedDeps.flatMap { - it.moduleArtifacts - } + return projectDependencies } + /** + * Resolves all child compile dependencies of the project + * + * THIS MUST BE IN "afterEvaluate" or run from a specific task. + */ + fun resolveCompileDependencies(): List { + val projectDependencies = mutableListOf() + val existingNames = mutableSetOf() + + DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames) + + return projectDependencies + } /** - * Recursively resolves all dependencies of the project + * Resolves all child compile dependencies of the project + * + * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ - fun resolveAllDependencies(): List { - val configuration = project.configurations.getByName("default") as Configuration + fun resolveRuntimeDependencies(): List { + val projectDependencies = mutableListOf() + val existingNames = mutableSetOf() - val includedDeps = mutableSetOf() - val depsToSearch = LinkedList() - depsToSearch.addAll(configuration.resolvedConfiguration.firstLevelModuleDependencies) + DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames) - while (depsToSearch.isNotEmpty()) { - val dep = depsToSearch.removeFirst() - includedDeps.add(dep) - - depsToSearch.addAll(dep.children) - } - - return includedDeps.flatMap { - it.moduleArtifacts - } + return projectDependencies } /** @@ -165,6 +169,46 @@ open class StaticMethodsAndTools(private val project: Project) { it.testOutputDir = mainDir } } + + // this has the side-effect of NOT creating the gradle directories.... + + // make sure that the source set directories all exist. THIS SHOULD NOT BE A PROBLEM! + project.afterEvaluate { prj -> + prj.allprojects.forEach { proj -> + val javaPlugin: JavaPluginConvention = proj.convention.getPlugin(JavaPluginConvention::class.java) + val sourceSets = javaPlugin.sourceSets + + sourceSets.forEach { set -> + set.output.classesDirs.forEach { dir -> + if (!dir.exists()) { + dir.mkdirs() + } + } + } + } + } + } + + /** + * Configure a default resolution strategy. While not necessary, this is used for enforcing sane project builds + */ + fun defaultResolutionStrategy() { + project.configurations.forEach { config -> + config.resolutionStrategy { + // fail eagerly on version conflict (includes transitive dependencies) + // e.g. multiple different versions of the same dependency (group and name are equal) + it.failOnVersionConflict() + + // if there is a version we specified, USE THAT VERSION (over transitive versions) + it.preferProjectModules() + + // cache dynamic versions for 10 minutes + it.cacheDynamicVersionsFor(10 * 60, "seconds") + + // don't cache changing modules at all + it.cacheChangingModulesFor(0, "seconds") + } + } } private fun idea(project: Project, configure: org.gradle.plugins.ide.idea.model.IdeaModel.() -> Unit): Unit =