[Gradle] Migrate Xcode/CocoaPods warnings to the new diagnostics infra + elevate deprecation of useLibraries

^KT-59313 Verification Pending
^KT-58519 Verification Pending
This commit is contained in:
Artem Daugel-Dauge
2023-06-20 17:22:20 +02:00
committed by Space Team
parent 67c19b00e0
commit 6861591840
15 changed files with 208 additions and 132 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.native
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.condition.OS
@@ -27,7 +28,7 @@ class AppleFrameworkNonMacIT : KGPBaseTest() {
) )
build("podImport", buildOptions = buildOptions) { build("podImport", buildOptions = buildOptions) {
assertOutputContains("Kotlin Cocoapods Plugin is fully supported on mac machines only. Gradle tasks that can not run on non-mac hosts will be skipped.") assertHasDiagnostic(CocoapodsPluginDiagnostics.UnsupportedOs)
} }
} }
} }
@@ -10,7 +10,6 @@ import io.ktor.client.*
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.util.*
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -21,6 +20,7 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SETUP_BUILD_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SETUP_BUILD_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.assertProcessRunResult import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
import org.jetbrains.kotlin.gradle.util.capitalize import org.jetbrains.kotlin.gradle.util.capitalize
@@ -435,21 +435,11 @@ class CocoaPodsGitIT : KGPBaseTest() {
@DisplayName("Checking useLibraries mode") @DisplayName("Checking useLibraries mode")
@GradleTest @GradleTest
fun testUseLibrariesMode(gradleVersion: GradleVersion) { fun testUseLibrariesMode(gradleVersion: GradleVersion) {
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { nativeProjectWithCocoapodsAndIosAppPodFile(projectName = "native-cocoapods-template-groovy", gradleVersion = gradleVersion) {
buildGradleKts.addCocoapodsBlock("useLibraries()") buildGradle.addCocoapodsBlock("useLibraries()".trimIndent())
buildGradleKts.addPod("AFNetworking", configuration = "headers = \"AFNetworking/AFNetworking.h\"") buildGradle.addPod("AFNetworking", configuration = "headers = \"AFNetworking/AFNetworking.h\"")
testImport()
}
}
@DisplayName("Checking that useLibraries mode warns when pod is added without headers specified")
@GradleTest
fun testUseLibrariesModeWarnsWhenPodIsAddedWithoutHeadersSpecified(gradleVersion: GradleVersion) {
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) {
buildGradleKts.addCocoapodsBlock("useLibraries()")
buildGradleKts.addPod("AFNetworking")
testImport { testImport {
assertOutputContains("w: Pod 'AFNetworking' should have 'headers' property specified when using 'useLibraries()'") assertHasDiagnostic(CocoapodsPluginDiagnostics.UseLibrariesUsed)
} }
} }
} }
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.DUMMY_FRAMEWORK_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.DUMMY_FRAMEWORK_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.assertProcessRunResult import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
import org.jetbrains.kotlin.gradle.util.replaceText import org.jetbrains.kotlin.gradle.util.replaceText
@@ -85,19 +86,6 @@ class CocoaPodsIT : KGPBaseTest() {
} }
} }
@DisplayName("Checking the warning about using deprecated podspec path")
@GradleTest
fun warnIfDeprecatedPodspecPathIsUsed(gradleVersion: GradleVersion) {
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsSingleKtPod, gradleVersion) {
build(":kotlin-library:tasks") {
assertOutputContains(
listOf("Deprecated DSL found on ${projectPath.toRealPath().absolutePathString()}", "kotlin-library", "build.gradle.kts")
.joinToString(separator = File.separator)
)
}
}
}
@DisplayName("Build with error if project version is not specified for cocoapods") @DisplayName("Build with error if project version is not specified for cocoapods")
@GradleTest @GradleTest
fun errorIfVersionIsNotSpecified(gradleVersion: GradleVersion) { fun errorIfVersionIsNotSpecified(gradleVersion: GradleVersion) {
@@ -691,7 +679,7 @@ class CocoaPodsIT : KGPBaseTest() {
""".trimIndent() """.trimIndent()
) )
buildWithCocoapodsWrapper(":linkPodDebugFrameworkIOS") { buildWithCocoapodsWrapper(":linkPodDebugFrameworkIOS") {
assertOutputContains("Dependency on 'AFNetworking' with option 'linkOnly=true' is unused for building static frameworks") assertHasDiagnostic(CocoapodsPluginDiagnostics.LinkOnlyUsedWithStaticFramework)
} }
} }
} }
@@ -704,9 +692,9 @@ class CocoaPodsIT : KGPBaseTest() {
} }
} }
@DisplayName("Configuration fails when trying to depend on non-declared pod") @DisplayName("Error reported when trying to depend on non-declared pod")
@GradleTest @GradleTest
fun testConfigurationFailsWhenTryingToDependOnNonDeclaredPod(gradleVersion: GradleVersion) { fun testErrorReportedWhenTryingToDependOnNonDeclaredPod(gradleVersion: GradleVersion) {
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsDependantPodsProjectName, gradleVersion) { nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsDependantPodsProjectName, gradleVersion) {
buildGradleKts.addCocoapodsBlock( buildGradleKts.addCocoapodsBlock(
""" """
@@ -720,15 +708,15 @@ class CocoaPodsIT : KGPBaseTest() {
) )
) )
buildAndFail(":help", buildOptions = buildOptions) { build(":help", buildOptions = buildOptions) {
assertOutputContains("Couldn't find declaration of pod 'JBNonExistent' (interop-binding dependency of pod 'Foo')") assertOutputContains("Couldn't find declaration of pod 'JBNonExistent' (interop-binding dependency of pod 'Foo')")
} }
} }
} }
@DisplayName("Configuration fails when dependant pods are in the wrong order") @DisplayName("Error reported when dependant pods are in the wrong order")
@GradleTest @GradleTest
fun testConfigurationFailsWhenDependantPodsAreInTheWrongOrder(gradleVersion: GradleVersion) { fun testErrorReportedWhenDependantPodsAreInTheWrongOrder(gradleVersion: GradleVersion) {
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsDependantPodsProjectName, gradleVersion) { nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsDependantPodsProjectName, gradleVersion) {
buildGradleKts.addCocoapodsBlock( buildGradleKts.addCocoapodsBlock(
""" """
@@ -743,15 +731,15 @@ class CocoaPodsIT : KGPBaseTest() {
) )
) )
buildAndFail(":help", buildOptions = buildOptions) { build(":help", buildOptions = buildOptions) {
assertOutputContains("Couldn't find declaration of pod 'Bar' (interop-binding dependency of pod 'Foo')") assertHasDiagnostic(CocoapodsPluginDiagnostics.InteropBindingUnknownDependency)
} }
} }
} }
@DisplayName("Configuration fails when pod depends on itself") @DisplayName("Error reported when pod depends on itself")
@GradleTest @GradleTest
fun testConfigurationFailsWhenPodDependsOnItself(gradleVersion: GradleVersion) { fun testErrorReportedWhenPodDependsOnItself(gradleVersion: GradleVersion) {
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsDependantPodsProjectName, gradleVersion) { nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsDependantPodsProjectName, gradleVersion) {
buildGradleKts.addCocoapodsBlock( buildGradleKts.addCocoapodsBlock(
""" """
@@ -765,8 +753,8 @@ class CocoaPodsIT : KGPBaseTest() {
) )
) )
buildAndFail(":help", buildOptions = buildOptions) { build(":help", buildOptions = buildOptions) {
assertOutputContains("Pod 'Foo' has an interop-binding dependency on itself") assertHasDiagnostic(CocoapodsPluginDiagnostics.InteropBindingSelfDependency)
} }
} }
} }
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.native package org.jetbrains.kotlin.gradle.native
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.replaceFirst import org.jetbrains.kotlin.gradle.util.replaceFirst
import org.jetbrains.kotlin.gradle.util.replaceText import org.jetbrains.kotlin.gradle.util.replaceText
@@ -69,7 +70,7 @@ class XCFrameworkIT : KGPBaseTest() {
assertTasksExecuted(":shared:assembleOtherDebugXCFramework") assertTasksExecuted(":shared:assembleOtherDebugXCFramework")
assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/other.xcframework") assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/other.xcframework")
assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/other.xcframework/ios-arm64/dSYMs/shared.framework.dSYM") assertDirectoryInProjectExists("shared/build/XCFrameworks/debug/other.xcframework/ios-arm64/dSYMs/shared.framework.dSYM")
assertOutputContains("Name of XCFramework 'other' differs from inner frameworks name 'shared'! Framework renaming is not supported yet") assertHasDiagnostic(KotlinToolingDiagnostics.XCFrameworkDifferentInnerFrameworksName)
} }
} }
} }
@@ -20,8 +20,7 @@ kotlin {
cocoapods { cocoapods {
summary = "CocoaPods test library" summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin" homepage = "https://github.com/JetBrains/kotlin"
// test warnIfDeprecatedPodspecPathIsUsed depends on deprecated api usage pod("pod_dependency", "1.0", project.file("../pod_dependency"))
pod("pod_dependency", "1.0", project.file("../pod_dependency/pod_dependency.podspec"))
pod("subspec_dependency/Core", "1.0", project.file("../subspec_dependency")) pod("subspec_dependency/Core", "1.0", project.file("../subspec_dependency"))
podfile = project.file("../ios-app/Podfile") podfile = project.file("../ios-app/Podfile")
@@ -506,6 +506,20 @@ object KotlinToolingDiagnostics {
) )
} }
object XCFrameworkDifferentInnerFrameworksName : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(xcFramework: String, innerFrameworks: String) = build(
"Name of XCFramework '$xcFramework' differs from inner frameworks name '$innerFrameworks'! Framework renaming is not supported yet"
)
}
object UnknownAppleFrameworkBuildType : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(envConfiguration: String) = build(
"""
Unable to detect Kotlin framework build type for CONFIGURATION=$envConfiguration automatically.
Specify 'KOTLIN_FRAMEWORK_BUILD_TYPE' to 'debug' or 'release'
""".trimIndent()
)
}
} }
private fun String.indentLines(nSpaces: Int = 4, skipFirstLine: Boolean = true): String { private fun String.indentLines(nSpaces: Int = 4, skipFirstLine: Boolean = true): String {
@@ -14,6 +14,8 @@ import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost
@@ -106,10 +108,7 @@ private fun Project.registerAssembleAppleFrameworkTask(framework: Framework): Ta
if (envBuildType == null || envTargets.isEmpty() || envFrameworkSearchDir == null) { if (envBuildType == null || envTargets.isEmpty() || envFrameworkSearchDir == null) {
val envConfiguration = System.getenv("CONFIGURATION") val envConfiguration = System.getenv("CONFIGURATION")
if (envTargets.isNotEmpty() && envConfiguration != null) { if (envTargets.isNotEmpty() && envConfiguration != null) {
logger.warn( project.reportDiagnostic(KotlinToolingDiagnostics.UnknownAppleFrameworkBuildType(envConfiguration))
"Unable to detect Kotlin framework build type for CONFIGURATION=$envConfiguration automatically. " +
"Specify 'KOTLIN_FRAMEWORK_BUILD_TYPE' to 'debug' or 'release'"
)
} else { } else {
logger.debug("Not registering $frameworkTaskName, since not called from Xcode") logger.debug("Not registering $frameworkTaskName, since not called from Xcode")
} }
@@ -8,12 +8,13 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.apple
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.ProjectLayout import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.UsesKotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.tasks.*
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@Suppress("unused") // used through .values() call
internal enum class AppleTarget( internal enum class AppleTarget(
val targetName: String, val targetName: String,
val targets: List<KonanTarget> val targets: List<KonanTarget>
@@ -147,7 +149,7 @@ abstract class XCFrameworkTask
internal constructor( internal constructor(
private val execOperations: ExecOperations, private val execOperations: ExecOperations,
private val projectLayout: ProjectLayout, private val projectLayout: ProjectLayout,
) : DefaultTask() { ) : DefaultTask(), UsesKotlinToolingDiagnostics {
init { init {
onlyIf { HostManager.hostIsMac } onlyIf { HostManager.hostIsMac }
} }
@@ -238,9 +240,10 @@ internal constructor(
frameworks.joinToString("\n") { it.file.path }) frameworks.joinToString("\n") { it.file.path })
} }
if (name != xcfName) { if (name != xcfName) {
logger.warn( toolingDiagnosticsCollector.get().report(this, KotlinToolingDiagnostics.XCFrameworkDifferentInnerFrameworksName(
"Name of XCFramework '$rawXcfName' differs from inner frameworks name '$name'! Framework renaming is not supported yet" xcFramework = rawXcfName,
) innerFrameworks = name,
))
} }
} }
@@ -14,8 +14,10 @@ import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX
import org.jetbrains.kotlin.gradle.plugin.diagnostics.kotlinToolingDiagnosticsCollector
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.jetbrains.kotlin.gradle.utils.getFile import org.jetbrains.kotlin.gradle.utils.getFile
import java.io.File import java.io.File
import java.net.URI import java.net.URI
@@ -49,18 +51,12 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
/** /**
* Setup plugin to generate synthetic xcodeproj compatible with static libraries * Setup plugin to generate synthetic xcodeproj compatible with static libraries
*
* This option is not supported and scheduled to be removed. If you are using this please
* file an issue with your case to [https://kotl.in/issue](https://kotl.in/issue)
*/ */
@Suppress("DeprecatedCallableAddReplaceWith") @Deprecated("'useLibraries' mode is removed", level = DeprecationLevel.ERROR)
@Deprecated("This option is not supported and scheduled to be removed")
fun useLibraries() { fun useLibraries() {
useLibraries = true project.kotlinToolingDiagnosticsCollector.report(project, CocoapodsPluginDiagnostics.UseLibrariesUsed())
} }
internal var useLibraries: Boolean = false
/** /**
* Configure name of the pod built from this project. * Configure name of the pod built from this project.
*/ */
@@ -169,31 +165,6 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
// Empty string will lead to an attempt to create two podDownload tasks. // Empty string will lead to an attempt to create two podDownload tasks.
// One is original podDownload and second is podDownload + pod.name // One is original podDownload and second is podDownload + pod.name
require(name.isNotEmpty()) { "Please provide not empty pod name to avoid ambiguity" } require(name.isNotEmpty()) { "Please provide not empty pod name to avoid ambiguity" }
var podSource = path
if (path != null && !path.isDirectory) {
val pattern = "\\W*pod(.*\"${name}\".*)".toRegex()
val buildScript = project.buildFile
val lines = buildScript.readLines()
val lineNumber = lines.indexOfFirst { pattern.matches(it) }
val warnMessage = if (lineNumber != -1) run {
val lineContent = lines[lineNumber].trimIndent()
val newContent = lineContent.replace(path.name, "")
"""
|Deprecated DSL found on ${buildScript.absolutePath}${File.pathSeparator}${lineNumber + 1}:
|Found: "$lineContent"
|Expected: "$newContent"
|Please, change the path to avoid this warning.
|
""".trimMargin()
} else
"""
|Deprecated DSL is used for pod "$name".
|Please, change its path from ${path.path} to ${path.parentFile.path}
|
""".trimMargin()
project.logger.warn(warnMessage)
podSource = path.parentFile
}
addToPods( addToPods(
project.objects.newInstance( project.objects.newInstance(
CocoapodsDependency::class.java, CocoapodsDependency::class.java,
@@ -202,7 +173,7 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
).apply { ).apply {
this.headers = headers this.headers = headers
this.version = version this.version = version
source = podSource?.let { Path(it) } source = path?.let(::Path)
this.linkOnly = linkOnly this.linkOnly = linkOnly
} }
) )
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2023 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.gradle.targets.native.cocoapods
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.ERROR
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.WARNING
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticFactory
@InternalKotlinGradlePluginApi // used in integration tests
object CocoapodsPluginDiagnostics {
object DeprecatedPropertiesUsed : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(usedDeprecatedProperties: List<String>) = build(
"""
|Properties
| ${usedDeprecatedProperties.joinToString(separator = "\n| ")}
|are not supported and will be ignored since CocoaPods plugin generates all required properties automatically.
""".trimMargin()
)
}
object LinkOnlyUsedWithStaticFramework : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(podName: String) = build(
"""
Dependency on '$podName' with option 'linkOnly=true' is unused for building static frameworks.
When using static linkage you will need to provide all dependencies for linking the framework into a final application.
""".trimIndent()
)
}
object UnsupportedOs : ToolingDiagnosticFactory(WARNING) {
operator fun invoke() = build(
"""
Kotlin CocoaPods Plugin is fully supported on MacOS machines only. Gradle tasks that can not run on non-mac hosts will be skipped.
""".trimIndent()
)
}
object UseLibrariesUsed : ToolingDiagnosticFactory(ERROR) {
operator fun invoke() = build("'useLibraries' mode is removed")
}
object InteropBindingSelfDependency : ToolingDiagnosticFactory(ERROR) {
operator fun invoke(podName: String) = build("Pod '$podName' has an interop-binding dependency on itself")
}
object InteropBindingUnknownDependency : ToolingDiagnosticFactory(ERROR) {
operator fun invoke(podName: String, dependencyName: String) = build("Couldn't find declaration of pod '$dependencyName' (interop-binding dependency of pod '${podName}')")
}
}
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.addExtension import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnosticOncePerProject
import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api
import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
@@ -30,6 +32,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleSdk import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleSdk
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleTarget import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleTarget
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.KotlinArtifactsPodspecExtension import org.jetbrains.kotlin.gradle.targets.native.cocoapods.KotlinArtifactsPodspecExtension
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.kotlinArtifactsPodspecExtension import org.jetbrains.kotlin.gradle.targets.native.cocoapods.kotlinArtifactsPodspecExtension
import org.jetbrains.kotlin.gradle.targets.native.tasks.* import org.jetbrains.kotlin.gradle.targets.native.tasks.*
@@ -243,22 +246,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val platforms = project.findProperty(PLATFORM_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() } val platforms = project.findProperty(PLATFORM_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() }
val archs = project.findProperty(ARCHS_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() } val archs = project.findProperty(ARCHS_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() }
if (
project.findProperty(CFLAGS_PROPERTY) != null ||
project.findProperty(FRAMEWORK_PATHS_PROPERTY) != null ||
project.findProperty(HEADER_PATHS_PROPERTY) != null
) {
logger.warn(
"""
Properties
kotlin.native.cocoapods.cflags
kotlin.native.cocoapods.paths.frameworks
kotlin.native.cocoapods.paths.headers
are not supported and will be ignored since Cocoapods plugin generates all required properties automatically.
""".trimIndent()
)
}
if (platforms == null || archs == null) { if (platforms == null || archs == null) {
check(project.findProperty(TARGET_PROPERTY) == null) { check(project.findProperty(TARGET_PROPERTY) == null) {
""" """
@@ -298,6 +285,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
} }
} }
private fun reportDeprecatedPropertiesUsage(project: Project) {
listOf(CFLAGS_PROPERTY, FRAMEWORK_PATHS_PROPERTY, HEADER_PATHS_PROPERTY)
.filter { project.findProperty(it) != null }
.takeIf { it.isNotEmpty() }
?.let {
project.reportDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed(it))
}
}
private fun createInterops( private fun createInterops(
project: Project, project: Project,
kotlinExtension: KotlinMultiplatformExtension, kotlinExtension: KotlinMultiplatformExtension,
@@ -315,7 +311,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
lowerCamelCaseName("generateDef", pod.moduleName).asValidTaskName() lowerCamelCaseName("generateDef", pod.moduleName).asValidTaskName()
) { ) {
it.pod.set(pod) it.pod.set(pod)
it.useLibraries.set(cocoapodsExtension.useLibraries)
it.description = "Generates a def file for CocoaPods dependencies with module ${pod.moduleName}" it.description = "Generates a def file for CocoaPods dependencies with module ${pod.moduleName}"
// This task is an implementation detail so we don't add it in any group // This task is an implementation detail so we don't add it in any group
// to avoid showing it in the `tasks` output. // to avoid showing it in the `tasks` output.
@@ -374,11 +369,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
dependencyName: String, dependencyName: String,
) { ) {
if (pod.name == dependencyName) { if (pod.name == dependencyName) {
error("Pod '${pod.name}' has an interop-binding dependency on itself") project.reportDiagnosticOncePerProject(CocoapodsPluginDiagnostics.InteropBindingSelfDependency(pod.name))
return
} }
val dependencyPod = cocoapodsExtension.pods.findByName(dependencyName) val dependencyPod = cocoapodsExtension.pods.findByName(dependencyName)
?: error("Couldn't find declaration of pod '$dependencyName' (interop-binding dependency of pod '${pod.name}')") ?: run {
project.reportDiagnosticOncePerProject(CocoapodsPluginDiagnostics.InteropBindingUnknownDependency(pod.name, dependencyName))
return
}
val dependencyTaskName = cinterops.getByName(dependencyPod.moduleName).interopProcessingTaskName val dependencyTaskName = cinterops.getByName(dependencyPod.moduleName).interopProcessingTaskName
val dependencyTask = project.tasks.named<CInteropProcess>(dependencyTaskName) val dependencyTask = project.tasks.named<CInteropProcess>(dependencyTaskName)
@@ -520,7 +519,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
task.podspec.set(podspecTaskProvider.map { it.outputFile }) task.podspec.set(podspecTaskProvider.map { it.outputFile })
task.podName.set(project.provider { cocoapodsExtension.name }) task.podName.set(project.provider { cocoapodsExtension.name })
task.useLibraries.set(project.provider { cocoapodsExtension.useLibraries })
task.specRepos.set(project.provider { cocoapodsExtension.specRepos }) task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
task.family.set(family) task.family.set(family)
task.platformSettings.set(platformSettings) task.platformSettings.set(platformSettings)
@@ -799,10 +797,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project.runProjectConfigurationHealthCheckWhenEvaluated { project.runProjectConfigurationHealthCheckWhenEvaluated {
cocoapodsExtension.pods.all { pod -> cocoapodsExtension.pods.all { pod ->
if (pod.linkOnly && cocoapodsExtension.podFrameworkIsStatic.get()) { if (pod.linkOnly && cocoapodsExtension.podFrameworkIsStatic.get()) {
logger.warn(""" project.reportDiagnostic(CocoapodsPluginDiagnostics.LinkOnlyUsedWithStaticFramework(pod.name))
Dependency on '${pod.name}' with option 'linkOnly=true' is unused for building static frameworks.
When using static linkage you will need to provide all dependencies for linking the framework into a final application.
""".trimIndent())
} }
} }
} }
@@ -854,12 +849,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
registerPodPublishTasks(project, cocoapodsExtension) registerPodPublishTasks(project, cocoapodsExtension)
if (!HostManager.hostIsMac) { if (!HostManager.hostIsMac) {
logger.warn( reportDiagnostic(CocoapodsPluginDiagnostics.UnsupportedOs())
"""
Kotlin Cocoapods Plugin is fully supported on mac machines only. Gradle tasks that can not run on non-mac hosts will be skipped.
""".trimIndent()
)
} }
reportDeprecatedPropertiesUsage(project)
createInterops(project, kotlinExtension, cocoapodsExtension) createInterops(project, kotlinExtension, cocoapodsExtension)
configureLinkingOptions(project, cocoapodsExtension) configureLinkingOptions(project, cocoapodsExtension)
checkLinkOnlyNotUsedWithStaticFramework(project, cocoapodsExtension) checkLinkOnlyNotUsedWithStaticFramework(project, cocoapodsExtension)
@@ -11,7 +11,6 @@ import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
@@ -31,9 +30,6 @@ abstract class DefFileTask @Inject constructor(projectLayout: ProjectLayout) : D
@get:Nested @get:Nested
abstract val pod: Property<CocoapodsDependency> abstract val pod: Property<CocoapodsDependency>
@get:Input
abstract val useLibraries: Property<Boolean>
@get:OutputFile @get:OutputFile
val defFile: Provider<RegularFile> = projectLayout.cocoapodsBuildDirs.defs.map { it.file("${pod.get().moduleName}.def") } val defFile: Provider<RegularFile> = projectLayout.cocoapodsBuildDirs.defs.map { it.file("${pod.get().moduleName}.def") }
@@ -51,12 +47,6 @@ abstract class DefFileTask @Inject constructor(projectLayout: ProjectLayout) : D
with(pod.get()) { with(pod.get()) {
when { when {
headers != null -> appendLine("headers = $headers") headers != null -> appendLine("headers = $headers")
useLibraries.get() -> logger.warn(
"""
w: Pod '$moduleName' should have 'headers' property specified when using 'useLibraries()'.
Otherwise code from this pod won't be accessible from Kotlin.
""".trimIndent()
)
else -> { else -> {
appendLine("modules = $moduleName") appendLine("modules = $moduleName")
@@ -39,9 +39,6 @@ abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : Co
@get:Input @get:Input
internal abstract val podName: Property<String> internal abstract val podName: Property<String>
@get:Input
internal abstract val useLibraries: Property<Boolean>
@get:Input @get:Input
internal abstract val family: Property<Family> internal abstract val family: Property<Family>
@@ -80,9 +77,8 @@ abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : Co
} }
appendLine("target '${family.platformLiteral}' do") appendLine("target '${family.platformLiteral}' do")
if (useLibraries.get().not()) { appendLine("\tuse_frameworks!")
appendLine("\tuse_frameworks!")
}
val settings = platformSettings.get() val settings = platformSettings.get()
val deploymentTarget = settings.deploymentTarget val deploymentTarget = settings.deploymentTarget
if (deploymentTarget != null) { if (deploymentTarget != null) {
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2023 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.CFLAGS_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.FRAMEWORK_PATHS_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.HEADER_PATHS_PROPERTY
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.gradle.util.assertContainsDiagnostic
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assume
import org.junit.Test
class CocoapodsUnitTests {
@Test
fun `warning is reported on non-mac machines`() {
Assume.assumeTrue(!HostManager.hostIsMac)
buildProjectWithCocoapods {
assertContainsDiagnostic(CocoapodsPluginDiagnostics.UnsupportedOs)
}
}
@Test
fun `warning is reported when using deprecated properties`() {
buildProjectWithMPP {
propertiesExtension.set(CFLAGS_PROPERTY, "cflags value")
applyCocoapodsPlugin()
assertContainsDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed)
}
buildProjectWithMPP {
propertiesExtension.set(HEADER_PATHS_PROPERTY, "header paths value")
applyCocoapodsPlugin()
assertContainsDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed)
}
buildProjectWithMPP {
propertiesExtension.set(FRAMEWORK_PATHS_PROPERTY, "framework paths value")
applyCocoapodsPlugin()
assertContainsDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed)
}
buildProjectWithMPP {
propertiesExtension.set(FRAMEWORK_PATHS_PROPERTY, "framework paths value")
propertiesExtension.set(HEADER_PATHS_PROPERTY, "header paths value")
applyCocoapodsPlugin()
assertContainsDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed(listOf(FRAMEWORK_PATHS_PROPERTY, HEADER_PATHS_PROPERTY)))
}
}
}
@@ -19,6 +19,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_COMPATIBILITY_METADATA_VARIANT import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_COMPATIBILITY_METADATA_VARIANT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_INTRANSITIVE_METADATA_CONFIGURATION import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_INTRANSITIVE_METADATA_CONFIGURATION
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension
import org.jetbrains.kotlin.gradle.plugin.getExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.unitTests.kpm.applyKpmPlugin import org.jetbrains.kotlin.gradle.unitTests.kpm.applyKpmPlugin
@@ -50,6 +52,11 @@ fun buildProjectWithJvm(projectBuilder: ProjectBuilder.() -> Unit = {}, code: Pr
code() code()
} }
fun buildProjectWithCocoapods(projectBuilder: ProjectBuilder.() -> Unit = {}, code: Project.() -> Unit = {}) = buildProject(projectBuilder) {
project.applyCocoapodsPlugin()
code()
}
fun Project.applyKotlinJvmPlugin() { fun Project.applyKotlinJvmPlugin() {
project.plugins.apply(KotlinPluginWrapper::class.java) project.plugins.apply(KotlinPluginWrapper::class.java)
} }
@@ -87,6 +94,14 @@ fun Project.applyMultiplatformPlugin(): KotlinMultiplatformExtension {
return extensions.getByName("kotlin") as KotlinMultiplatformExtension return extensions.getByName("kotlin") as KotlinMultiplatformExtension
} }
fun Project.applyCocoapodsPlugin(): CocoapodsExtension {
val kotlinExtension = applyMultiplatformPlugin()
plugins.apply("org.jetbrains.kotlin.native.cocoapods")
return kotlinExtension.getExtension<CocoapodsExtension>("cocoapods")!!.also {
it.version = "1.0"
}
}
val Project.propertiesExtension: ExtraPropertiesExtension val Project.propertiesExtension: ExtraPropertiesExtension
get() = extensions.getByType(ExtraPropertiesExtension::class.java) get() = extensions.getByType(ExtraPropertiesExtension::class.java)