Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .run/taskTree.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--stacktrace --rerun-tasks" />
<option name="scriptParameters" value="--stacktrace" />
<option name="taskDescriptions">
<list />
</option>
Expand Down
2 changes: 1 addition & 1 deletion libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[versions]

tasktree = "0.0.10"
tasktree = "0.0.12"

[libraries]

Expand Down
11 changes: 6 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Apply plugin in your module's `build.gradle`:

```kotlin
plugins {
id("com.github.klee0kai.tasktree") version "0.0.10"
id("com.github.klee0kai.tasktree") version "0.0.12"
}

tasktree {
Expand All @@ -20,7 +20,7 @@ tasktree {
}
```

Report your build graph
Project build report in the form of a build graph. [Diagon](https://github.com/ArthurSonzogni/Diagon) must be installed

```bash
./gradlew taskTree assemble
Expand All @@ -37,7 +37,8 @@ Verify project's module dependency depth
```bash
./gradlew projectTree --verifyDepth=1

>> Heavy projects: ':example' depth: 2
>> :dynamic_findstorage price: 3; depth: 3; importance: 0; relativePrice: 1,00; relativeDepth: 1,00; depth dependencies: :dynamic_findstorage <- :app_mobile <- :core;
Heavy projects: ':dynamic_findstorage' depth: 3
```

Build graphs
Expand Down Expand Up @@ -65,11 +66,11 @@ initscript {
maven(url = "https://jitpack.io")
}
dependencies {
classpath("com.github.klee0kai.tasktree:com.github.klee0kai.tasktree.gradle.plugin:0.0.10")
classpath("com.github.klee0kai:tasktree:0.0.12")
}
}

rootProject{
rootProject {
pluginManager.apply(com.github.klee0kai.tasktree.TaskTreePlugin::class.java)

extensions.findByType(com.github.klee0kai.tasktree.TaskTreeExtension::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,34 @@ open class TaskTreePlugin : Plugin<Project> {


private fun Project.applyTaskReportOnProject(ext: TaskTreeExtension) {
afterEvaluate {
afterEvaluate(10) {
val taskTree = tasks.register("taskTree", TaskTreeTask::class.java, ext)
val taskDag = tasks.register("taskGraph", TaskGraphTask::class.java)
val flatlist = tasks.register("flatList", FlatListTask::class.java, ext)

taskGraph.whenReady {
val isTaskTreeRequested = hasTask(taskTree.get()) || hasTask(taskDag.get()) || hasTask(flatlist.get())
val isTaskTreeRequested =
hasTask(taskTree.get()) || hasTask(taskDag.get()) || hasTask(flatlist.get())
if (isTaskTreeRequested) {
allRequestedTasks.forEach {
it.enabled = false
}
}
}
}
}

/**
* we make sure that dependencies between tasks will no longer be configured
*/
private fun Project.afterEvaluate(count: Int = 1, block: () -> Unit) {
if (count <= 0) {
block()
} else {
afterEvaluate {
afterEvaluate(count - 1, block)
}
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,28 @@ class TaskStat(
return field
}

var depth: Int = 0
val depth: Int get() = depthDependencies.size

var depthDependencies: List<TaskStat> = emptyList()
private set
get() {
if (field != 0) return field
var maxDepth = 1
val checked = mutableSetOf<Int>()
val deps = LinkedList(dependencies.map { it to 2 }.toMutableList())
if (field.isNotEmpty()) return field
val checked = mutableMapOf<Int, List<TaskStat>>()
val deps = LinkedList(dependencies.map { listOf(this@TaskStat, it) }.toMutableList())
while (deps.isNotEmpty()) {
val dep = deps.pollFirst()
if (checked.contains(dep.first.id)) continue
if (dep.second > maxDepth) maxDepth = dep.second
checked.add(dep.first.id)
deps.addAll(0, dep.first.dependencies.map { it to dep.second + 1 })
val checkedDepthDeps = checked.getOrDefault(dep.last().id, emptyList())
if (checkedDepthDeps.size >= dep.size
|| checkedDepthDeps.isNotEmpty()
&& dep.take(checkedDepthDeps.size).map { it.id } == checkedDepthDeps.map { it.id } // ignore doubles
) {
continue
}
checked[dep.last().id] = dep
deps.removeAll { task -> task.last().id in dep.last().dependencies.map { it.id } }
deps.addAll(0, dep.last().dependencies.map { dep + it })
}
field = maxDepth
field = checked.values.maxByOrNull { it.size } ?: emptyList()
return field
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import org.gradle.api.tasks.diagnostics.internal.ProjectDetails
object TaskStatHelper {

fun collectAllTasksInfo(project: Project): List<TaskInfo> {
val tasksInfos = project.tasks.map { task ->
val allTasks = project.tasks.toList()
val tasksInfos = allTasks.map { task ->
TaskInfo(
id = System.identityHashCode(task),
taskName = task.name,
Expand All @@ -20,7 +21,7 @@ object TaskStatHelper {
)
}.associateBy { task -> task.id }

project.tasks.forEach { task ->
allTasks.forEach { task ->
runCatching {
task.taskDependencies.getDependencies(task).forEach { dependsOn ->
val taskStat = tasksInfos[System.identityHashCode(dependsOn)] ?: return@forEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ProjectInfo(
private set
get() {
if (field != 0) return field
field = allDependedOnTasks.count()
field = allDependedOnProject.count()
return field
}

Expand All @@ -44,7 +44,7 @@ class ProjectInfo(
}
}

val allDependedOnTasks = sequence {
val allDependedOnProject = sequence {
val sent = mutableSetOf<String>()
val deps = LinkedList(dependedOnProjects.toMutableList())
while (deps.isNotEmpty()) {
Expand All @@ -56,21 +56,27 @@ class ProjectInfo(
}
}

var depth: Int = 0
val depth: Int get() = depthDependencies.size

var depthDependencies: List<ProjectInfo> = emptyList()
private set
get() {
if (field != 0) return field
var maxDepth = 1
val checked = mutableSetOf<String>()
val deps = LinkedList(dependencies.map { it to 2 }.toMutableList())
if (field.isNotEmpty()) return field
val checked = mutableMapOf<String, List<ProjectInfo>>()
val deps = LinkedList(dependencies.map { listOf(this@ProjectInfo, it) }.toMutableList())
while (deps.isNotEmpty()) {
val dep = deps.pollFirst()
if (checked.contains(dep.first.path)) continue
if (dep.second > maxDepth) maxDepth = dep.second
checked.add(dep.first.path)
deps.addAll(0, dep.first.dependencies.map { it to dep.second + 1 })
val checkedDepthDeps = checked.getOrDefault(dep.last().path, emptyList())
if (checkedDepthDeps.size >= dep.size
|| checkedDepthDeps.isNotEmpty()
&& dep.take(checkedDepthDeps.size).map { it.path } == checkedDepthDeps.map { it.path } // ignore doubles
) {
continue
}
checked[dep.last().path] = dep
deps.addAll(0, dep.last().dependencies.map { dep + it })
}
field = maxDepth
field = checked.values.maxByOrNull { it.size } ?: emptyList()
return field
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ object ProjectStatHelper {
path = it.path,
projectDetails = ProjectDetails.of(it),
)
}.associateBy { it.name }
}.associateBy { it.path }

rootProject.allprojects.forEach { project ->
val projectInfo = projectInfos[project.name] ?: return@forEach
val projectInfo = projectInfos[project.path] ?: return@forEach
project.configurations
.firstOrNull { it.name.contains("implementation") }
?.allDependencies
?.filterIsInstance<ProjectDependency>()
?.forEach { dep ->
val depProjectInfo = projectInfos[dep.name] ?: return@forEach
val depProjectInfo = runCatching { projectInfos[dep.path] }.getOrNull()
?: runCatching { projectInfos[dep.dependencyProject.path] }.getOrNull()
?: return@forEach
projectInfo.dependencies.add(depProjectInfo)
}
}
Expand Down Expand Up @@ -54,4 +56,14 @@ object ProjectStatHelper {
}


fun filterByRequestedProject(
projectStats: List<ProjectInfo>,
target: String?,
): List<ProjectInfo> {
if (target.isNullOrBlank()) return projectStats
return projectStats.filter {
it.fullName == target || it.allDependedOnProject.any { it.fullName == target }
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ open class ProjectTreeTask @Inject constructor(

private val projectsInfos = Cached.of { ProjectStatHelper.collectProjectDependencies(project) }

private val projectsStats by lazy { ProjectStatHelper.calcToProjectStats(projectsInfos.get()) }
private var projectsStats: List<ProjectInfo> = emptyList()

@Input
@Optional
@set:Option(option = "target", description = "Build graph to module as target")
protected var projectTarget: String? = null

@Input
@Optional
Expand All @@ -35,16 +40,27 @@ open class ProjectTreeTask @Inject constructor(
@set:Option(option = "verifyPrice", description = "Verify project's module price")
protected var verifyPrice: String? = null

override fun getDescription(): String = "Display the hierarchy of module dependencies in a project"

@TaskAction
fun generate() {
projectsStats = ProjectStatHelper.calcToProjectStats(projectInfos = projectsInfos.get())
.let { ProjectStatHelper.filterByRequestedProject(projectStats = it, target = projectTarget) }

renderedProjects.clear()
reportGenerator().generateReport(
listOf(projectDetails.get()),
{ it }
) { projects ->
val graphRenderer = GraphRenderer(renderer.textOutput)
val topProjects = projectsStats
.filter { project -> project.allDependedOnCount <= 0L }
.filter { project ->
if (!projectTarget.isNullOrBlank()) {
project.fullName == projectTarget
} else {
project.allDependedOnCount <= 0L
}
}
.sortedByDescending { it.depth }
topProjects.forEach { graphRenderer.render(it) }

Expand Down Expand Up @@ -103,14 +119,17 @@ open class ProjectTreeTask @Inject constructor(

allStat.forEach {
renderer.textOutput
.printProjectShort(it)
.printProjectShort(projectInfo = it, printDepthLine = true)
.println()
}
textOutput.println()
}
}

private fun StyledTextOutput.printProjectShort(projectInfo: ProjectInfo) = apply {
private fun StyledTextOutput.printProjectShort(
projectInfo: ProjectInfo,
printDepthLine: Boolean = false,
) = apply {
withStyle(Identifier)
.text(projectInfo.fullName)

Expand All @@ -120,6 +139,8 @@ open class ProjectTreeTask @Inject constructor(

withStyle(Description)
.text(" depth: ${projectInfo.depth};")


}
if (ext.printImportance) {
withStyle(Description)
Expand All @@ -132,6 +153,11 @@ open class ProjectTreeTask @Inject constructor(
withStyle(Description)
.text(" relativeDepth: ${projectInfo.relativeDepth.formatString()};")
}

if (ext.printPrice && printDepthLine) {
withStyle(Description)
.text(" depth dependencies: ${projectInfo.depthDependencies.joinToString(" <- ") { it.fullName }};")
}
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.github.klee0kai.tasktree.tasks

import com.github.klee0kai.tasktree.projectInfo.ProjectInfo
import com.github.klee0kai.tasktree.projectInfo.ProjectStatHelper
import org.apache.tools.ant.util.TeeOutputStream
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.internal.serialization.Cached
import org.gradle.process.internal.ExecActionFactory
import org.gradle.process.internal.ExecException
Expand All @@ -22,15 +24,22 @@ open class ProjectsGraphTask @Inject constructor(

private val projectsInfos = Cached.of { ProjectStatHelper.collectProjectDependencies(project) }

private val projectsStats by lazy { ProjectStatHelper.calcToProjectStats(projectsInfos.get()) }
private var projectsStats: List<ProjectInfo> = emptyList()

@Input
@Optional
@set:Option(option = "target", description = "Build graph to module as target")
protected var target: String? = null

@Internal
override fun getDescription(): String =
"Draw tasktree graph use Diagon. More: https://github.com/ArthurSonzogni/Diagon"
"Draw project's dependencies graph use Diagon. More: https://github.com/ArthurSonzogni/Diagon"


@TaskAction
fun generate() {
projectsStats = ProjectStatHelper.calcToProjectStats(projectInfos = projectsInfos.get())
.let { ProjectStatHelper.filterByRequestedProject(projectStats = it, target = target) }

val depsCode = projectsStats.joinToString("\n") { project ->
project.dependencies.joinToString("\n") { dep ->
"${dep.fullName} -> ${project.fullName}"
Expand Down
Loading