Compare commits

..

9 Commits

Author SHA1 Message Date
renovate[bot]
52487c6481 Update dependency com.android.tools.build:gradle to v9 2026-07-14 17:46:16 +00:00
a76d04e2a4 Merge pull request #669 from InsanusMokrassar/0.30.0
0.30.0
2026-06-29 15:34:06 +06:00
e0468bc2d7 update dependencies 2026-06-29 15:14:09 +06:00
aeccfbb214 start 0.30.0 2026-06-29 14:47:56 +06:00
c793bea0c3 Merge pull request #663 from InsanusMokrassar/0.29.4
0.29.4
2026-05-23 17:12:28 +06:00
e3913f7600 fix dokka build :( 2026-05-23 16:54:55 +06:00
8dbb35f378 add meta 2026-05-23 12:14:15 +06:00
47d12e3740 start 0.29.4 2026-05-23 11:46:21 +06:00
45bff98a2c Merge pull request #662 from InsanusMokrassar/0.29.3
0.29.3
2026-05-21 20:12:29 +06:00
69 changed files with 193 additions and 678 deletions

View File

@@ -1,73 +1,5 @@
# Changelog # Changelog
## 0.32.1
* `Coroutines`:
* `SmartSemaphore`:
* Optimize permit checks, acquisition, uncontended releases and already satisfied waits to reduce allocations while preserving cancellation handling.
* `SmartMutex`:
* Optimize waits for unlocked mutexes and uncontended unlocks to reduce allocations while preserving cancellation handling.
* `SmartRWLocker`:
* Remove redundant coroutine contexts during read releases, write unlocks and cancellation cleanup.
* `SmartKeyRWLocker`:
* Reduce allocations during keyed operations by inlining the locker allocation helper and using a hash map without insertion order tracking.
## 0.32.0
* `Versions`:
* `crypto-js`: `4.1.1` -> `4.2.0`
* `AndroidX Fragment`: `1.9.0` -> `1.9.1`
* `AndroidX Core KTX`: `1.19.0` -> `1.19.1`
* `NMCP`: `1.6.1` -> `1.6.2`
* `Gradle Versions`: `0.61.0` -> `0.65.0`
* `KotlinPoet`: `2.3.0` -> `2.4.0`
* `Okio`: `3.18.1` -> `3.18.2`
* `Ktor`: `3.5.2` -> `3.6.0`
* `Exposed`: `1.5.0` -> `1.5.1`
* `Compose Material3`: `1.11.0-alpha07` -> `1.9.0`
* `KSLog`: `1.7.0` -> `2.1.0`
* `Kotlin`: `2.4.10` -> `2.4.21`
* `Compose`: `1.12.0` -> `1.12.1`
* `Gradle wrapper`: `9.3.1` -> `9.7.0`
* `Coroutines`:
* `SmartSemaphore`:
* Rewrite docs for `freePermits` property.
* Add constructor arguments requirements to fail on invalid `permits` and `acquiredPermits` values.
* `SmartRWLocker`:
* Add constructor arguments requirements to fail on invalid `readPermits` values.
* `SmartKeyRWLocker`:
* Add constructor arguments requirements to fail on invalid `globalLockerReadPermits` and `perKeyReadPermits` values.
## 0.31.1
* `Coroutines`:
* `SmartRWLocker`:
* Fix of `unlockWrite`, `lockWrite` and `releaseRead` calls to pass correct number of permits
* `SmartMutex`:
* Fix `unlock` call
* `SmartSemaphore`:
* Fix same issues to avoid cancellation exceptions handling errors and several other problems
## 0.31.0
* `Versions`:
* `Kotlin`: `2.3.21` -> `2.4.10`
* `SQLite`: `3.53.2.1` -> `3.53.4.0`
* `KSP`: `2.3.9` -> `2.3.11`
* `Exposed`: `1.3.0` -> `1.5.0`
* `NMCP`: `1.5.0` -> `1.6.1`
* `AndroidX AppCompat`: `1.7.1` -> `1.8.0`
* `AndroidX Fragment`: `1.8.9` -> `1.9.0`
## 0.30.1
* `Versions`:
* `KSLog`: `1.6.1` -> `1.7.0`
* `SQLite`: `3.53.2.0` -> `3.53.2.1`
* `Ktor`: `3.5.1` -> `3.5.2`
* `Okio`: `3.17.0` -> `3.18.1`
## 0.30.0 ## 0.30.0
* `Versions`: * `Versions`:

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppAndroidProject" apply from: "$mppAndroidProject"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppAndroidProject" apply from: "$mppAndroidProject"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
alias(libs.plugins.jb.compose) alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose) alias(libs.plugins.kt.jb.compose)
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppAndroidProject" apply from: "$mppAndroidProject"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
alias(libs.plugins.jb.compose) alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose) alias(libs.plugins.kt.jb.compose)
} }

View File

