InvalidMutabilityExceptionInvalidMutabilityException on common test with Ktor - kotlin-coroutines

I have a multiplatform library to do few APIs calls with Ktor (2.0.0-beta-1)
class DiscoveryServicesImpl(private val client: HttpClient) : DiscoveryServices {
override suspend fun getServers(domain: String): DAAServers {
return client.get(Servers.Domain(domain = domain)).body()
}
}
// Where client is created with specific engine (OkHttp for Android and Darwin for iOS)
This code works as expected (implemented in both Android and iOS apps)
To secure this code, I've added a unit test to verify that the url is well builded.
#Test
fun getServersSuccessful() {
runBlocking {
var _request: HttpRequestData? = null
//Given
val mockEngine = MockEngine {
_request = it
}
val discoveryApiMock = DiscoveryApi(mockEngine)
val service = DiscoveryServicesImpl(discoveryApiMock.client)
//When
val servers: DAAServers = service.getServers(domain)
//Then
assertEquals("https://****/$domain", _request!!.url.toString())
}
}
But when a run this test I got an error (for iOS test only) :
> Task :api:discovery:iosTest
***.discovery.DiscoveryServicesImplTest.getServersSuccessful FAILED
kotlin.native.concurrent.InvalidMutabilityException at /Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref#15cb7c8
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref#15cb7c8
at kotlin.Throwable#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24)
at kotlin.Exception#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
at kotlin.RuntimeException#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
at kotlin.native.concurrent.InvalidMutabilityException#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:24)
at <global>.ThrowInvalidMutabilityException(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:109)
at <global>.MutationCheck(Unknown Source)
at kotlin.native.internal.Ref#<set-element>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Ref.kt:12)
It's happening only on the ios target
To fix this, I've added this code pretty much everywhere (commonMain, commonTest, iosMain, iosTest) but it doesn't change anything
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
Below, my gradles files :
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(Ktor.clientCore)
implementation(Ktor.clientResources)
implementation(Ktor.contentNegotiation)
implementation(Ktor.clientJson)
implementation(Ktor.clientLogging)
implementation(Ktor.clientAuth)
implementation(Koin.koinMultiplatform)
implementation(project(":serialization"))
implementation(project(":transverse:log"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
}
}
val commonTest by getting {
dependencies {
implementation(Ktor.clientMock)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
}
}
val iosMain by getting {
dependencies {
implementation(Ktor.clientDarwin)
}
}
}
}

If you're using the latest version of ktor and coroutines, you'll probably want to use the new memory model, and just the 1.6.0 kotlinx.coroutines rather than the native-mt version.
https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/
https://github.com/touchlab/KaMPKit/blob/main/gradle.properties#L26
Most library dev from Jetbrains will likely focus on the new memory model from now on, so unless there are specific near term concerns on moving, I would do that.

Related

TransformerAction is not triggered on dependency resolution

