16
16
package slack.cli.gradle
17
17
18
18
import com.github.ajalt.clikt.core.CliktCommand
19
+ import com.github.ajalt.clikt.parameters.options.flag
20
+ import com.github.ajalt.clikt.parameters.options.multiple
19
21
import com.github.ajalt.clikt.parameters.options.option
20
22
import com.github.ajalt.clikt.parameters.options.required
21
23
import com.github.ajalt.clikt.parameters.types.path
22
24
import com.google.auto.service.AutoService
23
25
import java.io.File
26
+ import java.nio.file.Path
24
27
import kotlin.io.path.ExperimentalPathApi
28
+ import kotlin.io.path.absolute
29
+ import kotlin.io.path.deleteRecursively
25
30
import kotlin.io.path.exists
26
31
import kotlin.io.path.isDirectory
27
32
import kotlin.io.path.name
@@ -30,6 +35,7 @@ import kotlin.io.path.relativeTo
30
35
import kotlin.system.exitProcess
31
36
import slack.cli.CommandFactory
32
37
import slack.cli.projectDirOption
38
+ import slack.cli.skipBuildAndCacheDirs
33
39
34
40
/* * A CLI that verifies a given settings file has only valid projects. */
35
41
public class GradleSettingsVerifierCli : CliktCommand (help = DESCRIPTION ) {
@@ -59,8 +65,53 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
59
65
.path(mustExist = true , canBeDir = false )
60
66
.required()
61
67
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" )
62
91
@ExperimentalPathApi
63
92
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
+
64
115
val projectPaths =
65
116
settingsFile
66
117
.readText()
@@ -71,14 +122,14 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
71
122
.joinToString(" \n " )
72
123
.removePrefix(" include(" )
73
124
.removeSuffix(" )" )
74
- .split(" ," )
125
+ .splitToSequence(" ," )
126
+ .associateBy { line -> line.trim().removeSuffix(" ," ).removeSurrounding(" \" " ) }
127
+ .plus(implicitPaths.mapValues { " <implicit>" })
75
128
76
129
val errors = mutableListOf<String >()
77
130
@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)
82
133
83
134
fun reportError (message : String , column : Int ) {
84
135
errors + = buildString {
@@ -89,7 +140,7 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
89
140
}
90
141
91
142
when {
92
- path .endsWith(' :' ) -> {
143
+ gradlePath .endsWith(' :' ) -> {
93
144
reportError(" Project paths should not end with ':'" , line.lastIndexOf(' :' ) - 1 )
94
145
}
95
146
! realPath.exists() -> {
@@ -113,6 +164,23 @@ public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
113
164
}
114
165
}
115
166
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
+
116
184
if (errors.isNotEmpty()) {
117
185
echo(" Errors found in '${settingsFile.name} '. Please fix or remove these." , err = true )
118
186
echo(errors.joinToString(" " ), err = true )
0 commit comments