mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +07:00
[Kotlin] Resolve parts of KMP libraries in multiplatform descriptor
KTIJ-29725 GitOrigin-RevId: c7f8cb5e25abd7db02e9d3404283d6b3db04a96c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1f8598525c
commit
a05c97a762
@@ -23,7 +23,7 @@ public enum ArtifactKind {
|
||||
ARTIFACT("", "jar"), SOURCES("sources", "jar"), JAVADOC("javadoc", "jar"),
|
||||
ANNOTATIONS("annotations", "zip"), AAR_ARTIFACT("", "aar"), POM("", "pom"),
|
||||
ALL("all", "jar"), HTTP("http", "jar"), DLL("", "dll"),
|
||||
ZIP("", "zip");
|
||||
ZIP("", "zip"), KLIB("", "klib");
|
||||
|
||||
private final String myClassifier;
|
||||
private final String myExtension;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
package org.jetbrains.kotlin.idea.artifacts
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.util.io.JarUtil
|
||||
import com.intellij.util.SystemProperties
|
||||
import org.eclipse.aether.repository.RemoteRepository
|
||||
import org.jetbrains.idea.maven.aether.ArtifactKind
|
||||
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager
|
||||
import org.jetbrains.idea.maven.aether.ProgressConsumer
|
||||
import org.jetbrains.kotlin.konan.file.unzipTo
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.EnumSet
|
||||
|
||||
/**
|
||||
* Utility test downloader of KMP library parts for manual module configuration without Gradle import.
|
||||
* The possible kinds of dependencies are described in [DependencyKind].
|
||||
* Use factory methods defined in [KmpAwareLibraryDependency] to specify KMP dependencies for downloading.
|
||||
*/
|
||||
object KmpLightFixtureDependencyDownloader {
|
||||
private const val MAVEN_CENTRAL_CACHE_REDIRECTOR_URL =
|
||||
"https://cache-redirector.jetbrains.com/repo.maven.apache.org/maven2/"
|
||||
|
||||
/**
|
||||
* Download or resolve from local cache a part of a KMP library and transform if necessary.
|
||||
*
|
||||
* @param kmpDependency description of dependency kind and coordinates.
|
||||
* @param directoryForTransformedDependencies an optional output directory for the extracted common metadata library part.
|
||||
* If the directory is not specified, a temporary directory will be used.
|
||||
* @return a path to a downloaded platform artifact or a downloaded and transformed common metadata artifact; `null` in case of errors.
|
||||
*/
|
||||
fun resolveDependency(kmpDependency: KmpAwareLibraryDependency, directoryForTransformedDependencies: Path? = null): Path? {
|
||||
val coordinates = kmpDependency.coordinates
|
||||
val kmpDependencyKind = kmpDependency.kind
|
||||
|
||||
val resolvedDependencyArtifact = resolveArtifact(coordinates, kmpDependencyKind.artifactKind) ?: return null
|
||||
|
||||
return when (kmpDependencyKind) {
|
||||
DependencyKind.PLATFORM_KLIB, DependencyKind.JAR -> resolvedDependencyArtifact
|
||||
DependencyKind.COMMON_METADATA_JAR, DependencyKind.ALL_METADATA_JAR -> {
|
||||
check(coordinates.sourceSet != null) { "Unable to resolve a metadata KLIB without a source set, coordinates: $coordinates" }
|
||||
resolveSourceSetKlib(resolvedDependencyArtifact, coordinates, directoryForTransformedDependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveArtifact(coordinates: KmpCoordinates, artifactKind: ArtifactKind): Path? {
|
||||
val mavenLocalDir = File(SystemProperties.getUserHome(), ".m2/repository")
|
||||
|
||||
val repositories = listOf(
|
||||
RemoteRepository.Builder("mavenLocal", "default", "file://" + mavenLocalDir.absolutePath).build(),
|
||||
RemoteRepository.Builder("mavenCentral", "default", MAVEN_CENTRAL_CACHE_REDIRECTOR_URL).build(),
|
||||
)
|
||||
|
||||
val resolvedDependencyArtifact = ArtifactRepositoryManager(
|
||||
mavenLocalDir, repositories, ProgressConsumer.DEAF
|
||||
).resolveDependencyAsArtifact(
|
||||
/* groupId = */ coordinates.group,
|
||||
/* artifactId = */ coordinates.artifact,
|
||||
/* versionConstraint = */ coordinates.version,
|
||||
/* artifactKinds = */ EnumSet.of(artifactKind),
|
||||
/* includeTransitiveDependencies = */ false,
|
||||
/* excludedDependencies = */ emptyList()
|
||||
).singleOrNull()?.file?.toPath() ?: return null
|
||||
|
||||
return resolvedDependencyArtifact
|
||||
}
|
||||
|
||||
private fun resolveSourceSetKlib(
|
||||
resolvedArtifactPath: Path,
|
||||
kmpCoordinates: KmpCoordinates,
|
||||
directoryForTransformedDependencies: Path?
|
||||
): Path? {
|
||||
val sourceSet = kmpCoordinates.sourceSet
|
||||
check(sourceSet != null)
|
||||
if (!JarUtil.containsEntry(resolvedArtifactPath.toFile(), sourceSet)) return null
|
||||
|
||||
val transformedLibrariesRoot = directoryForTransformedDependencies
|
||||
?: FileUtilRt.createTempDirectory("kotlinTransformedMetadataLibraries", "").toPath()
|
||||
val destination = transformedLibrariesRoot.resolve(kmpCoordinates.toString())
|
||||
|
||||
resolvedArtifactPath.unzipTo(destination, fromSubdirectory = Paths.get("$sourceSet/"))
|
||||
return destination
|
||||
}
|
||||
}
|
||||
|
||||
enum class DependencyKind(val artifactKind: ArtifactKind) {
|
||||
/**
|
||||
* JVM JAR with .class files.
|
||||
*/
|
||||
JAR(ArtifactKind.ARTIFACT),
|
||||
|
||||
/**
|
||||
* Platform KLIB with target-specific binaries.
|
||||
*/
|
||||
PLATFORM_KLIB(ArtifactKind.KLIB),
|
||||
|
||||
/**
|
||||
* Fat composite Kotlin metadata artifact, published by KMP libraries.
|
||||
* Contains unpacked common metadata KLIBs with bodiless declaration headers serialized in .knm format
|
||||
* One inner KLIB corresponds to one shared source set.
|
||||
* Platform (target-specific) binaries and platform source sets' content are not included in this artifact.
|
||||
*/
|
||||
COMMON_METADATA_JAR(ArtifactKind.ARTIFACT),
|
||||
|
||||
/**
|
||||
* Same as metadata KLIB, but published as a special `-all` variant.
|
||||
* The default variant in such cases is used for a compatibility artifact in a legacy format.
|
||||
* This format is used by older KMP libraries, such as kotlin-stdlib and a few others.
|
||||
*/
|
||||
ALL_METADATA_JAR(ArtifactKind.ALL),
|
||||
}
|
||||
|
||||
class KmpCoordinates(
|
||||
val group: String,
|
||||
val artifact: String,
|
||||
val version: String,
|
||||
val sourceSet: String?,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
val sourceSetIfNotNull = sourceSet?.let { ":$sourceSet" }.orEmpty()
|
||||
return "$group:$artifact$sourceSetIfNotNull:$version"
|
||||
}
|
||||
}
|
||||
|
||||
class KmpAwareLibraryDependency private constructor(
|
||||
val coordinates: KmpCoordinates,
|
||||
val kind: DependencyKind,
|
||||
) {
|
||||
companion object {
|
||||
// expected format: org.example:some:1.2.3
|
||||
fun klib(coordinates: String) = createFromCoordinates(coordinates, DependencyKind.PLATFORM_KLIB)
|
||||
|
||||
// expected format: org.example:some:1.2.3
|
||||
fun jar(coordinates: String) = createFromCoordinates(coordinates, DependencyKind.JAR)
|
||||
|
||||
// expected format: org.example:some:commonMain:1.2.3
|
||||
fun metadataKlib(coordinates: String) = createFromCoordinates(coordinates, DependencyKind.COMMON_METADATA_JAR)
|
||||
|
||||
// expected format: org.example:some:commonMain:1.2.3
|
||||
fun allMetadataJar(coordinates: String) = createFromCoordinates(coordinates, DependencyKind.ALL_METADATA_JAR)
|
||||
|
||||
private fun createFromCoordinates(coordinatesString: String, kind: DependencyKind): KmpAwareLibraryDependency {
|
||||
return when (kind) {
|
||||
DependencyKind.JAR, DependencyKind.PLATFORM_KLIB -> {
|
||||
val (group, artifact, version) = coordinatesString.split(":").also { check(it.size == 3) }
|
||||
KmpAwareLibraryDependency(KmpCoordinates(group, artifact, version, null), kind)
|
||||
}
|
||||
|
||||
DependencyKind.COMMON_METADATA_JAR, DependencyKind.ALL_METADATA_JAR -> {
|
||||
val (group, artifact, sourceSet, version) = coordinatesString.split(":").also { check(it.size == 4) }
|
||||
KmpAwareLibraryDependency(KmpCoordinates(group, artifact, version, sourceSet), kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,11 @@ public class K2KmpLightFixtureHighlightingTestGenerated extends AbstractK2KmpLig
|
||||
runTest("testData/kmp/highlighting/collectionBuilders.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("coroutines.kt")
|
||||
public void testCoroutines() throws Exception {
|
||||
runTest("testData/kmp/highlighting/coroutines.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("expectActualFunctions.kt")
|
||||
public void testExpectActualFunctions() throws Exception {
|
||||
runTest("testData/kmp/highlighting/expectActualFunctions.kt");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// PLATFORM: Common
|
||||
// FILE: A.kt
|
||||
// FILE: common.kt
|
||||
|
||||
fun commonThings() {
|
||||
val c = listOf(1, 2, 3)
|
||||
@@ -8,7 +8,7 @@ fun commonThings() {
|
||||
}
|
||||
|
||||
// PLATFORM: Jvm
|
||||
// FILE: B.kt
|
||||
// FILE: jvm.kt
|
||||
|
||||
fun jvmThings() {
|
||||
val c = listOf(1, 2, 3)
|
||||
@@ -17,7 +17,7 @@ fun jvmThings() {
|
||||
}
|
||||
|
||||
// PLATFORM: Js
|
||||
// FILE: C.kt
|
||||
// FILE: js.kt
|
||||
|
||||
fun jsThings() {
|
||||
val c = listOf(1, 2, 3)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// PLATFORM: Common
|
||||
// FILE: common.kt
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
suspend fun commonCoroutines() {
|
||||
coroutineScope {
|
||||
launch {
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PLATFORM: Jvm
|
||||
// FILE: jvm.kt
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
suspend fun jvmCoroutines() {
|
||||
coroutineScope {
|
||||
launch {
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PLATFORM: Js
|
||||
// FILE: js.kt
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
suspend fun jsCoroutines() {
|
||||
coroutineScope {
|
||||
launch {
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// PLATFORM: Common
|
||||
// FILE: A.kt
|
||||
// FILE: common.kt
|
||||
|
||||
expect fun foo(): String
|
||||
|
||||
@@ -8,7 +8,7 @@ fun useFoo() {
|
||||
}
|
||||
|
||||
// PLATFORM: Jvm
|
||||
// FILE: B.kt
|
||||
// FILE: jvm.kt
|
||||
|
||||
actual fun foo(): String = "JVM"
|
||||
|
||||
@@ -17,7 +17,7 @@ fun useFoo() {
|
||||
}
|
||||
|
||||
// PLATFORM: Js
|
||||
// FILE: C.kt
|
||||
// FILE: js.kt
|
||||
|
||||
actual fun foo(): String = "JS"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// PLATFORM: Common
|
||||
// FILE: A.kt
|
||||
// FILE: common.kt
|
||||
|
||||
fun common() {
|
||||
val f: Float = 0.0f
|
||||
@@ -14,7 +14,7 @@ fun common() {
|
||||
}
|
||||
|
||||
// PLATFORM: Jvm
|
||||
// FILE: B.kt
|
||||
// FILE: jvm.kt
|
||||
|
||||
fun jvm() {
|
||||
val f: Float = 0.0f
|
||||
@@ -29,7 +29,7 @@ fun jvm() {
|
||||
}
|
||||
|
||||
// PLATFORM: Js
|
||||
// FILE: C.kt
|
||||
// FILE: js.kt
|
||||
|
||||
fun js() {
|
||||
val f: Float = 0.0f
|
||||
|
||||
@@ -8,14 +8,16 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.*
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.ex.temp.TempFileSystem
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
import com.intellij.testFramework.IdeaTestUtil
|
||||
import com.intellij.testFramework.IndexingTestUtil
|
||||
import com.intellij.testFramework.fixtures.MavenDependencyUtil
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.kotlin.idea.artifacts.KmpAwareLibraryDependency
|
||||
import org.jetbrains.kotlin.idea.artifacts.KmpLightFixtureDependencyDownloader
|
||||
import org.jetbrains.kotlin.idea.framework.KotlinSdkType
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.platform.js.JsPlatforms
|
||||
@@ -24,50 +26,56 @@ import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
/**
|
||||
* The project is created with three modules: Common, Jvm -> Common, Js -> Common.
|
||||
*
|
||||
* Standard library dependency is added to all modules.
|
||||
* Standard library and kotlinx-coroutines-core of fixed versions are added to all modules.
|
||||
*
|
||||
* Since we can't use Gradle in light fixture tests due to performance reasons, correct libraries should be mapped to modules manually.
|
||||
*/
|
||||
object KotlinMultiPlatformProjectDescriptor : KotlinLightProjectDescriptor() {
|
||||
enum class PlatformDescriptor(
|
||||
val moduleName: String,
|
||||
val sourceRootName: String? = null,
|
||||
val targetPlatform: TargetPlatform,
|
||||
val isKotlinSdkUsed: Boolean = true,
|
||||
val refinementDependencies: List<PlatformDescriptor> = emptyList(),
|
||||
val dependencyCoordinates: List<String> = emptyList(),
|
||||
val libraryDependencies: List<KmpAwareLibraryDependency> = emptyList(),
|
||||
) {
|
||||
COMMON(
|
||||
moduleName = "Common",
|
||||
sourceRootName = "src_common",
|
||||
targetPlatform = TargetPlatform(
|
||||
setOf(
|
||||
JvmPlatforms.jvm8.single(),
|
||||
JsPlatforms.defaultJsPlatform.single()
|
||||
)
|
||||
),
|
||||
dependencyCoordinates = listOf(
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-common:1.9.23", // TODO (KTIJ-29725): make stdlib version dynamic
|
||||
libraryDependencies = listOf(
|
||||
KmpAwareLibraryDependency.allMetadataJar("org.jetbrains.kotlin:kotlin-stdlib:commonMain:1.9.23"), // TODO (KTIJ-29725): sliding version
|
||||
KmpAwareLibraryDependency.metadataKlib("org.jetbrains.kotlinx:kotlinx-coroutines-core:commonMain:1.8.0")
|
||||
),
|
||||
),
|
||||
JVM(
|
||||
moduleName = "Jvm",
|
||||
sourceRootName = "src_jvm",
|
||||
targetPlatform = JvmPlatforms.jvm8,
|
||||
isKotlinSdkUsed = false,
|
||||
refinementDependencies = listOf(COMMON),
|
||||
dependencyCoordinates = listOf(
|
||||
"org.jetbrains.kotlin:kotlin-stdlib:1.9.23",
|
||||
libraryDependencies = listOf(
|
||||
KmpAwareLibraryDependency.jar("org.jetbrains.kotlin:kotlin-stdlib:1.9.23"),
|
||||
KmpAwareLibraryDependency.jar("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0"),
|
||||
KmpAwareLibraryDependency.jar("org.jetbrains:annotations:23.0.0"),
|
||||
),
|
||||
),
|
||||
JS(
|
||||
moduleName = "Js",
|
||||
sourceRootName = "src_js",
|
||||
targetPlatform = JsPlatforms.defaultJsPlatform,
|
||||
refinementDependencies = listOf(COMMON),
|
||||
dependencyCoordinates = listOf(
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-js:1.9.23",
|
||||
libraryDependencies = listOf(
|
||||
KmpAwareLibraryDependency.klib("org.jetbrains.kotlin:kotlin-stdlib-js:1.9.23"),
|
||||
KmpAwareLibraryDependency.klib("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.8.0"),
|
||||
KmpAwareLibraryDependency.klib("org.jetbrains.kotlinx:atomicfu-js:0.23.1"),
|
||||
),
|
||||
);
|
||||
|
||||
val sourceRootName: String
|
||||
get() = "src_${moduleName.lowercase()}"
|
||||
|
||||
fun sourceRoot(): VirtualFile? = findRoot(sourceRootName)
|
||||
|
||||
private fun findRoot(rootName: String?): VirtualFile? =
|
||||
@@ -102,10 +110,8 @@ object KotlinMultiPlatformProjectDescriptor : KotlinLightProjectDescriptor() {
|
||||
|
||||
private fun configureModule(module: Module, model: ModifiableRootModel, descriptor: PlatformDescriptor) {
|
||||
model.getModuleExtension(LanguageLevelModuleExtension::class.java).languageLevel = LanguageLevel.HIGHEST
|
||||
if (descriptor.sourceRootName != null) {
|
||||
val sourceRoot = createSourceRoot(module, descriptor.sourceRootName)
|
||||
model.addContentEntry(sourceRoot).addSourceFolder(sourceRoot, JavaSourceRootType.SOURCE)
|
||||
}
|
||||
val sourceRoot = createSourceRoot(module, descriptor.sourceRootName)
|
||||
model.addContentEntry(sourceRoot).addSourceFolder(sourceRoot, JavaSourceRootType.SOURCE)
|
||||
|
||||
setUpSdk(module, model, descriptor)
|
||||
|
||||
@@ -115,9 +121,18 @@ object KotlinMultiPlatformProjectDescriptor : KotlinLightProjectDescriptor() {
|
||||
dependsOnModuleNames = descriptor.refinementDependencies.map(PlatformDescriptor::moduleName),
|
||||
pureKotlinSourceFolders = listOf(descriptor.sourceRoot()!!.path),
|
||||
)
|
||||
for (libraryCoordinates in descriptor.libraryDependencies) {
|
||||
val library = setUpLibraryFromCoordinates(module.project, libraryCoordinates)
|
||||
model.addLibraryEntry(library)
|
||||
}
|
||||
}
|
||||
|
||||
for (libraryCoordinates in descriptor.dependencyCoordinates) {
|
||||
MavenDependencyUtil.addFromMaven(model, libraryCoordinates)
|
||||
private fun setUpLibraryFromCoordinates(project: Project, dependency: KmpAwareLibraryDependency): Library {
|
||||
val dependencyRoot = KmpLightFixtureDependencyDownloader.resolveDependency(dependency)?.toFile()
|
||||
?: error("Unable to download library ${dependency.coordinates}")
|
||||
return ConfigLibraryUtil.addProjectLibrary(project = project, name = dependency.coordinates.toString()) {
|
||||
addRoot(dependencyRoot, OrderRootType.CLASSES)
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user