Не подтверждена Коммит 6e316974 создал по автору Osip Fatkullin's avatar Osip Fatkullin Зафиксировано автором GitHub
Просмотр файлов

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
владелец bbf42373
......@@ -8,6 +8,7 @@ plugins {
dependencies {
implementation(libs.kotlin.gradlePlugin)
implementation(libs.kotlin.serialization)
implementation(libs.kotlinx.atomicfu.gradlePlugin)
implementation(libs.kotlinx.binaryCompatibilityValidator)
implementation(libs.dokka.gradlePlugin)
......
......@@ -2,9 +2,10 @@
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
import ktorbuild.KtorBuildExtension
import ktorbuild.*
import ktorbuild.internal.resolveVersion
version = resolveVersion()
ProjectTagsService.register(project)
extensions.create<KtorBuildExtension>(KtorBuildExtension.NAME)
......@@ -24,7 +24,7 @@ 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 = ktorBuild.isCI.get() ||
findProperty("doctor.enableTaskMonitoring")?.toString().toBoolean()
providers.gradleProperty("doctor.enableTaskMonitoring").orNull.toBoolean()
if (!enableTasksMonitoring) {
logger.info("Gradle Doctor task monitoring is disabled.")
......
......@@ -5,7 +5,7 @@
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
import ktorbuild.internal.*
import ktorbuild.maybeNamed
import ktorbuild.internal.gradle.maybeNamed
import ktorbuild.targets.*
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
......
/*
* Copyright 2014-2021 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.
*/
description = "Ktor client plugins"
import ktorbuild.internal.ktorBuild
kotlin.sourceSets {
commonMain {
dependencies {
api(project(":ktor-client:ktor-client-core"))
}
}
plugins {
id("ktorbuild.project.library")
}
subprojects {
kotlin.sourceSets {
commonMain {
dependencies {
api(project(":ktor-client:ktor-client-core"))
}
kotlin {
sourceSets {
commonMain.dependencies {
api(project(":ktor-client:ktor-client-core"))
}
commonTest.dependencies {
implementation(project(":ktor-client:ktor-client-tests"))
}
jvmTest {
dependencies {
if (ktorBuild.targets.hasJvm) {
jvmTest.dependencies {
runtimeOnly(project(":ktor-client:ktor-client-okhttp"))
runtimeOnly(project(":ktor-client:ktor-client-apache"))
runtimeOnly(project(":ktor-client:ktor-client-cio"))
runtimeOnly(project(":ktor-client:ktor-client-android"))
runtimeOnly(project(":ktor-client:ktor-client-okhttp"))
try {
runtimeOnly(project(":ktor-client:ktor-client-java"))
} catch (_: UnknownProjectException) {
}
runtimeOnly(project(":ktor-client:ktor-client-java"))
}
}
findByName("jsTest")?.dependencies {
api(project(":ktor-client:ktor-client-js"))
}
commonTest {
dependencies {
api(project(":ktor-client:ktor-client-tests"))
if (ktorBuild.targets.hasJs) {
jsTest.dependencies {
runtimeOnly(project(":ktor-client:ktor-client-js"))
}
}
}
......
......@@ -2,8 +2,12 @@
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
import ktorbuild.*
plugins {
id("ktorbuild.kmp")
id("ktorbuild.dokka")
id("ktorbuild.publish")
}
addProjectTag(ProjectTag.Library)
/*
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
plugins {
id("ktorbuild.project.library")
}
kotlin {
sourceSets {
commonMain.dependencies {
api(project(":ktor-server:ktor-server-core"))
}
commonTest.dependencies {
implementation(project(":ktor-server:ktor-server-test-base"))
}
}
}
......@@ -2,7 +2,8 @@
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
import ktorbuild.findByName
import ktorbuild.*
import ktorbuild.internal.gradle.findByName
import ktorbuild.internal.ktorBuild
import ktorbuild.internal.publish.*
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
......@@ -13,6 +14,8 @@ plugins {
id("signing") apply false
}
addProjectTag(ProjectTag.Published)
publishing {
publications.configureEach {
if (this !is MavenPublication) return@configureEach
......
......@@ -2,6 +2,7 @@
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
import ktorbuild.*
import ktorbuild.internal.publish.*
import ktorbuild.internal.publish.TestRepository.configureTestRepository
import ktorbuild.internal.publish.TestRepository.locateTestRepository
......@@ -10,13 +11,15 @@ val cleanTestRepository by tasks.registering(Delete::class) {
delete(locateTestRepository())
}
val publishedProjects = projectsWithTag(ProjectTag.Published)
tasks.register<ValidatePublishedArtifactsTask>(ValidatePublishedArtifactsTask.NAME) {
dependsOn(cleanTestRepository)
rootProject.subprojects.forEach { subproject ->
subproject.plugins.withId("maven-publish") {
subproject.configureTestRepository()
val publishTasks = subproject.tasks.withType<PublishToMavenRepository>()
publishedProjects.get().forEach { project ->
with(project) {
configureTestRepository()
val publishTasks = tasks.withType<PublishToMavenRepository>()
.matching { it.repository.name == TestRepository.NAME }
publishTasks.configureEach { mustRunAfter(cleanTestRepository) }
dependsOn(publishTasks)
......
/*
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package ktorbuild
import ktorbuild.ProjectTagsService.Companion.projectTagsService
import org.gradle.api.Project
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.registerIfAbsent
/**
* Service allowing to aggregate projects by [ProjectTag] attached to it
* in a way (potentially) compatible with "isolated projects" feature.
*
* Usage:
* ```
* // In some subproject
* addProjectTag(ProjectTag.Library)
*
* // In a project aggregating libraries
* val libraryProjects = projectsWithTag(ProjectTag.Library) // Provider<List<Project>>
* ```
*
* @see addProjectTag
* @see projectsWithTag
*/
abstract class ProjectTagsService : BuildService<BuildServiceParameters.None> {
internal abstract val projectTags: MapProperty<String, Set<ProjectTag>>
private val Project.tags: Set<ProjectTag>
get() = projectTags.getting(path).orNull.orEmpty()
internal fun addTag(project: Project, tag: ProjectTag) {
projectTags.put(project.path, project.tags + tag)
}
internal fun hasTag(project: Project, tag: ProjectTag): Boolean = tag in project.tags
internal fun getTagged(tag: ProjectTag): Set<String> {
projectTags.finalizeValue()
return projectTags.get().filterValues { tag in it }.keys
}
companion object {
private const val NAME = "subprojectService"
internal val Project.projectTagsService: ProjectTagsService
get() = project.extensions.getByType<ProjectTagsService>()
fun register(project: Project) {
val service = project.gradle.sharedServices.registerIfAbsent(NAME, ProjectTagsService::class).get()
project.extensions.add(NAME, service)
}
}
}
/** Adds the specified [tag] to this project. */
fun Project.addProjectTag(tag: ProjectTag) {
projectTagsService.addTag(this, tag)
}
/**
* Returns lazy property collecting list of projects marked with the specified [tag].
*
* Warning: Calling [Provider.get] evaluates all projects, so it should be done only when needed.
* For example, if you need to add all projects with tag `Library` as "api" dependency to your project,
* prefer lazy API to do so:
*
* ```
* val libraryProjects = projectsWithTag(ProjectTag.Library)
*
* // Eager API (evaluates all project immediately)
* dependencies {
* libraryProjects.get().forEach { api(it) }
* }
*
* // Lazy API (evaluates all project only when 'api' configuration is needed)
* configurations.api {
* dependencies.addAllLater(libraryProjects.mapValue { project.dependencies.create(it) })
* }
* ```
*
* Implicitly adds tag [ProjectTag.Meta] to this project.
*/
fun Project.projectsWithTag(tag: ProjectTag): Provider<List<Project>> = projectsWithTag(tag) { it }
/**
* Returns lazy property collecting list of projects marked with the specified [tag] with [transform] applied to them.
*/
fun <T> Project.projectsWithTag(tag: ProjectTag, transform: (Project) -> T): Provider<List<T>> {
addProjectTag(ProjectTag.Meta)
// Postpone projects evaluation and tags freezing
return provider {
ensureAllProjectsEvaluated()
projectTagsService.getTagged(tag).map { transform(project(it)) }
}
}
private fun Project.ensureAllProjectsEvaluated() {
val service = projectTagsService
for (subproject in rootProject.subprojects) {
if (subproject == this || service.hasTag(subproject, ProjectTag.Meta)) continue
evaluationDependsOn(subproject.path)
}
}
enum class ProjectTag {
/** Project published to the Maven repository. */
Published,
/** Public library project. Implies [Published]. */
Library,
/** Project containing JVM target. Implies [Library]. */
Jvm,
/** Project aggregating information about other projects. */
Meta,
}
......@@ -2,9 +2,19 @@
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
// We want these extensions to be available without importing
@file:Suppress("PackageDirectoryMismatch")
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.provider.Provider
fun <T> NamedDomainObjectContainer<T>.maybeRegister(name: String, configure: T.() -> Unit): NamedDomainObjectProvider<T> {
return if (name in names) named(name, configure) else register(name, configure)
}
inline fun <T, R> Provider<out Iterable<T>>.mapValue(crossinline transform: (T) -> R): Provider<List<R>> =
map { it.map(transform) }
inline fun <T, R> Provider<out Iterable<T>>.flatMapValue(crossinline transform: (T) -> Iterable<R>): Provider<List<R>> =
map { it.flatMap(transform) }
/*
* 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 org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.NamedDomainObjectProvider
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
private typealias KotlinSourceSets = NamedDomainObjectContainer<KotlinSourceSet>
private typealias KotlinSourceSetProvider = NamedDomainObjectProvider<KotlinSourceSet>
// Additional accessors to the ones declared in KotlinMultiplatformSourceSetConventions
val KotlinSourceSets.posixMain: KotlinSourceSetProvider by KotlinSourceSetConvention
val KotlinSourceSets.darwinMain: KotlinSourceSetProvider by KotlinSourceSetConvention
val KotlinSourceSets.darwinTest: KotlinSourceSetProvider by KotlinSourceSetConvention
val KotlinSourceSets.desktopMain: KotlinSourceSetProvider by KotlinSourceSetConvention
val KotlinSourceSets.desktopTest: KotlinSourceSetProvider by KotlinSourceSetConvention
val KotlinSourceSets.windowsMain: KotlinSourceSetProvider by KotlinSourceSetConvention
val KotlinSourceSets.windowsTest: KotlinSourceSetProvider by KotlinSourceSetConvention
......@@ -53,17 +53,18 @@ private fun Project.printManifest() {
}
private fun Project.configureVersion() {
version = findProperty("DeployVersion") ?: return
version = providers.gradleProperty("DeployVersion").orNull ?: return
val skipSnapshotChecks = providers.gradleProperty("skip_snapshot_checks").orNull.toBoolean()
if (buildSnapshotTrain && !rootProject.hasProperty("skip_snapshot_checks")) {
check(version, rootProject.libs.versions.atomicfu, "atomicfu")
check(version, rootProject.libs.versions.coroutines, "coroutines")
check(version, rootProject.libs.versions.serialization, "serialization")
if (buildSnapshotTrain && !skipSnapshotChecks) {
check(version, libs.versions.atomicfu, "atomicfu")
check(version, libs.versions.coroutines, "coroutines")
check(version, libs.versions.serialization, "serialization")
}
}
private val Project.buildSnapshotTrain: Boolean
get() = rootProject.findProperty("build_snapshot_train")?.toString().toBoolean()
get() = providers.gradleProperty("build_snapshot_train").orNull.toBoolean()
private fun check(version: Any, libVersionProvider: Provider<String>, libName: String) {
val libVersion = libVersionProvider.get()
......
......@@ -15,8 +15,8 @@ import org.gradle.api.Project
*/
internal fun Project.resolveVersion(): String {
val projectVersion = project.version.toString()
val releaseVersion = findProperty("releaseVersion")?.toString()
val eapVersion = findProperty("eapVersion")?.toString()
val releaseVersion = providers.gradleProperty("releaseVersion").orNull
val eapVersion = providers.gradleProperty("eapVersion").orNull
return when {
releaseVersion != null -> releaseVersion
......
......@@ -2,7 +2,7 @@
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package ktorbuild
package ktorbuild.internal.gradle
import org.gradle.api.NamedDomainObjectCollection
import org.gradle.api.NamedDomainObjectProvider
......
......@@ -4,6 +4,17 @@
package ktorbuild.internal.gradle
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import java.io.File
internal fun <T> Property<T>.finalizedOnRead(): Property<T> = apply { finalizeValueOnRead() }
internal fun Project.directoryProvider(fileProvider: () -> File): Provider<Directory> =
layout.dir(provider(fileProvider))
internal fun Project.regularFileProvider(fileProvider: () -> File): Provider<RegularFile> =
layout.file(provider(fileProvider))
......@@ -5,7 +5,7 @@
package ktorbuild.internal.publish
import ktorbuild.internal.capitalized
import ktorbuild.maybeNamed
import ktorbuild.internal.gradle.maybeNamed
import ktorbuild.targets.KtorTargets
import org.gradle.api.Project
import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
......
......@@ -4,15 +4,15 @@
package ktorbuild.internal.publish
import ktorbuild.internal.gradle.directoryProvider
import ktorbuild.internal.gradle.regularFileProvider
import ktorbuild.internal.publish.TestRepository.locateTestRepository
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.tasks.*
import org.gradle.api.tasks.options.Option
......@@ -55,9 +55,11 @@ internal abstract class ValidatePublishedArtifactsTask : DefaultTask() {
outputs.cacheIf("Enable cache only when collecting a dump") { dump.get() }
repositoryDirectory.convention(project.locateTestRepository())
artifactsDump.convention(project.rootProject.layout.projectDirectory.file("gradle/artifacts.txt"))
packageName.convention(project.rootProject.group.toString())
with(project) {
repositoryDirectory.convention(directoryProvider { locateTestRepository() })
artifactsDump.convention(regularFileProvider { rootDir.resolve("gradle/artifacts.txt") })
packageName.convention(rootProject.group.toString())
}
dump.convention(false)
}
......@@ -129,6 +131,6 @@ internal object TestRepository {
}
}
fun Project.locateTestRepository(): Provider<Directory> =
rootProject.layout.buildDirectory.dir("${NAME}Repository")
fun Project.locateTestRepository(): File =
rootDir.resolve("build/${ValidatePublishedArtifactsTask.NAME}Repository")
}
......@@ -5,9 +5,9 @@
package ktorbuild.targets
import ktorbuild.internal.capitalized
import ktorbuild.internal.gradle.maybeNamed
import ktorbuild.internal.kotlin
import ktorbuild.internal.libs
import ktorbuild.maybeNamed
import org.gradle.api.Project
import org.gradle.kotlin.dsl.invoke
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl
......
......@@ -4,6 +4,8 @@
package ktorbuild.targets
import ktorbuild.ProjectTag
import ktorbuild.addProjectTag
import ktorbuild.internal.kotlin
import ktorbuild.internal.ktorBuild
import ktorbuild.internal.libs
......@@ -17,6 +19,8 @@ import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
internal fun Project.configureJvm() {
addProjectTag(ProjectTag.Jvm)
kotlin {
sourceSets {
jvmMain.dependencies {
......@@ -99,7 +103,9 @@ private fun Test.configureJavaToolchain(
}
fun Project.javaModuleName(): String {
return (if (this.name.startsWith("ktor-")) "io.${project.name}" else "io.ktor.${project.name}")
check(name.startsWith("ktor-")) { "Project name should start with prefix 'ktor-'." }
return "io.$name"
.replace('-', '.')
.replace("default.headers", "defaultheaders")
.replace("double.receive", "doublereceive")
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать