Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.

Commit 4b88e49

Browse files
authored
Implement GradleProjectFlattenerCli (#65)
* Implement GradleProjectFlattenerCli * Spotless * Final touchups * More flags + error handling * Suppression * More flags and fixes from testing
1 parent c3e0f54 commit 4b88e49

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

api/kotlin-cli-util.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ public final class slack/cli/Toml {
3737
public final fun parseVersion (Ljava/io/File;)Ljava/util/Map;
3838
}
3939

40+
public final class slack/cli/gradle/GradleProjectFlattenerCli : com/github/ajalt/clikt/core/CliktCommand {
41+
public fun <init> ()V
42+
public fun run ()V
43+
}
44+
4045
public final class slack/cli/lint/LintBaselineMergerCli : com/github/ajalt/clikt/core/CliktCommand {
4146
public fun <init> ()V
4247
public fun run ()V
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright (C) 2023 Slack Technologies, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package slack.cli.gradle
17+
18+
import com.github.ajalt.clikt.core.CliktCommand
19+
import com.github.ajalt.clikt.parameters.options.default
20+
import com.github.ajalt.clikt.parameters.options.flag
21+
import com.github.ajalt.clikt.parameters.options.option
22+
import com.github.ajalt.clikt.parameters.options.required
23+
import com.github.ajalt.clikt.parameters.types.path
24+
import java.io.File
25+
import kotlin.io.path.ExperimentalPathApi
26+
import kotlin.io.path.appendLines
27+
import kotlin.io.path.appendText
28+
import kotlin.io.path.copyToRecursively
29+
import kotlin.io.path.exists
30+
import kotlin.io.path.isDirectory
31+
import kotlin.io.path.readText
32+
import kotlin.io.path.relativeTo
33+
import slack.cli.dryRunOption
34+
import slack.cli.projectDirOption
35+
36+
/**
37+
* A CLI that flattens all gradle projects in a given directory to be top level while preserving
38+
* their original project paths.
39+
*
40+
* This is useful for flattening nested projects that use Dokka, which does not currently support
41+
* easy doc gen for nested projects and end up with colliding names.
42+
*
43+
* It's recommended to run `./gradlew clean` first before running this script to minimize work.
44+
*/
45+
public class GradleProjectFlattenerCli :
46+
CliktCommand(
47+
help =
48+
"A CLI that flattens all gradle projects in a given directory to be top level while " +
49+
"preserving their original project paths."
50+
) {
51+
52+
private val projectDir by projectDirOption()
53+
54+
private val settingsFile by
55+
option(
56+
"--settings-file",
57+
"-s",
58+
help =
59+
"The settings.gradle file to use. Note this file _must_ only have a single, top-level `include()` call " +
60+
"with vararg project args."
61+
)
62+
.path(mustExist = true, canBeDir = false)
63+
.required()
64+
65+
private val projectDelimiter by option().default("--")
66+
67+
private val dryRun by dryRunOption()
68+
69+
private val strict by
70+
option("--strict", help = "If true, will fail if any of the projects to flatten don't exist.")
71+
.flag()
72+
73+
private val verbose by
74+
option("--verbose", "-v", help = "If true, will print out more information.").flag()
75+
76+
private val force by
77+
option("--force", "-f", help = "If true, force overwrite new projects.").flag()
78+
79+
@ExperimentalPathApi
80+
override fun run() {
81+
val projectPaths =
82+
settingsFile
83+
.readText()
84+
.trim()
85+
.lines()
86+
// Filter out commented lines
87+
.filterNot { it.trimStart().startsWith("//") }
88+
.joinToString("\n")
89+
.removePrefix("include(")
90+
.removeSuffix(")")
91+
.split(",")
92+
.map { it.trim().removeSurrounding("\"") }
93+
94+
val newPathMapping = mutableMapOf<String, String>()
95+
@Suppress("LoopWithTooManyJumpStatements")
96+
for (path in projectPaths) {
97+
val realPath =
98+
projectDir.resolve(path.removePrefix(":").removeSuffix(":").replace(":", File.separator))
99+
if (strict) {
100+
check(!path.endsWith(":")) { "Project paths cannot end with ':'" }
101+
check(realPath.exists()) { "Expected $realPath to exist." }
102+
check(realPath.isDirectory()) { "Expected $realPath to be a directory." }
103+
} else if (!realPath.exists()) {
104+
echo("Skipping $path as it doesn't exist", err = true)
105+
continue
106+
}
107+
val newPath =
108+
projectDir.resolve(path.removePrefix(":").removeSuffix(":").replace(":", projectDelimiter))
109+
if (newPath == realPath) {
110+
// Already top-level, move on
111+
continue
112+
}
113+
newPathMapping[path] = newPath.relativeTo(projectDir).toString()
114+
if (verbose) {
115+
echo("Flattening $realPath to $newPath")
116+
}
117+
if (!dryRun) {
118+
realPath.copyToRecursively(newPath, followLinks = false, overwrite = force)
119+
}
120+
}
121+
122+
if (verbose) {
123+
echo("Finished flattening projects. Updating settings file")
124+
}
125+
val newPaths =
126+
projectPaths.mapNotNull { path ->
127+
// Point at their new paths
128+
// Example:
129+
// project(":libraries:compose-extensions:pull-refresh").projectDir =
130+
// file("libraries--compose-extensions--pull-refresh")
131+
val newPath = newPathMapping[path] ?: return@mapNotNull null
132+
"project(\"$path\").projectDir = file(\"$newPath\")"
133+
.also {
134+
if (verbose) {
135+
echo("+ $it")
136+
}
137+
}
138+
}
139+
140+
if (!dryRun) {
141+
settingsFile.appendText("\n\n")
142+
settingsFile.appendLines(newPaths)
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)