Skip to content

Commit ed21402

Browse files
committed
Verify generated code in CI
1 parent 7e03b14 commit ed21402

26 files changed

Lines changed: 13848 additions & 13818 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Verify Codegen
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
verify-codegen:
18+
name: Output matches committed code
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 45
21+
22+
steps:
23+
- name: Check out code
24+
uses: actions/checkout@v4
25+
26+
- name: Fetch origin/main for Spotless ratchet
27+
run: git fetch origin main
28+
29+
- name: Validate Gradle Wrapper
30+
uses: gradle/actions/wrapper-validation@v4
31+
32+
- name: Set up JDK 21
33+
uses: actions/setup-java@v4
34+
with:
35+
distribution: 'zulu'
36+
java-version: 21
37+
38+
- name: Add swap space
39+
run: |
40+
sudo swapoff -a || true
41+
sudo rm -f /mnt/swapfile
42+
sudo dd if=/dev/zero of=/mnt/swapfile bs=1M count=16384
43+
sudo chmod 600 /mnt/swapfile
44+
sudo mkswap /mnt/swapfile
45+
sudo swapon /mnt/swapfile
46+
free -h
47+
48+
- name: Setup Gradle
49+
uses: gradle/actions/setup-gradle@v4
50+
51+
- name: Verify codegen output matches committed sources
52+
env:
53+
GRADLE_OPTS: -Xmx6g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8
54+
run: >-
55+
./gradlew verifyCodegen
56+
--stacktrace
57+
--no-daemon
58+
--no-parallel
59+
--no-configuration-cache
60+
--build-cache
61+
-Dorg.gradle.jvmargs="-Xmx6g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8"
62+
-Dorg.gradle.workers.max=1
63+
-Dkotlin.daemon.enabled=false

README.md

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Kotlin FHIRPath
22

