1616package slack.cli.gradle
1717
1818import com.github.ajalt.clikt.core.CliktCommand
19+ import com.github.ajalt.clikt.parameters.options.flag
20+ import com.github.ajalt.clikt.parameters.options.multiple
1921import com.github.ajalt.clikt.parameters.options.option
2022import com.github.ajalt.clikt.parameters.options.required
2123import com.github.ajalt.clikt.parameters.types.path
2224import com.google.auto.service.AutoService
2325import java.io.File
26+ import java.nio.file.Path
2427import kotlin.io.path.ExperimentalPathApi
28+ import kotlin.io.path.absolute
29+ import kotlin.io.path.deleteRecursively
2530import kotlin.io.path.exists
2631import kotlin.io.path.isDirectory
2732import kotlin.io.path.name
@@ -30,6 +35,7 @@ import kotlin.io.path.relativeTo
3035import kotlin.system.exitProcess
3136import slack.cli.CommandFactory
3237import slack.cli.projectDirOption
38+ import slack.cli.skipBuildAndCacheDirs
3339
3440/* * A CLI that verifies a given settings file has only valid projects. */
3541public class GradleSettingsVerifierCli : CliktCommand (help = DESCRIPTION ) {
@@ -59,8 +65,53 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
5965 .path(mustExist = true , canBeDir = false )
6066 .required()
6167
68+ private val implicitPaths by
69+ option(
70+ " --implicit-path" ,
71+ " -i" ,
72+ help =
73+ " Implicit project names that may not be present in the settings file but should be assumed present."
74+ )
75+ .multiple()
76+
77+ private val deleteUnIncludedPaths by
78+ option(
79+ " --delete-un-included-paths" ,
80+ " -d" ,
81+ help = " Delete any paths that are not included in the settings file."
82+ )
83+ .flag()
84+
85+ private fun resolveProjectFromGradlePath (relativePath : String ): Path {
86+ val gradlePath = relativePath.removePrefix(" :" ).removeSuffix(" :" ).replace(" :" , File .separator)
87+ return projectDir.resolve(gradlePath)
88+ }
89+
90+ @Suppress(" LongMethod" )
6291 @ExperimentalPathApi
6392 override fun run () {
93+ val implicitPaths = implicitPaths.associateWith { resolveProjectFromGradlePath(it) }
94+ val projectsViaBuildFiles =
95+ projectDir
96+ .absolute()
97+ .toFile()
98+ .walkTopDown()
99+ .skipBuildAndCacheDirs()
100+ .filter { it.name == " build.gradle.kts" }
101+ .associate {
102+ val path = it.toPath()
103+ // Get the gradle path relative to the root project dir as the key
104+ val gradlePath =
105+ " :" +
106+ path.parent // project dir
107+ .relativeTo(projectDir)
108+ .toString()
109+ .replace(File .separator, " :" )
110+ gradlePath to path
111+ }
112+ .filterValues { it.parent != projectDir }
113+ .plus(implicitPaths)
114+
64115 val projectPaths =
65116 settingsFile
66117 .readText()
@@ -71,14 +122,14 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
71122 .joinToString(" \n " )
72123 .removePrefix(" include(" )
73124 .removeSuffix(" )" )
74- .split(" ," )
125+ .splitToSequence(" ," )
126+ .associateBy { line -> line.trim().removeSuffix(" ," ).removeSurrounding(" \" " ) }
127+ .plus(implicitPaths.mapValues { " <implicit>" })
75128
76129 val errors = mutableListOf<String >()
77130 @Suppress(" LoopWithTooManyJumpStatements" )
78- for (line in projectPaths) {
79- val path = line.trim().removeSurrounding(" \" " )
80- val realPath =
81- projectDir.resolve(path.removePrefix(" :" ).removeSuffix(" :" ).replace(" :" , File .separator))
131+ for ((gradlePath, line) in projectPaths) {
132+ val realPath = resolveProjectFromGradlePath(gradlePath)
82133
83134 fun reportError (message : String , column : Int ) {
84135 errors + = buildString {
@@ -89,7 +140,7 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
89140 }
90141
91142 when {
92- path .endsWith(' :' ) -> {
143+ gradlePath .endsWith(' :' ) -> {
93144 reportError(" Project paths should not end with ':'" , line.lastIndexOf(' :' ) - 1 )
94145 }
95146 ! realPath.exists() -> {
@@ -113,6 +164,23 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
113164 }
114165 }
115166
167+ for ((path, buildFile) in projectsViaBuildFiles) {
168+ if (path !in projectPaths) {
169+ val projectPath = buildFile.parent
170+ if (deleteUnIncludedPaths) {
171+ echo(" Deleting un-included project '$path ' at $projectPath " )
172+ projectPath.deleteRecursively()
173+ } else {
174+ errors + = buildString {
175+ appendLine(" Project '$path ' is present in the filesystem but not in the settings file." )
176+ appendLine(" Please add it to the settings file or delete it." )
177+ appendLine(" Project dir:\t ${projectPath.relativeTo(projectDir)} " )
178+ appendLine(" Build file:\t ${buildFile.relativeTo(projectDir)} " )
179+ }
180+ }
181+ }
182+ }
183+
116184 if (errors.isNotEmpty()) {
117185 echo(" Errors found in '${settingsFile.name} '. Please fix or remove these." , err = true )
118186 echo(errors.joinToString(" " ), err = true )
0 commit comments