diff --git a/src/dorkbox/gradle/DependencyScanner.kt b/src/dorkbox/gradle/DependencyScanner.kt index 3f62181..cfe3d23 100644 --- a/src/dorkbox/gradle/DependencyScanner.kt +++ b/src/dorkbox/gradle/DependencyScanner.kt @@ -19,22 +19,71 @@ import org.gradle.api.Project import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedDependency import java.io.File +import java.util.* object DependencyScanner { /** * THIS MUST BE IN "afterEvaluate" or run from a specific task. * - * 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 + * we can have quite deep recursion. A project can never depend on itself, but we check if a project has already been added, and + * don't parse it more than once + * + * This is an actual problem... */ - fun scan( + fun scan(project: Project, configurationName: String, includeChildren: Boolean = true): List { + + val projectDependencies = mutableListOf() + val config = project.configurations.getByName(configurationName) + if (!config.isCanBeResolved) { + return projectDependencies + } + + config.resolve() + + val list = LinkedList() + + config.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies(org.gradle.api.specs.Specs.SATISFIES_ALL).forEach { dep -> + list.add(dep) + } + + var next: ResolvedDependency + while (list.isNotEmpty()) { + next = list.poll() + + val module = next.module.id + val group = module.group + val name = module.name + val version = module.version + + val artifacts: List = next.moduleArtifacts.map { artifact: ResolvedArtifact -> + val artifactModule = artifact.moduleVersion.id + Artifact(artifactModule.group, artifactModule.name, artifactModule.version, artifact.file.absoluteFile) + } + + projectDependencies.add(DependencyData(group, name, version, artifacts)) + if (includeChildren) { + list.addAll(next.children) + } + } + + return projectDependencies + } + /** + * THIS MUST BE IN "afterEvaluate" or run from a specific task. + * + * 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 quite deep recursion. A project can never depend on itself, but we check if a project has already been added, and + * don't parse it more than once + * + * This is an actual problem... + */ + fun createTree( project: Project, configurationName: String, projectDependencies: MutableList, - existingNames: MutableSet, + existingDeps: MutableMap, ) { val config = project.configurations.getByName(configurationName) @@ -44,50 +93,48 @@ object DependencyScanner { config.resolve() + // the root parent is tossed out, but not the topmost list of dependencies + val rootParent = Dependency("", "", "", listOf(), projectDependencies) + + val parentList = LinkedList() + val list = LinkedList() + config.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies(org.gradle.api.specs.Specs.SATISFIES_ALL).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 - if (!projectDependencies.contains(makeDepTree)) { - projectDependencies.add(makeDepTree) - } - } - } - } - - // 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")) { - existingNames.add("$group:$name") - - // println("Searching: $group:$name:$version") - val artifacts: List = dep.moduleArtifacts.map { artifact: ResolvedArtifact -> - val artifactModule = artifact.moduleVersion.id - Artifact(artifactModule.group, artifactModule.name, artifactModule.version, artifact.file.absoluteFile) - } - - val children = mutableListOf() - dep.children.forEach { child -> - val makeDep = makeDepTree(child, existingNames) - if (makeDep != null) { - children.add(makeDep) - } - } - - return Dependency(group, name, version, artifacts, children.toList()) + list.add(dep) + parentList.add(rootParent) } - // we already have this dependency in our chain. - return null + + var next: ResolvedDependency + while (list.isNotEmpty()) { + next = list.poll() + + val module = next.module.id + val group = module.group + val name = module.name + val version = module.version + val mavenId = "$group:$name:$version" + + if (!existingDeps.containsKey(mavenId)) { + val artifacts: List = next.moduleArtifacts.map { artifact: ResolvedArtifact -> + val artifactModule = artifact.moduleVersion.id + Artifact(artifactModule.group, artifactModule.name, artifactModule.version, artifact.file.absoluteFile) + } + + val dependency = Dependency(group, name, version, artifacts, mutableListOf()) + + // now add to our parent + val parent = parentList.poll() + (parent.children as MutableList).add(dependency) + + next.children.forEach { child -> + parentList.add(dependency) + list.add(child) + } + + existingDeps[mavenId] = dependency + } + } } /** @@ -106,6 +153,24 @@ object DependencyScanner { } } + data class ProjectDependencies(val tree: List, val dependencies: List) + + data class DependencyData( + val group: String, + val name: String, + val version: String, + val artifacts: List + ) { + + fun mavenId(): String { + return "$group:$name:$version" + } + + override fun toString(): String { + return mavenId() + } + } + data class Dependency( val group: String, val name: String, diff --git a/src/dorkbox/gradle/StaticMethodsAndTools.kt b/src/dorkbox/gradle/StaticMethodsAndTools.kt index a0855e2..fa85c86 100644 --- a/src/dorkbox/gradle/StaticMethodsAndTools.kt +++ b/src/dorkbox/gradle/StaticMethodsAndTools.kt @@ -175,14 +175,49 @@ open class StaticMethodsAndTools(private val project: Project) { return repositories } + /** + * Gets all of the Maven-style Repository URLs for the specified project (or for the root project if not specified). + * + * @param project which project to get the repository root URLs for + * @param onlyRemote true to ONLY get the remote repositories (ie: don't include mavenLocal) + */ + fun getProjectBuildScriptRepositoryUrls(project: Project = this.project, onlyRemote: Boolean = true): List { + val repositories = mutableListOf() + project.buildscript.repositories.filterIsInstance() + .forEach { + repo -> + val resolver = repo.createResolver() + if (resolver is org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver) { + // println("searching ${resolver.name}") + // println(resolver.root) + // all maven patterns are the same! + // https://plugins.gradle.org/m2/com/dorkbox/Utilities/maven-metadata.xml + // https://repo1.maven.org/maven2/com/dorkbox/Utilities/maven-metadata.xml + // https://repo.maven.apache.org/com/dorkbox/Utilities/maven-metadata.xml + + if ((onlyRemote && !resolver.isLocal) || !onlyRemote) { + try { + val toURL = resolver.root.toASCIIString() + if (toURL.endsWith('/')) { + repositories.add(toURL) + } else { + // the root doesn't always end with a '/', and we must guarantee that + repositories.add("$toURL/") + } + } catch (e: Exception) { + } + } + } + } + return repositories + } + /** * Resolves all dependencies of the project buildscript * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ fun resolveBuildScriptDependencies(project: Project = this.project): List { - val existingNames = mutableSetOf() - return project.buildscript.configurations.flatMap { config -> config.resolvedConfiguration .lenientConfiguration @@ -192,59 +227,71 @@ open class StaticMethodsAndTools(private val project: Project) { val group = module.group val name = module.name val version = module.version - val moduleName = "$group:$name" - if (!existingNames.contains(moduleName)) { - existingNames.add(moduleName) - DependencyScanner.Maven(group, name, version) - } else { - null - } + DependencyScanner.Maven(group, name, version) } } } /** - * Resolves all child dependencies of the project + * Resolves all *declared* dependencies of the project * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ - fun resolveAllDependencies(project: Project = this.project): List { - val projectDependencies = mutableListOf() - val existingNames = mutableSetOf() + fun resolveAllDeclaredDependencies(project: Project = this.project): List { + // NOTE: we cannot createTree("compile") and createTree("runtime") using the same exitingNames and expect correct results. + // This is because a dependency might exist for compile and runtime, but have different children, therefore, the list + // will be incomplete - DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames) - DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames) + // there will be DUPLICATES! (we don't care about children or hierarchy, so we remove the dupes) + return (DependencyScanner.scan(project, "compileClasspath", false) + + DependencyScanner.scan(project, "runtimeClasspath", false) + ).toSet().toList() + } - return projectDependencies + + /** + * Recursively resolves all child dependencies of the project + * + * THIS MUST BE IN "afterEvaluate" or run from a specific task. + */ + fun resolveAllDependencies(project: Project = this.project): List { + // NOTE: we cannot createTree("compile") and createTree("runtime") using the same exitingNames and expect correct results. + // This is because a dependency might exist for compile and runtime, but have different children, therefore, the list + // will be incomplete + + // there will be DUPLICATES! (we don't care about children or hierarchy, so we remove the dupes) + return (DependencyScanner.scan(project, "compileClasspath") + + DependencyScanner.scan(project, "runtimeClasspath") + ).toSet().toList() } /** - * Resolves all child compile dependencies of the project + * Recursively resolves all child compile dependencies of the project * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ - fun resolveCompileDependencies(project: Project = this.project): List { + fun resolveCompileDependencies(project: Project = this.project): DependencyScanner.ProjectDependencies { val projectDependencies = mutableListOf() - val existingNames = mutableSetOf() + val existingNames = mutableMapOf() - DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames) + DependencyScanner.createTree(project, "compileClasspath", projectDependencies, existingNames) - return projectDependencies + return DependencyScanner.ProjectDependencies(projectDependencies, existingNames.map { it.value }) } /** - * Resolves all child compile dependencies of the project + * Recursively resolves all child compile dependencies of the project * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ - fun resolveRuntimeDependencies(project: Project = this.project): List { + fun resolveRuntimeDependencies(project: Project = this.project): DependencyScanner.ProjectDependencies { val projectDependencies = mutableListOf() - val existingNames = mutableSetOf() + val existingNames = mutableMapOf() - DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames) + DependencyScanner.createTree(project, "runtimeClasspath", projectDependencies, existingNames) - return projectDependencies + return DependencyScanner.ProjectDependencies(projectDependencies, existingNames.map { it.value }) } /**