@@ -41,7 +41,7 @@ allprojects {
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
// maven { url "https://nexus.inmo.dev/repository/maven-releases/" } maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
mavenLocal() mavenLocal()
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
@@ -13,7 +13,7 @@ kotlin {
api libs.klock api libs.klock
} }
} }
jvmAndAndroidMain { jvmMain {
dependencies { dependencies {
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
alias(libs.plugins.jb.compose) alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose) alias(libs.plugins.kt.jb.compose)
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
alias(libs.plugins.jb.compose) alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose) alias(libs.plugins.kt.jb.compose)
} }

View File

@@ -28,16 +28,11 @@ class SmartKeyRWLocker<T>(
globalLockerWriteIsLocked: Boolean = false, globalLockerWriteIsLocked: Boolean = false,
private val perKeyReadPermits: Int = Int.MAX_VALUE private val perKeyReadPermits: Int = Int.MAX_VALUE
) { ) {
init {
require(globalLockerReadPermits > 0) { "The maximal number 'globalLockerReadPermits' of global permits on reading ($globalLockerReadPermits) must be positive." }
require(perKeyReadPermits > 0) { "The maximal number 'perKeyReadPermits' of permits on reading per key ($perKeyReadPermits) must be positive." }
}
private val globalRWLocker: SmartRWLocker = SmartRWLocker( private val globalRWLocker: SmartRWLocker = SmartRWLocker(
readPermits = globalLockerReadPermits, readPermits = globalLockerReadPermits,
writeIsLocked = globalLockerWriteIsLocked writeIsLocked = globalLockerWriteIsLocked
) )
private val lockers = hashMapOf<T, SmartRWLocker>() private val lockers = mutableMapOf<T, SmartRWLocker>()
private val lockersMutex = Mutex() private val lockersMutex = Mutex()
private val lockersWritingLocker = SmartSemaphore.Mutable(Int.MAX_VALUE) private val lockersWritingLocker = SmartSemaphore.Mutable(Int.MAX_VALUE)
private val globalWritingLocker = SmartSemaphore.Mutable(Int.MAX_VALUE) private val globalWritingLocker = SmartSemaphore.Mutable(Int.MAX_VALUE)
@@ -45,7 +40,7 @@ class SmartKeyRWLocker<T>(
private fun allocateLockerWithoutLock(key: T) = lockers.getOrPut(key) { private fun allocateLockerWithoutLock(key: T) = lockers.getOrPut(key) {
SmartRWLocker(perKeyReadPermits) SmartRWLocker(perKeyReadPermits)
} }
private suspend inline fun allocateLocker(key: T) = lockersMutex.withLock { private suspend fun allocateLocker(key: T) = lockersMutex.withLock {
lockers.getOrPut(key) { lockers.getOrPut(key) {
SmartRWLocker(perKeyReadPermits) SmartRWLocker(perKeyReadPermits)
} }

View File

@@ -1,15 +1,12 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
@@ -96,32 +93,19 @@ sealed interface SmartMutex {
* [SmartMutex] - false * [SmartMutex] - false
*/ */
suspend fun unlock(): Boolean { suspend fun unlock(): Boolean {
if (!_lockStateFlow.value) { return if (_lockStateFlow.value) {
return false
}
// The uncontended path cannot suspend, including during cancelled cleanup.
if (internalChangesMutex.tryLock()) {
try {
return unlockState()
} finally {
internalChangesMutex.unlock()
}
}
return withContext(NonCancellable) {
internalChangesMutex.withLock { internalChangesMutex.withLock {
unlockState() if (_lockStateFlow.value) {
_lockStateFlow.value = false
true
} else {
false
}
} }
} else {
false
} }
} }
// The caller must hold internalChangesMutex while updating the state.
private fun unlockState(): Boolean {
if (!_lockStateFlow.value) {
return false
}
_lockStateFlow.value = false
return true
}
} }
} }
@@ -148,11 +132,4 @@ suspend inline fun <T> SmartMutex.Mutable.withLock(action: () -> T): T {
* Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact * Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact
* that some other parties may lock it again * that some other parties may lock it again
*/ */
suspend fun SmartMutex.waitUnlock(): Boolean { suspend fun SmartMutex.waitUnlock() = lockStateFlow.first { !it }
currentCoroutineContext().ensureActive()
return if (!lockStateFlow.value) {
false
} else {
lockStateFlow.first { !it }
}
}

View File

@@ -15,17 +15,12 @@ import kotlin.contracts.contract
* * [unlockWrite] will just unlock [writeMutex] * * [unlockWrite] will just unlock [writeMutex]
*/ */
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) { class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
init {
require(readPermits > 0) { "The maximal number 'readPermits' of permits on reading ($readPermits) must be positive." }
}
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = if (writeIsLocked) readPermits else 0) private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = if (writeIsLocked) readPermits else 0)
private val _writeMutex = SmartMutex.Mutable(locked = writeIsLocked) private val _writeMutex = SmartMutex.Mutable(locked = writeIsLocked)
val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable() val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable()
val writeMutex: SmartMutex.Immutable = _writeMutex.immutable() val writeMutex: SmartMutex.Immutable = _writeMutex.immutable()
/** /**
* Do lock in [readSemaphore] inside of [writeMutex] locking * Do lock in [readSemaphore] inside of [writeMutex] locking
*/ */
@@ -37,7 +32,9 @@ class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked:
/** /**
* Release one read permit in [readSemaphore] * Release one read permit in [readSemaphore]
*/ */
suspend fun releaseRead(): Boolean = _readSemaphore.release() suspend fun releaseRead(): Boolean {
return _readSemaphore.release()
}
/** /**
* Locking [writeMutex] and wait while all [readSemaphore] permits will be freed * Locking [writeMutex] and wait while all [readSemaphore] permits will be freed
@@ -55,12 +52,13 @@ class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked:
/** /**
* Unlock [writeMutex] * Unlock [writeMutex]
*/ */
suspend fun unlockWrite(): Boolean = suspend fun unlockWrite(): Boolean {
_writeMutex.unlock().also { unlocked -> return _writeMutex.unlock().also {
if (unlocked) { if (it) {
_readSemaphore.release(readPermits) _readSemaphore.release(readPermits)
} }
} }
}
} }
/** /**

View File

@@ -1,8 +1,6 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -10,7 +8,6 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
@@ -30,10 +27,8 @@ sealed interface SmartSemaphore {
val permitsStateFlow: StateFlow<Int> val permitsStateFlow: StateFlow<Int>
/** /**
* The number of the available permits. * * True - locked
* * * False - unlocked
* If returns `0`, it is fully acquired (locked).
* If it returns the same value as `maxPermits`, it is fully released (unlocked).
*/ */
val freePermits: Int val freePermits: Int
get() = permitsStateFlow.value get() = permitsStateFlow.value
@@ -50,12 +45,6 @@ sealed interface SmartSemaphore {
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow] * @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
*/ */
class Mutable(permits: Int, acquiredPermits: Int = 0) : SmartSemaphore { class Mutable(permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
init {
require(permits > 0) { "The maximal number 'permits' of permits ($permits) must be positive." }
require(acquiredPermits >= 0) { "The number 'acquiredPermits' of initially acquired permits ($acquiredPermits) must be non-negative." }
require(permits >= acquiredPermits) { "The number 'acquiredPermits' of acquired initially permits ($acquiredPermits) must not exceed the maximal number 'permits' of permits ($permits)." }
}
override val maxPermits: Int = permits override val maxPermits: Int = permits
private val _freePermitsStateFlow = MutableRedeliverStateFlow<Int>(permits - acquiredPermits) private val _freePermitsStateFlow = MutableRedeliverStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow() override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
@@ -64,7 +53,7 @@ sealed interface SmartSemaphore {
fun immutable() = Immutable(permitsStateFlow, maxPermits) fun immutable() = Immutable(permitsStateFlow, maxPermits)
private fun checkedPermits(permits: Int) = permits.coerceIn(1, maxPermits) private fun checkedPermits(permits: Int) = permits.coerceIn(1 .. this.maxPermits)
/** /**
* Holds call until this [SmartSemaphore] will be re-locked. That means that current method will * Holds call until this [SmartSemaphore] will be re-locked. That means that current method will
@@ -76,8 +65,7 @@ sealed interface SmartSemaphore {
do { do {
val shouldContinue = internalChangesMutex.withLock { val shouldContinue = internalChangesMutex.withLock {
val requiredPermits = checkedPermits - acquiredPermits val requiredPermits = checkedPermits - acquiredPermits
val acquiring = minOf(freePermits, requiredPermits) val acquiring = minOf(freePermits, requiredPermits).takeIf { it > 0 } ?: return@withLock true
if (acquiring <= 0) return@withLock true
acquiredPermits += acquiring acquiredPermits += acquiring
_freePermitsStateFlow.value -= acquiring _freePermitsStateFlow.value -= acquiring
@@ -88,9 +76,7 @@ sealed interface SmartSemaphore {
} }
} while (shouldContinue && currentCoroutineContext().isActive) } while (shouldContinue && currentCoroutineContext().isActive)
} catch (e: Throwable) { } catch (e: Throwable) {
if (acquiredPermits > 0) { release(acquiredPermits)
release(acquiredPermits)
}
throw e throw e
} }
} }
@@ -121,9 +107,9 @@ sealed interface SmartSemaphore {
*/ */
suspend fun tryAcquire(permits: Int = 1): Boolean { suspend fun tryAcquire(permits: Int = 1): Boolean {
val checkedPermits = checkedPermits(permits) val checkedPermits = checkedPermits(permits)
return if (_freePermitsStateFlow.value >= checkedPermits) { return if (_freePermitsStateFlow.value < checkedPermits) {
internalChangesMutex.withLock { internalChangesMutex.withLock {
if (_freePermitsStateFlow.value >= checkedPermits) { if (_freePermitsStateFlow.value < checkedPermits) {
_freePermitsStateFlow.value -= checkedPermits _freePermitsStateFlow.value -= checkedPermits
true true
} else { } else {
@@ -141,30 +127,19 @@ sealed interface SmartSemaphore {
*/ */
suspend fun release(permits: Int = 1): Boolean { suspend fun release(permits: Int = 1): Boolean {
val checkedPermits = checkedPermits(permits) val checkedPermits = checkedPermits(permits)
if (_freePermitsStateFlow.value >= maxPermits) return false return if (_freePermitsStateFlow.value < this.maxPermits) {
// Updating permits without suspension remains safe in a cancelled coroutine.
if (internalChangesMutex.tryLock()) {
try {
return releaseLocked(checkedPermits)
} finally {
internalChangesMutex.unlock()
}
}
return withContext(NonCancellable) {
internalChangesMutex.withLock { internalChangesMutex.withLock {
releaseLocked(checkedPermits) if (_freePermitsStateFlow.value < this.maxPermits) {
_freePermitsStateFlow.value = minOf(_freePermitsStateFlow.value + checkedPermits, this.maxPermits)
true
} else {
false
}
} }
} else {
false
} }
} }
private fun releaseLocked(permits: Int): Boolean {
val freePermits = _freePermitsStateFlow.value
if (freePermits >= maxPermits) return false
_freePermitsStateFlow.value = minOf(freePermits + permits, maxPermits)
return true
}
} }
} }
@@ -191,14 +166,5 @@ suspend inline fun <T> SmartSemaphore.Mutable.withAcquire(permits: Int = 1, acti
* Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to * Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to
* the fact that some other parties may lock it again * the fact that some other parties may lock it again
*/ */
suspend fun SmartSemaphore.waitRelease(permits: Int = 1): Int { suspend fun SmartSemaphore.waitRelease(permits: Int = 1) = permitsStateFlow.first { it >= permits }
currentCoroutineContext().ensureActive() suspend fun SmartSemaphore.waitReleaseAll() = permitsStateFlow.first { it == maxPermits }
val availablePermits = freePermits
return if (availablePermits >= permits) availablePermits else permitsStateFlow.first { it >= permits }
}
suspend fun SmartSemaphore.waitReleaseAll(): Int {
currentCoroutineContext().ensureActive()
val availablePermits = freePermits
return if (availablePermits == maxPermits) availablePermits else permitsStateFlow.first { it == maxPermits }
}

View File

@@ -1,73 +0,0 @@
import dev.inmo.micro_utils.coroutines.SmartMutex
import dev.inmo.micro_utils.coroutines.withLock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
class SmartMutexTests {
@Test
fun cancelledUnlockCompletesUnderContention() = runTest(timeout = 5.seconds) {
val mutex = SmartMutex.Mutable()
// Delegate this acquisition's release to another coroutine. The
// unconfined collector runs while lock() still holds its internal mutex.
val releaser = launch(Dispatchers.Unconfined) {
mutex.lockStateFlow.first { it }
currentCoroutineContext().cancel()
mutex.unlock()
}
// Keep acquisition on the normal test dispatcher so the collector
// attempts cancelled cleanup before the internal mutex is released.
mutex.lock()
releaser.join()
assertTrue(releaser.isCancelled)
assertFalse(mutex.isLocked, "Cancellation must not prevent the delegated unlock")
assertTrue(mutex.tryLock())
assertTrue(mutex.unlock())
}
@Test
fun cancellingWithLockBodyReleasesMutex() = runTest(timeout = 5.seconds) {
val mutex = SmartMutex.Mutable()
val holder = launch(Dispatchers.Unconfined) {
mutex.withLock {
awaitCancellation()
}
}
assertTrue(mutex.isLocked)
holder.cancelAndJoin()
assertFalse(mutex.isLocked)
}
@Test
fun cancelledWaiterDoesNotEnterOrReleaseHeldMutex() = runTest(timeout = 5.seconds) {
val mutex = SmartMutex.Mutable()
var entered = false
mutex.withLock {
val waiter = launch(Dispatchers.Unconfined) {
mutex.withLock {
entered = true
}
}
waiter.cancelAndJoin()
assertFalse(entered)
assertTrue(mutex.isLocked)
}
assertFalse(mutex.isLocked)
}
}

View File

@@ -9,7 +9,6 @@ import kotlin.test.assertEquals
import kotlin.test.assertFails import kotlin.test.assertFails
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class SmartRWLockerTests { class SmartRWLockerTests {
@@ -110,59 +109,6 @@ class SmartRWLockerTests {
} }
} }
@Test
fun failureOnReadFreeingRead() = runTest {
val locker = SmartRWLocker()
val job = launch {
locker.withReadAcquire {
while (isActive) {
delay(1.days)
}
}
}
locker.readSemaphore.permitsStateFlow.first {
it == locker.readSemaphore.maxPermits - 1
}
job.cancelAndJoin()
locker.readSemaphore.permitsStateFlow.first {
it == locker.readSemaphore.maxPermits
}
}
@Test
fun cancelledReaderReleasesPermitUnderContention() = runTest(timeout = 5.seconds) {
val locker = SmartRWLocker(readPermits = 2)
val reader = launch(Dispatchers.Unconfined) {
locker.withReadAcquire {
awaitCancellation()
}
}
assertEquals(1, locker.readSemaphore.freePermits)
// Observe the second acquisition synchronously while it still holds the
// semaphore's internal mutex. Cancelling the unconfined reader makes its
// cleanup contend for that mutex before the acquisition can release it.
val cancellation = launch(Dispatchers.Unconfined) {
locker.readSemaphore.permitsStateFlow.first { it == 0 }
reader.cancel()
}
// Keep this acquisition on the normal test dispatcher: making it
// unconfined would change the ordering that forces cleanup contention.
locker.withReadAcquire {
cancellation.join()
reader.join()
assertTrue(reader.isCancelled)
assertEquals(
1,
locker.readSemaphore.freePermits,
"The cancelled reader must release its permit while the other reader still holds one"
)
}
assertEquals(2, locker.readSemaphore.freePermits)
}
@Test @Test
fun simpleWithReadAcquireTest() { fun simpleWithReadAcquireTest() {
val locker = SmartRWLocker() val locker = SmartRWLocker()

View File

@@ -1,72 +0,0 @@
import dev.inmo.micro_utils.coroutines.SmartSemaphore
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
class SmartSemaphoreConcurrentReleaseTests {
@Test
fun queuedReleasesRestoreEveryAcquiredPermit() = runTest(timeout = 5.seconds) {
val semaphore = SmartSemaphore.Mutable(permits = 4, acquiredPermits = 4)
val results = releaseWithQueuedContenders(semaphore, contenderCount = 3)
assertEquals(listOf(true, true, true), results)
assertEquals(4, semaphore.freePermits)
assertTrue(semaphore.tryAcquire(4))
assertFalse(semaphore.tryAcquire())
assertEquals(0, semaphore.freePermits)
}
@Test
fun queuedReleasesRecheckCapacityAfterAcquiringTheMutex() = runTest(timeout = 5.seconds) {
val semaphore = SmartSemaphore.Mutable(permits = 2, acquiredPermits = 2)
val results = releaseWithQueuedContenders(semaphore, contenderCount = 3)
assertEquals(1, results.count { released -> released })
assertEquals(2, results.count { released -> !released })
assertEquals(2, semaphore.freePermits)
assertFalse(semaphore.release())
}
private suspend fun releaseWithQueuedContenders(
semaphore: SmartSemaphore.Mutable,
contenderCount: Int
): List<Boolean> = coroutineScope {
val contenders = mutableListOf<Deferred<Boolean>>()
val observer = launch(Dispatchers.Unconfined) {
semaphore.permitsStateFlow.first { freePermits ->
if (freePermits == 1) {
// The first release still holds the changes mutex while publishing.
// Every contender observes spare capacity before waiting for the mutex.
repeat(contenderCount) {
val contender = async(Dispatchers.Unconfined, start = CoroutineStart.UNDISPATCHED) {
semaphore.release()
}
contenders.add(contender)
assertFalse(contender.isCompleted, "Competing releases must wait for the publishing release")
}
true
} else {
false
}
}
}
assertTrue(semaphore.release())
observer.join()
assertEquals(contenderCount, contenders.size)
contenders.awaitAll()
}
}

View File

@@ -1,104 +0,0 @@
import dev.inmo.micro_utils.coroutines.SmartSemaphore
import dev.inmo.micro_utils.coroutines.withAcquire
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
class SmartSemaphoreTests {
@Test
fun cancelledHolderReleasesPermitUnderContention() = runTest(timeout = 5.seconds) {
val semaphore = SmartSemaphore.Mutable(permits = 2)
val holder = launch(Dispatchers.Unconfined) {
semaphore.withAcquire {
awaitCancellation()
}
}
assertEquals(1, semaphore.freePermits)
// The synchronous observer cancels the holder while the second
// acquisition still owns the semaphore's internal changes mutex.
val cancellation = launch(Dispatchers.Unconfined) {
semaphore.permitsStateFlow.first { it == 0 }
holder.cancel()
}
semaphore.withAcquire {
cancellation.join()
holder.join()
assertTrue(holder.isCancelled)
assertEquals(1, semaphore.freePermits, "The cancelled holder must return its permit")
}
assertEquals(2, semaphore.freePermits)
}
@Test
fun cancelledAcquireReturnsPartialPermitsUnderContention() = runTest(timeout = 5.seconds) {
// One permit belongs to another holder; the waiter can acquire two
// permits immediately, but must wait for the third.
val semaphore = SmartSemaphore.Mutable(permits = 3, acquiredPermits = 1)
lateinit var waiter: kotlinx.coroutines.Job
val cancellation = launch(Dispatchers.Unconfined) {
semaphore.permitsStateFlow.first { it == 1 }
waiter.cancel()
}
waiter = launch(Dispatchers.Unconfined) {
semaphore.acquire(3)
}
assertEquals(0, semaphore.freePermits)
assertFalse(waiter.isCompleted)
// Publishing this release resumes the observer while the internal
// mutex is held. The cancelled acquire must wait to roll back safely.
semaphore.release()
cancellation.join()
waiter.join()
assertTrue(waiter.isCancelled)
assertEquals(3, semaphore.freePermits, "Cancellation must return both partially acquired permits")
semaphore.withAcquire(3) {
assertEquals(0, semaphore.freePermits)
}
assertEquals(3, semaphore.freePermits)
}
@Test
fun cancelledAcquireWithoutPermitsDoesNotReleaseAnotherHoldersPermit() = runTest(timeout = 5.seconds) {
val semaphore = SmartSemaphore.Mutable(permits = 1, acquiredPermits = 1)
val waiter = launch(Dispatchers.Unconfined) {
semaphore.acquire()
}
assertFalse(waiter.isCompleted)
waiter.cancelAndJoin()
assertEquals(0, semaphore.freePermits, "A cancelled waiter that acquired nothing must release nothing")
assertTrue(semaphore.release())
assertEquals(1, semaphore.freePermits)
}
@Test
fun tryAcquireUsesAvailablePermits() = runTest {
val semaphore = SmartSemaphore.Mutable(permits = 3)
assertTrue(semaphore.tryAcquire(2))
assertEquals(1, semaphore.freePermits)
assertTrue(semaphore.tryAcquire())
assertEquals(0, semaphore.freePermits)
assertTrue(semaphore.release(3))
assertEquals(3, semaphore.freePermits)
}
@Test
fun tryAcquireWithInsufficientPermitsLeavesStateUnchanged() = runTest {
val semaphore = SmartSemaphore.Mutable(permits = 3, acquiredPermits = 2)
assertFalse(semaphore.tryAcquire(2))
assertEquals(1, semaphore.freePermits)
semaphore.acquire()
assertFalse(semaphore.tryAcquire())
assertEquals(0, semaphore.freePermits)
}
}

View File

@@ -1,78 +0,0 @@
package dev.inmo.micro_utils.coroutines
import java.util.concurrent.CyclicBarrier
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
class SmartSemaphoreParallelReleaseTests {
@Test
fun parallelSinglePermitReleasesRestoreEveryPermit() = runTest(timeout = 30.seconds) {
assertParallelReleases(permits = 8, releasePermits = List(8) { 1 }, expectedSuccessfulReleases = 8)
}
@Test
fun parallelBulkReleasesRestoreEveryPermit() = runTest(timeout = 30.seconds) {
val releasePermits = (1 .. 8).toList()
assertParallelReleases(
permits = releasePermits.sum(),
releasePermits = releasePermits,
expectedSuccessfulReleases = releasePermits.size
)
}
@Test
fun parallelExcessReleasesStopAtCapacity() = runTest(timeout = 30.seconds) {
assertParallelReleases(permits = 3, releasePermits = List(8) { 1 }, expectedSuccessfulReleases = 3)
assertParallelReleases(permits = 5, releasePermits = List(8) { 3 }, expectedSuccessfulReleases = 2)
}
private suspend fun assertParallelReleases(
permits: Int,
releasePermits: List<Int>,
expectedSuccessfulReleases: Int
) = coroutineScope {
val semaphore = SmartSemaphore.Mutable(permits = permits, acquiredPermits = permits)
val barrier = CyclicBarrier(releasePermits.size)
val results = BooleanArray(releasePermits.size)
val rounds = 100
Executors.newFixedThreadPool(releasePermits.size).asCoroutineDispatcher().use { dispatcher ->
releasePermits.mapIndexed { worker, count ->
launch(dispatcher) {
repeat(rounds) { round ->
// Blocking barriers on dedicated JVM threads produce actual parallel releases.
barrier.await(5, TimeUnit.SECONDS)
results[worker] = semaphore.release(count)
barrier.await(5, TimeUnit.SECONDS)
if (worker == 0) {
assertEquals(permits, semaphore.freePermits, "Round $round must preserve released permits")
assertEquals(expectedSuccessfulReleases, results.count { released -> released })
assertFalse(semaphore.release(), "A full semaphore must reject another release")
assertEquals(permits, semaphore.freePermits)
if (round < rounds - 1) {
assertTrue(semaphore.tryAcquire(permits))
assertEquals(0, semaphore.freePermits)
}
}
// Finish validation and reset before starting the next release wave.
barrier.await(5, TimeUnit.SECONDS)
}
}
}.joinAll()
}
assertEquals(permits, semaphore.freePermits)
}
}

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
@@ -21,3 +21,11 @@ kotlin {
} }
} }
} }
android {
sourceSets {
main {
java.srcDirs += [ "src/jvmMain/kotlin" ]
}
}
}

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
id "org.jetbrains.dokka" id "org.jetbrains.dokka"
} }
@@ -11,17 +11,13 @@ repositories {
mavenCentral() mavenCentral()
} }
apply from: "$defaultAndroidSettings"
kotlin { kotlin {
jvm() jvm()
js(IR) { js(IR) {
browser() browser()
nodejs() nodejs()
} }
android { androidTarget {}
project.ext.configureAndroidTarget(delegate)
}
sourceSets { sourceSets {
commonMain { commonMain {
@@ -141,3 +137,5 @@ dokka {
// } // }
// } // }
//} //}
apply from: "$defaultAndroidSettings"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,9 +1,11 @@
kotlin.code.style=official kotlin.code.style=official
org.gradle.parallel=true org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
#kotlin.experimental.tryK2=true #kotlin.experimental.tryK2=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
## https://github.com/google/ksp/issues/2491 ## https://github.com/google/ksp/issues/2491
@@ -11,10 +13,10 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
# JS NPM # JS NPM
crypto_js_version=4.2.0 crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.32.1 version=0.30.0
android_code_version=320 android_code_version=315

View File

@@ -1,51 +1,51 @@
[versions] [versions]
kt = "2.4.21" kt = "2.3.21"
kt-serialization = "1.11.0" kt-serialization = "1.11.0"
kt-coroutines = "1.11.0" kt-coroutines = "1.11.0"
kotlinx-browser = "0.5.0" kotlinx-browser = "0.5.0"
kslog = "2.1.0" kslog = "1.6.1"
jb-compose = "1.12.1" jb-compose = "1.11.1"
jb-compose-material3 = "1.9.0" jb-compose-material3 = "1.11.0-alpha07"
jb-compose-icons = "1.7.8" jb-compose-icons = "1.7.8"
jb-exposed = "1.5.1" jb-exposed = "1.3.0"
jb-dokka = "2.2.0" jb-dokka = "2.2.0"
sqlite = "3.53.4.0" sqlite = "3.53.2.0"
korlibs = "5.4.0" korlibs = "5.4.0"
uuid = "0.8.4" uuid = "0.8.4"
ktor = "3.6.0" ktor = "3.5.1"
gh-release = "2.5.2" gh-release = "2.5.2"
koin = "4.2.2" koin = "4.2.2"
okio = "3.18.2" okio = "3.17.0"
ksp = "2.3.11" ksp = "2.3.9"
kotlin-poet = "2.4.0" kotlin-poet = "2.3.0"
versions = "0.65.0" versions = "0.54.0"
nmcp = "1.6.2" nmcp = "1.5.0"
android-gradle = "9.1.1" android-gradle = "9.3.+"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.19.1" android-coreKtx = "1.19.0"
android-recyclerView = "1.4.0" android-recyclerView = "1.4.0"
android-appCompat = "1.8.0" android-appCompat = "1.7.1"
android-fragment = "1.9.1" android-fragment = "1.8.9"
android-espresso = "3.7.0" android-espresso = "3.7.0"
android-test = "1.3.0" android-test = "1.3.0"
android-props-minSdk = "21" android-props-minSdk = "21"
android-props-compileSdk = "37" android-props-compileSdk = "36"
android-props-buildTools = "37.0.0" android-props-buildTools = "36.0.0"
[libraries] [libraries]
@@ -126,5 +126,5 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }
kt-jb-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kt" } kt-jb-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kt" }
versions = { id = "io.github.ben-manes.versions", version.ref = "versions" } versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }
nmcp-aggregation = { id = "com.gradleup.nmcp.aggregation", version.ref = "nmcp" } nmcp-aggregation = { id = "com.gradleup.nmcp.aggregation", version.ref = "nmcp" }

View File

@@ -1,6 +1,6 @@
kotlin { kotlin {
sourceSets { sourceSets {
androidHostTest { androidUnitTest {
dependencies { dependencies {
implementation compose.uiTest implementation compose.uiTest
} }

View File

@@ -1,18 +1,34 @@
apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.getkeepsafe.dexcount'
ext.configureAndroidTarget = { target -> android {
target.compileSdk = libs.versions.android.props.compileSdk.get().toInteger() compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
target.buildToolsVersion = libs.versions.android.props.buildTools.get() buildToolsVersion libs.versions.android.props.buildTools.get()
target.minSdk = libs.versions.android.props.minSdk.get().toInteger()
target.namespace = "${project.group}.${project.name}" defaultConfig {
target.packaging { minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
resources { compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
excludes.add 'META-INF/kotlinx-serialization-runtime.kotlin_module' targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
excludes.add 'META-INF/kotlinx-serialization-cbor.kotlin_module' versionCode "${android_code_version}".toInteger()
excludes.add 'META-INF/kotlinx-serialization-properties.kotlin_module' versionName "$version"
namespace "${project.group}.${project.name}"
}
buildTypes {
release {
minifyEnabled false
}
debug {
debuggable true
} }
} }
target.compilerOptions.jvmTarget.set(
target.compilerOptions.jvmTarget.get().class.fromTarget("17") packagingOptions {
) exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
} }

View File

@@ -1,31 +1,25 @@
apply from: "$defaultAndroidSettings"
kotlin { kotlin {
android { androidTarget {
project.ext.configureAndroidTarget(delegate) publishLibraryVariants(
withHostTest {} "release",
withDeviceTest {} "debug",
)
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
} }
sourceSets { sourceSets {
jvmAndAndroidMain { androidUnitTest {
dependsOn commonMain
kotlin.srcDir("src/jvmMain/kotlin")
resources.srcDir("src/jvmMain/resources")
}
jvmMain {
dependsOn jvmAndAndroidMain
kotlin.setSrcDirs([])
resources.setSrcDirs([])
}
androidHostTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit
implementation libs.android.espresso implementation libs.android.espresso
} }
} }
androidDeviceTest { androidInstrumentedTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit
@@ -33,6 +27,8 @@ kotlin {
} }
} }
androidMain.dependsOn jvmAndAndroidMain androidMain.dependsOn jvmMain
} }
} }
apply from: "$defaultAndroidSettings"

Binary file not shown.

View File

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

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
@@ -14,7 +14,7 @@ kotlin {
api libs.uuid api libs.uuid
} }
} }
jvmAndAndroidMain { jvmMain {
dependencies { dependencies {
api libs.kt.reflect api libs.kt.reflect
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
id "com.google.devtools.ksp" id "com.google.devtools.ksp"
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
id "com.google.devtools.ksp" id "com.google.devtools.ksp"
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -53,7 +53,7 @@ class Processor(
val annotation = ksClassDeclaration.getGenerateSealedWorkaroundAnnotation val annotation = ksClassDeclaration.getGenerateSealedWorkaroundAnnotation
val subClasses = ksClassDeclaration.resolveSubclasses( val subClasses = ksClassDeclaration.resolveSubclasses(
searchIn = resolver.getAllFiles(), searchIn = resolver.getAllFiles(),
allowNonSealed = withNoSuchElementWorkaround(null) { annotation ?.includeNonSealedSubTypes } ?: false allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false
).distinct() ).distinct()
val subClassesNames = subClasses.filter { val subClassesNames = subClasses.filter {
when (it.classKind) { when (it.classKind) {
@@ -165,7 +165,15 @@ class Processor(
@OptIn(KspExperimental::class) @OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
(resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { (resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
val prefix = withNoSuchElementWorkaround(null) { (it.getGenerateSealedWorkaroundAnnotation) ?.prefix } ?.takeIf { val prefix = runCatching {
(it.getGenerateSealedWorkaroundAnnotation) ?.prefix
}.getOrElse {
if (it is NoSuchElementException) {
""
} else {
throw it
}
} ?.takeIf {
it.isNotEmpty() it.isNotEmpty()
} ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "") } ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "")
it.writeFile(prefix = prefix, suffix = "SealedWorkaround") { it.writeFile(prefix = prefix, suffix = "SealedWorkaround") {

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
id "com.google.devtools.ksp" id "com.google.devtools.ksp"
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
id "com.google.devtools.ksp" id "com.google.devtools.ksp"
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -11,7 +11,7 @@ package dev.inmo.micro_utils.meta
RequiresOptIn.Level.WARNING RequiresOptIn.Level.WARNING
) )
@Target( @Target(
AnnotationTarget.PROPERTY, AnnotationTarget.FIELD,
) )
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class MetaContainerRootMapWarning annotation class MetaContainerRootMapWarning

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
alias(libs.plugins.jb.compose) alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose) alias(libs.plugins.kt.jb.compose)
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
@@ -17,7 +17,7 @@ kotlin {
} }
} }
jvmAndAndroidMain { jvmMain {
dependencies { dependencies {
api internalProject("micro_utils.common") api internalProject("micro_utils.common")
} }

View File

@@ -1,14 +1,12 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
project.version = "$version" project.version = "$version"
project.group = "$group" project.group = "$group"
apply from: "$defaultAndroidSettings"
kotlin { kotlin {
jvm { jvm {
compilations.main { compilations.main {
@@ -25,8 +23,12 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
android { androidTarget {
project.ext.configureAndroidTarget(delegate) compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
} }
linuxX64() linuxX64()
mingwX64() mingwX64()
@@ -49,11 +51,6 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidMain {
dependencies {
implementation kotlin('test-junit')
}
}
jsMain { jsMain {
dependencies { dependencies {
implementation kotlin('test-js') implementation kotlin('test-js')
@@ -64,10 +61,13 @@ kotlin {
mingwX64Main.dependsOn nativeMain mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
} }
} }
apply from: "$defaultAndroidSettings"
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
id "com.google.devtools.ksp" id "com.google.devtools.ksp"
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
@@ -20,4 +20,4 @@ kotlin {
} }
} }
} }
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
alias(libs.plugins.compose) alias(libs.plugins.compose)
} }

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.kotlin.multiplatform.library" id "com.android.library"
} }
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project" apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"