Skip to content

Commit 962b633

Browse files
perf(internal): make formatting faster
Running the formatter through Spotless is slow because Spotless synchronously runs the formatter on each file. Running the formatter directly parallelizes the formatting across cores.
1 parent e858395 commit 962b633

File tree

8 files changed

+183
-25
lines changed

8 files changed

+183
-25
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
.gradle
33
.idea
44
.kotlin
5-
build
5+
build/
66
codegen.log
77
kls_database.db

build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ allprojects {
1212
version = "0.0.2" // x-release-please-version
1313
}
1414

15+
subprojects {
16+
// These are populated with dependencies by `buildSrc` scripts.
17+
tasks.register("format") {
18+
group = "Verification"
19+
description = "Formats all source files."
20+
}
21+
tasks.register("lint") {
22+
group = "Verification"
23+
description = "Verifies all source files are formatted."
24+
}
25+
apply(plugin = "org.jetbrains.dokka")
26+
}
27+
1528
subprojects {
1629
apply(plugin = "org.jetbrains.dokka")
1730
}

buildSrc/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ repositories {
88
}
99

1010
dependencies {
11-
implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.2")
1211
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
1312
}

buildSrc/src/main/kotlin/scrapegraphai.java.gradle.kts

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import com.diffplug.gradle.spotless.SpotlessExtension
21
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
32

43
plugins {
54
`java-library`
6-
id("com.diffplug.spotless")
75
}
86