I would like to transform some dependencies when Gradle resolves them.
I have followed the 'Transforming dependency artifacts on resolution' documentation, but my TransformAction is not triggered.
I have
created a custom configuration, gitHub
created custom attributes, and both
applied them to the gitHub configuration,
and registered them in the dependencies {} block)
created and registered a custom TransformAction.
created a task, resolveGitHubDependencies, that will resolve the gitHub configuration.
I have tried a few different adjustments, but nothing seems to have an impact.
What am I missing? Or is this a bug?
Here's the code I've created. It can be tested by running ./gradlew resolveGitHubDependencies. I expect that MyTransformer will print some log statements, but it doesn't print anything.
import org.slf4j.LoggerFactory
plugins {
base
}
repositories {
ivy("https://github.com/") {
name = "GitHub"
patternLayout {
artifact("/[organisation]/[module]/archive/[revision].[ext]")
artifact("/[organisation]/[module]/releases/download/[revision]/[module](-[revision]).[ext]")
}
metadataSources { artifact() }
}
}
// from https://github.com/gradle/gradle/issues/19707
val transformed = Attribute.of("my-library.transformed", Boolean::class.javaObjectType)
val dependencySource = Attribute.of("dependency source", String::class.java)
// my custom configuration - I want to transform all dependencies from this configuration
val gitHub by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(dependencySource, "gitHub")
attribute(transformed, false)
}
}
// this is the transformer that I expect to be triggered on dependency resolution
abstract class MyTransformer : TransformAction<MyTransformer.Parameters> {
private val logger = LoggerFactory.getLogger(this::class.java)
interface Parameters : TransformParameters {
#get:Input
val sourceAttributes: MapProperty<Attribute<*>, Any?>
}
#get:PathSensitive(PathSensitivity.NAME_ONLY)
#get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val sourceAttributes = parameters.sourceAttributes.get()
logger.error("sourceAttributes: $sourceAttributes")
logger.error("sourceAttributes: ${sourceAttributes.keys}")
logger.error("sourceAttributes: ${sourceAttributes.values.joinToString { it.toString() }}")
val input = inputArtifact.get().asFile
outputs.file(input)
}
}
dependencies {
// this is the dependency I want to transform
gitHub("emcrisostomo:fswatch:1.17.1#tar.gz")
attributesSchema {
attribute(dependencySource)
attribute(transformed)
}
// artifactTypes.configureEach {
// attributes.attribute(transformed, false)
// }
registerTransform(MyTransformer::class) {
// from.attributes.attribute(dependencySource, "gitHub")
// to.attributes.attribute(dependencySource, "gitHub")
from.attributes.attribute(dependencySource, "gitHub").attribute(transformed, false)
to.attributes.attribute(dependencySource, "gitHub").attribute(transformed, true)
parameters {
sourceAttributes.set(
from.attributes.keySet().associateWith { key ->
from.attributes.getAttribute(key)
}
)
}
}
}
val resolveGitHubDependencies by tasks.registering(Sync::class) {
group = "gh transform"
from(gitHub)
into(temporaryDir)
}
Gradle version: 7.6

Use enforcePlatform in Kotlin multiplatform [duplicate]

This question already has an answer here:
Why do I get "Unresolved reference: platform" when using the firebase-bom dependency with KMM
(1 answer)
Closed 8 months ago.
How can I use enforcePlatform function in Kotlin multiplatform?
This works well:
plugins {
kotlin("js")
}
dependencies {
fun kotlinw(target: String): String =
"org.jetbrains.kotlin-wrappers:kotlin-$target"
implementation(enforcedPlatform(kotlinw("wrappers-bom:1.0.0-pre.341")))
implementation(kotlinw("react"))
implementation(kotlinw("react-dom"))
implementation(kotlinw("css"))
implementation(kotlinw("react-router-dom"))
}
kotlin {
js(IR) {
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
binaries.executable()
}
}
This reports an error Unresolved reference: enforcedPlatform:
plugins {
kotlin("multiplatform")
}
kotlin {
js(IR) {
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
}
sourceSets {
val jsMain by getting {
dependencies {
fun kotlinw(target: String): String =
"org.jetbrains.kotlin-wrappers:kotlin-$target"
implementation(enforcedPlatform(kotlinw("wrappers-bom:1.0.0-pre.341")))
implementation(kotlinw("react"))
implementation(kotlinw("react-dom"))
implementation(kotlinw("css"))
implementation(kotlinw("react-router-dom"))
}
}
}
}
I'm aware that this works in a multiplatform project, but the aim is not having to specify the version for each kotlin-wrappers dependency.
fun kotlinw(target: String): String =
"org.jetbrains.kotlin-wrappers:kotlin-$target-pre.341"
implementation(kotlinw("react:18.1.0"))
implementation(kotlinw("react-dom:18.1.0"))
You can only use it for JVM targets at the moment. See KT-40489

How to add arguments to a org.gradle.api.Action?

I'm writing a custom Gradle plugin that registers an extension and uses Actions for configuring it so you can use it like
myExtension {
info {
someConfig("a") {
// config for someConfig["a"]
}
someConfig("b") {
// config for someConfig["b"]
}
}
}
Based on how much someConfig you define, the plugin created new Gradle tasks dynamically. I've seen some scenarios in which it'd useful to configure all someConfig with the same values, so instead of writing
myExtension {
info {
someConfig("a") {
someValue = x
}
someConfig("b") {
someValue = x
}
}
}
I was thinking in providing something like
myExtension {
info {
someConfig("a")
someConfig("b")
allConfigs { configName ->
someValue = x
}
}
}
When trying to code it, I've used an objectFactory for creating instances of Action so I can configure the extension values
val allMyConfigs = mutableMapOf<String, SomeConfig>()
fun someConfig(configName: String, init: Action<in EnvironmentInfo>) {
val myConfig = allMyConfigs.computeIfAbsent(env) {
objectFactory.newInstance(SomeConfig::class.java, env)
}
init.execute(environmentInfo)
}
but since Action is a (SomeConfig) -> Unit (i.e. a function receiving a SomeConfig instance) I'm unable to pass the config name (an String) as param
Is there a way to create an Action, so it's compatible with both build.gradle and build.gradle.kts files, that receives an extra parameter and maintain the SomeConfig as delegate?

How to configure JVM args for the new Gradle TestSuit?

I am trying the new TestSuit from Gradle. I have this in my project:
testing {
suites {
val integrationTest by registering(JvmTestSuite::class) {
useJUnitJupiter()
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
jvmArgs("--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED")
}
}
But when I run ./gradlew integrationTest, I get:
/buildSrc/src/main/kotlin/quarkus-testing.quarkus-conventions.gradle.kts: (35, 13): Unresolved reference: jvmArgs
How to configure jvmArgs now?
I recently solved this by working down the hierarchy, from the JvmTestSuite object to the Test object which is the object that contains the jvmArgs reference.
testing {
suites {
val integrationTest by registering(JvmTestSuite::class) {
useJUnitJupiter()
// here: really calling JvmTestSuite#getTargets() then,
// DomainObjectCollection#all(Action)
targets.all {
// here: really calling JvmTestSuiteTarget#getTestTask() then,
// TaskProvider#configure(Action)
testTask.configure {
// now we're operating on a Test instance
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
jvmArgs("--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED")
}
}
}
}
}

Publish Kotlin MPP metadata with Gradle Kotlin DSL

I have created a Kotlin MPP to share Json utilities between JVM and JS. All the code lies in the common source set and I have configured the necessary targets with their respective dependencies. Without further configuration I'm able to use the utilities from both JVM and JS but not from the common source set of another MPP, which has to do with the way Gradle handles metadata.
I already found the solution (taken from https://medium.com/xorum-io/crafting-and-publishing-kotlin-multiplatform-library-to-bintray-cbc00a4f770)
afterEvaluate {
project.publishing.publications.all {
groupId = group
if (it.name.contains('metadata')) {
artifactId = "$libraryName"
} else {
artifactId = "$libraryName-$name"
}
}
}
and I also got it to work with the Gradle Kotlin DSL:
afterEvaluate {
publishing.publications.all {
this as MavenPublication
artifactId = project.name + "-$name".takeUnless { "metadata" in name }.orEmpty()
}
}
However, this doesn't feel quite right yet.
There is no such code snippet in the official documentation.
The documentation advertises that a single dependency from the common source set should suffice to automatically resolve target specific dependencies: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#metadata-publishing. I had to add the dependency for each target, respectively, for it to work.
this as MavenPublication is necessary because Publication has no field artifactId.
I use project.name instead of libraryName.
Is this even remotely the right way to do things or am I missing some other option which would make the whole process trivial?
Right now I'm using Kotlin 1.3.72 and Gradle 5.2.1 with enableFeaturePreview("GRADLE_METADATA") in settings.gradle.kts. I also tried it with Gradle 6.5.1 (latest) but it behaves exactly the same.
For now I'm glad that it's working at all but I suspect there is a cleaner way to do this. I'd really appreciate if someone with a bit more Gradle expertise could clear things up for me or point me into the right direction.
Edit:
gradle.build.kts for completeness. Although there isn't much going on here.
group = "org.example"
version = "1.0-SNAPSHOT"
plugins {
kotlin("multiplatform") version "1.3.72"
`maven-publish`
}
repositories {
mavenCentral()
}
kotlin {
jvm()
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib"))
}
}
}
}
There wasn't really a problem after all. The solution is to simply add enableFeaturePreview("GRADLE_METADATA") to the consuming project too.
According to https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#metadata-publishing this shouldn't be necessary:
In earlier Gradle versions starting from 5.3, the module metadata is
used during dependency resolution, but publications don't include any
module metadata by default. To enable module metadata publishing, add
enableFeaturePreview("GRADLE_METADATA") to the root project's
settings.gradle file.
Weirdly it only works when both publishing project and consuming project have metadata enabled, even when both use the latest Gradle version.
enableFeaturePreview("GRADLE_METADATA") is enabled by default in latest gradle.
According to this you need to substitute "kotlinMultiplatform" by "" an empty string.
I finally managed to accomplish publishing to bintray with maven-publish plugin only, without outdated bintray library.
Here is my full maven.publish.gradle.kts:
import java.io.FileInputStream
import java.util.*
import org.gradle.api.publish.PublishingExtension
apply(plugin = "maven-publish")
val fis = FileInputStream("local.properties")
val properties = Properties().apply {
load(fis)
}
val bintrayUser = properties.getProperty("bintray.user")
val bintrayApiKey = properties.getProperty("bintray.apikey")
val bintrayPassword = properties.getProperty("bintray.gpg.password")
val libraryVersion: String by project
val publishedGroupId: String by project
val artifact: String by project
val bintrayRepo: String by project
val libraryName: String by project
val bintrayName: String by project
val libraryDescription: String by project
val siteUrl: String by project
val gitUrl: String by project
val licenseName: String by project
val licenseUrl: String by project
val developerOrg: String by project
val developerName: String by project
val developerEmail: String by project
val developerId: String by project
project.group = publishedGroupId
project.version = libraryVersion
afterEvaluate {
configure<PublishingExtension> {
publications.all {
val mavenPublication = this as? MavenPublication
mavenPublication?.artifactId =
"${project.name}${"-$name".takeUnless { "kotlinMultiplatform" in name }.orEmpty()}"
}
}
}
configure<PublishingExtension> {
publications {
withType<MavenPublication> {
groupId = publishedGroupId
artifactId = artifact
version = libraryVersion
pom {
name.set(libraryName)
description.set(libraryDescription)
url.set(siteUrl)
licenses {
license {
name.set(licenseName)
url.set(licenseUrl)
}
}
developers {
developer {
id.set(developerId)
name.set(developerName)
email.set(developerEmail)
}
}
organization {
name.set(developerOrg)
}
scm {
connection.set(gitUrl)
developerConnection.set(gitUrl)
url.set(siteUrl)
}
}
}
}
repositories {
maven("https://api.bintray.com/maven/${developerOrg}/${bintrayRepo}/${artifact}/;publish=1") {
credentials {
username = bintrayUser
password = bintrayApiKey
}
}
}
}
And build.gradle.kts:
plugins {
id("kotlin-multiplatform")
}
kotlin {
sourceSets {
jvm()
js() {
browser()
nodejs()
}
linuxX64()
linuxArm64()
mingwX64()
macosX64()
iosArm64()
iosX64()
val commonMain by getting {
dependencies {
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jsMain by getting {
dependencies {
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
val jvmMain by getting {
dependencies {
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val nativeMain by creating {
dependsOn(commonMain)
dependencies {
}
}
val linuxX64Main by getting {
dependsOn(nativeMain)
}
val linuxArm64Main by getting {
dependsOn(nativeMain)
}
val mingwX64Main by getting {
dependsOn(nativeMain)
}
val macosX64Main by getting {
dependsOn(nativeMain)
}
val iosArm64Main by getting {
dependsOn(nativeMain)
}
val iosX64Main by getting {
dependsOn(nativeMain)
}
}
}
apply(from = "maven.publish.gradle.kts")
Please note, there are bintray.user and bintray.apikey properties in local.properties file.
Also gradle.properties contains rest listed properties above:
libraryVersion = 0.5.22
libraryName = MultiplatformCommon
libraryDescription = Kotlin multiplatform extensions
publishedGroupId = com.olekdia
artifact = multiplatform-common
bintrayRepo = olekdia
bintrayName = multiplatform-common
siteUrl = https://gitlab.com/olekdia/common/libraries/multiplatform-common
gitUrl = https://gitlab.com/olekdia/common/libraries/multiplatform-common.git
.........
kotlin.mpp.enableGranularSourceSetsMetadata = true
systemProp.org.gradle.internal.publish.checksums.insecure = true
If you haven't created organisation in bintray you need to change in this url:
https://api.bintray.com/maven/${developerOrg}/${bintrayRepo}/${artifact}/;publish=1
developerOrg by bintrayUser, where the last one is your user name at bintray.com

Resources