Refactor "lite" API for Kotlin/Native libraries
This commit is contained in:
+12
-10
@@ -29,7 +29,7 @@ import org.jetbrains.kotlin.idea.configuration.DependencySubstitute.NoSubstitute
|
|||||||
import org.jetbrains.kotlin.idea.configuration.DependencySubstitute.YesSubstitute
|
import org.jetbrains.kotlin.idea.configuration.DependencySubstitute.YesSubstitute
|
||||||
import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion
|
import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion
|
||||||
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
|
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
|
||||||
import org.jetbrains.kotlin.konan.library.lite.LiteKonanLibraryInfoProvider
|
import org.jetbrains.kotlin.konan.library.lite.LiteKonanLibraryFacade
|
||||||
import org.jetbrains.plugins.gradle.ExternalDependencyId
|
import org.jetbrains.plugins.gradle.ExternalDependencyId
|
||||||
import org.jetbrains.plugins.gradle.model.DefaultExternalMultiLibraryDependency
|
import org.jetbrains.plugins.gradle.model.DefaultExternalMultiLibraryDependency
|
||||||
import org.jetbrains.plugins.gradle.model.ExternalDependency
|
import org.jetbrains.plugins.gradle.model.ExternalDependency
|
||||||
@@ -86,7 +86,7 @@ class KotlinNativeLibraryDataService : AbstractProjectDataService<LibraryData, L
|
|||||||
|
|
||||||
// KT-29613, KT-29783
|
// KT-29613, KT-29783
|
||||||
internal class KotlinNativeLibrariesDependencySubstitutor(
|
internal class KotlinNativeLibrariesDependencySubstitutor(
|
||||||
private val mppModel: KotlinMPPGradleModel,
|
mppModel: KotlinMPPGradleModel,
|
||||||
private val gradleModule: IdeaModule,
|
private val gradleModule: IdeaModule,
|
||||||
private val resolverCtx: ProjectResolverContext
|
private val resolverCtx: ProjectResolverContext
|
||||||
) {
|
) {
|
||||||
@@ -112,7 +112,9 @@ internal class KotlinNativeLibrariesDependencySubstitutor(
|
|||||||
private val ProjectResolverContext.dependencySubstitutionCache
|
private val ProjectResolverContext.dependencySubstitutionCache
|
||||||
get() = getUserData(KLIB_DEPENDENCY_SUBSTITUTION_CACHE) ?: putUserDataIfAbsent(KLIB_DEPENDENCY_SUBSTITUTION_CACHE, HashMap())
|
get() = getUserData(KLIB_DEPENDENCY_SUBSTITUTION_CACHE) ?: putUserDataIfAbsent(KLIB_DEPENDENCY_SUBSTITUTION_CACHE, HashMap())
|
||||||
|
|
||||||
private val libraryInfoProvider by lazy { LiteKonanLibraryInfoProvider(mppModel.kotlinNativeHome) }
|
private val libraryProvider = LiteKonanLibraryFacade.getDistributionLibraryProvider(
|
||||||
|
mppModel.kotlinNativeHome.takeIf { it != KotlinMPPGradleModel.NO_KOTLIN_NATIVE_HOME }?.let { File(it) }
|
||||||
|
)
|
||||||
|
|
||||||
private val kotlinVersion: String? by lazy {
|
private val kotlinVersion: String? by lazy {
|
||||||
val classpathData = buildClasspathData(gradleModule, resolverCtx)
|
val classpathData = buildClasspathData(gradleModule, resolverCtx)
|
||||||
@@ -150,20 +152,20 @@ internal class KotlinNativeLibrariesDependencySubstitutor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSubstituteIfNecessary(libraryFile: File): DependencySubstitute {
|
private fun buildSubstituteIfNecessary(libraryFile: File): DependencySubstitute {
|
||||||
// need to check whether `libraryFile` points to a real KLIB,
|
// need to check whether `library` points to a real KLIB,
|
||||||
// and if answer is yes then build a new dependency that will substitute original one
|
// and if answer is yes then build a new dependency that will substitute original one
|
||||||
val libraryInfo = libraryInfoProvider.getDistributionLibraryInfo(libraryFile) ?: return NoSubstitute
|
val library = libraryProvider.getLibrary(libraryFile) ?: return NoSubstitute
|
||||||
val nonNullKotlinVersion = kotlinVersion ?: return NoSubstitute
|
val nonNullKotlinVersion = kotlinVersion ?: return NoSubstitute
|
||||||
|
|
||||||
val platformNamePart = libraryInfo.platform?.let { " [$it]" }.orEmpty()
|
val platformNamePart = library.platform?.let { " [$it]" }.orEmpty()
|
||||||
val newLibraryName = "$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE$nonNullKotlinVersion - ${libraryInfo.name}$platformNamePart"
|
val newLibraryName = "$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE$nonNullKotlinVersion - ${library.name}$platformNamePart"
|
||||||
|
|
||||||
val substitute = DefaultExternalMultiLibraryDependency().apply {
|
val substitute = DefaultExternalMultiLibraryDependency().apply {
|
||||||
classpathOrder = if (libraryInfo.name == KONAN_STDLIB_NAME) -1 else 0 // keep stdlib upper
|
classpathOrder = if (library.name == KONAN_STDLIB_NAME) -1 else 0 // keep stdlib upper
|
||||||
name = newLibraryName
|
name = newLibraryName
|
||||||
packaging = DEFAULT_PACKAGING
|
packaging = DEFAULT_PACKAGING
|
||||||
files += libraryInfo.path
|
files += library.path
|
||||||
sources += libraryInfo.sourcePaths
|
sources += library.sourcePaths
|
||||||
scope = DependencyScope.PROVIDED.name
|
scope = DependencyScope.PROVIDED.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -5,8 +5,11 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.konan.library.lite
|
package org.jetbrains.kotlin.konan.library.lite
|
||||||
|
|
||||||
data class LiteKonanDistribution(
|
import org.jetbrains.kotlin.konan.KonanVersion
|
||||||
val distributionHome: String,
|
import java.io.File
|
||||||
val kotlinNativeVersion: KotlinVersion,
|
|
||||||
val kotlinNativeVersionString: String
|
class LiteKonanDistribution(
|
||||||
|
val distributionHome: File,
|
||||||
|
val konanVersion: KonanVersion,
|
||||||
|
val konanVersionString: String
|
||||||
)
|
)
|
||||||
|
|||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.konan.library.lite
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.konanCommonLibraryPath
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
class LiteKonanDistributionInfoProvider(private val konanHomeDir: String) {
|
|
||||||
|
|
||||||
fun getDistributionInfo(): LiteKonanDistribution? {
|
|
||||||
val stdlibInfo = LiteKonanLibraryInfoProvider(konanHomeDir).getDistributionLibraryInfo(
|
|
||||||
Paths.get(konanHomeDir).resolve(konanCommonLibraryPath(KONAN_STDLIB_NAME)).toFile()
|
|
||||||
) ?: return null
|
|
||||||
|
|
||||||
val versionString = stdlibInfo.compilerVersion
|
|
||||||
val version = versionString.substringBefore('-')
|
|
||||||
.split('.')
|
|
||||||
.let {
|
|
||||||
when (it.size) {
|
|
||||||
2 -> listOf(it[0], it[1], "0")
|
|
||||||
3 -> it
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.mapNotNull { it.toIntOrNull() }
|
|
||||||
?.takeIf { it.size == 3 }
|
|
||||||
?.let {
|
|
||||||
KotlinVersion(it[0], it[1], it[2])
|
|
||||||
} ?: return null
|
|
||||||
|
|
||||||
return LiteKonanDistribution(konanHomeDir, version, versionString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.konan.library.lite
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.konan.KonanVersion
|
||||||
|
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
|
||||||
|
import org.jetbrains.kotlin.konan.library.konanCommonLibraryPath
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object LiteKonanDistributionProvider {
|
||||||
|
fun getDistribution(konanHomeDir: File): LiteKonanDistribution? {
|
||||||
|
val stdlib = LiteKonanLibraryFacade.getDistributionLibraryProvider(konanHomeDir)
|
||||||
|
.getLibrary(konanHomeDir.resolve(konanCommonLibraryPath(KONAN_STDLIB_NAME).toFile())) as? LiteKonanLibraryImpl ?: return null
|
||||||
|
|
||||||
|
return LiteKonanDistribution(
|
||||||
|
konanHomeDir,
|
||||||
|
KonanVersion.fromString(stdlib.compilerVersion),
|
||||||
|
stdlib.compilerVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-7
@@ -7,10 +7,9 @@ package org.jetbrains.kotlin.konan.library.lite
|
|||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
data class LiteKonanLibrary(
|
interface LiteKonanLibrary {
|
||||||
val path: File,
|
val path: File
|
||||||
val sourcePaths: Collection<File>,
|
val sourcePaths: Collection<File>
|
||||||
val name: String,
|
val name: String
|
||||||
val platform: String?,
|
val platform: String?
|
||||||
internal val compilerVersion: String
|
}
|
||||||
)
|
|
||||||
|
|||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.konan.library.lite
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.konan.library.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
// TODO: agree how to distinguish between Native and non-Native (ex: JS) KLIBs
|
||||||
|
object LiteKonanLibraryFacade {
|
||||||
|
/**
|
||||||
|
* Use this [LiteKonanLibraryProvider] to get information about any Kotlin/Native library.
|
||||||
|
*/
|
||||||
|
fun getLibraryProvider(): LiteKonanLibraryProvider = DefaultLiteKonanLibraryProvider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this [LiteKonanLibraryProvider] to get information about libraries inside of Kotlin/Native distribution.
|
||||||
|
*/
|
||||||
|
fun getDistributionLibraryProvider(customKonanHomeDir: File?): LiteKonanLibraryProvider =
|
||||||
|
FromDistributionLiteKonanLibraryProvider(customKonanHomeDir)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LiteKonanLibraryProvider {
|
||||||
|
/**
|
||||||
|
* Returns either [LiteKonanLibrary], or null if path does not point to a valid Kotlin/Native library
|
||||||
|
* or the current [LiteKonanLibraryProvider] does not accept the library due to internal restrictions.
|
||||||
|
*/
|
||||||
|
fun getLibrary(libraryPath: File): LiteKonanLibrary?
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DefaultLiteKonanLibraryProvider : LiteKonanLibraryProvider {
|
||||||
|
override fun getLibrary(libraryPath: File): LiteKonanLibraryImpl? {
|
||||||
|
val manifest = loadManifest(libraryPath) ?: return null
|
||||||
|
|
||||||
|
val name = manifest.getProperty(KLIB_PROPERTY_UNIQUE_NAME) ?: return null
|
||||||
|
val compilerVersion = manifest.getProperty(KLIB_PROPERTY_COMPILER_VERSION) ?: return null
|
||||||
|
|
||||||
|
return LiteKonanLibraryImpl(
|
||||||
|
path = libraryPath,
|
||||||
|
name = name,
|
||||||
|
compilerVersion = compilerVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadManifest(libraryPath: File): Properties? {
|
||||||
|
val manifestFile = libraryPath.resolve(KLIB_MANIFEST_FILE_NAME).takeIf { it.isFile } ?: return null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
manifestFile.inputStream().use { Properties().apply { load(it) } }
|
||||||
|
} catch (_: IOException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FromDistributionLiteKonanLibraryProvider(customKonanHomeDir: File?) : LiteKonanLibraryProvider {
|
||||||
|
private val konanHomeDir: Path? by lazy {
|
||||||
|
customKonanHomeDir?.takeIf {
|
||||||
|
// small sanity check to ensure that it's a valid Kotlin/Native home directory
|
||||||
|
customKonanHomeDir.resolve(KLIB_DIR_NAME).isDirectory
|
||||||
|
}?.toPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val konanDataDir: Path by lazy {
|
||||||
|
val path = System.getenv("KONAN_DATA_DIR")?.let { Paths.get(it) }
|
||||||
|
?: Paths.get(System.getProperty("user.home"), ".konan")
|
||||||
|
path.toAbsolutePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLibrary(libraryPath: File): LiteKonanLibrary? {
|
||||||
|
// check whether it under Kotlin/Native root
|
||||||
|
if (!isUnderKonanRoot(libraryPath)) return null
|
||||||
|
|
||||||
|
val library = DefaultLiteKonanLibraryProvider.getLibrary(libraryPath) ?: return null
|
||||||
|
|
||||||
|
val (platform: String?, dataDir: File) = getPlatformAndDataDir(libraryPath) ?: return null
|
||||||
|
library.platform = platform
|
||||||
|
|
||||||
|
if (library.name == KONAN_STDLIB_NAME)
|
||||||
|
library.sourcePaths = getStdlibSources(dataDir)
|
||||||
|
|
||||||
|
return library
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isUnderKonanRoot(libraryPath: File): Boolean {
|
||||||
|
with(libraryPath.toPath()) {
|
||||||
|
if (konanHomeDir != null && startsWith(konanHomeDir))
|
||||||
|
return true
|
||||||
|
|
||||||
|
return startsWith(konanDataDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPlatformAndDataDir(libraryPath: File): Pair<String?, File>? {
|
||||||
|
val parentDir = libraryPath.parentFile ?: return null
|
||||||
|
val parentDirName = parentDir.name
|
||||||
|
|
||||||
|
fun getDataDir(platformDir: File): File = platformDir.parentFile.parentFile
|
||||||
|
|
||||||
|
return when (parentDirName) {
|
||||||
|
KONAN_COMMON_LIBS_DIR_NAME -> null to getDataDir(parentDir)
|
||||||
|
else -> {
|
||||||
|
val grandParentDir = parentDir.parentFile ?: return null
|
||||||
|
when {
|
||||||
|
grandParentDir.name == KONAN_PLATFORM_LIBS_DIR_NAME -> parentDirName to getDataDir(grandParentDir)
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStdlibSources(dataDir: File): Collection<File> {
|
||||||
|
val sourcesDir = dataDir.resolve(KONAN_SOURCES_DIR_NAME).takeIf { it.isDirectory } ?: return emptyList()
|
||||||
|
|
||||||
|
return sourcesDir.walkTopDown().maxDepth(1)
|
||||||
|
.filter { it.isFile }
|
||||||
|
.filter {
|
||||||
|
with(it.name) { endsWith(".zip") && (startsWith("kotlin-stdlib") || startsWith("kotlin-test")) }
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LiteKonanLibraryImpl(
|
||||||
|
override val path: File,
|
||||||
|
override val name: String,
|
||||||
|
internal val compilerVersion: String
|
||||||
|
) : LiteKonanLibrary {
|
||||||
|
override var platform: String? = null
|
||||||
|
override var sourcePaths: Collection<File> = emptyList()
|
||||||
|
}
|
||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.konan.library.lite
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.konan.library.KLIB_DIR_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.KLIB_MANIFEST_FILE_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.KLIB_PROPERTY_COMPILER_VERSION
|
|
||||||
import org.jetbrains.kotlin.konan.library.KLIB_PROPERTY_UNIQUE_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.KONAN_COMMON_LIBS_DIR_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.KONAN_PLATFORM_LIBS_DIR_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.KONAN_SOURCES_DIR_NAME
|
|
||||||
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class LiteKonanLibraryInfoProvider(customKonanHomeDir: String? = null) {
|
|
||||||
|
|
||||||
private val konanHomeDir by lazy {
|
|
||||||
customKonanHomeDir
|
|
||||||
?.takeIf { it.isNotEmpty() }
|
|
||||||
?.let { Paths.get(it) }
|
|
||||||
?.takeIf {
|
|
||||||
// small sanity check to ensure that it's a valid Kotlin/Native home directory
|
|
||||||
Files.isDirectory(it.resolve(KLIB_DIR_NAME))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val konanDataDir by lazy {
|
|
||||||
val path = System.getenv("KONAN_DATA_DIR")?.let { Paths.get(it) }
|
|
||||||
?: Paths.get(System.getProperty("user.home"), ".konan")
|
|
||||||
path.toAbsolutePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns either [LiteKonanLibrary], or null if there is no such library in
|
|
||||||
* Kotlin/Native distribution.
|
|
||||||
*/
|
|
||||||
fun getDistributionLibraryInfo(libraryPath: File): LiteKonanLibrary? {
|
|
||||||
// check whether it under Kotlin/Native root
|
|
||||||
if (!isUnderKonanRoot(libraryPath))
|
|
||||||
return null
|
|
||||||
|
|
||||||
val manifestFile = libraryPath.resolve(KLIB_MANIFEST_FILE_NAME).takeIf { it.isFile } ?: return null
|
|
||||||
val (platform: String?, dataDir: File) = getPlatformAndDataDir(libraryPath) ?: return null
|
|
||||||
|
|
||||||
val manifestProperties = Properties().apply {
|
|
||||||
try {
|
|
||||||
manifestFile.inputStream().use { load(it) }
|
|
||||||
} catch (e: IOException) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val name = manifestProperties[KLIB_PROPERTY_UNIQUE_NAME]?.toString() ?: return null
|
|
||||||
val compilerVersion = manifestProperties[KLIB_PROPERTY_COMPILER_VERSION]?.toString() ?: return null
|
|
||||||
|
|
||||||
val sourcePaths = if (name == KONAN_STDLIB_NAME) getStdlibSources(dataDir) else emptyList()
|
|
||||||
|
|
||||||
return LiteKonanLibrary(libraryPath, sourcePaths, name, platform, compilerVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isUnderKonanRoot(libraryPath: File): Boolean {
|
|
||||||
with(libraryPath.toPath()) {
|
|
||||||
if (konanHomeDir != null && startsWith(konanHomeDir))
|
|
||||||
return true
|
|
||||||
|
|
||||||
return startsWith(konanDataDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPlatformAndDataDir(libraryPath: File): Pair<String?, File>? {
|
|
||||||
val parentDir = libraryPath.parentFile ?: return null
|
|
||||||
val parentDirName = parentDir.name
|
|
||||||
|
|
||||||
fun getDataDir(platformDir: File): File = platformDir.parentFile.parentFile
|
|
||||||
|
|
||||||
return when (parentDirName) {
|
|
||||||
KONAN_COMMON_LIBS_DIR_NAME -> null to getDataDir(parentDir)
|
|
||||||
else -> {
|
|
||||||
val grandParentDir = parentDir.parentFile ?: return null
|
|
||||||
when {
|
|
||||||
grandParentDir.name == KONAN_PLATFORM_LIBS_DIR_NAME -> parentDirName to getDataDir(grandParentDir)
|
|
||||||
else -> return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getStdlibSources(dataDir: File): Collection<File> {
|
|
||||||
val sourcesDir = dataDir.resolve(KONAN_SOURCES_DIR_NAME).takeIf { it.isDirectory } ?: return emptyList()
|
|
||||||
|
|
||||||
return sourcesDir.walkTopDown().maxDepth(1)
|
|
||||||
.filter { it.isFile }
|
|
||||||
.filter {
|
|
||||||
with(it.name) { endsWith(".zip") && (startsWith("kotlin-stdlib") || startsWith("kotlin-test")) }
|
|
||||||
}.toList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user