97
repositories {
@@ -15,15 +13,6 @@ configure<JavaPluginExtension> {
1513
withSourcesJar()
1614
}
1715

18-
configure<SpotlessExtension> {
19-
java {
20-
importOrder()
21-
removeUnusedImports()
22-
palantirJavaFormat()
23-
toggleOffOn()
24-
}
25-
}
26-
2716
java {
2817
toolchain {
2918
languageVersion.set(JavaLanguageVersion.of(21))
@@ -62,3 +51,86 @@ tasks.withType<Test>().configureEach {
6251
exceptionFormat = TestExceptionFormat.FULL
6352
}
6453
}
54+
55+
val palantir by configurations.creating
56+
dependencies {
57+
palantir("com.palantir.javaformat:palantir-java-format:2.73.0")
58+
}
59+
60+
fun registerPalantir(
61+
name: String,
62+
description: String,
63+
) {
64+
val javaName = "${name}Java"
65+
tasks.register<JavaExec>(javaName) {
66+
group = "Verification"
67+
this.description = description
68+
69+
classpath = palantir
70+
mainClass = "com.palantir.javaformat.java.Main"
71+
72+
// Avoid an `IllegalAccessError` on Java 9+.
73+
jvmArgs(
74+
"--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
75+
"--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
76+
"--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
77+
"--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
78+
"--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
79+
)
80+
81+
// Use paths relative to the current module.
82+
val argumentFile =
83+
project.layout.buildDirectory.file("palantir-$name-args.txt").get().asFile
84+
val lastRunTimeFile =
85+
project.layout.buildDirectory.file("palantir-$name-last-run.txt").get().asFile
86+
87+
// Read the time when this task was last executed for this module (if ever).
88+
val lastRunTime = lastRunTimeFile.takeIf { it.exists() }?.readText()?.toLongOrNull() ?: 0L
89+
90+
// Use a `fileTree` relative to the module's source directory.
91+
val javaFiles = project.fileTree("src") { include("**/*.java") }
92+
93+
// Determine if any files need to be formatted or linted and continue only if there is at least
94+
// one file.
95+
onlyIf { javaFiles.any { it.lastModified() > lastRunTime } }
96+
97+
inputs.files(javaFiles)
98+
99+
doFirst {
100+
// Create the argument file and set the preferred formatting style.
101+
argumentFile.parentFile.mkdirs()
102+
argumentFile.writeText("--palantir\n")
103+
104+
if (name == "lint") {
105+
// For lint, do a dry run, so no files are modified. Set the exit code to 1 (instead of
106+
// the default 0) if any files need to be formatted, indicating that linting has failed.
107+
argumentFile.appendText("--dry-run\n")
108+
argumentFile.appendText("--set-exit-if-changed\n")
109+
} else {
110+
// `--dry-run` and `--replace` (for in-place formatting) are mutually exclusive.
111+
argumentFile.appendText("--replace\n")
112+
}
113+
114+
// Write the modified files to the argument file.
115+
javaFiles.filter { it.lastModified() > lastRunTime }
116+
.forEach { argumentFile.appendText("${it.absolutePath}\n") }
117+
}
118+
119+
doLast {
120+
// Record the last execution time for later up-to-date checking.
121+
lastRunTimeFile.writeText(System.currentTimeMillis().toString())
122+
}
123+
124+
// Pass the argument file using the @ symbol
125+
args = listOf("@${argumentFile.absolutePath}")
126+
127+
outputs.upToDateWhen { javaFiles.none { it.lastModified() > lastRunTime } }
128+
}
129+
130+
tasks.named(name) {
131+
dependsOn(tasks.named(javaName))
132+
}
133+
}
134+
135+
registerPalantir(name = "format", description = "Formats all Java source files.")
136+
registerPalantir(name = "lint", description = "Verifies all Java source files are formatted.")

buildSrc/src/main/kotlin/scrapegraphai.kotlin.gradle.kts

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import com.diffplug.gradle.spotless.SpotlessExtension
21
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
32
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
43

@@ -7,6 +6,10 @@ plugins {
76
kotlin("jvm")
87
}
98

9+
repositories {
10+
mavenCentral()
11+
}
12+
1013
kotlin {
1114
jvmToolchain {
1215
languageVersion.set(JavaLanguageVersion.of(21))
@@ -27,14 +30,77 @@ kotlin {
2730
}
2831
}
2932

30-
configure<SpotlessExtension> {
31-
kotlin {
32-
ktfmt().kotlinlangStyle()
33-
toggleOffOn()
34-
}
35-
}
36-
3733
tasks.withType<Test>().configureEach {
3834
systemProperty("junit.jupiter.execution.parallel.enabled", true)
3935
systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
4036
}
37+
38+
val ktfmt by configurations.creating
39+
dependencies {
40+
ktfmt("com.facebook:ktfmt:0.56")
41+
}
42+
43+
fun registerKtfmt(
44+
name: String,
45+
description: String,
46+
) {
47+
val kotlinName = "${name}Kotlin"
48+
tasks.register<JavaExec>(kotlinName) {
49+
group = "Verification"
50+
this.description = description
51+
52+
classpath = ktfmt
53+
mainClass = "com.facebook.ktfmt.cli.Main"
54+
55+
// Use paths relative to the current module.
56+
val argumentFile = project.layout.buildDirectory.file("ktfmt-$name-args.txt").get().asFile
57+
val lastRunTimeFile =
58+
project.layout.buildDirectory.file("ktfmt-$name-last-run.txt").get().asFile
59+
60+
// Read the time when this task was last executed for this module (if ever).
61+
val lastRunTime = lastRunTimeFile.takeIf { it.exists() }?.readText()?.toLongOrNull() ?: 0L
62+
63+
// Use a `fileTree` relative to the module's source directory.
64+
val kotlinFiles = project.fileTree("src") { include("**/*.kt") }
65+
66+
// Determine if any files need to be formatted or linted and continue only if there is at least
67+
// one file (otherwise Ktfmt will fail).
68+
onlyIf { kotlinFiles.any { it.lastModified() > lastRunTime } }
69+
70+
inputs.files(kotlinFiles)
71+
72+
doFirst {
73+
// Create the argument file and set the preferred formatting style.
74+
argumentFile.parentFile.mkdirs()
75+
argumentFile.writeText("--kotlinlang-style\n")
76+
77+
if (name == "lint") {
78+
// For lint, do a dry run, so no files are modified. Set the exit code to 1 (instead of
79+
// the default 0) if any files need to be formatted, indicating that linting has failed.
80+
argumentFile.appendText("--dry-run\n")
81+
argumentFile.appendText("--set-exit-if-changed\n")
82+
}
83+
84+
// Write the modified files to the argument file.
85+
kotlinFiles.filter { it.lastModified() > lastRunTime }
86+
.forEach { argumentFile.appendText("${it.absolutePath}\n") }
87+
}
88+
89+
doLast {
90+
// Record the last execution time for later up-to-date checking.
91+
lastRunTimeFile.writeText(System.currentTimeMillis().toString())
92+
}
93+
94+
// Pass the argument file using the @ symbol
95+
args = listOf("@${argumentFile.absolutePath}")
96+
97+
outputs.upToDateWhen { kotlinFiles.none { it.lastModified() > lastRunTime } }
98+
}
99+
100+
tasks.named(name) {
101+
dependsOn(tasks.named(kotlinName))
102+
}
103+
}
104+
105+
registerKtfmt(name = "format", description = "Formats all Kotlin source files.")
106+
registerKtfmt(name = "lint", description = "Verifies all Kotlin source files are formatted.")

scripts/build

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
cd "$(dirname "$0")/.."
6+
7+
echo "==> Building classes"
8+
./gradlew build testClasses -x test

scripts/format

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ set -e
44

55
cd "$(dirname "$0")/.."
66

7-
echo "==> Running spotlessApply"
8-
./gradlew spotlessApply
7+
echo "==> Running formatters"
8+
./gradlew format

scripts/lint

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ set -e
44

55
cd "$(dirname "$0")/.."
66

7-
echo "==> Build classes"
8-
./gradlew build testClasses -x test
7+
echo "==> Running lints"
8+
./gradlew lint

0 commit comments

Comments
 (0)