-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathbuild.gradle
More file actions
314 lines (281 loc) · 11.3 KB
/
Copy pathbuild.gradle
File metadata and controls
314 lines (281 loc) · 11.3 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
plugins {
id 'java'
id 'jacoco'
id 'checkstyle'
id 'pmd'
}
group = 'org.alxkm'
version = '1.0-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation platform('org.junit:junit-bom:6.1.3')
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
// Running one example.
//
// Almost every class here has a main method and is meant to be read and run on its own. Reaching one
// used to mean spelling out a classpath and a fully qualified name, which is a poor first impression
// for a repository whose whole point is that you can run the thing.
//
// ./gradlew listExamples
// ./gradlew runExample -Pexample=VisibilityExample
// ./gradlew runExample -Pexample=org.alxkm.memorymodel.VisibilityExample
//
// A simple name is enough unless two examples share it, in which case the task says so and lists the
// candidates rather than guessing.
/** Maps every example's simple name to the fully qualified names that carry it. */
ext.exampleIndex = {
def index = [:]
file('src/main/java').eachFileRecurse { candidate ->
if (!candidate.name.endsWith('.java')) {
return
}
def text = candidate.getText('UTF-8')
if (!text.contains('static void main')) {
return
}
def packageMatch = text =~ /(?m)^package\s+([\w.]+);/
if (!packageMatch) {
return
}
def simple = candidate.name - '.java'
index.computeIfAbsent(simple) { [] } << (packageMatch[0][1] + '.' + simple)
}
index
}
tasks.register('listExamples') {
group = 'application'
description = 'Lists every example that has a main method.'
doLast {
def index = exampleIndex()
def names = index.values().flatten().sort()
names.each { println it }
println()
println("${names.size()} runnable examples. Run one with:")
println(' ./gradlew runExample -Pexample=<simple or fully qualified name>')
}
}
tasks.register('runExample', JavaExec) {
group = 'application'
description = 'Runs one example: -Pexample=VisibilityExample'
classpath = sourceSets.main.runtimeClasspath
mainClass = providers.provider {
def requested = (project.findProperty('example') ?: '').toString().trim()
if (requested.isEmpty()) {
throw new GradleException(
'Which example? Pass -Pexample=<name>, or run ./gradlew listExamples to see them.')
}
def index = exampleIndex()
// Not withDefault: in Groovy that inserts the key on read, so a typo would end up in the
// index and then be offered back as a suggestion for itself.
def matches = requested.contains('.')
? index.values().flatten().findAll { it == requested }
: (index.containsKey(requested) ? index[requested] : [])
if (matches.isEmpty()) {
def near = index.keySet().findAll { it.toLowerCase().contains(requested.toLowerCase()) }.sort()
def hint = near.isEmpty()
? 'Run ./gradlew listExamples to see them.'
: "Did you mean: ${near.join(', ')}"
throw new GradleException("No example named '${requested}'. ${hint}")
}
if (matches.size() > 1) {
throw new GradleException(
"'${requested}' is ambiguous. Use the full name, one of: ${matches.sort().join(', ')}")
}
matches.first()
}
}
// JMH -- the OpenJDK microbenchmark harness.
//
// Same arrangement as jcstress below: its own source set, compiled by `build` so CI catches breakage,
// but never run as part of it. A benchmark needs warmup and several forks to say anything trustworthy,
// which takes minutes. Run it with:
//
// ./gradlew jmh
// ./gradlew jmh -PjmhArgs="Counter -f 1 -wi 3 -i 3" # one benchmark, quicker
//
// The point of having it is that this repository makes performance claims. Anything it asserts about
// speed should come from a measurement someone can reproduce, not from a number quoted from elsewhere.
sourceSets {
jmh {
java.srcDir 'src/jmh/java'
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
dependencies {
jmhImplementation 'org.openjdk.jmh:jmh-core:1.37'
jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
}
tasks.register('jmh', JavaExec) {
group = 'verification'
description = 'Runs the JMH benchmarks (minutes, not seconds).'
classpath = sourceSets.jmh.runtimeClasspath
mainClass = 'org.openjdk.jmh.Main'
def extra = (project.findProperty('jmhArgs') ?: '').toString()
def resultFile = layout.buildDirectory.file('reports/jmh/results.txt').get().asFile
doFirst {
resultFile.parentFile.mkdirs()
}
args(extra.isEmpty() ? ['-rf', 'text', '-rff', resultFile.toString()]
: extra.split('\s+').toList())
}
// jcstress -- the OpenJDK harness for Java Memory Model races.
//
// Kept in its own source set, off the main build, on purpose. A jcstress run forks a JVM per test and
// hammers each one for seconds at a time, so a full run takes minutes; wiring it into `check` would make
// every build slow for a result that only changes when the memory-model examples change. Run it with:
//
// ./gradlew jcstress
//
// The tests here exist because these races cannot be demonstrated by an ordinary unit test: lining two
// threads up needs synchronisation, and that synchronisation is itself a barrier that hides the effect.
sourceSets {
jcstress {
java.srcDir 'src/jcstress/java'
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
dependencies {
jcstressImplementation 'org.openjdk.jcstress:jcstress-core:0.16'
jcstressAnnotationProcessor 'org.openjdk.jcstress:jcstress-core:0.16'
}
tasks.register('jcstress', JavaExec) {
group = 'verification'
description = 'Runs the Java Memory Model stress tests (minutes, not seconds).'
classpath = sourceSets.jcstress.runtimeClasspath
mainClass = 'org.openjdk.jcstress.Main'
// Default mode, not sanity: sanity collects a few dozen samples per test, and the reordering
// these tests exist to demonstrate shows up in roughly 4% of samples -- far too rare to appear.
// A full run is a few minutes. Pass -PjcstressArgs=... to override, e.g.
// ./gradlew jcstress -PjcstressArgs="-t PlainFields -m quick"
def extra = (project.findProperty('jcstressArgs') ?: '').toString()
args(extra.isEmpty() ? ['-v', '-r', "${layout.buildDirectory.get()}/reports/jcstress"]
: extra.split('\s+').toList())
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.compilerArgs << '-Xlint:all'
}
test {
useJUnitPlatform()
// A bug in a concurrency example is far more likely to hang than to fail an assertion. Without a
// default timeout a deadlock regression would stall CI until the job is killed, with no clue as to
// which test caused it; with one, the offending test fails by name.
systemProperty 'junit.jupiter.execution.timeout.default', '60s'
finalizedBy jacocoTestReport
// Report failures as they happen, with enough detail to diagnose them from a CI log.
testLogging {
events 'failed', 'skipped'
exceptionFormat = 'full'
showCauses = true
showExceptions = true
showStackTraces = true
showStandardStreams = false
info {
events 'started', 'passed', 'failed', 'skipped'
exceptionFormat = 'full'
}
debug {
events 'started', 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
exceptionFormat = 'full'
}
}
// A one-line tally once the run finishes. The afterSuite/beforeTest/afterTest closures this
// replaces are deprecated and go away in Gradle 10; addTestListener is their replacement.
addTestListener(new TestListener() {
@Override void beforeSuite(TestDescriptor suite) {}
@Override void beforeTest(TestDescriptor test) {}
@Override void afterTest(TestDescriptor test, TestResult result) {}
@Override
void afterSuite(TestDescriptor suite, TestResult result) {
if (suite.parent != null) {
return // only the root suite carries the totals
}
def summary = "Results: ${result.resultType} (${result.testCount} tests, " +
"${result.successfulTestCount} successes, ${result.failedTestCount} failures, " +
"${result.skippedTestCount} skipped)"
def rule = '-' * (summary.length() + 6)
println()
println(rule)
println("| ${summary} |")
println(rule)
if (result.failedTestCount > 0) {
println()
println('Failed tests are listed above. Reports: build/reports/tests/test/index.html')
}
}
})
}
// Custom task to print failed test details
task printFailedTests {
doLast {
def testResultsDir = layout.buildDirectory.dir('test-results/test').get().asFile
if (testResultsDir.exists()) {
testResultsDir.listFiles().findAll { it.name.endsWith('.xml') }.each { file ->
def xml = new groovy.xml.XmlSlurper().parse(file)
xml.testcase.each { testcase ->
if (testcase.failure.size() > 0) {
println("\n💥 FAILED TEST: ${testcase.@classname}.${testcase.@name}")
println(" Time: ${testcase.@time}s")
testcase.failure.each { failure ->
println(" Type: ${failure.@type}")
println(" Message: ${failure.@message}")
if (failure.text()) {
println(" Details:")
failure.text().split('\n').take(10).each { line ->
println(" ${line}")
}
}
}
}
}
}
}
}
}
// JaCoCo Configuration - NON-BREAKING
jacoco {
toolVersion = "0.8.11"
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
}
}
// Checkstyle Configuration - NON-BREAKING
checkstyle {
toolVersion = '10.12.7'
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
ignoreFailures = true // 🛡️ WON'T BREAK BUILD
}
// Fix Guava dependency conflict
configurations.checkstyle {
resolutionStrategy.capabilitiesResolution.withCapability("com.google.collections:google-collections") {
select("com.google.guava:guava:23.0")
}
}
// PMD Configuration - NON-BREAKING
pmd {
consoleOutput = true
toolVersion = '7.0.0'
ignoreFailures = true // 🛡️ WON'T BREAK BUILD
ruleSets = [
'category/java/errorprone.xml',
'category/java/bestpractices.xml',
'category/java/multithreading.xml'
]
}