Refactor "lite" API for Kotlin/Native libraries

This commit is contained in:
Dmitriy Dolovov
2019-04-26 19:57:14 +07:00
parent 6039d86388
commit 07f6059f44
7 changed files with 186 additions and 162 deletions
@@ -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.inspections.gradle.findKotlinPluginVersion
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.model.DefaultExternalMultiLibraryDependency
import org.jetbrains.plugins.gradle.model.ExternalDependency
@@ -86,7 +86,7 @@ class KotlinNativeLibraryDataService : AbstractProjectDataService<LibraryData, L
// KT-29613, KT-29783
internal class KotlinNativeLibrariesDependencySubstitutor(
private val mppModel: KotlinMPPGradleModel,
mppModel: KotlinMPPGradleModel,
private val gradleModule: IdeaModule,
private val resolverCtx: ProjectResolverContext
) {
@@ -112,7 +112,9 @@ internal class KotlinNativeLibrariesDependencySubstitutor(
private val ProjectResolverContext.dependencySubstitutionCache
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 {
val classpathData = buildClasspathData(gradleModule, resolverCtx)
@@ -150,20 +152,20 @@ internal class KotlinNativeLibrariesDependencySubstitutor(
}
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
val libraryInfo = libraryInfoProvider.getDistributionLibraryInfo(libraryFile) ?: return NoSubstitute
val library = libraryProvider.getLibrary(libraryFile) ?: return NoSubstitute
val nonNullKotlinVersion = kotlinVersion ?: return NoSubstitute
val platformNamePart = libraryInfo.platform?.let { " [$it]" }.orEmpty()
val newLibraryName = "$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE$nonNullKotlinVersion - ${libraryInfo.name}$platformNamePart"
val platformNamePart = library.platform?.let { " [$it]" }.orEmpty()
val newLibraryName = "$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE$nonNullKotlinVersion - ${library.name}$platformNamePart"
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
packaging = DEFAULT_PACKAGING
files += libraryInfo.path
sources += libraryInfo.sourcePaths
files += library.path
sources += library.sourcePaths
scope = DependencyScope.PROVIDED.name
}
@@ -5,8 +5,11 @@
package org.jetbrains.kotlin.konan.library.lite
data class LiteKonanDistribution(
val distributionHome: String,
val kotlinNativeVersion: KotlinVersion,
val kotlinNativeVersionString: String
import org.jetbrains.kotlin.konan.KonanVersion
import java.io.File
class LiteKonanDistribution(
val distributionHome: File,
val konanVersion: KonanVersion,
val konanVersionString: String
)
@@ -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)
}
}
@@ -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
)
}
}
@@ -7,10 +7,9 @@ package org.jetbrains.kotlin.konan.library.lite
import java.io.File
data class LiteKonanLibrary(
val path: File,
val sourcePaths: Collection<File>,
val name: String,
val platform: String?,
internal val compilerVersion: String
)
interface LiteKonanLibrary {
val path: File
val sourcePaths: Collection<File>
val name: String
val platform: String?
}
@@ -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()
}
@@ -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()
}
}