Skip to content

Instantly share code, notes, and snippets.

@pablisco
Last active September 7, 2020 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pablisco/e50c792f1febb77af0fbc2d4f8f2810e to your computer and use it in GitHub Desktop.
Save pablisco/e50c792f1febb77af0fbc2d4f8f2810e to your computer and use it in GitHub Desktop.
Automatic Gradle module graph and dependencies generator
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.kotlin.dsl.project
val DependencyHandler.local: Local
get() = Local(this)
class Local(dh: DependencyHandler) {
val app = dh.project(":app")
val modules = Modules(dh)
}
class Modules(dh: DependencyHandler) {
val core = dh.project(":modules:core")
val androidx = Androidx(dh)
val support = Support(dh)
}
class Androidx(dh: DependencyHandler) {
val core = dh.project(":modules:androidx:core")
}
class Support(dh: DependencyHandler) {
val listenerProperty = dh.project(":modules:support:listener-property")
}
import kotlin.system.measureTimeMillis
val modulesFile = file("$rootDir/buildSrc/src/main/kotlin/_modules.kt")
measureTimeMillis {
rootDir.walkTopDown()
.filter { file -> "build.gradle" in file.name }
.map { file -> file.parentFile }
.filterNot { dir -> dir.startsWith(".") }
.filterNot { dir -> dir.endsWith("buildSrc") }
.filterNot { dir -> dir == rootDir }
.map { dir -> dir.path }
.map { path -> path.removePrefix(rootDir.path) }
.map { path -> path.replace(File.separatorChar, ':') }
.onEach { path -> include(path) }
.onEach { path -> logger.info("include(\"$path\")") }
.toNodes()
.writeTo(modulesFile)
}.let { time ->
logger.debug("Generated modules in ${time}ms")
}
data class Node(
val name: String,
val path: String?,
val nodes: List<Node> = emptyList()
)
fun List<String>.createNode(path: String? = null): Node {
val name = first()
val newPath = (path ?: "") + ":" + name
return when {
isEmpty() -> error("oops")
size == 1 -> Node(name, path = newPath)
else -> Node(
name = name,
path = newPath,
nodes = listOf(drop(1).createNode(newPath))
)
}
}
fun Sequence<String>.toNodes(): Collection<Node> = map { path -> path.toNode() }
.groupingBy { node -> node.name }
.aggregate { _, acc: Node?, node: Node, _ ->
acc?.run { copy(nodes = nodes + node.nodes) } ?: node
}.values
fun Collection<Node>.writeTo(file: File) {
check(file.parentFile.exists()) { "Should have buildSrc module defined" }
file.delete()
file.createNewFile()
file.appendText(toCode())
}
fun String.toNode(): Node =
split(":").filterNot(String::isBlank).createNode()
fun Collection<Node>.toCode() = """
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.kotlin.dsl.project
val DependencyHandler.local: Local
get() = Local(this)
class Local(dh: DependencyHandler) {
${joinToString("\n ") { it.toProperty() }}
}
${flatten().filterNot { it.nodes.isEmpty() }.joinToString("\n") {
it.nodes.toModuleType(it.name)
}}
""".trim()
fun Collection<Node>.flatten(): Sequence<Node> =
asSequence().fold(emptySequence()) { acc, it ->
acc + it + it.nodes.asSequence()
}
fun Node.toProperty() = when {
nodes.isEmpty() -> """val ${name.snakeCase()} = dh.project("$path")"""
else -> """val ${name.snakeCase()} = ${name.camelCase()}(dh)"""
}
fun List<Node>.toModuleType(name: String) = """
class ${name.camelCase()}(dh: DependencyHandler) {
${joinToString("\n ") { it.toProperty() }}
}
""".trimStart()
fun String.sanitize(replacement: Char = ' '): String = toCharArray().joinToString("") { c ->
when {
!c.isJavaIdentifierPart() -> replacement
this[0] == c && !c.isJavaIdentifierStart() -> replacement
else -> c
}.toString()
}
fun String.camelCase(): String =
sanitize().split(' ').joinToString("") { it.capitalize() }
fun String.snakeCase(): String =
sanitize().split(' ')
.mapIndexed { index, s -> if (index == 0) s else s.capitalize() }
.joinToString("")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment