Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
java-version: 25

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
Expand All @@ -45,11 +45,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
java-version: 25

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
Expand Down
22 changes: 18 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,32 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

### Added
- Kotlinter Gradle plugin (`org.jmailen.kotlinter` 5.4.2) wired into the build for Kotlin lint and formatting.
- Detekt Gradle plugin (`dev.detekt` 2.0.0-alpha.3) with a `detekt { ... }` configuration block.
- Kotlinter Gradle plugin (`org.jmailen.kotlinter` 5.5.0) wired into the build for Kotlin lint and formatting.
- Detekt Gradle plugin (`dev.detekt` 2.0.0-alpha.5) with a `detekt { ... }` configuration block.
- `.editorconfig` pinning charset, end-of-line, indent style, and ktlint rule overrides so kotlinter aligns with the project's existing Kotlin style.
- Makefile targets: `help` (self-documenting target list), `lint`, `format`, `detekt`, `detekt-baseline`.
- GitHub Actions CI (`.github/workflows/ci.yml`) running `make lint` and `make tests` as parallel jobs on every push and pull request to `master`, with Gradle caching, concurrency cancellation, and uploaded lint/test reports.
- Makefile targets: `help` (self-documenting target list), `lint`, `format`, `detekt`, `detekt-baseline`, `versions`, `upgrade-wrapper`.
- Makefile guard `_require-gradle-version` that fails fast if the Gradle version cannot be read from `gradle/libs.versions.toml`.

