Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KTOR-7743 Refactor Gradle configuration to use precompiled script plugins #4577

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
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
22 changes: 22 additions & 0 deletions build-logic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# build-logic

Build logic shared between Ktor subprojects.

This is similar to `buildSrc`, but uses [composite builds](https://docs.gradle.org/current/userguide/composite_builds.html)
to prevent projects from becoming out-of-date on any change in `buildSrc`.

This project should be included in the root `settings.gradle.kts`:

`<root project dir>/settings.gradle.kts`
```kotlin
includeBuild("build-logic")
```

`<root project dir>/build.gradle.kts`
```kotlin
plugins {
id("ktorbuild.base")
}
```

*The structure of this project is inspired by the structure used in [Dokka](https://github.com/Kotlin/dokka/tree/v2.0.0/build-logic/src/main/kotlin) and [Gradle](https://github.com/gradle/gradle/tree/v8.12.0/build-logic/jvm/src/main/kotlin).*
24 changes: 24 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
`kotlin-dsl`
}

dependencies {
implementation(libs.kotlinx.atomicfu.gradlePlugin)
implementation(libs.kotlin.gradlePlugin)
implementation(libs.dokka.gradlePlugin)
implementation(libs.develocity)
implementation(libs.gradleDoctor)

// A hack to make version catalogs accessible from buildSrc sources
// https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
}

// Should be synced with gradle/gradle-daemon-jvm.properties
kotlin {
jvmToolchain(21)
}
21 changes: 21 additions & 0 deletions build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

pluginManagement {
includeBuild("../build-settings-logic")
}

plugins {
id("conventions-dependency-resolution-management")
}

dependencyResolutionManagement {
// Additional repositories for build-logic
@Suppress("UnstableApiUsage")
repositories {
gradlePluginPortal()
}
}

rootProject.name = "build-logic"
10 changes: 10 additions & 0 deletions build-logic/src/main/kotlin/ktorbuild.base.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

import ktorbuild.KtorBuildExtension
import ktorbuild.internal.resolveVersion

version = resolveVersion()

extensions.create<KtorBuildExtension>(KtorBuildExtension.NAME)
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

import ktorbuild.internal.ktorBuild
import org.gradle.api.services.internal.RegisteredBuildServiceProvider

plugins {
id("ktorbuild.base")
id("com.osacky.doctor")
}

Expand All @@ -21,8 +23,8 @@ doctor {

// Always monitor tasks on CI, but disable it locally by default with providing an option to opt-in.
// See 'doctor.enableTaskMonitoring' in gradle.properties for details.
val enableTasksMonitoring = CI ||
properties.getOrDefault("doctor.enableTaskMonitoring", "false").toString().toBoolean()
val enableTasksMonitoring = ktorBuild.isCI.get() ||
findProperty("doctor.enableTaskMonitoring")?.toString().toBoolean()

if (!enableTasksMonitoring) {
logger.info("Gradle Doctor task monitoring is disabled.")
Expand Down
26 changes: 26 additions & 0 deletions build-logic/src/main/kotlin/ktorbuild.dokka.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

import ktorbuild.internal.libs
import org.jetbrains.dokka.gradle.DokkaMultiModuleTask

plugins {
id("org.jetbrains.dokka")
}

dependencies {
dokkaPlugin(libs.dokka.plugin.versioning)
}

if (project == rootProject) {
tasks.withType<DokkaMultiModuleTask>().configureEach {
val version = project.version
val dokkaOutputDir = "../versions"
val id = "org.jetbrains.dokka.versioning.VersioningPlugin"
val config = """{ "version": "$version", "olderVersionsDir":"$dokkaOutputDir" }"""

outputDirectory = project.layout.projectDirectory.dir("$dokkaOutputDir/$version")
pluginsMapConfiguration = mapOf(id to config)
}
}
70 changes: 70 additions & 0 deletions build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:OptIn(ExperimentalKotlinGradlePluginApi::class)

import ktorbuild.internal.gradle.*
import ktorbuild.internal.ktorBuild
import ktorbuild.maybeNamed
import ktorbuild.targets.*
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi

plugins {
id("ktorbuild.base")
kotlin("multiplatform")
id("org.jetbrains.kotlinx.atomicfu")
}

kotlin {
explicitApi()

compilerOptions {
progressiveMode = true
apiVersion = ktorBuild.kotlinApiVersion
languageVersion = ktorBuild.kotlinLanguageVersion
freeCompilerArgs.addAll("-Xexpect-actual-classes")
}

applyHierarchyTemplate(KtorTargets.hierarchyTemplate)
addTargets(ktorBuild.targets)

// Specify JVM toolchain later to prevent it from being evaluated before it was configured.
// TODO: Remove `afterEvaluate` when the BCV issue triggering JVM toolchain evaluation is fixed
// https://github.com/Kotlin/binary-compatibility-validator/issues/286
afterEvaluate {
jvmToolchain {
languageVersion = ktorBuild.jvmToolchain
}
}
}

val targets = ktorBuild.targets

configureCommon()
if (targets.hasJvm) configureJvm()
if (targets.hasJs) configureJs()
if (targets.hasWasmJs) configureWasmJs()

if (targets.hasJsOrWasmJs) {
tasks.configureEach {
if (name == "compileJsAndWasmSharedMainKotlinMetadata") enabled = false
}
}

// Run native tests only on matching host.
// There is no need to configure `onlyIf` for Darwin targets as they're configured by KGP.
@Suppress("UnstableApiUsage")
if (targets.hasNative) {
tasks.maybeNamed("linkDebugTestLinuxX64") {
onlyIf("run only on Linux") { ktorBuild.os.get().isLinux() }
}
tasks.maybeNamed("linkDebugTestLinuxArm64") {
onlyIf("run only on Linux") { ktorBuild.os.get().isLinux() }
}
tasks.maybeNamed("linkDebugTestMingwX64") {
onlyIf("run only on Windows") { ktorBuild.os.get().isWindows() }
}
}

if (ktorBuild.isCI.get()) configureTestTasksOnCi()
12 changes: 12 additions & 0 deletions build-logic/src/main/kotlin/ktorbuild.project.internal.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
id("ktorbuild.kmp")
}

kotlin {
// Disable explicit API by default for internal projects
explicitApi = null
}
68 changes: 68 additions & 0 deletions build-logic/src/main/kotlin/ktorbuild/CInterop.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package ktorbuild

import ktorbuild.targets.KtorTargets
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.provideDelegate
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultCInteropSettings
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.external.project

/**
* Creates a CInterop configuration for all Native targets using the given [sourceSet]
* in a Kotlin Multiplatform project.
*
* The [name] defines the CInterop configuration name. Definition file is expected to be located
* at `[sourceSet]/interop/[name].def` by default, but can be customized via [definitionFilePath].
* Additional configuration can be provided through [configure] block.
*
* Simple usage:
* ```
* kotlin {
* createCInterop("ssl", "posix")
* }
* ```
*
* Advanced usage with a separate definition for each target and additional configuration:
* ```
* kotlin {
* createCInterop(
* name = "ssl",
* sourceSet = "posix",
* definitionFilePath = { target -> "$target/interop/ssl.def" },
* configure = { target ->
* includeDirs("$target/interop/include")
* compilerOpts("-DUSE_SSL")
* }
* )
* }
* ```
*/
@Suppress("UnstableApiUsage")
fun KotlinMultiplatformExtension.createCInterop(
name: String,
sourceSet: String,
definitionFilePath: (String) -> String = { "$sourceSet/interop/$name.def" },
configure: DefaultCInteropSettings.(String) -> Unit = {}
) {
val cinteropTargets = KtorTargets.resolveTargets(sourceSet)
@OptIn(ExternalKotlinTargetApi::class)
val projectDirectory = project.isolated.projectDirectory

targets.named { it in cinteropTargets }
.all {
check(this is KotlinNativeTarget) { "Can't create cinterop for non-native target $targetName" }

val main by compilations
main.cinterops.create(name) {
definitionFile = projectDirectory.file(definitionFilePath(targetName))
configure(targetName)
}
}
}
105 changes: 105 additions & 0 deletions build-logic/src/main/kotlin/ktorbuild/KtorBuildExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package ktorbuild

import ktorbuild.internal.gradle.finalizedOnRead
import ktorbuild.targets.KtorTargets
import org.gradle.api.JavaVersion
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.newInstance
import org.gradle.kotlin.dsl.property
import org.gradle.platform.BuildPlatform
import org.gradle.platform.OperatingSystem
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import javax.inject.Inject

@Suppress("UnstableApiUsage")
abstract class KtorBuildExtension(
objects: ObjectFactory,
providers: ProviderFactory,
buildPlatform: BuildPlatform,
val targets: KtorTargets,
) {

@Inject
constructor(
objects: ObjectFactory,
providers: ProviderFactory,
buildPlatform: BuildPlatform,
) : this(objects, providers, buildPlatform, targets = objects.newInstance())

private val buildingOnTeamCity: Provider<Boolean> =
providers.environmentVariable("TEAMCITY_VERSION").map(String::isNotBlank)

val isCI: Provider<Boolean> =
providers.environmentVariable("CI")
.map(String::isNotBlank)
.orElse(buildingOnTeamCity)
.orElse(false)

/**
* The JDK version to be used to build the project.
* By default, the minimal supported JDK version is used.
*/
val jvmToolchain: Property<JavaLanguageVersion> =
objects.property<JavaLanguageVersion>()
.convention(DEFAULT_JDK)
.finalizedOnRead()

fun jvmToolchain(version: Int) {
jvmToolchain.set(JavaLanguageVersion.of(version))
}

/**
* The JDK version to be used for testing.
*
* The value is determined from the Gradle property "test.jdk".
* If the property is not specified, it defaults to the current JDK used by Gradle.
*
* For example, to run tests against JDK 8, run a test task with flag "-Ptest.jdk=8"
* or put this property to `gradle.properties`.
*/
val jvmTestToolchain: Provider<JavaLanguageVersion> =
providers.gradleProperty("test.jdk")
.orElse(providers.provider { JavaVersion.current().majorVersion })
.map(JavaLanguageVersion::of)

/**
* The Kotlin API version Ktor should be compatible with.
*
* DON'T change the property name as it is used in Kotlin Libraries train.
*/
val kotlinApiVersion: Provider<KotlinVersion> =
providers.gradleProperty("kotlin_api_version")
.map(KotlinVersion::fromVersion)
.orElse(DEFAULT_KOTLIN_VERSION)

/**
* The Kotlin Language version Ktor should be compatible with.
*
* DON'T change the property name as it is used in Kotlin Libraries train.
*/
val kotlinLanguageVersion: Provider<KotlinVersion> =
providers.gradleProperty("kotlin_language_version")
.map(KotlinVersion::fromVersion)
.orElse(DEFAULT_KOTLIN_VERSION)

/** Host operating system. */
val os: Provider<OperatingSystem> = providers.provider { buildPlatform.operatingSystem }

companion object {
const val NAME = "ktorBuild"

/** The default (minimal) JDK version used for building the project. */
private val DEFAULT_JDK = JavaLanguageVersion.of(8)

/** The default (minimal) Kotlin version used as API and Language version. */
private val DEFAULT_KOTLIN_VERSION = KotlinVersion.KOTLIN_2_0
}
}
Loading
Loading