Merge pull request 'dev' (!55) from dev into master

Reviewed-on: #55
This commit was merged in pull request #55.
This commit is contained in:
2025-12-17 08:15:45 +03:00
10 changed files with 217 additions and 137 deletions

View File

@@ -7,7 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- WasmWasi target
### Changed
@@ -19,6 +18,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security
## 0.20.2-kotlin-2.3.0 - 2025-12-17
### Added
- WasmWasi target
### Changed
- Kotlin 2.3.0
- Maturity moved to kscience extension.
- Readme extension now requires kscience extension.
- Project plugin overhaul (not only publish)
- ABI validation is configurable from the root project
### Fixed
- Context parameter flag
- Deploy problem with opensavvy-resources
## 0.19.0-kotlin-2.2.0 - 2025-07-24
### Changed
@@ -38,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `fullStackApplication` configuration. Replaced by optional field in `fullStack`
- Jupyter integration
## 0.17.x
## 0.17.0
### Added

View File

@@ -26,7 +26,6 @@ dependencies {
api("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.asProvider().get()}")
api("org.gradle.toolchains:foojay-resolver:1.0.0")
api("com.vanniktech:gradle-maven-publish-plugin:0.34.0")
api("org.jetbrains.kotlinx:binary-compatibility-validator:0.18.0")
api("org.jetbrains.intellij.plugins:gradle-changelog-plugin:${libs.versions.changelog.get()}")
api("org.jetbrains.dokka:dokka-gradle-plugin:${libs.versions.dokka.get()}")
@@ -159,6 +158,7 @@ mavenPublishing {
}
kotlin {
abiValidation
explicitApiWarning()
jvmToolchain(21)
}

View File

@@ -1,16 +1,17 @@
[versions]
# @pin
kotlin = "2.2.20"
kotlin = "2.3.0"
# @pin
tools = "0.19.2-kotlin-2.2.20"
tools = "0.20.2-kotlin-2.3.0"
atomicfu = "0.29.0"
changelog = "2.4.0"
compose = "1.8.2"
dokka = "2.0.0"
jsBom = "2025.9.6"
changelog = "2.5.0"
compose = "1.9.3"
dokka = "2.1.0"
jsBom = "2025.12.6"
junit = "5.10.2"
kotlin-jupyter = "0.15.0-616"
kotlinx-benchmark = "0.4.14"
# @pin
kotlin-jupyter = "0.15.0-634"
kotlinx-benchmark = "0.4.15"
kotlinx-cli = "0.3.6"
kotlinx-coroutines = "1.10.2"
kotlinx-datetime = "0.7.1"
@@ -18,26 +19,26 @@ kotlinx-html = "0.12.0"
kotlinx-knit = "0.5.0"
kotlinx-nodejs = "0.0.7"
kotlinx-serialization = "1.9.0"
kotlinx-io = "0.8.0"
kover = "0.9.1"
ktor = "3.2.3"
ksp = "2.2.20-2.0.3"
logback = "1.5.18"
kotlinx-io = "0.8.2"
kover = "0.9.4"
ktor = "3.3.3"
ksp = "2.3.4"
logback = "1.5.22"
slf4j = "2.0.17"
xmlutil = "0.91.2"
xmlutil = "0.91.3"
yamlkt = "0.13.0"
opensavvy-resources = "0.5.1"
opensavvy-resources = "0.6.0"
[plugins]
maven-publish = "com.vanniktech.maven.publish:0.34.0"
maven-publish-base = "com.vanniktech.maven.publish.base:0.34.0"
maven-publish = "com.vanniktech.maven.publish:0.35.0"
maven-publish-base = "com.vanniktech.maven.publish.base:0.35.0"
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
compose-jb = { id = "org.jetbrains.compose", version.ref = "compose" }
jetbrains-changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-android-extensions = { id = "org.jetbrains.kotlin.android.extensions", version.ref = "kotlin" }
kotlin-dsl = "org.gradle.kotlin.kotlin-dsl:6.4.0"
kotlin-dsl = "org.gradle.kotlin.kotlin-dsl:6.5.1"
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-js = { id = "org.jetbrains.kotlin.js", version.ref = "kotlin" }
@@ -58,11 +59,11 @@ kotlinx-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
kscience-mpp = { id = "space.kscience.gradle.mpp", version.ref = "tools" }
kscience-project = { id = "space.kscience.gradle.project", version.ref = "tools" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ktor = "io.ktor.plugin:3.2.3"
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
opensavvy-resources-producer = { id = "dev.opensavvy.resources.producer", version.ref = "opensavvy-resources" }
opensavvy-resources-consumer = { id = "dev.opensavvy.resources.consumer", version.ref = "opensavvy-resources" }
versions = "com.github.ben-manes.versions:0.52.0"
versions-update = "nl.littlerobots.version-catalog-update:1.0.0"
versions = "com.github.ben-manes.versions:0.53.0"
versions-update = "nl.littlerobots.version-catalog-update:1.0.1"
[libraries]
atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -9,6 +9,6 @@ pluginManagement {
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0")
id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0")
}

View File

@@ -10,6 +10,8 @@ import org.gradle.kotlin.dsl.*
import org.gradle.language.jvm.tasks.ProcessResources
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.HasConfigurableKotlinCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -20,7 +22,6 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmWasiTargetDsl
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import space.kscience.gradle.internal.defaultKotlinJvmOpts
import space.kscience.gradle.internal.requestPropertyOrNull
import space.kscience.gradle.internal.useCommonDependency
@@ -37,6 +38,13 @@ public enum class DependencySourceSet(public val setName: String, public val suf
TEST("test", "Test")
}
public enum class Maturity {
PROTOTYPE,
EXPERIMENTAL,
DEVELOPMENT,
STABLE,
DEPRECATED
}
/**
* Check if this project version has a development tag (`development` property to true, "dev" in the middle or "SNAPSHOT" in the end).
@@ -47,9 +55,22 @@ public val Project.isInDevelopment: Boolean
|| version.toString().endsWith("SNAPSHOT")
private const val defaultJdkVersion = 17
/**
* Check if the project has a kscience extension that declares it as immature. Returns true if the project does not have readme extension
*/
public fun Project.isMature(): Boolean = extensions.findByType<KSciencePlatformExtension>()?.let { ext ->
ext.maturity == Maturity.DEVELOPMENT && ext.maturity == Maturity.STABLE
} ?: true
public abstract class KScienceExtension @Inject constructor(public val project: Project) : ExtensionAware {
private const val defaultJdkVersion = 21
public interface KSciencePlatformExtension: ExtensionAware {
public val project: Project
public var maturity: Maturity
}
public abstract class KScienceExtension @Inject constructor(override val project: Project) : KSciencePlatformExtension {
public val jdkVersionProperty: Property<Int> = project.objects.property<Int>().apply {
set(defaultJdkVersion)
@@ -57,6 +78,9 @@ public abstract class KScienceExtension @Inject constructor(public val project:
public var jdkVersion: Int by jdkVersionProperty
override var maturity: Maturity = Maturity.EXPERIMENTAL
/**
* Use coroutines-core with default version or [version]
*/
@@ -152,10 +176,9 @@ public abstract class KScienceExtension @Inject constructor(public val project:
* Add context parameters to the project
*/
public fun useContextParameters() {
project.tasks.withType<KotlinCompile> {
compilerOptions {
freeCompilerArgs.addAll("-Xcontext-parameters")
}
@Suppress("UNCHECKED_CAST")
(project.extensions.getByName("kotlin") as? HasConfigurableKotlinCompilerOptions<KotlinCommonCompilerOptions>)?.compilerOptions {
freeCompilerArgs.addAll("-Xcontext-parameters")
}
}

View File

@@ -42,6 +42,7 @@ public open class KScienceJVMPlugin : KSciencePlugin {
}
if (explicitApi == null) explicitApiWarning()
jvmToolchain {
languageVersion.set(extension.jdkVersionProperty.map { JavaLanguageVersion.of(it) })
}

View File

@@ -2,8 +2,6 @@ package space.kscience.gradle
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import com.vanniktech.maven.publish.MavenPublishBasePlugin
import kotlinx.validation.ApiValidationExtension
import kotlinx.validation.BinaryCompatibilityValidatorPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.publish.maven.MavenPom
@@ -12,8 +10,9 @@ import org.gradle.kotlin.dsl.*
import org.gradle.plugins.signing.Sign
import org.jetbrains.changelog.ChangelogPlugin
import org.jetbrains.changelog.ChangelogPluginExtension
import org.jetbrains.dokka.gradle.AbstractDokkaTask
import org.jetbrains.dokka.gradle.DokkaPlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.abi.AbiValidationVariantSpec
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension
@@ -22,11 +21,16 @@ import org.jetbrains.kotlin.gradle.targets.wasm.yarn.WasmYarnRootExtension
import space.kscience.gradle.internal.addPublishing
import space.kscience.gradle.internal.setupPublication
import space.kscience.gradle.internal.withKScience
import javax.inject.Inject
/**
* Simplifies adding repositories for Maven publishing, responds for releasing tasks for projects.
*/
public class KSciencePublishingExtension(public val project: Project) {
public abstract class KScienceProjectExtension @Inject constructor(override val project: Project) :
KSciencePlatformExtension {
override var maturity: Maturity = Maturity.EXPERIMENTAL
private var isVcsInitialized = false
/**
@@ -63,7 +67,7 @@ public class KSciencePublishingExtension(public val project: Project) {
* Add a repository with [repositoryName]. Uses "publishing.$repositoryName.user" and "publishing.$repositoryName.token"
* properties pattern to store user and token
*/
public fun repository(
public fun publishTo(
repositoryName: String,
url: String,
) {
@@ -74,7 +78,7 @@ public class KSciencePublishingExtension(public val project: Project) {
/**
* Add publishing to maven central "new" API
*/
public fun central(): Unit = with(project) {
public fun publishToCentral(): Unit = with(project) {
require(isVcsInitialized) { "The project vcs is not set up use 'pom' method to do so" }
if (isInDevelopment) {
logger.info("Maven central publishing skipped for development version")
@@ -88,26 +92,44 @@ public class KSciencePublishingExtension(public val project: Project) {
}
}
}
/**
* Configure ABI validation for the project and all subprojects.
*/
public fun abiValidation(block: AbiValidationVariantSpec.() -> Unit): Unit = project.allprojects {
extensions.findByType<KotlinMultiplatformExtension>()?.apply {
extensions.findByType<AbiValidationVariantSpec>()?.apply(block)
}
}
//
// @Suppress("UNCHECKED_CAST")
// public fun kotlinCompilerOptions(block: KotlinCommonCompilerOptions.() -> Unit): Unit = project.allprojects {
// (project.extensions.getByName("kotlin") as? HasConfigurableKotlinCompilerOptions<KotlinCommonCompilerOptions>)?.compilerOptions(
// block
// )
// }
}
/**
* Applies third-party plugins (Dokka, Changelog, binary compatibility validator); configures Maven publishing, README
* Applies third-party plugins (Dokka, Changelog); configures Maven publishing, README
* generation.
*/
public open class KScienceProjectPlugin : Plugin<Project> {
override fun apply(target: Project): Unit = target.run {
apply<ChangelogPlugin>()
apply<DokkaPlugin>()
apply<BinaryCompatibilityValidatorPlugin>()
val ksciencePublish = KSciencePublishingExtension(this)
extensions.add("ksciencePublish", ksciencePublish)
val kscienceProjectExtension = extensions.create("kscienceProject", KScienceProjectExtension::class.java)
withKScience {
extensions.add("publish", ksciencePublish)
}
//configure readme for root project
kscienceProjectExtension.configureReadme()
allprojects {
//Add repositories
repositories {
mavenCentral()
maven("https://repo.kotlin.link")
@@ -118,67 +140,10 @@ public open class KScienceProjectPlugin : Plugin<Project> {
tasks.withType<AbstractPublishToMaven>().configureEach {
mustRunAfter(tasks.withType<Sign>())
}
}
afterEvaluate {
if (isInDevelopment) {
configure<ApiValidationExtension> {
validationDisabled = true
}
} else {
configure<ChangelogPluginExtension> {
version.set(project.version.toString())
}
}
}
//Add readme generators to individual subprojects and root project
allprojects {
val readmeExtension = KScienceReadmeExtension(this)
extensions.add("readme", readmeExtension)
//configure readme for all subprojects that have KScience plugins. If the root project also has the kscience plugin, it is skipped.
withKScience {
extensions.add("readme", readmeExtension)
}
val generateReadme by tasks.registering {
group = "documentation"
description = "Generate a README file if stub is present"
inputs.property("features", readmeExtension.features)
if (readmeExtension.readmeTemplate.exists()) {
inputs.file(readmeExtension.readmeTemplate)
}
readmeExtension.inputFiles.forEach {
if (it.exists()) {
inputs.file(it)
}
}
subprojects {
extensions.findByType<KScienceReadmeExtension>()?.let { subProjectReadmeExtension ->
tasks.findByName("generateReadme")?.let { readmeTask ->
dependsOn(readmeTask)
}
inputs.property("features-${name}", subProjectReadmeExtension.features)
}
}
val readmeFile = this@allprojects.file("README.md")
outputs.file(readmeFile)
doLast {
val readmeString = readmeExtension.readmeString()
if (readmeString != null) {
readmeFile.writeText(readmeString)
}
}
}
tasks.withType<AbstractDokkaTask> {
dependsOn(generateReadme)
configureReadme()
}
}
@@ -194,11 +159,11 @@ public open class KScienceProjectPlugin : Plugin<Project> {
}
}
// Disable API validation for snapshots
if (isInDevelopment) {
extensions.findByType<ApiValidationExtension>()?.apply {
validationDisabled = true
logger.warn("API validation is disabled for snapshot or dev version")
afterEvaluate {
if (!isInDevelopment) {
configure<ChangelogPluginExtension> {
version.set(project.version.toString())
}
}
}
@@ -208,6 +173,7 @@ public open class KScienceProjectPlugin : Plugin<Project> {
yarnLockMismatchReport = YarnLockMismatchReport.WARNING
}
}
plugins.withType<WasmYarnPlugin> {
rootProject.configure<WasmYarnRootExtension> {
lockFileDirectory = rootDir.resolve("gradle/wasm")

View File

@@ -7,21 +7,18 @@ import freemarker.template.TemplateNotFoundException
import kotlinx.html.TagConsumer
import kotlinx.html.div
import kotlinx.html.stream.createHTML
import kotlinx.validation.ApiValidationExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.*
import org.intellij.lang.annotations.Language
import org.jetbrains.dokka.gradle.AbstractDokkaTask
import org.jetbrains.kotlin.gradle.dsl.abi.AbiValidationExtension
import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation
import space.kscience.gradle.internal.withKScience
import space.kscience.gradle.internal.withKotlin
import java.io.File
import java.io.Serializable
import java.io.StringWriter
public enum class Maturity {
PROTOTYPE,
EXPERIMENTAL,
DEVELOPMENT,
STABLE,
DEPRECATED
}
private fun Template.processToString(args: Map<String, Any?>): String {
val writer = StringWriter()
@@ -29,26 +26,13 @@ private fun Template.processToString(args: Map<String, Any?>): String {
return writer.toString()
}
public class KScienceReadmeExtension(private val kscience: KSciencePlatformExtension) {
public val project: Project get() = kscience.project
public class KScienceReadmeExtension(public val project: Project) {
public var description: String? = null
get() = field ?: project.description
public var maturity: Maturity = Maturity.EXPERIMENTAL
set(value) {
field = value
val projectName = project.name
if (value == Maturity.EXPERIMENTAL || value == Maturity.PROTOTYPE) {
project.rootProject.run {
plugins.withId("org.jetbrains.kotlinx.binary-compatibility-validator") {
extensions.findByType<ApiValidationExtension>()?.apply {
project.logger.warn("$value project $projectName is excluded from API validation")
ignoredProjects.add(projectName)
}
}
}
}
}
public var maturity: Maturity by kscience::maturity
/**
* If true, use default templates provided by plugin if override is not defined
@@ -86,7 +70,8 @@ public class KScienceReadmeExtension(public val project: Project) {
templateLoader = fmLoader
}
public data class Feature(val id: String, val description: String, val ref: String?, val name: String = id): Serializable
public data class Feature(val id: String, val description: String, val ref: String?, val name: String = id) :
Serializable
public val features: MutableList<Feature> = mutableListOf()
@@ -192,7 +177,11 @@ public class KScienceReadmeExtension(public val project: Project) {
*/
internal fun featuresString(itemPrefix: String = " - ", pathPrefix: String = ""): String = buildString {
features.forEach {
appendLine("$itemPrefix[${it.name}]($pathPrefix${it.ref ?: "#"}) : ${it.description.lines().firstOrNull() ?: ""}")
appendLine(
"$itemPrefix[${it.name}]($pathPrefix${it.ref ?: "#"}) : ${
it.description.lines().firstOrNull() ?: ""
}"
)
}
}
@@ -240,3 +229,71 @@ public class KScienceReadmeExtension(public val project: Project) {
}
internal fun KSciencePlatformExtension.configureReadme() = with(project) {
//early return is readme is already configured
if (extensions.findByType<KScienceReadmeExtension>() != null) return@with
//Add readme generators to individual subprojects and root project
val readmeExtension = KScienceReadmeExtension(this@configureReadme)
this.extensions.add("readme", readmeExtension)
this@configureReadme.extensions.add("readme", readmeExtension)
val generateReadme by tasks.registering {
group = "documentation"
description = "Generate a README file if stub is present"
inputs.property("features", readmeExtension.features)
if (readmeExtension.readmeTemplate.exists()) {
inputs.file(readmeExtension.readmeTemplate)
}
readmeExtension.inputFiles.forEach {
if (it.exists()) {
inputs.file(it)
}
}
// add dependency for this task on subprojects readme tasks
subprojects {
withKScience {
extensions.findByType<KScienceReadmeExtension>()?.let { subProjectReadmeExtension ->
tasks.findByName("generateReadme")?.let { readmeTask ->
dependsOn(readmeTask)
}
inputs.property("features-${name}", subProjectReadmeExtension.features)
}
}
}
val readmeFile = file("README.md")
outputs.file(readmeFile)
doLast {
val readmeString = readmeExtension.readmeString()
if (readmeString != null) {
readmeFile.writeText(readmeString)
}
}
}
tasks.withType<AbstractDokkaTask> {
dependsOn(generateReadme)
}
// Enable API validation for production releases
if (!isInDevelopment && isMature()) {
withKotlin {
extensions.configure<AbiValidationExtension> {
@OptIn(ExperimentalAbiValidation::class)
enabled.set(true)
}
}
}
}

View File

@@ -4,7 +4,9 @@ import org.gradle.api.Project
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import space.kscience.gradle.KScienceExtension
import space.kscience.gradle.KSciencePlugin
@@ -51,4 +53,16 @@ internal fun Project.withKScience(block: KScienceExtension.() -> Unit) {
plugins.withType<KSciencePlugin>().configureEach {
extensions.findByType<KScienceExtension>()?.apply(block)
}
}
/**
* Configures the Kotlin environment in the current project by finding and applying a provided configuration block
* to the KotlinBaseExtension if the KotlinBasePlugin is applied.
*
* @param block The configuration block to apply to the KotlinBaseExtension.
*/
internal fun Project.withKotlin(block: KotlinBaseExtension.() -> Unit) {
plugins.withType<KotlinBasePlugin>().configureEach {
extensions.findByType<KotlinBaseExtension>()?.apply(block)
}
}