### Changed
- Bumped `readingbat-core` to 3.1.8.
- Raised the JVM toolchain from Java 17 to Java 25 (`jvm` in `gradle/libs.versions.toml`).
- Upgraded the Gradle wrapper from 9.5.0 to 9.6.1.
- Bumped dependencies: Kotlin → 2.4.0, Ktor → 3.5.1, Kotest → 6.2.1, `readingbat-core` → 3.2.1, `common-utils` → 2.9.3, `kotlin-logging` → 8.0.4, kotlinter → 5.5.0, detekt → 2.0.0-alpha.5.
- Refactored `build.gradle.kts` into per-concern helper functions (`configureKotlin`, `configureDetekt`, `configureKotlinter`, `configureKtor`, `configureShadowJar`, `configureTest`, `configureVersions`).
- Enabled the Kotlin unused-return-value checker (`-Xreturn-value-checker=check`) on production code only (the test source set is excluded to avoid false positives from Kotest's fluent assertions).
- Build the fat jar via Ktor's `fatJar` task; the `dependencyUpdates` check now rejects pre-release candidates for dependencies currently on a stable version.
- Renamed the `gradle` version key to `gradle-wrapper` in `gradle/libs.versions.toml`.
- Renamed the Makefile `versioncheck` target to `versions`, made `help` the default target, and added `--no-configuration-cache --no-parallel` to the dependency-update check.
- `Content.kt` now imports `ReturnType` directly instead of `ReturnType.*`; all challenge registrations qualify return types (e.g., `ReturnType.IntType`).
- Makefile `build` and `cc` targets use the canonical `-x test` flag instead of `-xtest`.
- Reordered `.PHONY` to match the in-file target definition order.

### Removed
- Makefile `heroku` and `logs` targets.

### Fixed
- `ContentTests` now calls `correctAnswers()` as a function, matching the upgraded `readingbat-kotest` API (previously a property).

## [Pre-Unreleased history]

Prior changes were tracked only via Git history; see `git log` for details.
Expand Down
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,17 @@ make lint # Run kotlinter (lintKotlin) + detekt
make format # Format Kotlin sources via formatKotlin
make detekt # Run detekt static analysis only
make detekt-baseline # Regenerate the detekt baseline file
make versioncheck # Check dependency updates
make versions # Check for dependency updates
make upgrade-wrapper # Upgrade Gradle wrapper
make uberjar # Build fat jar
make uber # Build and run fat jar (java -jar build/libs/server.jar)
```

JVM toolchain: Java 17. Testing: Kotest with JUnit5 platform. Lint/format: kotlinter + detekt, configured via `.editorconfig` (ktlint rule overrides) and the `detekt { ... }` block in `build.gradle.kts`.
JVM toolchain: Java 25 (set via `jvm` in `gradle/libs.versions.toml`, consumed by `jvmToolchain(...)` in `build.gradle.kts`). Testing: Kotest with JUnit5 platform. Lint/format: kotlinter + detekt, configured via `.editorconfig` (ktlint rule overrides) and the `detekt { ... }` block in `build.gradle.kts`.

## Continuous Integration

`.github/workflows/ci.yml` runs `make lint` and `make tests` as parallel jobs on every push and pull request to `master`, with Gradle caching, concurrency cancellation, and uploaded lint/test reports.

## Adding a New Challenge

Expand Down
16 changes: 5 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: default help clean build lint format detekt detekt-baseline tests uberjar uber \
cc run heroku logs versions upgrade-wrapper _require-gradle-version
cc run versions upgrade-wrapper _require-gradle-version

GRADLE_VERSION := $(shell sed -n 's/^gradle-wrapper = "\(.*\)"/\1/p' gradle/libs.versions.toml)

Expand Down Expand Up @@ -42,19 +42,13 @@ cc: ## Continuous compilation (no tests)
run: ## Start the server on port 8080
./gradlew run

heroku: ## Push master to Heroku
git push heroku master

logs: ## Tail Heroku logs
heroku logs --tail

# Gradle's documented upgrade procedure: the first run rewrites
# gradle-wrapper.properties using the *old* wrapper jar; the second run
# regenerates the wrapper itself with the new version.
versions: ## Check for dependency updates (default target)
./gradlew dependencyUpdates --no-configuration-cache --no-parallel

upgrade-wrapper: _require-gradle-version ## Upgrade Gradle wrapper to version in libs.versions.toml
upgrade-wrapper: _require-gradle-version ## Upgrade the Gradle wrapper to the catalog version
# Gradle's documented upgrade procedure: the first run rewrites
# gradle-wrapper.properties using the *old* wrapper jar; the second run
# regenerates the wrapper itself with the new version.
./gradlew wrapper --gradle-version=$(GRADLE_VERSION) --distribution-type=bin
./gradlew wrapper --gradle-version=$(GRADLE_VERSION) --distribution-type=bin

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Python programming challenges served by the [ReadingBat](https://github.com/read

## Prerequisites

- Java 17+
- Java 25+ (the Gradle toolchain targets Java 25; Gradle can auto-provision it)

## Setup

Expand Down
45 changes: 33 additions & 12 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,57 @@
# Release Notes

## Unreleased — Kotlinter, detekt, and Makefile help
## Unreleased — tooling, CI, and dependency upgrades

This release focuses on developer ergonomics and code-style enforcement. No
runtime behavior, content, or public DSL has changed.
This release focuses on developer ergonomics, continuous integration, and
toolchain/dependency upgrades. No runtime behavior, content, or public DSL has
changed.

### Highlights

- **Kotlin linting and formatting.** The build now applies the
[kotlinter](https://github.com/jeremymailen/kotlinter-gradle) Gradle plugin.
Run `make lint` to check Kotlin sources, and `make format` to apply fixes.
- **Kotlin linting and formatting.** The build applies the
[kotlinter](https://github.com/jeremymailen/kotlinter-gradle) Gradle plugin
(5.5.0). Run `make lint` to check Kotlin sources, and `make format` to apply
fixes.
- **Detekt static analysis.** Detekt is wired up via the `dev.detekt` plugin
and a `detekt { ... }` block in `build.gradle.kts`. Use `make detekt` to run
it standalone, or `make detekt-baseline` to (re)generate the baseline.
(2.0.0-alpha.5) and a `detekt { ... }` block in `build.gradle.kts`. Use
`make detekt` to run it standalone, or `make detekt-baseline` to (re)generate
the baseline.
- **GitHub Actions CI.** A new `.github/workflows/ci.yml` runs `make lint` and
`make tests` as parallel jobs on every push and pull request to `master`,
with Gradle caching, concurrency cancellation, and uploaded reports.
- **Self-documenting Makefile.** Run `make help` for a colorized list of every
target, generated from `## ` annotations in the Makefile itself.
- **`.editorconfig`.** New file pinning charset, EOL, indent width, and the
set of ktlint rules disabled to match the existing Kotlin style.
- **Dependency bump.** `readingbat-core` updated from 3.1.5 to 3.1.8.
- **`.editorconfig`.** Pins charset, EOL, indent width, and the set of ktlint
rules disabled to match the existing Kotlin style.
- **JVM toolchain → Java 25.** The Gradle toolchain now targets Java 25
(previously Java 17); Gradle can auto-provision it.
- **Dependency upgrades.** Kotlin → 2.4.0, Ktor → 3.5.1, Kotest → 6.2.1,
`readingbat-core` → 3.2.1, `common-utils` → 2.9.3, `kotlin-logging` → 8.0.4.
The Gradle wrapper moved from 9.5.0 to 9.6.1.
- **Build script cleanup.** `build.gradle.kts` is reorganized into per-concern
helper functions, the fat jar is built via Ktor's `fatJar` task, the Kotlin
unused-return-value checker runs over production code, and `dependencyUpdates`
now filters out pre-release candidates for stable dependencies.

### Source-level changes

- `Content.kt` no longer wildcard-imports `ReturnType.*`; all return types are
qualified (e.g., `ReturnType.IntType`). When adding new challenges, use the
qualified form.
- The `gradle` version key in `gradle/libs.versions.toml` was renamed to
`gradle-wrapper`.
- The Makefile `versioncheck` target was renamed to `versions`, `help` is now
the default target, and the dependency-update check runs with
`--no-configuration-cache --no-parallel`.
- The Makefile uses the canonical `-x test` flag (with a space) in `build` and
`cc` targets, replacing the deprecated `-xtest` short form.

### Upgrade notes

- Java 25 is now the toolchain target. Gradle will auto-provision a matching
JDK; ensure toolchain auto-download is enabled or a Java 25 JDK is installed.
- After pulling, run `./gradlew --refresh-dependencies` (or `make build`) once
so the new plugins resolve.
so the new plugins and dependencies resolve.
- Existing developer workflows are unchanged; the new `lint` / `format`
targets are optional but recommended before pushing.

Expand Down
120 changes: 91 additions & 29 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
application
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.versions)
alias(libs.plugins.ktor.plugin)
alias(libs.plugins.detekt)
alias(libs.plugins.kotlinter)
}