33
[![tests](https://github.com/ohs-foundation/kotlin-fhirpath/actions/workflows/run-tests.yml/badge.svg)](https://github.com/ohs-foundation/kotlin-fhirpath/actions/workflows/run-tests.yml)
4+
[![codegen](https://github.com/ohs-foundation/kotlin-fhirpath/actions/workflows/verify-codegen.yml/badge.svg)](https://github.com/ohs-foundation/kotlin-fhirpath/actions/workflows/verify-codegen.yml)
45
[![fhir-path-core](https://img.shields.io/maven-central/v/dev.ohs.fhir/fhir-path-core?color=yellow&label=fhir-path-core)](https://central.sonatype.com/artifact/dev.ohs.fhir/fhir-path-core)
56
[![FHIR R4](https://img.shields.io/maven-central/v/dev.ohs.fhir/fhir-path-r4?color=green&label=fhir-path-r4)](https://central.sonatype.com/artifact/dev.ohs.fhir/fhir-path-r4)
67
[![FHIR R4B](https://img.shields.io/maven-central/v/dev.ohs.fhir/fhir-path-r4b?color=orange&label=fhir-path-r4b)](https://central.sonatype.com/artifact/dev.ohs.fhir/fhir-path-r4b)
@@ -379,40 +380,29 @@ strictEngine.evaluateExpression("name.given1", patient) // Throws IllegalStateEx
379380

380381
## Developer Guide
381382

382-
### ANTLR
383+
### Code Generation
383384

384-
To generate the lexer, parser, and visitor locally using ANTLR Kotlin:
385+
This project tracks generated source code in version control under `src/commonMain/kotlin/...` to eliminate unnecessary build-time codegen overhead during routine builds:
386+
* **[ANTLR parser](fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/parsers)**: Generated from the formal [FHIRPath grammar](third_party/fhirpath-2.0.0).
387+
* **[UCUM helpers](fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/ucum)**: Generated from [ucum-essence.xml](third_party/ucum/ucum-essence.xml).
388+
* **Model extensions** ([R4](fhir-path-r4/src/commonMain/kotlin/dev/ohs/fhir/model/r4/ext), [R4B](fhir-path-r4b/src/commonMain/kotlin/dev/ohs/fhir/model/r4b/ext), and [R5](fhir-path-r5/src/commonMain/kotlin/dev/ohs/fhir/model/r5/ext)): Generated from FHIR StructureDefinitions.
385389

386-
```shell
387-
./gradlew :fhir-path-core:generateKotlinGrammarSource
388-
```
389-
390-
The generated code will be placed in `fhir-path-core/build/generated/grammar` under package
391-
`dev.ohs.fhir.fhirpath.parsers`.
392-
393-
### UCUM helpers
394-
395-
To generate UCUM helpers:
390+
To regenerate all sources across all modules at once:
396391

397392
```shell
398-
./gradlew :fhir-path-core:generateUcumHelpers
393+
./gradlew generateSources
399394
```
400395

401-
The generated code will be located in `fhir-path-core/build/generated/ucum` under package
402-
`dev.ohs.fhir.fhirpath.ucum`.
403-
404-
### Model extensions
405-
406-
To generate FHIR version specific model extensions:
396+
To verify that committed generated code matches generator output (as run in CI):
407397

408398
```shell
409-
./gradlew :fhir-path-r4:generateR4Helpers
410-
./gradlew :fhir-path-r4b:generateR4BHelpers
411-
./gradlew :fhir-path-r5:generateR5Helpers
399+
./gradlew verifyCodegen
412400
```
413401

414-
The generated code will be located in `fhir-path-<version>/build/generated` under packages
415-
`dev.ohs.fhir.model.<FHIR_VERSION>.ext` and `dev.ohs.fhir.fhirpath`.
402+
You can also run individual generation tasks locally:
403+
* `./gradlew :fhir-path-core:generateKotlinGrammarSource`
404+
* `./gradlew :fhir-path-core:generateUcumHelpers`
405+
* `./gradlew :fhir-path-r4:generateR4Helpers` (similarly for `:fhir-path-r4b:generateR4BHelpers` and `:fhir-path-r5:generateR5Helpers`)
416406

417407
### Tests
418408

build.gradle.kts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,33 @@ configure<com.diffplug.gradle.spotless.SpotlessExtension> {
66
ratchetFrom = "origin/main"
77
kotlin {
88
target("**/*.kt")
9+
targetExclude(
10+
"fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/ucum/**/*.kt",
11+
"fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/parsers/**/*.kt",
12+
"fhir-path-r4/src/commonMain/kotlin/**/*.kt",
13+
"fhir-path-r4b/src/commonMain/kotlin/**/*.kt",
14+
"fhir-path-r5/src/commonMain/kotlin/**/*.kt",
15+
)
916
ktfmt().googleStyle()
1017
licenseHeaderFile(
1118
"license-header.txt",
1219
)
1320
}
21+
22+
val currentYear = java.time.LocalDate.now().year.toString()
23+
val licenseHeaderTemplate = file("license-header.txt").readText()
24+
val generatedLicenseHeader = licenseHeaderTemplate.replace("\$YEAR", currentYear)
25+
format("generatedKotlin", com.diffplug.gradle.spotless.KotlinExtension::class.java) {
26+
target(
27+
"fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/ucum/**/*.kt",
28+
"fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/parsers/**/*.kt",
29+
"fhir-path-r4/src/commonMain/kotlin/**/*.kt",
30+
"fhir-path-r4b/src/commonMain/kotlin/**/*.kt",
31+
"fhir-path-r5/src/commonMain/kotlin/**/*.kt",
32+
)
33+
ktfmt().googleStyle()
34+
licenseHeader(generatedLicenseHeader)
35+
}
1436
flexmark {
1537
target("**/*.md")
1638
flexmark()
@@ -29,6 +51,76 @@ tasks.named("spotlessKotlin") {
2951
dependsOn(codegenTasks)
3052
}
3153

54+
tasks.named("spotlessGeneratedKotlin") {
55+
dependsOn(codegenTasks)
56+
}
57+
3258
tasks.named("spotlessFlexmark") {
3359
dependsOn(codegenTasks)
3460
}
61+
62+
tasks.register("generateSources") {
63+
group = "generation"
64+
description = "Regenerate all ANTLR grammar, UCUM helpers, and FHIR model helpers across all modules."
65+
dependsOn(codegenTasks)
66+
}
67+
68+
tasks.register("verifyCodegen") {
69+
description = "Verify committed generated code matches the codegen output."
70+
group = "verification"
71+
dependsOn(codegenTasks, "spotlessGeneratedKotlinApply")
72+
73+
val rootDir = project.rootDir
74+
75+
doLast {
76+
val generatedDirs = listOf(
77+
"fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/ucum",
78+
"fhir-path-core/src/commonMain/kotlin/dev/ohs/fhir/fhirpath/parsers",
79+
"fhir-path-r4/src/commonMain/kotlin",
80+
"fhir-path-r4b/src/commonMain/kotlin",
81+
"fhir-path-r5/src/commonMain/kotlin",
82+
)
83+
84+
val diffProcess = ProcessBuilder(
85+
listOf("git", "diff", "--exit-code", "--") + generatedDirs
86+
).directory(rootDir).redirectErrorStream(true).start()
87+
88+
val diffOutput = diffProcess.inputStream.bufferedReader().readText()
89+
val diffExitCode = diffProcess.waitFor()
90+
91+
if (diffExitCode != 0) {
92+
throw GradleException(
93+
buildString {
94+
appendLine("Codegen verification FAILED – the committed generated sources differ from the codegen output.")
95+
appendLine()
96+
appendLine("Changed files:")
97+
appendLine(diffOutput)
98+
appendLine()
99+
appendLine("Run `./gradlew generateSources spotlessApply` locally and commit the result.")
100+
}
101+
)
102+
}
103+
104+
val untrackedProcess = ProcessBuilder(
105+
listOf("git", "ls-files", "--others", "--exclude-standard", "--") + generatedDirs
106+
).directory(rootDir).redirectErrorStream(true).start()
107+
108+
val untrackedFiles = untrackedProcess.inputStream.bufferedReader().readText().trim()
109+
untrackedProcess.waitFor()
110+
111+
if (untrackedFiles.isNotEmpty()) {
112+
throw GradleException(
113+
buildString {
114+
appendLine("Codegen verification FAILED – codegen produced new files not present in the repository.")
115+
appendLine()
116+
appendLine("Untracked files:")
117+
appendLine(untrackedFiles)
118+
appendLine()
119+
appendLine("Run `./gradlew generateSources spotlessApply` locally and commit the result.")
120+
}
121+
)
122+
}
123+
124+
logger.lifecycle("✅ Codegen verification passed – all generated sources are up to date.")
125+
}
126+
}

buildSrc/src/main/kotlin/dev/ohs/fhir/fhirpath/codegen/model/FhirModelHelperGenerationTask.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ abstract class FhirModelHelperGenerationTask : DefaultTask() {
8484
// Filter out files like StructureDefinition-hdlcholesterol.json
8585
it.baseDefinition?.endsWith(it.type) == true
8686
}
87+
.sortedBy { it.name }
8788
.toList()
8889

8990
val fhirVersion = this.fhirVersion.get()

fhir-path-core/build.gradle.kts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,6 @@ configure<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension> {
9494
}
9595

9696

97-
// Ensure Kotlin compilation tasks depend on code generation
98-
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
99-
dependsOn(generateUcumHelpers)
100-
dependsOn(generateKotlinGrammarSource)
101-
}
102-
103-
// Ensure Jar packaging tasks depend on code generation
104-
tasks.withType<org.gradle.jvm.tasks.Jar>().configureEach {
105-
dependsOn(generateUcumHelpers)
106-
dependsOn(generateKotlinGrammarSource)
107-
}
10897

10998
mavenPublishing {
11099
publishToMavenCentral()

fhir-path-r4/build.gradle.kts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,4 @@ val generateR4Helpers = tasks.register<FhirModelHelperGenerationTask>("generateR
1414
finalizedBy(rootProject.tasks.named("spotlessApply"))
1515
}
1616

17-
// Ensure Kotlin compilation tasks depend on code generation
18-
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
19-
dependsOn(generateR4Helpers)
20-
}
21-
22-
// Ensure Jar packaging tasks depend on code generation
23-
tasks.withType<org.gradle.jvm.tasks.Jar>().configureEach {
24-
dependsOn(generateR4Helpers)
25-
}
2617

0 commit comments

Comments
 (0)