Skip to content

Commit 92b05e8

Browse files
authored
feat: Make JavaCC task cacheable (#24)
1 parent 50a6265 commit 92b05e8

2 files changed

Lines changed: 320 additions & 2 deletions

File tree

src/main/kotlin/com/intershop/gradle/javacc/task/JavaCCTask.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ import org.gradle.api.model.ObjectFactory
2727
import org.gradle.api.provider.ListProperty
2828
import org.gradle.api.provider.Property
2929
import org.gradle.api.provider.Provider
30+
import org.gradle.api.tasks.CacheableTask
31+
import org.gradle.api.tasks.Classpath
3032
import org.gradle.api.tasks.Input
3133
import org.gradle.api.tasks.InputFile
32-
import org.gradle.api.tasks.InputFiles
3334
import org.gradle.api.tasks.Nested
3435
import org.gradle.api.tasks.Optional
3536
import org.gradle.api.tasks.OutputDirectory
37+
import org.gradle.api.tasks.PathSensitive
38+
import org.gradle.api.tasks.PathSensitivity
3639
import org.gradle.api.tasks.TaskAction
3740
import org.gradle.workers.WorkerExecutor
3841
import java.io.File
@@ -44,6 +47,7 @@ import javax.inject.Inject
4447
*
4548
* @constructor Creates a task with a worker.
4649
*/
50+
@CacheableTask
4751
abstract class JavaCCTask @Inject constructor(objectFactory: ObjectFactory,
4852
private val workerExecutor: WorkerExecutor) : DefaultTask(){
4953

@@ -74,6 +78,7 @@ abstract class JavaCCTask @Inject constructor(objectFactory: ObjectFactory,
7478
*
7579
* @property inputFile
7680
*/
81+
@get:PathSensitive(PathSensitivity.RELATIVE)
7782
@get:InputFile
7883
var inputFile: File
7984
get() = inputFileProperty.get().asFile
@@ -763,7 +768,7 @@ abstract class JavaCCTask @Inject constructor(objectFactory: ObjectFactory,
763768
/**
764769
* Classpath with javaCC libraries.
765770
*/
766-
@get:InputFiles
771+
@get:Classpath
767772
val toolsclasspathfiles : FileCollection by lazy {
768773
val returnFiles = project.files()
769774
// find files of original JASPER and Eclipse compiler
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
package com.intershop.gradle.javacc
2+
3+
import com.intershop.gradle.test.AbstractIntegrationKotlinSpec
4+
5+
import java.nio.file.Files
6+
import java.nio.file.StandardCopyOption
7+
8+
import static org.gradle.testkit.runner.TaskOutcome.FROM_CACHE
9+
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
10+
11+
/**
12+
* Test to verify that JavaCC tasks are properly cacheable
13+
*/
14+
class CacheabilityKtsSpec extends AbstractIntegrationKotlinSpec {
15+
16+
// Base configuration for all tests to ensure consistent environment
17+
String TASK_BASE_CONFIGURATION = """
18+
plugins {
19+
`java`
20+
id("com.intershop.gradle.javacc")
21+
}
22+
23+
version = "1.0.0"
24+
group = "com.test.gradle"
25+
26+
java {
27+
toolchain {
28+
languageVersion = JavaLanguageVersion.of(17)
29+
}
30+
}
31+
32+
tasks {
33+
withType<JavaCompile> {
34+
options.compilerArgs.add("-Xlint:deprecation")
35+
options.compilerArgs.add("-Xlint:unchecked")
36+
}
37+
}
38+
39+
repositories {
40+
mavenCentral()
41+
}
42+
""".stripIndent()
43+
44+
// Unique, temporary build cache directory for each test method
45+
File tmpBuildCacheDir
46+
47+
def setup() {
48+
tmpBuildCacheDir = Files.createTempDirectory("gradle-build-cache-${CacheabilityKtsSpec.simpleName}-").toFile()
49+
50+
// Configure settings.gradle.kts to use our unique build cache directory
51+
settingsFile.text = """
52+
buildCache {
53+
local {
54+
directory = file("${tmpBuildCacheDir.absolutePath.replace('\\', '\\\\')}")
55+
}
56+
}
57+
""".stripIndent()
58+
}
59+
60+
def cleanup() {
61+
// Clean up the temporary build cache directory after each test
62+
tmpBuildCacheDir?.deleteDir()
63+
}
64+
65+
def 'JavaCC task should be cacheable'() {
66+
given:
67+
copyResources('examples/SimpleExamples/jj', 'jj')
68+
69+
buildFile << """
70+
${TASK_BASE_CONFIGURATION}
71+
72+
javacc {
73+
configs {
74+
register("simple1") {
75+
inputFile = file("jj/Simple1.jj")
76+
packageName = "com.test.simple1"
77+
}
78+
}
79+
}
80+
""".stripIndent()
81+
82+
when: 'First build with build cache'
83+
def result1 = getPreparedGradleRunner()
84+
.withArguments('javaccSimple1', '--build-cache', '-s', '--warning-mode', 'all')
85+
.withGradleVersion(gradleVersion)
86+
.build()
87+
88+
then: 'Task executes successfully and populates cache'
89+
result1.task(':javaccSimple1').outcome == SUCCESS
90+
new File(testProjectDir, 'build/generated/javacc/simple1/com/test/simple1/Simple1.java').exists()
91+
92+
when: 'Clean and rebuild with build cache'
93+
def result2 = getPreparedGradleRunner()
94+
.withArguments('clean', 'javaccSimple1', '--build-cache', '-s', '--warning-mode', 'all')
95+
.withGradleVersion(gradleVersion)
96+
.build()
97+
98+
then: 'Task is loaded from cache'
99+
result2.task(':javaccSimple1').outcome == FROM_CACHE
100+
new File(testProjectDir, 'build/generated/javacc/simple1/com/test/simple1/Simple1.java').exists()
101+
102+
where:
103+
gradleVersion << supportedGradleVersions
104+
}
105+
106+
def 'JavaCC task should be cacheable with JJTree'() {
107+
given:
108+
copyResources('examples/JJTreeExamples/jjt', 'jjt')
109+
110+
buildFile << """
111+
${TASK_BASE_CONFIGURATION}
112+
113+
javacc {
114+
configs {
115+
register("eg1") {
116+
inputFile = file("jjt/eg1.jjt")
117+
}
118+
}
119+
}
120+
""".stripIndent()
121+
122+
when: 'First build with build cache'
123+
def result1 = getPreparedGradleRunner()
124+
.withArguments('javaccEg1', '--build-cache', '-s', '--warning-mode', 'all')
125+
.withGradleVersion(gradleVersion)
126+
.build()
127+
128+
then: 'Task executes successfully and populates cache'
129+
result1.task(':javaccEg1').outcome == SUCCESS
130+
131+
when: 'Clean and rebuild with build cache'
132+
def result2 = getPreparedGradleRunner()
133+
.withArguments('clean', 'javaccEg1', '--build-cache', '-s', '--warning-mode', 'all')
134+
.withGradleVersion(gradleVersion)
135+
.build()
136+
137+
then: 'Task is loaded from cache'
138+
result2.task(':javaccEg1').outcome == FROM_CACHE
139+
140+
where:
141+
gradleVersion << supportedGradleVersions
142+
}
143+
144+
def 'JavaCC task should produce cache miss when input changes'() {
145+
given:
146+
copyResources('examples/SimpleExamples/jj', 'jj')
147+
148+
buildFile << """
149+
${TASK_BASE_CONFIGURATION}
150+
151+
javacc {
152+
configs {
153+
register("simple2") {
154+
inputFile = file("jj/Simple2.jj")
155+
}
156+
}
157+
}
158+
""".stripIndent()
159+
160+
when: 'First build with build cache'
161+
def result1 = getPreparedGradleRunner()
162+
.withArguments('javaccSimple2', '--build-cache', '-s', '--warning-mode', 'all')
163+
.withGradleVersion(gradleVersion)
164+
.build()
165+
166+
then: 'Task executes successfully and populates cache'
167+
result1.task(':javaccSimple2').outcome == SUCCESS
168+
169+
when: 'Modify input file'
170+
def inputFile = new File(testProjectDir, 'jj/Simple2.jj')
171+
inputFile.text = inputFile.text.replace('Simple2', 'Simple2Modified')
172+
173+
and: 'Rebuild with build cache'
174+
def result2 = getPreparedGradleRunner()
175+
.withArguments('javaccSimple2', '--build-cache', '-s', '--warning-mode', 'all')
176+
.withGradleVersion(gradleVersion)
177+
.build()
178+
179+
then: 'Task re-executes (not from cache) due to input change'
180+
result2.task(':javaccSimple2').outcome == SUCCESS
181+
182+
when: 'Revert input file and rebuild'
183+
copyResources('examples/SimpleExamples/jj', 'jj')
184+
def result3 = getPreparedGradleRunner()
185+
.withArguments('clean', 'javaccSimple2', '--build-cache', '-s', '--warning-mode', 'all')
186+
.withGradleVersion(gradleVersion)
187+
.build()
188+
189+
then: 'Task is loaded from cache (original input)'
190+
result3.task(':javaccSimple2').outcome == FROM_CACHE
191+
192+
where:
193+
gradleVersion << supportedGradleVersions
194+
}
195+
196+
def 'JavaCC task should produce cache miss when configuration changes'() {
197+
given:
198+
copyResources('examples/SimpleExamples/jj', 'jj')
199+
200+
buildFile << """
201+
${TASK_BASE_CONFIGURATION}
202+
203+
javacc {
204+
configs {
205+
register("simple3") {
206+
inputFile = file("jj/Simple3.jj")
207+
staticParam = "true"
208+
}
209+
}
210+
}
211+
""".stripIndent()
212+
213+
when: 'First build with build cache'
214+
def result1 = getPreparedGradleRunner()
215+
.withArguments('javaccSimple3', '--build-cache', '-s', '--warning-mode', 'all')
216+
.withGradleVersion(gradleVersion)
217+
.build()
218+
219+
then: 'Task executes successfully and populates cache'
220+
result1.task(':javaccSimple3').outcome == SUCCESS
221+
222+
when: 'Change configuration'
223+
buildFile.text = buildFile.text.replace('staticParam = "true"', 'staticParam = "false"')
224+
225+
and: 'Rebuild with build cache'
226+
def result2 = getPreparedGradleRunner()
227+
.withArguments('javaccSimple3', '--build-cache', '-s', '--warning-mode', 'all')
228+
.withGradleVersion(gradleVersion)
229+
.build()
230+
231+
then: 'Task re-executes (not from cache) due to configuration change'
232+
result2.task(':javaccSimple3').outcome == SUCCESS
233+
234+
when: 'Revert configuration and rebuild'
235+
buildFile.text = buildFile.text.replace('staticParam = "false"', 'staticParam = "true"')
236+
def result3 = getPreparedGradleRunner()
237+
.withArguments('clean', 'javaccSimple3', '--build-cache', '-s', '--warning-mode', 'all')
238+
.withGradleVersion(gradleVersion)
239+
.build()
240+
241+
then: 'Task is loaded from cache (original configuration)'
242+
result3.task(':javaccSimple3').outcome == FROM_CACHE
243+
244+
where:
245+
gradleVersion << supportedGradleVersions
246+
}
247+
248+
def 'JavaCC task should use cache across different directories with PathSensitivity.RELATIVE'() {
249+
given:
250+
copyResources('examples/SimpleExamples/jj', 'jj')
251+
252+
buildFile << """
253+
${TASK_BASE_CONFIGURATION}
254+
255+
javacc {
256+
configs {
257+
register("idList") {
258+
inputFile = file("jj/IdList.jj")
259+
}
260+
}
261+
}
262+
""".stripIndent()
263+
264+
when: 'First build with build cache'
265+
def result1 = getPreparedGradleRunner()
266+
.withArguments('javaccIdList', '--build-cache', '-s', '--warning-mode', 'all')
267+
.withGradleVersion(gradleVersion)
268+
.build()
269+
270+
then: 'Task executes successfully and populates cache'
271+
result1.task(':javaccIdList').outcome == SUCCESS
272+
273+
when: 'Create a new project directory with same content'
274+
def testProjectDir2 = Files.createTempDirectory("gradle-test-project-${CacheabilityKtsSpec.simpleName}-").toFile()
275+
testProjectDir2.deleteOnExit()
276+
277+
// Copy all files to new directory
278+
copyDirectory(testProjectDir, testProjectDir2)
279+
280+
and: 'Build in new directory with same build cache'
281+
def result2 = getPreparedGradleRunner()
282+
.withProjectDir(testProjectDir2)
283+
.withArguments('clean', 'javaccIdList', '--build-cache', '-s', '--warning-mode', 'all')
284+
.withGradleVersion(gradleVersion)
285+
.build()
286+
287+
then: 'Task is loaded from cache (path sensitivity is RELATIVE)'
288+
result2.task(':javaccIdList').outcome == FROM_CACHE
289+
290+
cleanup:
291+
testProjectDir2?.deleteDir()
292+
293+
where:
294+
gradleVersion << supportedGradleVersions
295+
}
296+
297+
private static void copyDirectory(File source, File target) {
298+
def sourceRoot = source.toPath()
299+
def targetRoot = target.toPath()
300+
301+
Files.walk(sourceRoot).withCloseable { stream ->
302+
stream.forEach { currentPath ->
303+
def relativePath = sourceRoot.relativize(currentPath)
304+
def destinationPath = targetRoot.resolve(relativePath)
305+
if (Files.isDirectory(currentPath)) {
306+
Files.createDirectories(destinationPath)
307+
} else {
308+
Files.copy(currentPath, destinationPath, StandardCopyOption.REPLACE_EXISTING)
309+
}
310+
}
311+
}
312+
}
313+
}

0 commit comments

Comments
 (0)