User friendly presentation of KLIBs in IDEA
Adds friendly names for KLIBs imported as file dependencies from Gradle project. Issue #KT-29613 Fixed Issue #KT-29783 Fixed
This commit is contained in:
@@ -534,6 +534,7 @@ tasks {
|
||||
create("idea-plugin-additional-tests") {
|
||||
dependsOn("dist")
|
||||
dependsOn(":idea:idea-gradle:test",
|
||||
":idea:idea-gradle-native:test",
|
||||
":idea:idea-maven:test",
|
||||
":j2k:test",
|
||||
":eval4j:test")
|
||||
|
||||
@@ -4,6 +4,8 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testRuntime(intellijDep())
|
||||
|
||||
compile(project(":kotlin-native:kotlin-native-library-reader"))
|
||||
|
||||
compileOnly(project(":idea:idea-gradle"))
|
||||
@@ -19,15 +21,53 @@ dependencies {
|
||||
|
||||
compile(project(":js:js.frontend"))
|
||||
|
||||
compileOnly(intellijDep())
|
||||
testCompile(projectTests(":idea"))
|
||||
testCompile(projectTests(":idea:idea-test-framework"))
|
||||
testCompile(projectTests(":idea:idea-gradle"))
|
||||
|
||||
compileOnly(intellijPluginDep("gradle"))
|
||||
compileOnly(intellijPluginDep("Groovy"))
|
||||
compileOnly(intellijPluginDep("junit"))
|
||||
compileOnly(intellijDep())
|
||||
|
||||
testCompile(intellijPluginDep("gradle"))
|
||||
testCompileOnly(intellijPluginDep("Groovy"))
|
||||
testCompileOnly(intellijDep())
|
||||
|
||||
testRuntime(project(":kotlin-reflect"))
|
||||
testRuntime(project(":idea:idea-jvm"))
|
||||
testRuntime(project(":idea:idea-android"))
|
||||
testRuntime(project(":plugins:kapt3-idea"))
|
||||
testRuntime(project(":plugins:android-extensions-ide"))
|
||||
testRuntime(project(":plugins:lint"))
|
||||
testRuntime(project(":sam-with-receiver-ide-plugin"))
|
||||
testRuntime(project(":allopen-ide-plugin"))
|
||||
testRuntime(project(":noarg-ide-plugin"))
|
||||
testRuntime(project(":kotlin-scripting-idea"))
|
||||
testRuntime(project(":kotlinx-serialization-ide-plugin"))
|
||||
// TODO: the order of the plugins matters here, consider avoiding order-dependency
|
||||
testRuntime(intellijPluginDep("junit"))
|
||||
testRuntime(intellijPluginDep("testng"))
|
||||
testRuntime(intellijPluginDep("properties"))
|
||||
testRuntime(intellijPluginDep("gradle"))
|
||||
testRuntime(intellijPluginDep("Groovy"))
|
||||
testRuntime(intellijPluginDep("coverage"))
|
||||
if (Ide.IJ()) {
|
||||
testRuntime(intellijPluginDep("maven"))
|
||||
}
|
||||
testRuntime(intellijPluginDep("android"))
|
||||
testRuntime(intellijPluginDep("smali"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { none() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
testsJar()
|
||||
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
useAndroidSdk()
|
||||
}
|
||||
|
||||
configureFormInstrumentation()
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.ide.konan.gradle
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.LibraryOrderEntry
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import com.intellij.openapi.vfs.VfsUtilCore.urlToPath
|
||||
import com.intellij.util.io.readText
|
||||
import org.jetbrains.kotlin.ide.konan.NativeLibraryKind
|
||||
import org.jetbrains.kotlin.idea.codeInsight.gradle.GradleImportingTestCase
|
||||
import org.jetbrains.kotlin.idea.configuration.externalCompilerVersion
|
||||
import org.jetbrains.kotlin.idea.framework.detectLibraryKind
|
||||
import org.jetbrains.kotlin.idea.project.platform
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
|
||||
import org.jetbrains.kotlin.konan.library.konanCommonLibraryPath
|
||||
import org.jetbrains.kotlin.konan.library.konanPlatformLibraryPath
|
||||
import org.jetbrains.kotlin.platform.impl.isKotlinNative
|
||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runners.Parameterized
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
class GradleNativeLibrariesInIDENamingTest : GradleImportingTestCase() {
|
||||
|
||||
// Test naming of Kotlin/Native libraries in projects with Gradle plugin 1.3.21+
|
||||
@Test
|
||||
fun testLibrariesNaming() {
|
||||
configureProject()
|
||||
importProject()
|
||||
|
||||
val projectRoot = Paths.get(projectPath)
|
||||
myProject.allModules().forEach { assertValidModule(it, projectRoot) }
|
||||
}
|
||||
|
||||
// Test naming of Kotlin/Native libraries in projects with Gradle plugin 1.3.20 or earlier
|
||||
@Test
|
||||
fun testLegacyLibrariesNaming() {
|
||||
configureProject()
|
||||
importProject()
|
||||
|
||||
val projectRoot = Paths.get(projectPath)
|
||||
myProject.allModules().forEach { assertValidModule(it, projectRoot) }
|
||||
}
|
||||
|
||||
override fun getExternalSystemConfigFileName() = GradleConstants.KOTLIN_DSL_SCRIPT_NAME
|
||||
|
||||
override fun testDataDirName() = "nativeLibraries"
|
||||
|
||||
private fun configureProject() {
|
||||
configureByFiles()
|
||||
|
||||
// include data dir with fake Kotlin/Native libraries
|
||||
val testSuiteDataDir = testDataDirectory().toPath().parent
|
||||
val kotlinNativeHome = testSuiteDataDir.resolve(FAKE_KOTLIN_NATIVE_HOME_RELATIVE_PATH)
|
||||
|
||||
Files.walk(kotlinNativeHome)
|
||||
.filter { Files.isRegularFile(it) }
|
||||
.forEach { pathInTestSuite ->
|
||||
// need to put copied file one directory upper than the project root, so adding ".." to the beginning of relative path
|
||||
// reason: distribution KLIBs should not be appear in IDEA indexes, so they should be located outside of the project root
|
||||
val relativePathInProject = DOUBLE_DOT_PATH.resolve(pathInTestSuite.toRelativePath(testSuiteDataDir))
|
||||
createProjectSubFile(relativePathInProject.toString(), pathInTestSuite.readText())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "{index}: with Gradle-{0}")
|
||||
@Throws(Throwable::class)
|
||||
@JvmStatic
|
||||
fun data() = listOf(arrayOf("4.10.2"))
|
||||
}
|
||||
}
|
||||
|
||||
private val FAKE_KOTLIN_NATIVE_HOME_RELATIVE_PATH = Paths.get("kotlin-native-data-dir", "kotlin-native-PLATFORM-VERSION")
|
||||
private val NATIVE_LIBRARY_NAME_REGEX = Regex("^Kotlin/Native ([\\d\\w\\.-]+) - ([\\w\\d]+)( \\[([\\w\\d_]+)\\])?$")
|
||||
|
||||
private val NATIVE_LINUX_LIBRARIES = listOf(
|
||||
"Kotlin/Native {version} - stdlib",
|
||||
"Kotlin/Native {version} - linux [linux_x64]",
|
||||
"Kotlin/Native {version} - posix [linux_x64]",
|
||||
"Kotlin/Native {version} - zlib [linux_x64]"
|
||||
)
|
||||
|
||||
private val NATIVE_MACOS_LIBRARIES = listOf(
|
||||
"Kotlin/Native {version} - stdlib",
|
||||
"Kotlin/Native {version} - Foundation [macos_x64]",
|
||||
"Kotlin/Native {version} - UIKit [macos_x64]",
|
||||
"Kotlin/Native {version} - objc [macos_x64]"
|
||||
)
|
||||
|
||||
private val NATIVE_MINGW_LIBRARIES = listOf(
|
||||
"Kotlin/Native {version} - stdlib",
|
||||
"Kotlin/Native {version} - iconv [mingw_x64]",
|
||||
"Kotlin/Native {version} - opengl32 [mingw_x64]",
|
||||
"Kotlin/Native {version} - windows [mingw_x64]"
|
||||
)
|
||||
private val DOUBLE_DOT_PATH = Paths.get("..")
|
||||
|
||||
private fun Path.toRelativePath(basePath: Path) = basePath.relativize(this)
|
||||
|
||||
private val Module.libraries
|
||||
get() = ModuleRootManager.getInstance(this).orderEntries
|
||||
.asSequence()
|
||||
.filterIsInstance<LibraryOrderEntry>()
|
||||
.mapNotNull { it.library }
|
||||
|
||||
private fun assertValidModule(module: Module, projectRoot: Path) {
|
||||
val (nativeLibraries, otherLibraries) = module.libraries.partition { library ->
|
||||
detectLibraryKind(library.getFiles(OrderRootType.CLASSES)) == NativeLibraryKind
|
||||
}
|
||||
|
||||
if (module.platform.isKotlinNative) {
|
||||
assertFalse("No Kotlin/Native libraries in $module", nativeLibraries.isEmpty())
|
||||
nativeLibraries.forEach { assertValidNativeLibrary(it, projectRoot) }
|
||||
|
||||
val kotlinVersion = requireNotNull(module.externalCompilerVersion) {
|
||||
"External compiler version shoul not be null"
|
||||
}
|
||||
|
||||
val expectedNativeLibraryNames = when {
|
||||
module.name.contains("linux", ignoreCase = true) -> NATIVE_LINUX_LIBRARIES
|
||||
module.name.contains("macos", ignoreCase = true) -> NATIVE_MACOS_LIBRARIES
|
||||
module.name.contains("mingw", ignoreCase = true) -> NATIVE_MINGW_LIBRARIES
|
||||
else -> emptyList()
|
||||
}.map { it.replace("{version}", kotlinVersion) }.sorted()
|
||||
|
||||
val actualNativeLibraryNames = nativeLibraries.map { it.name.orEmpty() }.sorted()
|
||||
|
||||
assertEquals("Different set of Kotlin/Native libraries in $module", expectedNativeLibraryNames, actualNativeLibraryNames)
|
||||
|
||||
} else {
|
||||
assertTrue("Unexpected Kotlin/Native libraries in $module: $nativeLibraries", nativeLibraries.isEmpty())
|
||||
}
|
||||
|
||||
otherLibraries.forEach(::assertValidNonNativeLibrary)
|
||||
}
|
||||
|
||||
private fun assertValidNativeLibrary(library: Library, projectRoot: Path) {
|
||||
val fullName = library.name.orEmpty()
|
||||
|
||||
val result = NATIVE_LIBRARY_NAME_REGEX.matchEntire(fullName)
|
||||
assertTrue("Invalid Kotlin/Native library name: $fullName", result?.groups?.size == 5)
|
||||
|
||||
val (_, name, _, platform) = result!!.destructured
|
||||
|
||||
val libraryUrls = library.getUrls(OrderRootType.CLASSES).toList()
|
||||
assertTrue("Kotlin/Native library $fullName has multiple roots (${libraryUrls.size}): $libraryUrls", libraryUrls.size == 1)
|
||||
|
||||
val actualShortPath = Paths.get(urlToPath(libraryUrls.single()))
|
||||
.toRelativePath(projectRoot.resolve(DOUBLE_DOT_PATH).normalize())
|
||||
.toRelativePath(FAKE_KOTLIN_NATIVE_HOME_RELATIVE_PATH)
|
||||
|
||||
val expectedShortPath = if (platform.isEmpty()) konanCommonLibraryPath(name) else konanPlatformLibraryPath(name, platform)
|
||||
assertEquals("The short path of $fullName does not match its location", expectedShortPath, actualShortPath)
|
||||
}
|
||||
|
||||
private fun assertValidNonNativeLibrary(library: Library) {
|
||||
val name = library.name.orEmpty()
|
||||
assertFalse("Invalid non-native library name: $name", name.contains("Kotlin/Native"))
|
||||
}
|
||||
+6
-9
@@ -64,13 +64,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
|
||||
override fun populateModuleExtraModels(gradleModule: IdeaModule, ideModule: DataNode<ModuleData>) {
|
||||
if (ExternalSystemApiUtil.find(ideModule, BuildScriptClasspathData.KEY) == null) {
|
||||
val buildScriptClasspathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel::class.java)
|
||||
val classpathEntries = buildScriptClasspathModel?.classpath?.map {
|
||||
BuildScriptClasspathData.ClasspathEntry(it.classes, it.sources, it.javadoc)
|
||||
} ?: emptyList()
|
||||
val buildScriptClasspathData = BuildScriptClasspathData(GradleConstants.SYSTEM_ID, classpathEntries).also {
|
||||
it.gradleHomeDir = buildScriptClasspathModel?.gradleHomeDir
|
||||
}
|
||||
val buildScriptClasspathData = buildClasspathData(gradleModule, resolverCtx)
|
||||
ideModule.createChild(BuildScriptClasspathData.KEY, buildScriptClasspathData)
|
||||
}
|
||||
|
||||
@@ -347,17 +341,20 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) ?: return
|
||||
val sourceSetMap = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return
|
||||
val artifactsMap = ideProject.getUserData(CONFIGURATION_ARTIFACTS) ?: return
|
||||
val substitutor = KotlinNativeLibrariesDependencySubstitutor(mppModel, gradleModule, resolverCtx)
|
||||
val processedModuleIds = HashSet<String>()
|
||||
processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation ->
|
||||
if (processedModuleIds.add(getKotlinModuleId(gradleModule, compilation, resolverCtx))) {
|
||||
val substitutedDependencies = substitutor.substituteDependencies(compilation.dependencies)
|
||||
buildDependencies(
|
||||
resolverCtx,
|
||||
sourceSetMap,
|
||||
artifactsMap,
|
||||
dataNode,
|
||||
preprocessDependencies(compilation.dependencies),
|
||||
preprocessDependencies(substitutedDependencies),
|
||||
ideProject
|
||||
)
|
||||
KotlinNativeLibrariesNameFixer.applyTo(dataNode)
|
||||
for (sourceSet in compilation.sourceSets) {
|
||||
if (sourceSet.fullName() == compilation.fullName()) continue
|
||||
val targetDataNode = getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx) ?: continue
|
||||
@@ -446,7 +443,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun preprocessDependencies(dependencies: Set<KotlinDependency>): List<ExternalDependency> {
|
||||
private fun preprocessDependencies(dependencies: Collection<KotlinDependency>): List<ExternalDependency> {
|
||||
return dependencies
|
||||
.groupBy { it.id }
|
||||
.mapValues { it.value.firstOrNull { it.scope == "COMPILE" } ?: it.value.lastOrNull() }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.idea.configuration
|
||||
|
||||
import org.gradle.tooling.model.idea.IdeaModule
|
||||
import org.jetbrains.plugins.gradle.model.BuildScriptClasspathModel
|
||||
import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData
|
||||
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
|
||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||
|
||||
fun buildClasspathData(
|
||||
gradleModule: IdeaModule,
|
||||
resolverCtx: ProjectResolverContext
|
||||
): BuildScriptClasspathData {
|
||||
val classpathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel::class.java)
|
||||
val classpathEntries = classpathModel?.classpath?.map {
|
||||
BuildScriptClasspathData.ClasspathEntry(it.classes, it.sources, it.javadoc)
|
||||
} ?: emptyList()
|
||||
return BuildScriptClasspathData(GradleConstants.SYSTEM_ID, classpathEntries).also {
|
||||
it.gradleHomeDir = classpathModel?.gradleHomeDir
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.idea.configuration
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.ProjectKeys
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.roots.DependencyScope
|
||||
import com.intellij.openapi.util.Key
|
||||
import org.gradle.tooling.model.idea.IdeaModule
|
||||
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
|
||||
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.lite.LiteKonanLibraryInfoProvider
|
||||
import org.jetbrains.plugins.gradle.ExternalDependencyId
|
||||
import org.jetbrains.plugins.gradle.model.DefaultExternalLibraryDependency
|
||||
import org.jetbrains.plugins.gradle.model.ExternalDependency
|
||||
import org.jetbrains.plugins.gradle.model.ExternalLibraryDependency
|
||||
import org.jetbrains.plugins.gradle.model.FileCollectionDependency
|
||||
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
|
||||
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
|
||||
import java.io.File
|
||||
|
||||
// KT-29613, KT-29783
|
||||
internal class KotlinNativeLibrariesDependencySubstitutor(
|
||||
private val mppModel: KotlinMPPGradleModel,
|
||||
private val gradleModule: IdeaModule,
|
||||
private val resolverCtx: ProjectResolverContext
|
||||
) {
|
||||
|
||||
// Substitutes `ExternalDependency` entries that represent KLIBs with new dependency entries with proper type and name:
|
||||
// - every `FileCollectionDependency` is checked whether it points to an existing KLIB, and substituted if it is
|
||||
// - similarly for every `ExternalLibraryDependency` with `groupId == "Kotlin/Native"` (legacy KLIB provided by Gradle plugin <= 1.3.20)
|
||||
fun substituteDependencies(dependencies: Collection<ExternalDependency>): List<ExternalDependency> {
|
||||
val result = ArrayList(dependencies)
|
||||
for (i in 0 until result.size) {
|
||||
val dependency = result[i]
|
||||
val dependencySubstitute = when (dependency) {
|
||||
is FileCollectionDependency -> getFileCollectionDependencySubstitute(dependency)
|
||||
is ExternalLibraryDependency -> getExternalLibraryDependencySubstitute(dependency)
|
||||
else -> NoSubstitute
|
||||
}
|
||||
|
||||
val newDependency = (dependencySubstitute as? YesSubstitute)?.substitute ?: continue
|
||||
result[i] = newDependency
|
||||
}
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
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 kotlinVersion: String? by lazy {
|
||||
val classpathData = buildClasspathData(gradleModule, resolverCtx)
|
||||
val result = findKotlinPluginVersion(classpathData)
|
||||
if (result == null)
|
||||
LOG.error(
|
||||
"""
|
||||
Unexpectedly can't obtain Kotlin Gradle plugin version for ${gradleModule.name} module.
|
||||
Build classpath is ${classpathData.classpathEntries.flatMap { it.classesFile }}.
|
||||
${KotlinNativeLibrariesDependencySubstitutor::class.java.simpleName} will run in idle mode. No dependencies will be substituted.
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
private fun getFileCollectionDependencySubstitute(dependency: FileCollectionDependency): DependencySubstitute =
|
||||
resolverCtx.dependencySubstitutionCache.getOrPut(dependency.id) {
|
||||
val libraryFile = dependency.files.firstOrNull() ?: return@getOrPut NoSubstitute
|
||||
buildSubstituteIfNecessary(libraryFile)
|
||||
}
|
||||
|
||||
private fun getExternalLibraryDependencySubstitute(dependency: ExternalLibraryDependency): DependencySubstitute =
|
||||
resolverCtx.dependencySubstitutionCache.getOrPut(dependency.id) {
|
||||
if (KOTLIN_NATIVE_LEGACY_GROUP_ID != dependency.group) return@getOrPut NoSubstitute
|
||||
val libraryFile = dependency.file ?: return@getOrPut NoSubstitute
|
||||
buildSubstituteIfNecessary(libraryFile)
|
||||
}
|
||||
|
||||
private fun buildSubstituteIfNecessary(libraryFile: File): DependencySubstitute {
|
||||
// need to check whether `libraryFile` 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.toPath()) ?: return NoSubstitute
|
||||
val nonNullKotlinVersion = kotlinVersion ?: return NoSubstitute
|
||||
|
||||
val platformNamePart = libraryInfo.platform?.let { " [$it]" }.orEmpty()
|
||||
val newLibraryName = "$KOTLIN_NATIVE_LIBRARY_PREFIX $nonNullKotlinVersion - ${libraryInfo.name}$platformNamePart"
|
||||
|
||||
val substitute = DefaultExternalLibraryDependency().apply {
|
||||
name = newLibraryName
|
||||
packaging = DEFAULT_PACKAGING
|
||||
file = libraryFile
|
||||
scope = DependencyScope.PROVIDED.name
|
||||
}
|
||||
|
||||
return YesSubstitute(substitute)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_PACKAGING = "jar"
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinNativeLibrariesDependencySubstitutor::class.java)
|
||||
|
||||
private val KLIB_DEPENDENCY_SUBSTITUTION_CACHE =
|
||||
Key.create<MutableMap<ExternalDependencyId, DependencySubstitute>>("KLIB_DEPENDENCY_SUBSTITUTION_CACHE")
|
||||
}
|
||||
}
|
||||
|
||||
internal object KotlinNativeLibrariesNameFixer {
|
||||
|
||||
// Gradle IDE plugin creates `LibraryData` nodes with internal name consisting of two parts:
|
||||
// - mandatory "Gradle: " prefix
|
||||
// - and library name
|
||||
// Then internal name is propagated to IDE `Library` object, and is displayed in IDE as "Gradle: <LIBRARY_NAME>".
|
||||
// KotlinNativeLibrariesNameFixer removes "Gradle: " prefix from all `LibraryData` nodes representing KLIBs.
|
||||
fun applyTo(ownerNode: DataNode<GradleSourceSetData>) {
|
||||
for (libraryDependency in ExternalSystemApiUtil.findAll(ownerNode, ProjectKeys.LIBRARY_DEPENDENCY)) {
|
||||
val libraryData = libraryDependency.data.target
|
||||
if (libraryData.internalName.startsWith("$GRADLE_LIBRARY_PREFIX$KOTLIN_NATIVE_LIBRARY_PREFIX")) {
|
||||
libraryData.internalName = libraryData.internalName.substringAfter(GRADLE_LIBRARY_PREFIX)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DependencySubstitute {
|
||||
object NoSubstitute : DependencySubstitute()
|
||||
class YesSubstitute(val substitute: ExternalLibraryDependency) : DependencySubstitute()
|
||||
}
|
||||
|
||||
private const val KOTLIN_NATIVE_LIBRARY_PREFIX = "Kotlin/Native"
|
||||
private const val KOTLIN_NATIVE_LEGACY_GROUP_ID = KOTLIN_NATIVE_LIBRARY_PREFIX
|
||||
private const val GRADLE_LIBRARY_PREFIX = "Gradle: "
|
||||
+2
-2
@@ -330,14 +330,14 @@ class NewMultiplatformProjectImportingTest : GradleImportingTestCase() {
|
||||
sourceFolder("shared/src/androidTest/resources", JavaResourceRootType.TEST_RESOURCE)
|
||||
}
|
||||
module("shared_iOSMain") {
|
||||
libraryDependency("Gradle: Kotlin/Native:stdlib:0.9.3", DependencyScope.COMPILE)
|
||||
libraryDependency("Kotlin/Native 0.9.3 - stdlib", DependencyScope.PROVIDED)
|
||||
libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE)
|
||||
moduleDependency("shared_commonMain", DependencyScope.COMPILE)
|
||||
sourceFolder("shared/src/iOSMain/kotlin", KotlinSourceRootType.Source)
|
||||
sourceFolder("shared/src/iOSMain/resources", KotlinResourceRootType.Resource)
|
||||
}
|
||||
module("shared_iOSTest") {
|
||||
libraryDependency("Gradle: Kotlin/Native:stdlib:0.9.3", DependencyScope.TEST)
|
||||
libraryDependency("Kotlin/Native 0.9.3 - stdlib", DependencyScope.PROVIDED)
|
||||
libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST)
|
||||
moduleDependency("shared_iOSMain", DependencyScope.TEST)
|
||||
moduleDependency("shared_commonMain", DependencyScope.TEST)
|
||||
|
||||
@@ -103,4 +103,9 @@ interface KotlinMPPGradleModel : Serializable {
|
||||
val sourceSets: Map<String, KotlinSourceSet>
|
||||
val targets: Collection<KotlinTarget>
|
||||
val extraFeatures: ExtraFeatures
|
||||
val kotlinNativeHome: String
|
||||
|
||||
companion object {
|
||||
const val NO_KOTLIN_NATIVE_HOME = ""
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.SourceDirectorySet
|
||||
import org.gradle.api.logging.Logging
|
||||
import org.gradle.api.provider.Property
|
||||
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel.Companion.NO_KOTLIN_NATIVE_HOME
|
||||
import org.jetbrains.plugins.gradle.model.*
|
||||
import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder
|
||||
import org.jetbrains.plugins.gradle.tooling.ModelBuilderService
|
||||
@@ -50,7 +51,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
|
||||
computeSourceSetsDeferredInfo(sourceSets, targets)
|
||||
val coroutinesState = getCoroutinesState(project)
|
||||
reportUnresolvedDependencies(targets)
|
||||
return KotlinMPPGradleModelImpl(sourceSetMap, targets, ExtraFeaturesImpl(coroutinesState))
|
||||
val kotlinNativeHome = KotlinNativeHomeEvaluator.getKotlinNativeHome(project) ?: NO_KOTLIN_NATIVE_HOME
|
||||
return KotlinMPPGradleModelImpl(sourceSetMap, targets, ExtraFeaturesImpl(coroutinesState), kotlinNativeHome)
|
||||
}
|
||||
|
||||
private fun reportUnresolvedDependencies(targets: Collection<KotlinTarget>) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.plugins.gradle.tooling.util.DependencyResolverImpl
|
||||
import org.jetbrains.plugins.gradle.tooling.util.SourceSetCachedFinder
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel.Companion.NO_KOTLIN_NATIVE_HOME
|
||||
|
||||
class KotlinMPPGradleModelBuilder : ModelBuilderService {
|
||||
override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder {
|
||||
@@ -50,7 +51,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
|
||||
computeSourceSetsDeferredInfo(sourceSets, targets)
|
||||
val coroutinesState = getCoroutinesState(project)
|
||||
reportUnresolvedDependencies(targets)
|
||||
return KotlinMPPGradleModelImpl(sourceSetMap, targets, ExtraFeaturesImpl(coroutinesState))
|
||||
return KotlinMPPGradleModelImpl(sourceSetMap, targets, ExtraFeaturesImpl(coroutinesState), NO_KOTLIN_NATIVE_HOME)
|
||||
}
|
||||
|
||||
private fun reportUnresolvedDependencies(targets: Collection<KotlinTarget>) {
|
||||
|
||||
@@ -88,5 +88,6 @@ class ExtraFeaturesImpl(
|
||||
class KotlinMPPGradleModelImpl(
|
||||
override val sourceSets: Map<String, KotlinSourceSet>,
|
||||
override val targets: Collection<KotlinTarget>,
|
||||
override val extraFeatures: ExtraFeatures
|
||||
override val extraFeatures: ExtraFeatures,
|
||||
override val kotlinNativeHome: String
|
||||
) : KotlinMPPGradleModel
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.gradle
|
||||
|
||||
import org.gradle.api.Project
|
||||
|
||||
// KT-29613, KT-29783
|
||||
internal object KotlinNativeHomeEvaluator {
|
||||
private const val KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY = "konanHome"
|
||||
|
||||
private const val FALLBACK_ACCESSOR_CLASS = "org.jetbrains.kotlin.compilerRunner.KotlinNativeToolRunnerKt"
|
||||
private const val FALLBACK_ACCESSOR_METHOD = "getKonanHome"
|
||||
|
||||
internal fun getKotlinNativeHome(project: Project): String? =
|
||||
getKotlinNativeHomePrimary(project) ?: getKotlinNativeHomeFallback(project)
|
||||
|
||||
// Read Kotlin/Native home from the predefined property in Gradle plugin.
|
||||
// Should work for Gradle plugin with version >= 1.3.20.
|
||||
private fun getKotlinNativeHomePrimary(project: Project) = project.findProperty(KOTLIN_NATIVE_HOME_PRIVATE_PROPERTY) as String?
|
||||
|
||||
// Evaluate Kotlin/Native home using reflection by internal val declared in Gradle plugin.
|
||||
// This should work for Gradle plugin with version < 1.3.20.
|
||||
private fun getKotlinNativeHomeFallback(project: Project): String? {
|
||||
val kotlinExtensionClassLoader = project.extensions.findByName("kotlin")?.javaClass?.classLoader ?: return null
|
||||
val accessorClass = try {
|
||||
Class.forName(FALLBACK_ACCESSOR_CLASS, true, kotlinExtensionClassLoader)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
return null
|
||||
}
|
||||
val accessorMethod = accessorClass.getMethodOrNull(FALLBACK_ACCESSOR_METHOD, Project::class.java) ?: return null
|
||||
return accessorMethod.invoke(null, project) as String
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=stdlib
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=linux
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=posix
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=zlib
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=Foundation
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=UIKit
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=objc
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=iconv
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=opengl32
|
||||
compiler_version=1.0
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
unique_name=windows
|
||||
compiler_version=1.0
|
||||
@@ -0,0 +1,48 @@
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.3.20"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
macosX64()
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-common"))
|
||||
}
|
||||
}
|
||||
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
|
||||
jvm().compilations["main"].defaultSourceSet {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
}
|
||||
|
||||
jvm().compilations["test"].defaultSourceSet {
|
||||
dependencies {
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
|
||||
js().compilations["main"].defaultSourceSet {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-js"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.native.home=../kotlin-native-data-dir/kotlin-native-PLATFORM-VERSION
|
||||
@@ -0,0 +1,48 @@
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.3.21"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
macosX64()
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-common"))
|
||||
}
|
||||
}
|
||||
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
|
||||
jvm().compilations["main"].defaultSourceSet {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
}
|
||||
|
||||
jvm().compilations["test"].defaultSourceSet {
|
||||
dependencies {
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
|
||||
js().compilations["main"].defaultSourceSet {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-js"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.native.home=../kotlin-native-data-dir/kotlin-native-PLATFORM-VERSION
|
||||
+7
-3
@@ -18,16 +18,20 @@ const val KDEFINITIONS_FILE_EXTENSION = "def"
|
||||
|
||||
const val KLIB_MODULE_METADATA_FILE_NAME = "module"
|
||||
|
||||
const val KLIB_MANIFEST_FILE_NAME = "manifest"
|
||||
|
||||
const val KONAN_STDLIB_NAME = "stdlib"
|
||||
|
||||
const val KLIB_DIR_NAME = "klib"
|
||||
|
||||
val KONAN_COMMON_LIBS_PATH: Path
|
||||
get() = Paths.get("klib", "common")
|
||||
get() = Paths.get(KLIB_DIR_NAME, "common")
|
||||
|
||||
val KONAN_ALL_PLATFORM_LIBS_PATH: Path
|
||||
get() = Paths.get("klib", "platform")
|
||||
get() = Paths.get(KLIB_DIR_NAME, "platform")
|
||||
|
||||
fun konanCommonLibraryPath(libraryName: String): Path = KONAN_COMMON_LIBS_PATH.resolve(libraryName)
|
||||
|
||||
fun konanSpecificPlatformLibrariesPath(platform: String): Path = KONAN_ALL_PLATFORM_LIBS_PATH.resolve(platform)
|
||||
|
||||
fun konanPlatformLibraryPath(platform: String, libraryName: String): Path = konanSpecificPlatformLibrariesPath(platform).resolve(libraryName)
|
||||
fun konanPlatformLibraryPath(libraryName: String, platform: String): Path = konanSpecificPlatformLibrariesPath(platform).resolve(libraryName)
|
||||
|
||||
@@ -21,7 +21,7 @@ interface KonanLibraryLayout {
|
||||
// This is a default implementation. Can't make it an assignment.
|
||||
val target: KonanTarget? get() = null
|
||||
|
||||
val manifestFile get() = File(libDir, "manifest")
|
||||
val manifestFile get() = File(libDir, KLIB_MANIFEST_FILE_NAME)
|
||||
val resourcesDir get() = File(libDir, "resources")
|
||||
|
||||
val targetsDir get() = File(libDir, "targets")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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 java.nio.file.Path
|
||||
|
||||
data class LiteKonanLibrary(
|
||||
val path: Path,
|
||||
val name: String,
|
||||
val platform: String?
|
||||
)
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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_UNIQUE_NAME
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
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: Path): LiteKonanLibrary? {
|
||||
// check whether it under Kotlin/Native root
|
||||
if (!isUnderKonanRoot(libraryPath))
|
||||
return null
|
||||
|
||||
val manifestFile = libraryPath.resolve(KLIB_MANIFEST_FILE_NAME)
|
||||
if (!Files.isRegularFile(manifestFile))
|
||||
return null
|
||||
|
||||
val parentPath = libraryPath.parent ?: return null
|
||||
val parentName = parentPath.toFile().name
|
||||
|
||||
val platform = when (parentName) {
|
||||
"common" -> null
|
||||
else -> {
|
||||
val grandParentName = parentPath.parent?.toFile()?.name ?: return null
|
||||
when (grandParentName) {
|
||||
"platform" -> parentName
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val manifestProperties = Properties().apply {
|
||||
try {
|
||||
Files.newInputStream(manifestFile).use { load(it) }
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val name = manifestProperties[KLIB_PROPERTY_UNIQUE_NAME]?.toString() ?: return null
|
||||
|
||||
return LiteKonanLibrary(libraryPath, name, platform)
|
||||
}
|
||||
|
||||
private fun isUnderKonanRoot(libraryPath: Path): Boolean {
|
||||
if (konanHomeDir != null && libraryPath.startsWith(konanHomeDir))
|
||||
return true
|
||||
|
||||
return libraryPath.startsWith(konanDataDir)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user