Skip to content

Commit 6e31697

Browse files
authored
KTOR-7743 Make projects isolated (#4740)
* Apply project plugin in project build scripts * Unify dependencies declaration API * Remove shared configurations from parent projects * Remove buildSrc * Don't publish empty artifacts (KTOR-8336) * Add ProjectTagsService to remove usage of rootProject.subprojects * Remove usage of findProperty and rootProject.layout
1 parent bbf4237 commit 6e31697

File tree

158 files changed

+1436
-1139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

158 files changed

+1436
-1139
lines changed

build-logic/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88

99
dependencies {
1010
implementation(libs.kotlin.gradlePlugin)
11+
implementation(libs.kotlin.serialization)
1112
implementation(libs.kotlinx.atomicfu.gradlePlugin)
1213
implementation(libs.kotlinx.binaryCompatibilityValidator)
1314
implementation(libs.dokka.gradlePlugin)

build-logic/src/main/kotlin/ktorbuild.base.gradle.kts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
import ktorbuild.KtorBuildExtension
5+
import ktorbuild.*
66
import ktorbuild.internal.resolveVersion
77

88
version = resolveVersion()
99

10+
ProjectTagsService.register(project)
1011
extensions.create<KtorBuildExtension>(KtorBuildExtension.NAME)

build-logic/src/main/kotlin/ktorbuild.doctor.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ doctor {
2424
// Always monitor tasks on CI, but disable it locally by default with providing an option to opt-in.
2525
// See 'doctor.enableTaskMonitoring' in gradle.properties for details.
2626
val enableTasksMonitoring = ktorBuild.isCI.get() ||
27-
findProperty("doctor.enableTaskMonitoring")?.toString().toBoolean()
27+
providers.gradleProperty("doctor.enableTaskMonitoring").orNull.toBoolean()
2828

2929
if (!enableTasksMonitoring) {
3030
logger.info("Gradle Doctor task monitoring is disabled.")

build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
66

77
import ktorbuild.internal.*
8-
import ktorbuild.maybeNamed
8+
import ktorbuild.internal.gradle.maybeNamed
99
import ktorbuild.targets.*
1010
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
1111
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
import ktorbuild.internal.ktorBuild
6+
7+
plugins {
8+
id("ktorbuild.project.library")
9+
}
10+
11+
kotlin {
12+
sourceSets {
13+
commonMain.dependencies {
14+
api(project(":ktor-client:ktor-client-core"))
15+
}
16+
commonTest.dependencies {
17+
implementation(project(":ktor-client:ktor-client-tests"))
18+
}
19+
20+
if (ktorBuild.targets.hasJvm) {
21+
jvmTest.dependencies {
22+
runtimeOnly(project(":ktor-client:ktor-client-okhttp"))
23+
runtimeOnly(project(":ktor-client:ktor-client-apache"))
24+
runtimeOnly(project(":ktor-client:ktor-client-cio"))
25+
runtimeOnly(project(":ktor-client:ktor-client-android"))
26+
runtimeOnly(project(":ktor-client:ktor-client-java"))
27+
}
28+
}
29+
30+
if (ktorBuild.targets.hasJs) {
31+
jsTest.dependencies {
32+
runtimeOnly(project(":ktor-client:ktor-client-js"))
33+
}
34+
}
35+
}
36+
}

build-logic/src/main/kotlin/ktorbuild.project.library.gradle.kts

+4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
import ktorbuild.*
6+
57
plugins {
68
id("ktorbuild.kmp")
79
id("ktorbuild.dokka")
810
id("ktorbuild.publish")
911
}
12+
13+
addProjectTag(ProjectTag.Library)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
plugins {
6+
id("ktorbuild.project.library")
7+
}
8+
9+
kotlin {
10+
sourceSets {
11+
commonMain.dependencies {
12+
api(project(":ktor-server:ktor-server-core"))
13+
}
14+
commonTest.dependencies {
15+
implementation(project(":ktor-server:ktor-server-test-base"))
16+
}
17+
}
18+
}

build-logic/src/main/kotlin/ktorbuild.publish.gradle.kts

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
import ktorbuild.findByName
5+
import ktorbuild.*
6+
import ktorbuild.internal.gradle.findByName
67
import ktorbuild.internal.ktorBuild
78
import ktorbuild.internal.publish.*
89
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
@@ -13,6 +14,8 @@ plugins {
1314
id("signing") apply false
1415
}
1516

17+
addProjectTag(ProjectTag.Published)
18+
1619
publishing {
1720
publications.configureEach {
1821
if (this !is MavenPublication) return@configureEach

build-logic/src/main/kotlin/ktorbuild.publish.verifier.gradle.kts

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
import ktorbuild.*
56
import ktorbuild.internal.publish.*
67
import ktorbuild.internal.publish.TestRepository.configureTestRepository
78
import ktorbuild.internal.publish.TestRepository.locateTestRepository
@@ -10,13 +11,15 @@ val cleanTestRepository by tasks.registering(Delete::class) {
1011
delete(locateTestRepository())
1112
}
1213

14+
val publishedProjects = projectsWithTag(ProjectTag.Published)
15+
1316
tasks.register<ValidatePublishedArtifactsTask>(ValidatePublishedArtifactsTask.NAME) {
1417
dependsOn(cleanTestRepository)
1518

16-
rootProject.subprojects.forEach { subproject ->
17-
subproject.plugins.withId("maven-publish") {
18-
subproject.configureTestRepository()
19-
val publishTasks = subproject.tasks.withType<PublishToMavenRepository>()
19+
publishedProjects.get().forEach { project ->
20+
with(project) {
21+
configureTestRepository()
22+
val publishTasks = tasks.withType<PublishToMavenRepository>()
2023
.matching { it.repository.name == TestRepository.NAME }
2124
publishTasks.configureEach { mustRunAfter(cleanTestRepository) }
2225
dependsOn(publishTasks)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package ktorbuild
6+
7+
import ktorbuild.ProjectTagsService.Companion.projectTagsService
8+
import org.gradle.api.Project
9+
import org.gradle.api.provider.MapProperty
10+
import org.gradle.api.provider.Provider
11+
import org.gradle.api.services.BuildService
12+
import org.gradle.api.services.BuildServiceParameters
13+
import org.gradle.kotlin.dsl.getByType
14+
import org.gradle.kotlin.dsl.registerIfAbsent
15+
16+
/**
17+
* Service allowing to aggregate projects by [ProjectTag] attached to it
18+
* in a way (potentially) compatible with "isolated projects" feature.
19+
*
20+
* Usage:
21+
* ```
22+
* // In some subproject
23+
* addProjectTag(ProjectTag.Library)
24+
*
25+
* // In a project aggregating libraries
26+
* val libraryProjects = projectsWithTag(ProjectTag.Library) // Provider<List<Project>>
27+
* ```
28+
*
29+
* @see addProjectTag
30+
* @see projectsWithTag
31+
*/
32+
abstract class ProjectTagsService : BuildService<BuildServiceParameters.None> {
33+
34+
internal abstract val projectTags: MapProperty<String, Set<ProjectTag>>
35+
36+
private val Project.tags: Set<ProjectTag>
37+
get() = projectTags.getting(path).orNull.orEmpty()
38+
39+
internal fun addTag(project: Project, tag: ProjectTag) {
40+
projectTags.put(project.path, project.tags + tag)
41+
}
42+
43+
internal fun hasTag(project: Project, tag: ProjectTag): Boolean = tag in project.tags
44+
45+
internal fun getTagged(tag: ProjectTag): Set<String> {
46+
projectTags.finalizeValue()
47+
return projectTags.get().filterValues { tag in it }.keys
48+
}
49+
50+
companion object {
51+
private const val NAME = "subprojectService"
52+
53+
internal val Project.projectTagsService: ProjectTagsService
54+
get() = project.extensions.getByType<ProjectTagsService>()
55+
56+
fun register(project: Project) {
57+
val service = project.gradle.sharedServices.registerIfAbsent(NAME, ProjectTagsService::class).get()
58+
project.extensions.add(NAME, service)
59+
}
60+
}
61+
}
62+
63+
/** Adds the specified [tag] to this project. */
64+
fun Project.addProjectTag(tag: ProjectTag) {
65+
projectTagsService.addTag(this, tag)
66+
}
67+
68+
/**
69+
* Returns lazy property collecting list of projects marked with the specified [tag].
70+
*
71+
* Warning: Calling [Provider.get] evaluates all projects, so it should be done only when needed.
72+
* For example, if you need to add all projects with tag `Library` as "api" dependency to your project,
73+
* prefer lazy API to do so:
74+
*
75+
* ```
76+
* val libraryProjects = projectsWithTag(ProjectTag.Library)
77+
*
78+
* // Eager API (evaluates all project immediately)
79+
* dependencies {
80+
* libraryProjects.get().forEach { api(it) }
81+
* }
82+
*
83+
* // Lazy API (evaluates all project only when 'api' configuration is needed)
84+
* configurations.api {
85+
* dependencies.addAllLater(libraryProjects.mapValue { project.dependencies.create(it) })
86+
* }
87+
* ```
88+
*
89+
* Implicitly adds tag [ProjectTag.Meta] to this project.
90+
*/
91+
fun Project.projectsWithTag(tag: ProjectTag): Provider<List<Project>> = projectsWithTag(tag) { it }
92+
93+
/**
94+
* Returns lazy property collecting list of projects marked with the specified [tag] with [transform] applied to them.
95+
*/
96+
fun <T> Project.projectsWithTag(tag: ProjectTag, transform: (Project) -> T): Provider<List<T>> {
97+
addProjectTag(ProjectTag.Meta)
98+
99+
// Postpone projects evaluation and tags freezing
100+
return provider {
101+
ensureAllProjectsEvaluated()
102+
projectTagsService.getTagged(tag).map { transform(project(it)) }
103+
}
104+
}
105+
106+
private fun Project.ensureAllProjectsEvaluated() {
107+
val service = projectTagsService
108+
109+
for (subproject in rootProject.subprojects) {
110+
if (subproject == this || service.hasTag(subproject, ProjectTag.Meta)) continue
111+
evaluationDependsOn(subproject.path)
112+
}
113+
}
114+
115+
enum class ProjectTag {
116+
/** Project published to the Maven repository. */
117+
Published,
118+
119+
/** Public library project. Implies [Published]. */
120+
Library,
121+
122+
/** Project containing JVM target. Implies [Library]. */
123+
Jvm,
124+
125+
/** Project aggregating information about other projects. */
126+
Meta,
127+
}
128+
129+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
// We want these extensions to be available without importing
6+
@file:Suppress("PackageDirectoryMismatch")
7+
8+
import org.gradle.api.NamedDomainObjectContainer
9+
import org.gradle.api.NamedDomainObjectProvider
10+
import org.gradle.api.provider.Provider
11+
12+
fun <T> NamedDomainObjectContainer<T>.maybeRegister(name: String, configure: T.() -> Unit): NamedDomainObjectProvider<T> {
13+
return if (name in names) named(name, configure) else register(name, configure)
14+
}
15+
16+
inline fun <T, R> Provider<out Iterable<T>>.mapValue(crossinline transform: (T) -> R): Provider<List<R>> =
17+
map { it.map(transform) }
18+
19+
inline fun <T, R> Provider<out Iterable<T>>.flatMapValue(crossinline transform: (T) -> Iterable<R>): Provider<List<R>> =
20+
map { it.flatMap(transform) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
6+
7+
import org.gradle.api.NamedDomainObjectContainer
8+
import org.gradle.api.NamedDomainObjectProvider
9+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
10+
import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention
11+
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
12+
13+
private typealias KotlinSourceSets = NamedDomainObjectContainer<KotlinSourceSet>
14+
private typealias KotlinSourceSetProvider = NamedDomainObjectProvider<KotlinSourceSet>
15+
16+
// Additional accessors to the ones declared in KotlinMultiplatformSourceSetConventions
17+
18+
val KotlinSourceSets.posixMain: KotlinSourceSetProvider by KotlinSourceSetConvention
19+
val KotlinSourceSets.darwinMain: KotlinSourceSetProvider by KotlinSourceSetConvention
20+
val KotlinSourceSets.darwinTest: KotlinSourceSetProvider by KotlinSourceSetConvention
21+
val KotlinSourceSets.desktopMain: KotlinSourceSetProvider by KotlinSourceSetConvention
22+
val KotlinSourceSets.desktopTest: KotlinSourceSetProvider by KotlinSourceSetConvention
23+
val KotlinSourceSets.windowsMain: KotlinSourceSetProvider by KotlinSourceSetConvention
24+
val KotlinSourceSets.windowsTest: KotlinSourceSetProvider by KotlinSourceSetConvention

build-logic/src/main/kotlin/ktorbuild/internal/Train.kt

+7-6
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,18 @@ private fun Project.printManifest() {
5353
}
5454

5555
private fun Project.configureVersion() {
56-
version = findProperty("DeployVersion") ?: return
56+
version = providers.gradleProperty("DeployVersion").orNull ?: return
57+
val skipSnapshotChecks = providers.gradleProperty("skip_snapshot_checks").orNull.toBoolean()
5758

58-
if (buildSnapshotTrain && !rootProject.hasProperty("skip_snapshot_checks")) {
59-
check(version, rootProject.libs.versions.atomicfu, "atomicfu")
60-
check(version, rootProject.libs.versions.coroutines, "coroutines")
61-
check(version, rootProject.libs.versions.serialization, "serialization")
59+
if (buildSnapshotTrain && !skipSnapshotChecks) {
60+
check(version, libs.versions.atomicfu, "atomicfu")
61+
check(version, libs.versions.coroutines, "coroutines")
62+
check(version, libs.versions.serialization, "serialization")
6263
}
6364
}
6465

6566
private val Project.buildSnapshotTrain: Boolean
66-
get() = rootProject.findProperty("build_snapshot_train")?.toString().toBoolean()
67+
get() = providers.gradleProperty("build_snapshot_train").orNull.toBoolean()
6768

6869
private fun check(version: Any, libVersionProvider: Provider<String>, libName: String) {
6970
val libVersion = libVersionProvider.get()

build-logic/src/main/kotlin/ktorbuild/internal/Version.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import org.gradle.api.Project
1515
*/
1616
internal fun Project.resolveVersion(): String {
1717
val projectVersion = project.version.toString()
18-
val releaseVersion = findProperty("releaseVersion")?.toString()
19-
val eapVersion = findProperty("eapVersion")?.toString()
18+
val releaseVersion = providers.gradleProperty("releaseVersion").orNull
19+
val eapVersion = providers.gradleProperty("eapVersion").orNull
2020

2121
return when {
2222
releaseVersion != null -> releaseVersion

build-logic/src/main/kotlin/ktorbuild/NamedDomainObjectCollection.kt renamed to build-logic/src/main/kotlin/ktorbuild/internal/gradle/NamedDomainObjectCollection.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
package ktorbuild
5+
package ktorbuild.internal.gradle
66

77
import org.gradle.api.NamedDomainObjectCollection
88
import org.gradle.api.NamedDomainObjectProvider

build-logic/src/main/kotlin/ktorbuild/internal/gradle/Property.kt

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
package ktorbuild.internal.gradle
66

7+
import org.gradle.api.Project
8+
import org.gradle.api.file.Directory
9+
import org.gradle.api.file.RegularFile
710
import org.gradle.api.provider.Property
11+
import org.gradle.api.provider.Provider
12+
import java.io.File
813

914
internal fun <T> Property<T>.finalizedOnRead(): Property<T> = apply { finalizeValueOnRead() }
15+
16+
internal fun Project.directoryProvider(fileProvider: () -> File): Provider<Directory> =
17+
layout.dir(provider(fileProvider))
18+
19+
internal fun Project.regularFileProvider(fileProvider: () -> File): Provider<RegularFile> =
20+
layout.file(provider(fileProvider))

0 commit comments

Comments
 (0)