[Gradle] Configuration cache support in CocoaPods plugin and Apple frameworks tasks

^KT-54362 Verification Pending

Merge-request: KT-MR-9947
This commit is contained in:
Artem Daugel-Dauge
2023-06-15 10:37:28 +00:00
committed by Space Team
parent 457837a255
commit b1faee5ec6
26 changed files with 605 additions and 523 deletions
@@ -9,7 +9,6 @@ import org.gradle.api.logging.LogLevel
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
@@ -79,53 +78,6 @@ class ConfigurationCacheIT : AbstractConfigurationCacheIT() {
}
}
@NativeGradlePluginTests
@DisplayName("works with native tasks in complex project")
@GradleTestVersions(
minVersion = TestVersions.Gradle.G_7_4,
additionalVersions = [TestVersions.Gradle.G_7_6],
maxVersion = TestVersions.Gradle.G_8_1
)
@GradleTest
fun testNativeTasks(gradleVersion: GradleVersion) {
val expectedTasks = mutableListOf(
":lib:cinteropMyCinteropLinuxX64",
":lib:commonizeCInterop",
":lib:compileKotlinLinuxX64",
":lib:linkExecutableDebugExecutableLinuxX64",
":lib:linkSharedDebugSharedLinuxX64",
":lib:linkStaticDebugStaticLinuxX64",
":lib:linkDebugTestLinuxX64",
)
if (HostManager.hostIsMac) {
expectedTasks += listOf(
":lib:cinteropMyCinteropIosX64",
":lib:compileKotlinIosX64",
":lib:assembleMyframeDebugFrameworkIosArm64",
":lib:assembleMyfatframeDebugFatFramework",
":lib:assembleLibDebugXCFramework",
":lib:compileTestKotlinIosX64",
":lib:linkDebugTestIosX64",
":lib:transformCommonMainDependenciesMetadata",
":lib:transformCommonMainCInteropDependenciesMetadata",
":lib:linkDebugFrameworkIosArm64",
":lib:linkDebugFrameworkIosX64",
":lib:linkDebugFrameworkIosFat",
":lib:linkReleaseFrameworkIosArm64",
":lib:linkReleaseFrameworkIosX64",
":lib:linkReleaseFrameworkIosFat",
)
}
project("native-configuration-cache", gradleVersion) {
testConfigurationCacheOf(
"build",
executedTaskNames = expectedTasks,
)
}
}
@NativeGradlePluginTests
@DisplayName("works with commonizer")
@GradleTestVersions(
@@ -0,0 +1,92 @@
/*
* 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
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
@DisplayName("Configuration cache tests that can be run on MacOS")
@NativeGradlePluginTests
@OsCondition(supportedOn = [OS.LINUX, OS.MAC, OS.WINDOWS], enabledOnCI = [OS.LINUX, OS.MAC, OS.WINDOWS])
class MacosCapableConfigurationCacheIT : AbstractConfigurationCacheIT() {
@DisplayName("works with native tasks in complex project")
@GradleTestVersions(
minVersion = TestVersions.Gradle.G_7_4,
additionalVersions = [TestVersions.Gradle.G_7_6],
maxVersion = TestVersions.Gradle.G_8_1
)
@GradleTest
fun testNativeTasks(gradleVersion: GradleVersion) {
val expectedTasks = mutableListOf(
":lib:cinteropMyCinteropLinuxX64",
":lib:commonizeCInterop",
":lib:compileKotlinLinuxX64",
":lib:linkExecutableDebugExecutableLinuxX64",
":lib:linkSharedDebugSharedLinuxX64",
":lib:linkStaticDebugStaticLinuxX64",
":lib:linkDebugTestLinuxX64",
)
if (HostManager.hostIsMac) {
expectedTasks += listOf(
":lib:cinteropMyCinteropIosX64",
":lib:compileKotlinIosX64",
":lib:assembleMyframeDebugFrameworkIosArm64",
":lib:assembleMyfatframeDebugFatFramework",
":lib:assembleLibDebugXCFramework",
":lib:compileTestKotlinIosX64",
":lib:linkDebugTestIosX64",
":lib:transformCommonMainDependenciesMetadata",
":lib:transformCommonMainCInteropDependenciesMetadata",
":lib:linkDebugFrameworkIosArm64",
":lib:linkDebugFrameworkIosX64",
":lib:linkDebugFrameworkIosFat",
":lib:linkReleaseFrameworkIosArm64",
":lib:linkReleaseFrameworkIosX64",
":lib:linkReleaseFrameworkIosFat",
)
}
project("native-configuration-cache", gradleVersion) {
testConfigurationCacheOf(
"build",
executedTaskNames = expectedTasks,
)
}
}
@OptIn(EnvironmentalVariablesOverride::class)
@DisplayName("works with apple framework embedding and signing")
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@GradleTestVersions(
minVersion = TestVersions.Gradle.G_7_4,
additionalVersions = [TestVersions.Gradle.G_7_6],
maxVersion = TestVersions.Gradle.G_8_1,
)
@GradleTest
fun testAppleFrameworkTasks(gradleVersion: GradleVersion, @TempDir targetBuildDir: Path) {
project(
projectName = "sharedAppleFramework",
gradleVersion = gradleVersion,
environmentVariables = EnvironmentalVariables(
"CONFIGURATION" to "Debug",
"SDK_NAME" to "iphoneos",
"ARCHS" to "arm64",
"EXPANDED_CODE_SIGN_IDENTITY" to "-",
"TARGET_BUILD_DIR" to targetBuildDir.toString(),
"FRAMEWORKS_FOLDER_PATH" to "testFrameworksDir"
),
) {
testConfigurationCacheOf(":shared:embedAndSignAppleFrameworkForXcode")
}
}
}
@@ -12,56 +12,44 @@ import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.replaceText
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.appendText
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Tests for K/N with Apple Framework")
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_70)
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@NativeGradlePluginTests
class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Assembling AppleFrameworkForXcode tasks for IosArm64")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@GradleTest
fun shouldAssembleAppleFrameworkForXcodeForIosArm64(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
buildOptions = defaultBuildOptions,
environmentVariables = EnvironmentalVariables(
"CONFIGURATION" to "debug",
"SDK_NAME" to "iphoneos123",
"ARCHS" to "arm64",
"TARGET_BUILD_DIR" to "no use",
"FRAMEWORKS_FOLDER_PATH" to "no use"
),
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
"CONFIGURATION" to "debug",
"SDK_NAME" to "iphoneos123",
"ARCHS" to "arm64",
"TARGET_BUILD_DIR" to "no use",
"FRAMEWORKS_FOLDER_PATH" to "no use"
)
)
build(
"assembleDebugAppleFrameworkForXcodeIosArm64",
environmentVariables = environmentVariables,
) {
build("assembleDebugAppleFrameworkForXcodeIosArm64") {
assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeIosArm64")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM")
}
build(
"assembleCustomDebugAppleFrameworkForXcodeIosArm64",
environmentVariables = environmentVariables
) {
build("assembleCustomDebugAppleFrameworkForXcodeIosArm64") {
assertTasksExecuted(":shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework.dSYM")
@@ -71,21 +59,15 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Assembling fat AppleFrameworkForXcode tasks for Arm64 and X64 simulators")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@GradleTest
fun shouldAssembleAppleFrameworkForXcodeForArm64AndX64Simulators(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
buildOptions = defaultBuildOptions
) {
val environmentVariables = mapOf(
"CONFIGURATION" to "Release",
@@ -106,57 +88,42 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("MacOS framework has symlinks")
@OptIn(EnvironmentalVariablesOverride::class)
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleAndroidTest
@GradleTest
fun shouldCheckThatMacOSFrameworkHasSymlinks(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
@TempDir testBuildDir: Path,
) {
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
) {
val environmentVariables = mapOf(
"CONFIGURATION" to "debug",
"SDK_NAME" to "macosx",
"ARCHS" to "x86_64",
"EXPANDED_CODE_SIGN_IDENTITY" to "-",
"TARGET_BUILD_DIR" to projectPath.absolutePathString(),
"TARGET_BUILD_DIR" to testBuildDir.toString(),
"FRAMEWORKS_FOLDER_PATH" to "build/xcode-derived"
)
build(":shared:embedAndSignAppleFrameworkForXcode", environmentVariables = EnvironmentalVariables(environmentVariables)) {
assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeMacosX64")
assertSymlinkInProjectExists("shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers")
assertSymlinkInProjectExists("build/xcode-derived/sdk.framework/Headers")
assertSymlinkExists(testBuildDir.resolve("build/xcode-derived/sdk.framework/Headers"))
}
}
}
@DisplayName("EmbedAnsSign executes normally when signing is disabled")
@DisplayName("embedAndSign executes normally when signing is disabled")
@OptIn(EnvironmentalVariablesOverride::class)
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleAndroidTest
@GradleTest
fun testEmbedAnsSignExecutionWithoutSigning(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
) {
val environmentVariables = mapOf(
"CONFIGURATION" to "debug",
@@ -172,19 +139,12 @@ class AppleFrameworkIT : KGPBaseTest() {
}
@DisplayName("embedAndSignAppleFrameworkForXcode fail")
@GradleAndroidTest
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleTest
fun shouldFailWithExecutingEmbedAndSignAppleFrameworkForXcode(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
nativeProject("sharedAppleFramework", gradleVersion, buildJdk = jdkProvider.location) {
buildAndFail(
":shared:embedAndSignAppleFrameworkForXcode",
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion)
) {
nativeProject("sharedAppleFramework", gradleVersion) {
buildAndFail(":shared:embedAndSignAppleFrameworkForXcode") {
assertOutputContains("Please run the embedAndSignAppleFrameworkForXcode task from Xcode")
}
}
@@ -192,28 +152,21 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Registered tasks with Xcode environment for Debug IosArm64 configuration")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@GradleTest
fun shouldCheckAllRegisteredTasksWithXcodeEnvironmentForDebugIosArm64(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
@TempDir testBuildDir: Path,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = buildOptions
) {
val environmentVariables = mapOf(
"CONFIGURATION" to "Debug",
"SDK_NAME" to "iphoneos",
"ARCHS" to "arm64",
"EXPANDED_CODE_SIGN_IDENTITY" to "-",
"TARGET_BUILD_DIR" to "testBuildDir",
"TARGET_BUILD_DIR" to testBuildDir.toString(),
"FRAMEWORKS_FOLDER_PATH" to "testFrameworksDir"
)
buildAndAssertAllTasks(
@@ -236,22 +189,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("embedAndSignAppleFrameworkForXcode was registered without required Xcode environments")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleTest
fun shouldCheckEmbedAndSignAppleFrameworkForXcodeDoesNotRequireXcodeEnv(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = buildOptions
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -289,21 +233,15 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Static framework for Arm64 is built but is not embedded")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@GradleTest
fun shouldCheckThatStaticFrameworkForArm64IsBuildAndNotEmbedded(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
buildOptions = defaultBuildOptions
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -333,22 +271,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Configuration errors reported to Xcode when embedAndSign task requested")
@OptIn(EnvironmentalVariablesOverride::class)
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleAndroidTest
@GradleTest
fun shouldReportConfErrorsToXcodeWhenRequestedByEmbedAndSign(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = buildOptions
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -378,22 +307,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors reported to Xcode when embedAndSign task requested")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleTest
fun shouldReportCompilationErrorsToXcodeWhenRequestedByEmbedAndSign(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -417,22 +337,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors printed with Gradle-style when any other task requested")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleTest
fun shouldPrintCompilationErrorsWithGradleStyle(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -456,22 +367,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors printed with Xcode-style with explicit option")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleTest
fun shouldPrintCompilationErrorsWithXcodeStyle(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -499,22 +401,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors reported to Xcode when embedAndSign task requested and compiler runs in a separate process")
@OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleTest
fun shouldReportErrorsToXcodeWhenEmbedAndSignRequestedAndDisableCompilerDaemon(
gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject(
"sharedAppleFramework",
gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) {
val environmentVariables = EnvironmentalVariables(
mapOf(
@@ -585,7 +478,7 @@ class AppleFrameworkIT : KGPBaseTest() {
assertContainsVariant("mainDynamicReleaseFrameworkIos")
}
// NB: '0' is required at the end since dependency is added with custom attribute and it creates new configuration
// NB: '0' is required at the end since dependency is added with custom attribute, and it creates new configuration
build(*dependencyInsight("iosAppIosX64DebugImplementation0"), "-PmultipleFrameworks") {
assertContainsVariant("mainStaticDebugFrameworkIos")
}
@@ -14,6 +14,8 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan
import org.jetbrains.kotlin.gradle.testbase.ImportMode
import org.jetbrains.kotlin.gradle.testbase.cocoaPodsEnvironmentVariables
import org.jetbrains.kotlin.gradle.testbase.ensureCocoapodsInstalled
import org.jetbrains.kotlin.gradle.testbase.TestVersions.Gradle.G_8_1
import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.util.runProcess
import org.jetbrains.kotlin.konan.target.HostManager
@@ -25,10 +27,7 @@ import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.zip.ZipFile
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
import kotlin.test.*
class CocoaPodsIT : BaseGradleIT() {
@@ -809,6 +808,72 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
@Test
@Ignore // will be fixed in the next step
fun `test configuration cache works in a complex scenario with Gradle 8_1`() {
project = transformProjectWithPluginsDsl(templateProjectName, GradleVersionRequired.Exact(G_8_1)).apply {
preparePodfile("ios-app", ImportMode.FRAMEWORKS)
}
`test configuration cache works in a complex scenario`()
}
@Test
fun `test configuration cache works in a complex scenario`() {
project.gradleBuildScript().appendToCocoapodsBlock("""pod("Base64", version = "1.1.2")""")
val tasks = arrayOf(
":podspec",
":podImport",
":podPublishDebugXCFramework",
":podPublishReleaseXCFramework",
":syncFramework",
)
val executableTasks = listOf(
":podspec",
":podPublishDebugXCFramework",
":podPublishReleaseXCFramework",
":linkPodDebugFrameworkIOS",
)
fun build(vararg tasks: String, check: CompiledProject.() -> Unit) {
project.build(
*tasks,
"-Pkotlin.native.cocoapods.generate.wrapper=true",
"-Pkotlin.native.cocoapods.platform=iphonesimulator",
"-Pkotlin.native.cocoapods.archs=x86_64",
"-Pkotlin.native.cocoapods.configuration=Debug",
options = defaultBuildOptions().copy(configurationCache = true),
check = check,
)
}
build(*tasks) {
assertSuccessful()
assertTasksExecuted(executableTasks)
assertContains("Calculating task graph as no configuration cache is available for tasks")
assertContains("Configuration cache entry stored.")
}
build("clean") {
assertSuccessful()
}
build(*tasks) {
assertSuccessful()
assertContains("Reusing configuration cache.")
}
build(*tasks) {
assertSuccessful()
assertTasksUpToDate(executableTasks)
}
}
// test configuration phase
private class CustomHooks {
@@ -7,12 +7,6 @@ package org.jetbrains.kotlin.gradle.testbase
import org.gradle.testkit.runner.BuildResult
import org.jetbrains.kotlin.gradle.BaseGradleIT
import java.net.URI
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.exists
import kotlin.io.path.name
import kotlin.test.fail
/**
* Tests whether configuration cache for the tasks specified by [buildArguments] works on simple scenario when project is built twice non-incrementally.
@@ -312,7 +312,9 @@ open class GradleProject(
}
@JvmInline
value class EnvironmentalVariables @EnvironmentalVariablesOverride constructor(val environmentalVariables: Map<String, String> = emptyMap())
value class EnvironmentalVariables @EnvironmentalVariablesOverride constructor(val environmentalVariables: Map<String, String> = emptyMap()) {
@EnvironmentalVariablesOverride constructor(vararg environmentVariables: Pair<String, String>) : this(mapOf(*environmentVariables))
}
@RequiresOptIn("Environmental variables override may lead to interference of parallel builds and breaks Gradle tests debugging")
annotation class EnvironmentalVariablesOverride
@@ -38,9 +38,10 @@ fun TestProject.makeSnapshotTo(destinationPath: String) {
dest.resolve("run.sh").run {
writeText(
"""
#!/usr/bin/env sh
./gradlew ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} ${'$'}@
""".trimIndent()
|#!/usr/bin/env sh
|${formatEnvironmentForScript(envCommand = "export")}
|./gradlew ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} ${'$'}@
|""".trimMargin()
)
setPosixFilePermissions(
@@ -55,9 +56,10 @@ fun TestProject.makeSnapshotTo(destinationPath: String) {
dest.resolve("run.bat").run {
writeText(
"""
@rem Executing Gradle build
gradlew.bat ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} %*
""".trimIndent()
|@rem Executing Gradle build
|${formatEnvironmentForScript(envCommand = "set")}
|gradlew.bat ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} %*
|""".trimMargin()
)
}
@@ -80,6 +82,12 @@ fun TestProject.makeSnapshotTo(destinationPath: String) {
}
}
private fun TestProject.formatEnvironmentForScript(envCommand: String): String {
return environmentVariables.environmentalVariables.asSequence().joinToString(separator = "\n|") { (key, value) ->
"$envCommand $key=\"$value\""
}
}
/**
*
* Configures the JVM memory settings for the Gradle project.
@@ -7,7 +7,6 @@ buildscript {
}
dependencies {
classpath(kotlin("gradle-plugin:${property("kotlin_version")}"))
classpath("com.android.tools.build:gradle:${property("android_tools_version")}")
}
}
@@ -1,10 +1,9 @@
plugins {
kotlin("multiplatform")
id("com.android.library")
}
kotlin {
android()
jvm()
val macosX64 = macosX64()
val iosX64 = iosX64()
@@ -35,12 +34,3 @@ kotlin {
}
}
}
android {
compileSdkVersion(30)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(21)
targetSdkVersion(30)
}
}
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.github.jetbrains.myapplication" />
@@ -1,5 +1,5 @@
package com.github.jetbrains.myapplication
actual class Platform actual constructor() {
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
actual val platform: String = "JVM ${System.getProperty("java.version")}"
}
@@ -6,19 +6,13 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.apple
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
@@ -27,9 +21,11 @@ import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.mapToFile
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import java.io.File
import javax.inject.Inject
internal object AppleXcodeTasks {
const val embedAndSignTaskPrefix = "embedAndSign"
@@ -70,7 +66,7 @@ private object XcodeEnvironment {
get() {
val xcodeTargetBuildDir = System.getenv("TARGET_BUILD_DIR") ?: return null
val xcodeFrameworksFolderPath = System.getenv("FRAMEWORKS_FOLDER_PATH") ?: return null
return File(xcodeTargetBuildDir, xcodeFrameworksFolderPath)
return File(xcodeTargetBuildDir, xcodeFrameworksFolderPath).absoluteFile
}
val sign: String? get() = System.getenv("EXPANDED_CODE_SIGN_IDENTITY")
@@ -135,9 +131,9 @@ private fun Project.registerAssembleAppleFrameworkTask(framework: Framework): Ta
else -> registerTask<FrameworkCopy>(frameworkTaskName) { task ->
task.description = "Packs $frameworkBuildType ${frameworkTarget.name} framework for Xcode"
task.isEnabled = frameworkBuildType == envBuildType
task.dependsOn(framework.linkTaskName)
task.files = files({ framework.outputDirectory.listFiles() })
task.destDir = appleFrameworkDir(envFrameworkSearchDir)
task.sourceFramework.fileProvider(framework.linkTaskProvider.flatMap { it.outputFile })
task.dependsOn(framework.linkTaskProvider)
task.destinationDirectory.set(appleFrameworkDir(envFrameworkSearchDir))
}
}
}
@@ -193,15 +189,16 @@ internal fun Project.registerEmbedAndSignAppleFrameworkTask(framework: Framework
if (framework.buildType != envBuildType || !envTargets.contains(framework.konanTarget)) return
embedAndSignTask.configure { task ->
val frameworkFile = framework.outputFile
task.dependsOn(assembleTask)
task.files = files(File(appleFrameworkDir(envFrameworkSearchDir), framework.outputFile.name))
task.destDir = envEmbeddedFrameworksDir
task.sourceFramework.set(File(appleFrameworkDir(envFrameworkSearchDir), frameworkFile.name))
task.destinationDirectory.set(envEmbeddedFrameworksDir)
if (envSign != null) {
task.doLast {
val binary = envEmbeddedFrameworksDir
.resolve(framework.outputFile.name)
.resolve(framework.outputFile.nameWithoutExtension)
exec {
.resolve(frameworkFile.name)
.resolve(frameworkFile.nameWithoutExtension)
task.execOperations.exec {
it.commandLine("codesign", "--force", "--sign", envSign, "--", binary)
}
}
@@ -224,26 +221,39 @@ private fun Project.appleFrameworkDir(frameworkSearchDir: File) =
* To preserve these symlinks we are using the `cp` command instead.
* See https://youtrack.jetbrains.com/issue/KT-48594.
*/
@Suppress("LeakingThis") // Should be extended only by Gradle
internal abstract class FrameworkCopy : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:InputDirectory
abstract val sourceFramework: DirectoryProperty
@get:InputFiles
@get:SkipWhenEmpty
@get:IgnoreEmptyDirectories
@get:PathSensitive(PathSensitivity.ABSOLUTE)
abstract var files: FileCollection
protected val sourceDsym = sourceFramework.mapToFile().map { File(it.path + ".dSYM") }
@get:OutputDirectory
abstract var destDir: File
abstract val destinationDirectory: DirectoryProperty
@TaskAction
fun copy() {
destDir.mkdirs()
files.forEach { file ->
File(destDir, file.name).let destFile@{ destFile ->
if (!destFile.exists()) return@destFile
project.exec { it.commandLine("rm", "-r", destFile.absolutePath) }
}
project.exec { it.commandLine("cp", "-R", file.absolutePath, destDir.absolutePath) }
open fun copy() {
copy(sourceFramework.mapToFile())
if (sourceDsym.get().exists()) {
copy(sourceDsym)
}
}
private fun copy(sourceProvider: Provider<File>) {
val source = sourceProvider.get()
val destination = destinationDirectory.asFile.get()
val destinationFile = File(destination, source.name)
if (destinationFile.exists()) {
execOperations.exec { it.commandLine("rm", "-r", destinationFile.absolutePath) }
}
execOperations.exec { it.commandLine("cp", "-R", source.absolutePath, destination.absolutePath) }
}
}
@@ -269,7 +269,6 @@ class Framework(
compilation: KotlinNativeCompilation
) : AbstractNativeLibrary(name, baseName, buildType, compilation), HasAttributes {
@Transient // Is required configuration cache support for KotlinNative tasks that capture whole binary object as task state
private val attributeContainer = HierarchyAttributeContainer(parent = compilation.attributes)
override fun getAttributes() = attributeContainer
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.Cocoapods
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.utils.getFile
import java.io.File
import java.net.URI
import javax.inject.Inject
@@ -135,7 +136,7 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
/**
* Configure output directory for pod publishing
*/
var publishDir: File = CocoapodsBuildDirs(project).publish
var publishDir: File = CocoapodsBuildDirs(project.layout).publish.getFile()
internal val specRepos = SpecRepos()
@@ -179,8 +180,8 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
val newContent = lineContent.replace(path.name, "")
"""
|Deprecated DSL found on ${buildScript.absolutePath}${File.pathSeparator}${lineNumber + 1}:
|Found: "${lineContent}"
|Expected: "${newContent}"
|Found: "$lineContent"
|Expected: "$newContent"
|Please, change the path to avoid this warning.
|
""".trimMargin()
@@ -10,9 +10,14 @@ import org.gradle.api.DefaultTask
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.wrapper.Wrapper
import org.jetbrains.kotlin.daemon.common.trimQuotes
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -21,10 +26,9 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.Cocoapods
import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api
import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn
import org.jetbrains.kotlin.gradle.plugin.mpp.*
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.AppleTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.FrameworkCopy
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.KotlinArtifactsPodspecExtension
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.kotlinArtifactsPodspecExtension
@@ -33,7 +37,6 @@ import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.kotlinArtifacts
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.asValidTaskName
import org.jetbrains.kotlin.gradle.utils.filesProvider
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.Family
@@ -44,34 +47,37 @@ import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File
internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs
internal val ProjectLayout.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this)
internal class CocoapodsBuildDirs(val project: Project) {
val root: File
get() = project.buildDir.resolve("cocoapods")
internal class CocoapodsBuildDirs(private val layout: ProjectLayout) {
val framework: File
get() = root.resolve("framework")
val root: Provider<Directory>
get() = layout.buildDirectory.dir("cocoapods")
val framework: Provider<Directory>
get() = dir("framework")
val dummyFramework: Provider<Directory>
get() = dir("dummy.framework")
val defs: Provider<Directory>
get() = dir("defs")
val publish: Provider<Directory>
get() = dir("publish")
val dummyFramework: File
get() = root.resolve("dummy.framework")
fun synthetic(family: Provider<Family>): Provider<Directory> {
return dir("synthetic").map { it.dir(family.get().platformLiteral) }
}
val defs: File
get() = root.resolve("defs")
fun fatFramework(buildType: NativeBuildType): Provider<Directory> {
return root.map { it.dir("fat-frameworks/${buildType.getName()}") }
}
val buildSettings: File
get() = root.resolve("buildSettings")
fun buildSettings(pod: Provider<CocoapodsDependency>, sdk: Provider<String>): Provider<RegularFile> {
return dir("buildSettings").map { it.file("build-settings-${sdk.get()}-${pod.get().schemeName}.properties") }
}
val synthetic: File
get() = root.resolve("synthetic")
fun synthetic(family: Family) = synthetic.resolve(family.platformLiteral)
val publish: File = root.resolve("publish")
fun fatFramework(buildType: NativeBuildType) =
root.resolve("fat-frameworks/${buildType.getName()}")
private fun dir(pathFromRoot: String): Provider<Directory> = root.map { it.dir(pathFromRoot) }
}
internal fun String.asValidFrameworkName() = replace('-', '_')
@@ -115,24 +121,13 @@ private val KotlinNativeTarget.toValidSDK: String
else -> throw IllegalArgumentException("Bad target ${konanTarget.name}.")
}
internal fun Project.getPodBuildTaskProvider(
private fun Project.getPodBuildTaskProvider(
target: KotlinNativeTarget,
pod: CocoapodsDependency
): TaskProvider<PodBuildTask> {
return tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java)
}
internal fun Project.getPodBuildSettingsProperties(
target: KotlinNativeTarget,
pod: CocoapodsDependency
): PodBuildSettingsProperties {
return getPodBuildTaskProvider(target, pod).get().buildSettingsFile.get()
.reader()
.use {
PodBuildSettingsProperties.readSettingsFromReader(it)
}
}
internal val PodBuildSettingsProperties.frameworkSearchPaths: List<String>
get() {
val frameworkPathsSelfIncluding = mutableListOf<String>()
@@ -154,6 +149,7 @@ private val PodBuildSettingsProperties.frameworkHeadersSearchPaths: List<String>
* Ignores whitespaces in quotes and drops quotes, e.g. a string
* `foo "bar baz" qux="quux"` will be split into ["foo", "bar baz", "qux=quux"].
*/
@Suppress("RegExpUnnecessaryNonCapturingGroup")
internal fun String.splitQuotedArgs(): List<String> =
Regex("""(?:[^\s"]|(?:"[^"]*"))+""").findAll(this).map {
it.value.replace("\"", "")
@@ -178,15 +174,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
private fun Project.createCopyFrameworkTask(
originalDirectory: Provider<File>,
frameworkFile: Provider<File>,
buildingTask: TaskProvider<*>
) = registerTask<FrameworkCopy>(SYNC_TASK_NAME) {
it.group = TASK_GROUP
it.description = "Copies a framework for given platform and build type into the CocoaPods build directory"
it.sourceFramework.fileProvider(frameworkFile)
it.dependsOn(buildingTask)
it.files = filesProvider { originalDirectory.map { dir -> dir.listFiles().orEmpty() } }
it.destDir = cocoapodsBuildDirs.framework
it.destinationDirectory.set(layout.cocoapodsBuildDirs.framework)
}
private fun createSyncForFatFramework(
@@ -210,7 +206,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val fatFrameworkTask = project.registerTask<FatFrameworkTask>("fatFramework") { task ->
task.group = TASK_GROUP
task.description = "Creates a fat framework for requested architectures"
task.destinationDir = project.cocoapodsBuildDirs.fatFramework(requestedBuildType)
task.destinationDir = project.layout.cocoapodsBuildDirs.fatFramework(requestedBuildType).getFile()
fatTargets.forEach { (_, targets) ->
targets.singleOrNull()?.let {
@@ -221,7 +217,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
}
project.createCopyFrameworkTask(fatFrameworkTask.map { it.destinationDir }, fatFrameworkTask)
project.createCopyFrameworkTask(fatFrameworkTask.map { it.fatFramework }, fatFrameworkTask)
}
private fun createSyncForRegularFramework(
@@ -236,7 +232,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
check(targets.size == 1) { "The project has more than one target for the requested platform: `${requestedPlatform.visibleName}`" }
val frameworkLinkTask = targets.single().binaries.getFramework(POD_FRAMEWORK_PREFIX, requestedBuildType).linkTaskProvider
project.createCopyFrameworkTask(frameworkLinkTask.flatMap { it.destinationDirectory.map { dir -> dir.asFile } }, frameworkLinkTask)
project.createCopyFrameworkTask(frameworkLinkTask.flatMap { it.outputFile }, frameworkLinkTask)
}
private fun createSyncTask(
@@ -332,6 +328,8 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val interopTask = project.tasks.named<CInteropProcess>(interop.interopProcessingTaskName).get()
interopTask.onlyIf { HostManager.hostIsMac }
interopTask.dependsOn(defTask)
pod.interopBindingDependencies.forEach { dependencyName ->
@@ -339,21 +337,20 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
with(interop) {
defFileProperty.set(defTask.map { it.outputFile })
defFileProperty.set(defTask.flatMap { it.defFile.mapToFile() })
_packageNameProp.set(project.provider { pod.packageName })
_extraOptsProp.addAll(project.provider { pod.extraOpts })
}
if (HostManager.hostIsMac) {
val podBuildTaskProvider = project.getPodBuildTaskProvider(target, pod)
interopTask.inputs.file(podBuildTaskProvider.map { it.buildSettingsFile })
interopTask.dependsOn(podBuildTaskProvider)
}
val podBuildTaskProvider = project.getPodBuildTaskProvider(target, pod)
val buildSettingsFileProvider = project.buildSettingsFileProvider(pod, target)
interopTask.inputs.file(buildSettingsFileProvider)
interopTask.dependsOn(podBuildTaskProvider)
interopTask.doFirst { _ ->
// Since we cannot expand the configuration phase of interop tasks
// receiving the required environment variables happens on execution phase.
// TODO This needs to be fixed to improve UP-TO-DATE checks.
val podBuildSettings = project.getPodBuildSettingsProperties(target, pod)
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFileProvider.getFile())
podBuildSettings.cflags?.let { args ->
// Xcode quotes around paths with spaces.
@@ -363,7 +360,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
interop.compilerOpts.addAll(podBuildSettings.frameworkHeadersSearchPaths.map { "-I$it" })
interop.compilerOpts.addAll(podBuildSettings.frameworkSearchPaths.map { "-F$it" })
}
}
}
@@ -409,28 +405,19 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project: Project,
cocoapodsExtension: CocoapodsExtension
) {
project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) {
it.group = TASK_GROUP
it.description = "Generates a podspec file for CocoaPods import"
it.needPodspec = project.provider { cocoapodsExtension.needPodspec }
it.publishing.set(false)
it.pods.set(cocoapodsExtension.pods)
it.version.set(cocoapodsExtension.version ?: project.version.toString())
it.specName.set(cocoapodsExtension.name)
it.extraSpecAttributes.set(cocoapodsExtension.extraSpecAttributes)
it.outputDir.set(project.projectDir)
it.homepage.set(cocoapodsExtension.homepage)
it.license.set(cocoapodsExtension.license)
it.authors.set(cocoapodsExtension.authors)
it.summary.set(cocoapodsExtension.summary)
it.frameworkName = cocoapodsExtension.podFrameworkName
it.ios = project.provider { cocoapodsExtension.ios }
it.osx = project.provider { cocoapodsExtension.osx }
it.tvos = project.provider { cocoapodsExtension.tvos }
it.watchos = project.provider { cocoapodsExtension.watchos }
project.registerTask<PodspecTask>(POD_SPEC_TASK_NAME) { task ->
task.group = TASK_GROUP
task.description = "Generates a podspec file for CocoaPods import"
task.outputDir.set(project.projectDir)
task.needPodspec.set(project.provider { cocoapodsExtension.needPodspec })
task.publishing.set(false)
task.configure(cocoapodsExtension, project)
task.gradleWrapperPath.set(project.gradleWrapperPath())
val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
if (generateWrapper) {
it.dependsOn(":wrapper")
task.dependsOn(":wrapper")
}
}
}
@@ -578,10 +565,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project.registerTask<PodSetupBuildTask>(sdk.toSetupBuildTaskName(pod)) { task ->
task.group = TASK_GROUP
task.description = "Collect environment variables from .xcworkspace file"
task.pod = project.provider { pod }
task.sdk = project.provider { sdk }
task.podsXcodeProjDir = podInstallTask.map { it.podsXcodeProjDirProvider.get() }
task.frameworkName = cocoapodsExtension.podFrameworkName
task.pod.set(pod)
task.sdk.set(sdk)
task.podsXcodeProjDir.set(podInstallTask.map { it.podsXcodeProjDirProvider.get() })
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
task.dependsOn(podInstallTask)
}
}
@@ -616,13 +603,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val podSetupBuildTaskProvider =
project.tasks.named(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java)
project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) {
it.group = TASK_GROUP
it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme"
it.sdk = project.provider { sdk }
it.pod = project.provider { pod }
it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() }
it.buildSettingsFile = podSetupBuildTaskProvider.map { task -> task.buildSettingsFile.get() }
project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) { task ->
task.group = TASK_GROUP
task.description = "Calls `xcodebuild` on xcworkspace for the pod scheme"
task.buildSettingsFile.set(podSetupBuildTaskProvider.flatMap { it.buildSettingsFile })
task.pod.set(pod)
task.sdk.set(sdk)
task.family.set(target.konanTarget.family)
task.podsXcodeProjDir.fileProvider(podSetupBuildTaskProvider.flatMap { it.podsXcodeProjDir })
task.dependsOn(podSetupBuildTaskProvider)
}
}
}
@@ -667,28 +656,32 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
private fun configureLinkingOptions(project: Project, cocoapodsExtension: CocoapodsExtension, nativeBinary: NativeBinary) {
cocoapodsExtension.pods.all { pod ->
nativeBinary.linkTaskProvider.configure { task ->
task.onlyIf { HostManager.hostIsMac }
val binary = task.binary
if (HostManager.hostIsMac) {
val podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod)
task.inputs.file(podBuildTaskProvider.map { it.buildSettingsFile })
task.dependsOn(podBuildTaskProvider)
}
task.doFirst { _ ->
val isExecutable = binary is AbstractExecutable
val isDynamicFramework = (binary is Framework && !binary.isStatic)
val podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod)
val buildSettingsFileProvider = project.buildSettingsFileProvider(pod, binary.target)
task.inputs.file(buildSettingsFileProvider)
task.dependsOn(podBuildTaskProvider)
val podBuildSettings = project.getPodBuildSettingsProperties(binary.target, pod)
val isExecutable = binary is AbstractExecutable
val isDynamicFramework = project.provider { binary is Framework && !binary.isStatic }
task.doFirst {
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFileProvider.getFile())
val frameworkFileName = pod.moduleName + ".framework"
val frameworkSearchPaths = podBuildSettings.frameworkSearchPaths
if (isExecutable || isDynamicFramework) {
val linkerOpts = task.additionalLinkerOpts
if (isExecutable || isDynamicFramework.get()) {
val frameworkFileExists = frameworkSearchPaths.any { dir -> File(dir, frameworkFileName).exists() }
if (frameworkFileExists) binary.linkerOpts.addArg("-framework", pod.moduleName)
binary.linkerOpts.addAll(frameworkSearchPaths.map { "-F$it" })
if (frameworkFileExists) linkerOpts.addArg("-framework", pod.moduleName)
linkerOpts.addAll(frameworkSearchPaths.map { "-F$it" })
}
if (isExecutable) binary.linkerOpts.addArgs("-rpath", frameworkSearchPaths)
if (isExecutable) linkerOpts.addArgs("-rpath", frameworkSearchPaths)
}
}
}
@@ -720,36 +713,39 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
cocoapodsExtension: CocoapodsExtension,
xcFrameworkTask: TaskProvider<XCFrameworkTask>,
buildType: NativeBuildType
): TaskProvider<PodspecTask> =
with(project) {
val task =
tasks.register(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "spec", buildType.getName()), PodspecTask::class.java) { task ->
task.description = "Generates podspec for ${buildType.getName().capitalizeAsciiOnly()} XCFramework publishing"
task.outputDir.set(xcFrameworkTask.map { it.outputDir.resolve(it.buildType.getName()) })
task.needPodspec = provider { true }
task.publishing.set(true)
task.pods.set(cocoapodsExtension.pods)
task.specName.set(cocoapodsExtension.name)
task.version.set(cocoapodsExtension.version ?: version.toString())
task.extraSpecAttributes.set(cocoapodsExtension.extraSpecAttributes)
task.homepage.set(cocoapodsExtension.homepage)
task.license.set(cocoapodsExtension.license)
task.authors.set(cocoapodsExtension.authors)
task.summary.set(cocoapodsExtension.summary)
task.source.set(cocoapodsExtension.source)
task.frameworkName = cocoapodsExtension.podFrameworkName
task.ios = provider { cocoapodsExtension.ios }
task.osx = provider { cocoapodsExtension.osx }
task.tvos = provider { cocoapodsExtension.tvos }
task.watchos = provider { cocoapodsExtension.watchos }
val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
if (generateWrapper) {
task.dependsOn(":wrapper")
}
}
xcFrameworkTask.dependsOn(task)
return task
): TaskProvider<PodspecTask> {
val task = project.registerTask<PodspecTask>(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "spec", buildType.getName())) { task ->
task.description = "Generates podspec for ${buildType.getName().capitalizeAsciiOnly()} XCFramework publishing"
task.outputDir.set(xcFrameworkTask.map { it.outputDir.resolve(it.buildType.getName()) })
task.needPodspec.set(true)
task.publishing.set(true)
task.source.set(project.provider { cocoapodsExtension.source })
task.configure(cocoapodsExtension, project)
}
xcFrameworkTask.dependsOn(task)
return task
}
private fun PodspecTask.configure(cocoapodsExtension: CocoapodsExtension, project: Project) {
fun <T> Property<T>.setProvider(provider: () -> T?) = set(project.provider(provider))
pods.set(cocoapodsExtension.pods)
specName.setProvider { cocoapodsExtension.name }
version.setProvider { cocoapodsExtension.version ?: project.version.toString() }
extraSpecAttributes.set(project.provider { cocoapodsExtension.extraSpecAttributes })
homepage.setProvider { cocoapodsExtension.homepage }
license.setProvider { cocoapodsExtension.license }
authors.setProvider { cocoapodsExtension.authors }
summary.setProvider { cocoapodsExtension.summary }
frameworkName.set(cocoapodsExtension.podFrameworkName)
ios.set(cocoapodsExtension.ios)
osx.set(cocoapodsExtension.osx)
tvos.set(cocoapodsExtension.tvos)
watchos.set(cocoapodsExtension.watchos)
projectPath.set(project.taskProjectPath())
hasPodfile.set(project.hasPodfileOwnOrParent())
}
private fun registerPodPublishFatFrameworkTasks(
project: Project,
@@ -811,6 +807,29 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
}
private fun Project.gradleWrapperPath(): Provider<String?> {
return provider { rootProject.tasks.locateTask<Wrapper>("wrapper")?.get()?.scriptFile?.absolutePath }
}
private fun Project.taskProjectPath(): String {
return if (project.depth != 0) project.path else ""
}
private fun Project.buildSettingsFileProvider(pod: CocoapodsDependency, target: KotlinNativeTarget): Provider<RegularFile> {
return layout.cocoapodsBuildDirs.buildSettings(provider { pod }, provider { target.toValidSDK })
}
private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension?
get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension
private fun Project.hasPodfileOwnOrParent(): Provider<Boolean> {
return provider {
multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|| (parent?.hasPodfileOwnOrParent()?.get() ?: false)
}
}
override fun apply(project: Project): Unit = with(project) {
pluginManager.withPlugin("kotlin-multiplatform") {
@@ -846,6 +865,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
}
companion object {
const val COCOAPODS_EXTENSION_NAME = "cocoapods"
const val TASK_GROUP = "CocoaPods"
@@ -53,6 +53,10 @@ data class PodBuildSettingsProperties(
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
internal fun readSettingsFromFile(file: File): PodBuildSettingsProperties {
return file.reader().use { readSettingsFromReader(it) }
}
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
with(Properties()) {
load(reader)
@@ -7,20 +7,26 @@
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.appendLine
import org.jetbrains.kotlin.gradle.utils.getFile
import java.io.File
import javax.inject.Inject
/**
* Generates a def-file for the given CocoaPods dependency.
*/
abstract class DefFileTask : DefaultTask() {
abstract class DefFileTask @Inject constructor(projectLayout: ProjectLayout) : DefaultTask() {
@get:Nested
abstract val pod: Property<CocoapodsDependency>
@@ -29,13 +35,18 @@ abstract class DefFileTask : DefaultTask() {
abstract val useLibraries: Property<Boolean>
@get:OutputFile
val defFile: Provider<RegularFile> = projectLayout.cocoapodsBuildDirs.defs.map { it.file("${pod.get().moduleName}.def") }
@get:Internal
@Deprecated("Use `defFile` instead", replaceWith = ReplaceWith("defFile.get().asFile"))
val outputFile: File
get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def")
get() = defFile.getFile()
@TaskAction
fun generate() {
outputFile.parentFile.mkdirs()
outputFile.writeText(buildString {
val output = defFile.getFile()
output.parentFile.mkdirs()
output.writeText(buildString {
appendLine("language = Objective-C")
with(pod.get()) {
when {
@@ -7,13 +7,16 @@
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.mapToFile
import java.io.File
import javax.inject.Inject
/**
* Creates a dummy framework in the target directory.
@@ -26,7 +29,7 @@ import java.io.File
* So we create a dummy static framework to allow CocoaPods install our pod correctly
* and then replace it with the real one during a real build process.
*/
abstract class DummyFrameworkTask : DefaultTask() {
abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayout) : DefaultTask() {
@get:Input
abstract val frameworkName: Property<String>
@@ -35,7 +38,7 @@ abstract class DummyFrameworkTask : DefaultTask() {
abstract val useStaticFramework: Property<Boolean>
@get:OutputDirectory
val outputFramework: Provider<File> = project.provider { project.cocoapodsBuildDirs.dummyFramework }
val outputFramework: Provider<File> = projectLayout.cocoapodsBuildDirs.dummyFramework.mapToFile()
private val dummyFrameworkResource: String
get() {
@@ -7,71 +7,80 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.getFile
import org.jetbrains.kotlin.gradle.utils.runCommand
import java.io.File
import java.util.*
import org.jetbrains.kotlin.konan.target.Family
import javax.inject.Inject
/**
* The task compiles external cocoa pods sources.
*/
open class PodBuildTask : CocoapodsTask() {
abstract class PodBuildTask @Inject constructor(
providerFactory: ProviderFactory,
projectLayout: ProjectLayout,
objectFactory: ObjectFactory,
) : CocoapodsTask() {
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:InputFile
lateinit var buildSettingsFile: Provider<File>
internal set
abstract val buildSettingsFile: RegularFileProperty
@get:Nested
internal lateinit var pod: Provider<CocoapodsDependency>
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories
@get:InputFiles
internal val srcDir: FileTree
get() = project.fileTree(
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
)
@get:Internal
internal var buildDir: Provider<File> = project.provider {
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
}
internal abstract val pod: Property<CocoapodsDependency>
@get:Input
internal lateinit var sdk: Provider<String>
internal abstract val sdk: Property<String>
@Suppress("unused") // declares an ouptut
@get:OutputFiles
internal val buildResult: Provider<FileCollection> = project.provider {
project.fileTree(buildDir.get()) {
it.include("**/${pod.get().schemeName}.*/")
it.include("**/${pod.get().schemeName}/")
@get:Input
internal abstract val family: Property<Family>
private val synthetic = projectLayout.cocoapodsBuildDirs.synthetic(family)
@get:IgnoreEmptyDirectories
@get:InputDirectory
internal val srcDir: Provider<Directory> = pod.flatMap { pod ->
val podLocation = pod.source
if (podLocation is Path) {
projectLayout.dir(providerFactory.provider { podLocation.dir })
} else {
synthetic.map { it.dir("Pods/${pod.schemeName}") }
}
}
@Suppress("unused") // declares an output
@get:OutputFiles
internal val buildResult: FileCollection = objectFactory.fileTree()
.from(synthetic.map { it.dir("build") })
.matching {
it.include("**/${pod.get().schemeName}.*/")
it.include("**/${pod.get().schemeName}/")
}
@get:Internal
internal lateinit var podsXcodeProjDir: Provider<File>
internal abstract val podsXcodeProjDir: DirectoryProperty
@TaskAction
fun buildDependencies() {
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFile.getFile())
val podsXcodeProjDir = podsXcodeProjDir.get()
val podXcodeBuildCommand = listOf(
"xcodebuild",
"-project", podsXcodeProjDir.name,
"-project", podsXcodeProjDir.asFile.name,
"-scheme", pod.get().schemeName,
"-sdk", sdk.get(),
"-configuration", podBuildSettings.configuration
)
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
runCommand(podXcodeBuildCommand, logger) { directory(podsXcodeProjDir.asFile.parentFile) }
}
}
@@ -7,6 +7,7 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
@@ -17,11 +18,12 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.platformLiteral
import org.jetbrains.kotlin.gradle.utils.XcodeVersion
import org.jetbrains.kotlin.konan.target.Family
import java.io.File
import javax.inject.Inject
/**
* The task generates a synthetic project with all cocoapods dependencies
*/
abstract class PodGenTask : CocoapodsTask() {
abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : CocoapodsTask() {
init {
onlyIf {
@@ -54,7 +56,7 @@ abstract class PodGenTask : CocoapodsTask() {
internal abstract val xcodeVersion: Property<XcodeVersion>
@get:OutputFile
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
val podfile: Provider<File> = projectLayout.cocoapodsBuildDirs.synthetic(family).map { it.file("Podfile").asFile }
@TaskAction
fun generate() {
@@ -7,6 +7,7 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
@@ -17,8 +18,9 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingCocoapodsMessage
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingSpecReposMessage
import java.io.File
import javax.inject.Inject
abstract class PodInstallTask : AbstractPodInstallTask() {
abstract class PodInstallTask @Inject constructor(projectLayout: ProjectLayout) : AbstractPodInstallTask() {
@get:Optional
@get:InputFile
@@ -36,7 +38,7 @@ abstract class PodInstallTask : AbstractPodInstallTask() {
@get:InputDirectory
abstract val dummyFramework: Property<File>
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
private val framework = projectLayout.cocoapodsBuildDirs.framework.map { it.file("${frameworkName.get()}.framework").asFile }
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
override fun doPodInstall() {
@@ -7,33 +7,34 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.getFile
import org.jetbrains.kotlin.gradle.utils.runCommand
import java.io.File
import javax.inject.Inject
open class PodSetupBuildTask : CocoapodsTask() {
abstract class PodSetupBuildTask @Inject constructor(projectLayout: ProjectLayout) : CocoapodsTask() {
@get:Input
lateinit var frameworkName: Provider<String>
abstract val frameworkName: Property<String>
@get:Input
internal lateinit var sdk: Provider<String>
internal abstract val sdk: Property<String>
@get:Nested
lateinit var pod: Provider<CocoapodsDependency>
@get:OutputFile
val buildSettingsFile: Provider<File> = project.provider {
project.cocoapodsBuildDirs
.buildSettings
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
}
abstract val pod: Property<CocoapodsDependency>
@get:Internal
internal lateinit var podsXcodeProjDir: Provider<File>
internal abstract val podsXcodeProjDir: Property<File>
@get:OutputFile
val buildSettingsFile: Provider<RegularFile> = projectLayout.cocoapodsBuildDirs.buildSettings(pod, sdk)
@TaskAction
fun setupBuild() {
@@ -46,15 +47,12 @@ open class PodSetupBuildTask : CocoapodsTask() {
"-sdk", sdk.get()
)
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
val outputText = runCommand(buildSettingsReceivingCommand, logger) { directory(podsXcodeProjDir.parentFile) }
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
buildSettingsFile.get().let { bsf ->
buildSettingsFile.getFile().let { bsf ->
buildSettingsProperties.writeSettings(bsf)
}
}
}
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
"build-settings-$sdk-${pod.schemeName}.properties"
@@ -3,93 +3,103 @@
* 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("PackageDirectoryMismatch") // Old package for compatibility
@file:Suppress("PackageDirectoryMismatch", "LeakingThis") // Old package for compatibility
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.wrapper.Wrapper
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.PodspecPlatformSettings
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.getFile
import java.io.File
import javax.inject.Inject
/**
* The task generates a podspec file which allows a user to
* integrate a Kotlin/Native framework into a CocoaPods project.
*/
open class PodspecTask : DefaultTask() {
abstract class PodspecTask @Inject constructor(private val projectLayout: ProjectLayout) : DefaultTask() {
@get:Input
internal val specName = project.objects.property(String::class.java)
internal abstract val specName: Property<String>
@get:Internal
internal val outputDir = project.objects.property(File::class.java)
internal abstract val outputDir: Property<File>
@get:OutputFile
val outputFile: File
get() = outputDir.get().resolve("${specName.get()}.podspec")
@get:Input
internal lateinit var needPodspec: Provider<Boolean>
internal abstract val needPodspec: Property<Boolean>
@get:Nested
val pods = project.objects.listProperty(CocoapodsDependency::class.java)
abstract val pods: ListProperty<CocoapodsDependency>
@get:Input
internal val version = project.objects.property(String::class.java)
internal abstract val version: Property<String>
@get:Input
internal val publishing = project.objects.property(Boolean::class.java)
internal abstract val publishing: Property<Boolean>
@get:Input
@get:Optional
internal val source = project.objects.property(String::class.java)
internal abstract val source: Property<String>
@get:Input
@get:Optional
internal val homepage = project.objects.property(String::class.java)
internal abstract val homepage: Property<String>
@get:Input
@get:Optional
internal val license = project.objects.property(String::class.java)
internal abstract val license: Property<String>
@get:Input
@get:Optional
internal val authors = project.objects.property(String::class.java)
internal abstract val authors: Property<String>
@get:Input
@get:Optional
internal val summary = project.objects.property(String::class.java)
internal abstract val summary: Property<String>
@get:Input
@get:Optional
internal val extraSpecAttributes = project.objects.mapProperty(String::class.java, String::class.java)
internal abstract val extraSpecAttributes: MapProperty<String, String>
@get:Input
internal lateinit var frameworkName: Provider<String>
internal abstract val frameworkName: Property<String>
@get:Nested
internal lateinit var ios: Provider<PodspecPlatformSettings>
internal abstract val ios: Property<PodspecPlatformSettings>
@get:Nested
internal lateinit var osx: Provider<PodspecPlatformSettings>
internal abstract val osx: Property<PodspecPlatformSettings>
@get:Nested
internal lateinit var tvos: Provider<PodspecPlatformSettings>
internal abstract val tvos: Property<PodspecPlatformSettings>
@get:Nested
internal lateinit var watchos: Provider<PodspecPlatformSettings>
internal abstract val watchos: Property<PodspecPlatformSettings>
@get:Input
@get:Optional
internal abstract val gradleWrapperPath: Property<String?>
@get:Input
internal abstract val projectPath: Property<String>
@get:Input
internal abstract val hasPodfile: Property<Boolean>
init {
onlyIf { needPodspec.get() }
@@ -107,17 +117,6 @@ open class PodspecTask : DefaultTask() {
""".trimIndent()
}
val gradleWrapper = (project.rootProject.tasks.getByName("wrapper") as? Wrapper)?.scriptFile
require(gradleWrapper != null && gradleWrapper.exists()) {
"""
The Gradle wrapper is required to run the build from Xcode.
Please run the same command with `-P$GENERATE_WRAPPER_PROPERTY=true` or run the `:wrapper` task to generate the wrapper manually.
See details about the wrapper at https://docs.gradle.org/current/userguide/gradle_wrapper.html
""".trimIndent()
}
val deploymentTargets = run {
listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") {
if (extraSpecAttributes.get().containsKey("${it.name}.deployment_target")) "" else "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'"
@@ -129,7 +128,7 @@ open class PodspecTask : DefaultTask() {
"| spec.dependency '${pod.name}'$versionSuffix"
}.joinToString(separator = "\n")
val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile)
val frameworkDir = projectLayout.cocoapodsBuildDirs.framework.getFile().relativeTo(outputFile.parentFile)
val vendoredFramework = if (publishing.get()) "${frameworkName.get()}.xcframework" else frameworkDir.resolve("${frameworkName.get()}.framework").invariantSeparatorsPath
val vendoredFrameworks = if (extraSpecAttributes.get().containsKey("vendored_frameworks")) "" else "| spec.vendored_frameworks = '$vendoredFramework'"
@@ -138,12 +137,11 @@ open class PodspecTask : DefaultTask() {
val xcConfig = if (publishing.get() || extraSpecAttributes.get().containsKey("pod_target_xcconfig")) "" else
""" |
| spec.pod_target_xcconfig = {
| 'KOTLIN_PROJECT_PATH' => '${if (project.depth != 0) project.path else ""}',
| 'KOTLIN_PROJECT_PATH' => '${projectPath.get()}',
| 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}',
| }
""".trimMargin()
val gradleCommand = "\$REPO_ROOT/${gradleWrapper.relativeTo(project.projectDir).invariantSeparatorsPath}"
val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else
""" |
| spec.script_phases = [
@@ -158,7 +156,7 @@ open class PodspecTask : DefaultTask() {
| fi
| set -ev
| REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
| "$gradleCommand" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:$SYNC_TASK_NAME \
| "${gradleCommand()}" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:$SYNC_TASK_NAME \
| -P${KotlinCocoapodsPlugin.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \
| -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \
| -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION"
@@ -191,34 +189,39 @@ open class PodspecTask : DefaultTask() {
""".trimMargin()
)
if (hasPodfileOwnOrParent(project) && publishing.get().not()) {
if (hasPodfile.get() && !publishing.get()) {
logger.quiet(
"""
Generated a podspec file at: ${absolutePath}.
To include it in your Xcode project, check that the following dependency snippet exists in your Podfile:
Generated a podspec file at: ${absolutePath}.
To include it in your Xcode project, check that the following dependency snippet exists in your Podfile:
pod '${specName.get()}', :path => '${parentFile.absolutePath}'
""".trimIndent()
pod '${specName.get()}', :path => '${parentFile.absolutePath}'
""".trimIndent()
)
}
}
}
private fun gradleCommand(): String {
val gradleWrapperPath: String? = gradleWrapperPath.get()
val gradleWrapper = gradleWrapperPath?.let(::File)
require(gradleWrapper != null && gradleWrapper.exists()) {
"""
The Gradle wrapper is required to run the build from Xcode.
Please run the same command with `-P$GENERATE_WRAPPER_PROPERTY=true` or run the `:wrapper` task to generate the wrapper manually.
See details about the wrapper at https://docs.gradle.org/current/userguide/gradle_wrapper.html
""".trimIndent()
}
return "\$REPO_ROOT/${gradleWrapper.relativeTo(projectLayout.projectDirectory.asFile).invariantSeparatorsPath}"
}
private fun Provider<String>.getOrEmpty(): String = getOrElse("")
private fun String.surroundWithSingleQuotesIfNeeded(): String =
if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'"
companion object {
private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension?
get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension
private fun hasPodfileOwnOrParent(project: Project): Boolean =
if (project.rootProject == project) project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|| (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false)
}
}
@@ -43,12 +43,14 @@ abstract class KotlinNativeLink
@Inject
constructor(
@Internal
@Transient // This property can't be accessed in the execution phase
val binary: NativeBinary,
private val objectFactory: ObjectFactory,
private val execOperations: ExecOperations
private val execOperations: ExecOperations,
) : AbstractKotlinCompileTool<K2NativeCompilerArguments>(objectFactory),
UsesKonanPropertiesBuildService,
KotlinToolTask<KotlinCommonCompilerToolOptions> {
@Deprecated("Visibility will be lifted to private in the future releases")
@get:Internal
val compilation: KotlinNativeCompilation
@@ -85,16 +87,19 @@ constructor(
)
@get:Input
val outputKind: CompilerOutputKind get() = binary.outputKind.compilerOutputKind
val outputKind: CompilerOutputKind by lazyConvention { binary.outputKind.compilerOutputKind }
@get:Input
val optimized: Boolean get() = binary.optimized
val optimized: Boolean by lazyConvention { binary.optimized }
@get:Input
val debuggable: Boolean get() = binary.debuggable
val debuggable: Boolean by lazyConvention { binary.debuggable }
@get:Input
val baseName: String get() = binary.baseName
val baseName: String by lazyConvention { binary.baseName }
@get:Input
internal val binaryName: String by lazyConvention { binary.name }
@Suppress("DEPRECATION")
private val konanTarget = compilation.konanTarget
@@ -132,24 +137,29 @@ constructor(
}
fun kotlinOptions(fn: Closure<*>) {
@Suppress("DEPRECATION")
fn.delegate = kotlinOptions
fn.call()
}
// Binary-specific options.
@get:Input
@get:Optional
val entryPoint: String? get() = (binary as? Executable)?.entryPoint
private val _entryPoint: String by lazyConvention { (binary as? Executable)?.entryPoint.orEmpty() }
@get:Input
val linkerOpts: List<String> get() = binary.linkerOpts
@get:Optional
val entryPoint: String?
get() = _entryPoint.ifEmpty { null }
@get:Input
val linkerOpts: List<String> by lazyConvention { binary.linkerOpts }
@get:Input
internal val additionalLinkerOpts: MutableList<String> = mutableListOf()
@get:Input
val binaryOptions: Map<String, String> by lazy { PropertiesProvider(project).nativeBinaryOptions + binary.binaryOptions }
@get:Input
val processTests: Boolean get() = binary is TestExecutable
val processTests: Boolean by lazyConvention { binary is TestExecutable }
@get:Classpath
val exportLibraries: FileCollection get() = exportLibrariesResolvedConfiguration?.files ?: objectFactory.fileCollection()
@@ -161,8 +171,7 @@ constructor(
}
@get:Input
val isStaticFramework: Boolean
get() = binary.let { it is Framework && it.isStatic }
val isStaticFramework: Boolean by lazyConvention { binary.let { it is Framework && it.isStatic } }
@Suppress("DEPRECATION")
@get:Input
@@ -221,7 +230,7 @@ constructor(
null, BitcodeEmbeddingMode.DISABLE -> Unit
}
args.singleLinkerArguments = linkerOpts.toTypedArray()
args.singleLinkerArguments = (linkerOpts + additionalLinkerOpts).toTypedArray()
args.binaryOptions = binaryOptions.map { (key, value) -> "$key=$value" }.toTypedArray()
args.staticFramework = isStaticFramework
@@ -269,7 +278,7 @@ constructor(
}
"""
|Following dependencies exported in the ${binary.name} binary are not specified as API-dependencies of a corresponding source set:
|Following dependencies exported in the $binaryName binary are not specified as API-dependencies of a corresponding source set:
|
$failedDependenciesList
|
@@ -378,4 +387,8 @@ constructor(
executionContext = executionContext
).run(buildArguments)
}
private inline fun <reified T : Any> lazyConvention(noinline lazyConventionValue: () -> T): Provider<T> {
return objectFactory.providerWithLazyConvention(lazyConventionValue)
}
}
@@ -721,16 +721,17 @@ internal class CacheBuilder(
val konanCacheKind: NativeCacheKind,
val libraries: FileCollection,
val gradleUserHomeDir: File,
val binary: NativeBinary,
val konanTarget: KonanTarget,
val toolOptions: KotlinCommonCompilerToolOptions,
val externalDependenciesArgs: List<String>
val externalDependenciesArgs: List<String>,
val debuggable: Boolean,
val optimized: Boolean,
) {
val rootCacheDirectory
get() = getRootCacheDirectory(
File(runnerSettings.parent.konanHome),
konanTarget,
binary.debuggable,
debuggable,
konanCacheKind
)
@@ -748,7 +749,11 @@ internal class CacheBuilder(
konanCacheKind = konanCacheKind,
libraries = binary.compilation.compileDependencyFiles,
gradleUserHomeDir = project.gradle.gradleUserHomeDir,
binary, konanTarget, toolOptions, externalDependenciesArgs
konanTarget = konanTarget,
toolOptions = toolOptions,
externalDependenciesArgs = externalDependenciesArgs,
debuggable = binary.debuggable,
optimized = binary.optimized,
)
}
}
@@ -760,17 +765,14 @@ internal class CacheBuilder(
listOf(KLIB_INTEROP_IR_PROVIDER_IDENTIFIER)
)
private val binary: NativeBinary
get() = settings.binary
private val konanTarget: KonanTarget
get() = settings.konanTarget
private val optimized: Boolean
get() = binary.optimized
get() = settings.optimized
private val debuggable: Boolean
get() = binary.debuggable
get() = settings.debuggable
private val konanCacheKind: NativeCacheKind
get() = settings.konanCacheKind
@@ -6,7 +6,9 @@
package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import java.io.File
import java.io.IOException
import java.nio.file.Files
@@ -116,4 +118,14 @@ fun contentEquals(file1: File, file2: File): Boolean {
}
}
internal fun RegularFile.toUri() = asFile.toPath().toUri()
internal fun RegularFile.toUri() = asFile.toPath().toUri()
internal fun Provider<RegularFile>.mapToFile(): Provider<File> = map { it.asFile }
@JvmName("mapDirectoryToFile") // avoids jvm signature clash
internal fun Provider<Directory>.mapToFile(): Provider<File> = map { it.asFile }
internal fun Provider<RegularFile>.getFile(): File = get().asFile
@JvmName("getDirectoryAsFile") // avoids jvm signature clash
internal fun Provider<Directory>.getFile(): File = get().asFile