// This is for ./gradlew run
description = "ReadingBat Site"

application {
mainClass = "ContentServerKt"
}

description = "ReadingBat Site"

dependencies {
implementation(libs.readingbat.core)
implementation(libs.core.utils)
Expand All @@ -24,43 +27,102 @@ dependencies {
testImplementation(libs.bundles.testing)
}

kotlin {
jvmToolchain(libs.versions.jvm.get().toInt())
tasks.register("stage") {
group = "distribution"
description = "Clean-builds the project for deployment."
dependsOn("clean", "build")
}

detekt {
source.setFrom("src/main/kotlin", "src/test/kotlin")
buildUponDefaultConfig = true
parallel = true
tasks.named("build") {
mustRunAfter("clean")
}

kotlinter {
ignoreFormatFailures = false
ignoreLintFailures = false
reporters = arrayOf("checkstyle", "plain")
configureKotlin()
configureDetekt()
configureKotlinter()
configureKtor()
configureShadowJar()
configureTest()
configureVersions()

fun Project.configureKotlin() {
kotlin {
jvmToolchain(libs.versions.jvm.get().toInt())
}

// Run the unused-return-value checker over production code only. Kotest's
// assertion DSL (e.g. shouldBe) returns its receiver, and tests intentionally
// discard that result, so applying the checker to the test source set would
// emit only false-positive warnings.
tasks.named<KotlinCompile>("compileKotlin") {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=check")
}
}
}

tasks.register("stage") {
dependsOn("clean", "build")
fun Project.configureDetekt() {
detekt {
source.setFrom("src/main/kotlin", "src/test/kotlin")
buildUponDefaultConfig = true
parallel = true
}
}

tasks.named("build") {
mustRunAfter("clean")
fun Project.configureKotlinter() {
kotlinter {
ignoreFormatFailures = false
ignoreLintFailures = false
reporters = arrayOf("checkstyle", "plain")
}
}

tasks.shadowJar {
archiveFileName.set("server.jar")
isZip64 = true
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
listOf("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "LICENSE*").forEach(::exclude)
fun Project.configureKtor() {
ktor {
fatJar {
archiveFileName = "server.jar"
}
}
}

tasks.test {
useJUnitPlatform()
fun Project.configureShadowJar() {
// Ktor's `buildFatJar` task delegates to shadow; this block configures that output.
tasks.shadowJar {
isZip64 = true
duplicatesStrategy = DuplicatesStrategy.WARN
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
}
}

fun Project.configureTest() {
tasks.test {
useJUnitPlatform()

testLogging {
events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED, TestLogEvent.STANDARD_ERROR)
exceptionFormat = TestExceptionFormat.FULL
showStandardStreams = false
}
}
}

fun Project.configureVersions() {
// A pre-release qualifier is a `.` or `-` delimiter followed by a known unstable
// keyword. `m\d` matches milestones (`-M1`/`.M2`) without catching stable classifiers
// like `-macos`/`-MR1`, and the `[.-]` delimiter catches both dash-style (`-alpha`)
// and dot-style (Netty's `.Beta1`) qualifiers while leaving `-jre`/`.Final` stable.
val preReleaseQualifier =
Regex("""[.-](rc|beta|alpha|m\d|cr|snapshot|eap|dev|milestone|pre)""", RegexOption.IGNORE_CASE)

fun isNonStable(version: String): Boolean = preReleaseQualifier.containsMatchIn(version)

testLogging {
events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED, TestLogEvent.STANDARD_ERROR)
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
showStandardStreams = false
tasks.withType<DependencyUpdatesTask>().configureEach {
notCompatibleWithConfigurationCache("the dependency updates plugin is not compatible with the configuration cache")
// Reject a pre-release candidate only when the current version is stable. For
// dependencies we intentionally track on a pre-release line (e.g. a detekt
// alpha), newer pre-releases are still surfaced as available updates.
rejectVersionIf {
isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
}
14 changes: 7 additions & 7 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[versions]
detekt = "2.0.0-alpha.3"
gradle-wrapper = "9.5.1"
jvm = "17"
kotest = "6.1.11"
detekt = "2.0.0-alpha.5"
gradle-wrapper = "9.6.1"
jvm = "25"
kotest = "6.2.1"
kotlin = "2.4.0"
kotlinter = "5.5.0"
ktor = "3.5.0"
ktor = "3.5.1"
logging = "8.0.4"
readingbat = "3.1.8"
utils = "2.9.0"
readingbat = "3.2.1"
utils = "2.9.3"
versions = "0.54.0"

[libraries]
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Expand Down
4 changes: 2 additions & 2 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading