-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathinfinity.java-conventions.gradle
More file actions
353 lines (320 loc) · 13.9 KB
/
infinity.java-conventions.gradle
File metadata and controls
353 lines (320 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/**
* Conventions for all moss java-based modules.
*/
plugins {
id 'java'
id 'groovy'
id 'checkstyle'
id 'pmd'
id 'jacoco'
id 'com.diffplug.spotless'
id 'net.ltgt.errorprone'
id 'com.github.ben-manes.versions'
}
// Projects should use Maven Central for external dependencies.
//
// Simsilica/Moss snapshots resolve from the vendored libs/m2/ tree (single
// source of truth for both local builds and CI). Refresh after publishing
// from a Moss checkout — see libs/README.md. We override mavenLocal()'s URL
// rather than declaring a plain `maven {}` repo so Gradle keeps the local
// Maven layout semantics (maven-metadata-local.xml, '+' version resolution).
repositories {
mavenLocal {
url = rootProject.file('libs/m2').toURI()
}
mavenCentral()
jcenter() // deprecated but some old deps still there
maven { url 'https://maven.scijava.org/content/repositories/public/' } // for de.lighti:Clipper
}
// Standard dependencies that we expect all of the java projects
// to have/need.
dependencies {
implementation "com.google.guava:guava:${guavaVersion}"
testImplementation (
"junit:junit:4.13.2"
)
implementation "org.codehaus.groovy:groovy-all:${groovyVersion}"
implementation "org.slf4j:slf4j-api:$slf4jVersion"
//A simple binding
//implementation "org.slf4j:slf4j-simple:$slf4jVersion"
//Log4j binding
//log4j-slf4j-impl should be used with SLF4J 1.7.x releases or older.
//log4j-slf4j2-impl should be used with SLF4J 2.0.x releases or newer.
implementation "org.apache.logging.log4j:log4j-slf4j2-impl:$log4jVersion"
implementation "org.apache.logging.log4j:log4j-api:$log4jVersion"
implementation "org.apache.logging.log4j:log4j-core:$log4jVersion"
// Static analysis (applied via Error Prone javac plugin)
errorprone 'com.google.errorprone:error_prone_core:2.31.0'
errorprone 'com.uber.nullaway:nullaway:0.11.3'
}
// =============================================================================
// Spotless — whitespace hygiene + unused imports.
// Run `./gradlew spotlessApply` to auto-fix violations.
//
// NOTE: license header enforcement is intentionally NOT configured.
// Spotless's licenseHeaderFile REPLACES any existing block comment above
// `package`, which would silently relicense upstream Simsilica/third-party
// code that carries its own BSD-3-clause header. Header correctness is a
// per-file judgement call — keep it manual.
// =============================================================================
spotless {
java {
target 'src/**/*.java'
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
}
}
// =============================================================================
// Checkstyle — style + common-bug rules. Config at config/checkstyle/.
// Starts as warnings-only so existing violations don't break the build; flip
// `ignoreFailures` to false once the repo is clean.
// =============================================================================
checkstyle {
toolVersion = '10.17.0'
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
ignoreFailures = true
showViolations = true
}
// =============================================================================
// PMD — focused dead-code rules (unused private fields/methods/locals,
// unnecessary imports, empty catch blocks). NOT a style/format tool —
// Spotless owns whitespace, Checkstyle owns convention. Config at
// config/pmd/ruleset.xml.
//
// Starts as warnings-only (ignoreFailures = true) so existing flags don't
// break the build; ratchet to failing once the codebase is clean.
// Test sources run through the same pipeline — the ceiling mechanism
// captures their baseline separately (`max<Project>PmdTestViolations`).
//
// ECS components in api/src/main/java/infinity/es/** carry no-arg constructors that
// PMD sees as unused: they're invoked via reflection by Zay-ES's
// FieldSerializer. Suppress that path explicitly so we don't drown in
// false positives there. Same shape as a Lombok-generated-code exclusion.
// =============================================================================
pmd {
toolVersion = '7.7.0'
ruleSets = [] // disable PMD's default ruleset; only use ours
ruleSetFiles = files(rootProject.file('config/pmd/ruleset.xml'))
ignoreFailures = true
consoleOutput = true
}
tasks.withType(Pmd).configureEach {
// Skip ECS components — no-arg ctors and final fields are reflection-
// accessed and look "unused" to static analysis.
exclude '**/infinity/es/**'
}
// Ad-hoc per-file / per-path PMD task — useful for scoping the report to
// just the files touched in the current change rather than re-scanning
// every source file in the module.
//
// Usage:
// ./gradlew :infinity-server:pmdPath -PpmdPath=src/main/java/infinity/systems/ship/WeaponsSystem.java
// ./gradlew :infinity-server:pmdPath -PpmdPath=src/main/java/infinity/systems/ # whole package
// ./gradlew :infinity-server:pmdPath -PpmdPath=src/main/java/A.java,src/main/java/B.java # multiple
//
// Path is comma-separated, relative to the module root. Uses the project
// ruleset and the same ignoreFailures + consoleOutput config as pmdMain
// so behaviour matches the full-scan baseline.
//
// Default (no -PpmdPath flag) is `src/main/java`, which is equivalent to
// pmdMain — so the task is safe to invoke without arguments.
tasks.register('pmdPath', Pmd) {
description = "Run PMD against -PpmdPath=<file_or_dir,...> (default: src/main/java)"
group = 'verification'
// Read the -PpmdPath property via providers.gradleProperty rather than
// project.findProperty('pmdPath'). findProperty walks the project's
// dynamic-property chain, which includes tasks — so once this task is
// registered, findProperty('pmdPath') returns the Pmd task itself,
// not the command-line property. That breaks Tooling-API model builds
// (Eclipse Buildship / jdtls import) which eagerly realize every task.
def rawPath = providers.gradleProperty('pmdPath').getOrElse('src/main/java')
def paths = rawPath.split(',').collect { it.trim() }.findAll { !it.isEmpty() }
source = files(paths)
classpath = sourceSets.main.runtimeClasspath
pmdClasspath = configurations.pmd
ruleSets = []
ruleSetFiles = files(rootProject.file('config/pmd/ruleset.xml'))
ignoreFailures = true
consoleOutput = true
reports {
html.required = true
xml.required = false
}
}
// =============================================================================
// Static-analysis ceilings — strict pin per-tool per-module.
//
// Each tool above runs warn-only at task level (`ignoreFailures = true`).
// That preserves the per-touched-file ratchet (per
// `.claude/rules/pmd-on-touched-files.md`) and per-batch refactor work —
// neither breaks every commit on existing baseline violations. The
// ceilings below are the gate: anything ABOVE the captured baseline
// (stored in `gradle.properties`) fails the build with a message naming
// the tool, count, and ceiling.
//
// To ratchet down: after a cleanup batch reduces the real count, capture
// the new count, lower the matching property in `gradle.properties`,
// commit. Bumping a ceiling is an explicit property edit — never silent.
//
// Property naming: `max<ProjectName>Checkstyle[Test]Violations` /
// `max<ProjectName>Pmd[Test]Violations`. Missing property = no ceiling
// (skipped) — useful for tasks like `pmdPath` that don't write XML.
//
// Out of scope: Error Prone + NullAway warnings flow through javac
// stdout, not separate report files; ratcheting them needs a different
// mechanism (parse javac output / wrap javac) and is deferred.
// =============================================================================
tasks.withType(Checkstyle).configureEach { task ->
def variant = task.name.substring('checkstyle'.length()) // 'Main' or 'Test'
def variantSuffix = (variant == 'Main') ? '' : variant
def ceilingKey = "max${project.name.capitalize()}Checkstyle${variantSuffix}Violations"
def rawCeiling = project.findProperty(ceilingKey)
// Register the ceiling value as a task input so property changes
// invalidate the cache and force the doLast check to re-run.
task.inputs.property("ceiling.${ceilingKey}", rawCeiling ?: 'absent')
task.doLast {
def reportFile = task.reports.xml.outputLocation.asFile.get()
if (!reportFile.exists() || rawCeiling == null) {
return // no report or no ceiling configured for this variant
}
def ceiling = rawCeiling.toString().toInteger()
def count = (reportFile.text =~ /<error /).size()
if (count > ceiling) {
throw new GradleException(
"Checkstyle (${task.name}): ${count} violations in ${project.name} " +
"exceeds ceiling ${ceiling} (${ceilingKey} in gradle.properties). " +
"Either fix the new violations or — only after net cleanup — " +
"lower the ceiling.")
}
}
}
tasks.withType(Pmd).configureEach { task ->
def variant = task.name.substring('pmd'.length()) // 'Main', 'Test', 'Path'
def variantSuffix = (variant == 'Main') ? '' : variant
def ceilingKey = "max${project.name.capitalize()}Pmd${variantSuffix}Violations"
def rawCeiling = project.findProperty(ceilingKey)
task.inputs.property("ceiling.${ceilingKey}", rawCeiling ?: 'absent')
task.doLast {
def reportFile = task.reports.xml.outputLocation.asFile.get()
if (!reportFile.exists() || rawCeiling == null) {
return // e.g. pmdPath disables XML; or no ceiling configured
}
def ceiling = rawCeiling.toString().toInteger()
def count = (reportFile.text =~ /<violation /).size()
if (count > ceiling) {
throw new GradleException(
"PMD (${task.name}): ${count} violations in ${project.name} " +
"exceeds ceiling ${ceiling} (${ceilingKey} in gradle.properties). " +
"Either fix the new violations or — only after net cleanup — " +
"lower the ceiling.")
}
}
}
// =============================================================================
// JaCoCo — test-coverage reporting + per-module ratchet floors.
//
// Same shape as PMD/Checkstyle ceilings above, inverted: PMD/Checkstyle count
// violations and gate on "AT or BELOW the captured baseline"; JaCoCo measures
// coverage ratio and gates on "AT or ABOVE the captured baseline". `test` runs
// without coverage failure; the verifier task is what gates `check`.
//
// Property naming: `min<ProjectName>LineCoverage` / `min<ProjectName>BranchCoverage`
// in `gradle.properties`. Default 0.0 (no gate) so new modules don't break
// the build before the property is captured.
//
// Reports: HTML (humans) + XML (SonarCloud) at
// `<module>/build/reports/jacoco/test/`.
// =============================================================================
jacocoTestReport {
dependsOn tasks.named('test')
reports {
xml.required = true
html.required = true
}
}
// Always produce a report after `test` so HTML/XML are available without an
// explicit `jacocoTestReport` invocation.
tasks.named('test').configure {
finalizedBy tasks.named('jacocoTestReport')
}
jacocoTestCoverageVerification {
dependsOn tasks.named('test')
def lineFloorKey = "min${project.name.capitalize()}LineCoverage"
def branchFloorKey = "min${project.name.capitalize()}BranchCoverage"
def lineFloor = (project.findProperty(lineFloorKey) ?: '0.0').toString().toBigDecimal()
def branchFloor = (project.findProperty(branchFloorKey) ?: '0.0').toString().toBigDecimal()
inputs.property("floor.${lineFloorKey}", lineFloor.toString())
inputs.property("floor.${branchFloorKey}", branchFloor.toString())
violationRules {
rule {
element = 'BUNDLE'
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = lineFloor
}
}
rule {
element = 'BUNDLE'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = branchFloor
}
}
}
}
tasks.named('check').configure {
dependsOn tasks.named('jacocoTestCoverageVerification')
}
// =============================================================================
// Error Prone + NullAway — compile-time bug detection.
// Starts as warnings-only; tighten by removing `allErrorsAsWarnings` and
// promoting NullAway to error once the code is annotated.
// =============================================================================
tasks.withType(JavaCompile).configureEach {
options.errorprone {
disableWarningsInGeneratedCode = true
allErrorsAsWarnings = true
excludedPaths = '.*/build/generated/.*'
warn('NullAway')
option('NullAway:AnnotatedPackages', 'infinity')
}
}
// Skip Error Prone for tests — too noisy for early adoption.
tasks.named('compileTestJava').configure {
options.errorprone.enabled = false
}
compileJava { // compile-time options:
options.encoding = 'UTF-8'
options.compilerArgs << '-Xlint:unchecked'
options.deprecation = true
}
java {
sourceCompatibility = 21
targetCompatibility = 21
withJavadocJar()
withSourcesJar()
}
javadoc {
options.addStringOption('Xdoclint:none', '-quiet')
}
test {
// Redirect JVM fatal-error logs (hs_err_pid*.log) to build/crash-logs/ per module.
def crashLogDir = layout.buildDirectory.dir("crash-logs").get().asFile
crashLogDir.mkdirs()
jvmArgs "-XX:ErrorFile=${crashLogDir}/hs_err_pid%p.log"
testLogging {
// I want to see the tests that are run and pass, etc.
events "passed", "skipped", "failed", "standardOut", "standardError"
exceptionFormat "full"
}
}
sourceSets {
main {
resources {
exclude "**/.backups/**"
}
}
}