[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.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path 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 @NativeGradlePluginTests
@DisplayName("works with commonizer") @DisplayName("works with commonizer")
@GradleTestVersions( @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.jetbrains.kotlin.gradle.util.replaceText
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
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.io.path.appendText import kotlin.io.path.appendText
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Tests for K/N with Apple Framework") @DisplayName("Tests for K/N with Apple Framework")
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_70) @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@NativeGradlePluginTests @NativeGradlePluginTests
class AppleFrameworkIT : KGPBaseTest() { class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Assembling AppleFrameworkForXcode tasks for IosArm64") @DisplayName("Assembling AppleFrameworkForXcode tasks for IosArm64")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
fun shouldAssembleAppleFrameworkForXcodeForIosArm64( fun shouldAssembleAppleFrameworkForXcodeForIosArm64(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location, buildOptions = defaultBuildOptions,
buildOptions = defaultBuildOptions.copy( environmentVariables = EnvironmentalVariables(
androidVersion = agpVersion "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( build("assembleDebugAppleFrameworkForXcodeIosArm64") {
"assembleDebugAppleFrameworkForXcodeIosArm64",
environmentVariables = environmentVariables,
) {
assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeIosArm64") assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeIosArm64")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework") assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM") assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/sdk.framework.dSYM")
} }
build( build("assembleCustomDebugAppleFrameworkForXcodeIosArm64") {
"assembleCustomDebugAppleFrameworkForXcodeIosArm64",
environmentVariables = environmentVariables
) {
assertTasksExecuted(":shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64") assertTasksExecuted(":shared:assembleCustomDebugAppleFrameworkForXcodeIosArm64")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework") assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework")
assertDirectoryInProjectExists("shared/build/xcode-frameworks/debug/iphoneos123/lib.framework.dSYM") 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") @DisplayName("Assembling fat AppleFrameworkForXcode tasks for Arm64 and X64 simulators")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
fun shouldAssembleAppleFrameworkForXcodeForArm64AndX64Simulators( fun shouldAssembleAppleFrameworkForXcodeForArm64AndX64Simulators(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location, buildOptions = defaultBuildOptions
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
) { ) {
val environmentVariables = mapOf( val environmentVariables = mapOf(
"CONFIGURATION" to "Release", "CONFIGURATION" to "Release",
@@ -106,57 +88,42 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("MacOS framework has symlinks") @DisplayName("MacOS framework has symlinks")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleAndroidTest
fun shouldCheckThatMacOSFrameworkHasSymlinks( fun shouldCheckThatMacOSFrameworkHasSymlinks(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String, @TempDir testBuildDir: Path,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
) { ) {
val environmentVariables = mapOf( val environmentVariables = mapOf(
"CONFIGURATION" to "debug", "CONFIGURATION" to "debug",
"SDK_NAME" to "macosx", "SDK_NAME" to "macosx",
"ARCHS" to "x86_64", "ARCHS" to "x86_64",
"EXPANDED_CODE_SIGN_IDENTITY" to "-", "EXPANDED_CODE_SIGN_IDENTITY" to "-",
"TARGET_BUILD_DIR" to projectPath.absolutePathString(), "TARGET_BUILD_DIR" to testBuildDir.toString(),
"FRAMEWORKS_FOLDER_PATH" to "build/xcode-derived" "FRAMEWORKS_FOLDER_PATH" to "build/xcode-derived"
) )
build(":shared:embedAndSignAppleFrameworkForXcode", environmentVariables = EnvironmentalVariables(environmentVariables)) { build(":shared:embedAndSignAppleFrameworkForXcode", environmentVariables = EnvironmentalVariables(environmentVariables)) {
assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeMacosX64") assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeMacosX64")
assertSymlinkInProjectExists("shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers") 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) @OptIn(EnvironmentalVariablesOverride::class)
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleAndroidTest
fun testEmbedAnsSignExecutionWithoutSigning( fun testEmbedAnsSignExecutionWithoutSigning(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
) { ) {
val environmentVariables = mapOf( val environmentVariables = mapOf(
"CONFIGURATION" to "debug", "CONFIGURATION" to "debug",
@@ -172,19 +139,12 @@ class AppleFrameworkIT : KGPBaseTest() {
} }
@DisplayName("embedAndSignAppleFrameworkForXcode fail") @DisplayName("embedAndSignAppleFrameworkForXcode fail")
@GradleAndroidTest @GradleTest
// 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)
fun shouldFailWithExecutingEmbedAndSignAppleFrameworkForXcode( fun shouldFailWithExecutingEmbedAndSignAppleFrameworkForXcode(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
nativeProject("sharedAppleFramework", gradleVersion, buildJdk = jdkProvider.location) { nativeProject("sharedAppleFramework", gradleVersion) {
buildAndFail( buildAndFail(":shared:embedAndSignAppleFrameworkForXcode") {
":shared:embedAndSignAppleFrameworkForXcode",
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion)
) {
assertOutputContains("Please run the embedAndSignAppleFrameworkForXcode task from Xcode") 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") @DisplayName("Registered tasks with Xcode environment for Debug IosArm64 configuration")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
fun shouldCheckAllRegisteredTasksWithXcodeEnvironmentForDebugIosArm64( fun shouldCheckAllRegisteredTasksWithXcodeEnvironmentForDebugIosArm64(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String, @TempDir testBuildDir: Path,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = buildOptions
) { ) {
val environmentVariables = mapOf( val environmentVariables = mapOf(
"CONFIGURATION" to "Debug", "CONFIGURATION" to "Debug",
"SDK_NAME" to "iphoneos", "SDK_NAME" to "iphoneos",
"ARCHS" to "arm64", "ARCHS" to "arm64",
"EXPANDED_CODE_SIGN_IDENTITY" to "-", "EXPANDED_CODE_SIGN_IDENTITY" to "-",
"TARGET_BUILD_DIR" to "testBuildDir", "TARGET_BUILD_DIR" to testBuildDir.toString(),
"FRAMEWORKS_FOLDER_PATH" to "testFrameworksDir" "FRAMEWORKS_FOLDER_PATH" to "testFrameworksDir"
) )
buildAndAssertAllTasks( buildAndAssertAllTasks(
@@ -236,22 +189,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("embedAndSignAppleFrameworkForXcode was registered without required Xcode environments") @DisplayName("embedAndSignAppleFrameworkForXcode was registered without required Xcode environments")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
// 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)
fun shouldCheckEmbedAndSignAppleFrameworkForXcodeDoesNotRequireXcodeEnv( fun shouldCheckEmbedAndSignAppleFrameworkForXcodeDoesNotRequireXcodeEnv(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = buildOptions
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( mapOf(
@@ -289,21 +233,15 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Static framework for Arm64 is built but is not embedded") @DisplayName("Static framework for Arm64 is built but is not embedded")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
fun shouldCheckThatStaticFrameworkForArm64IsBuildAndNotEmbedded( fun shouldCheckThatStaticFrameworkForArm64IsBuildAndNotEmbedded(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location, buildOptions = defaultBuildOptions
buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( mapOf(
@@ -333,22 +271,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Configuration errors reported to Xcode when embedAndSign task requested") @DisplayName("Configuration errors reported to Xcode when embedAndSign task requested")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
// We need to use Gradle 7.2 here because of the issue https://github.com/gradle/gradle/issues/13957 @GradleTest
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_2)
@GradleAndroidTest
fun shouldReportConfErrorsToXcodeWhenRequestedByEmbedAndSign( fun shouldReportConfErrorsToXcodeWhenRequestedByEmbedAndSign(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildJdk = jdkProvider.location,
buildOptions = buildOptions
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( mapOf(
@@ -378,22 +307,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors reported to Xcode when embedAndSign task requested") @DisplayName("Compilation errors reported to Xcode when embedAndSign task requested")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
// 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)
fun shouldReportCompilationErrorsToXcodeWhenRequestedByEmbedAndSign( fun shouldReportCompilationErrorsToXcodeWhenRequestedByEmbedAndSign(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( mapOf(
@@ -417,22 +337,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors printed with Gradle-style when any other task requested") @DisplayName("Compilation errors printed with Gradle-style when any other task requested")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
// 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)
fun shouldPrintCompilationErrorsWithGradleStyle( fun shouldPrintCompilationErrorsWithGradleStyle(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( mapOf(
@@ -456,22 +367,13 @@ class AppleFrameworkIT : KGPBaseTest() {
@DisplayName("Compilation errors printed with Xcode-style with explicit option") @DisplayName("Compilation errors printed with Xcode-style with explicit option")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
// 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)
fun shouldPrintCompilationErrorsWithXcodeStyle( fun shouldPrintCompilationErrorsWithXcodeStyle(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( 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") @DisplayName("Compilation errors reported to Xcode when embedAndSign task requested and compiler runs in a separate process")
@OptIn(EnvironmentalVariablesOverride::class) @OptIn(EnvironmentalVariablesOverride::class)
@GradleAndroidTest @GradleTest
// 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)
fun shouldReportErrorsToXcodeWhenEmbedAndSignRequestedAndDisableCompilerDaemon( fun shouldReportErrorsToXcodeWhenEmbedAndSignRequestedAndDisableCompilerDaemon(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
agpVersion: String,
jdkProvider: JdkVersions.ProvidedJdk,
) { ) {
val buildOptions = defaultBuildOptions.copy(
androidVersion = agpVersion
)
nativeProject( nativeProject(
"sharedAppleFramework", "sharedAppleFramework",
gradleVersion, gradleVersion,
buildOptions = buildOptions,
buildJdk = jdkProvider.location
) { ) {
val environmentVariables = EnvironmentalVariables( val environmentVariables = EnvironmentalVariables(
mapOf( mapOf(
@@ -585,7 +478,7 @@ class AppleFrameworkIT : KGPBaseTest() {
assertContainsVariant("mainDynamicReleaseFrameworkIos") 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") { build(*dependencyInsight("iosAppIosX64DebugImplementation0"), "-PmultipleFrameworks") {
assertContainsVariant("mainStaticDebugFrameworkIos") 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.ImportMode
import org.jetbrains.kotlin.gradle.testbase.cocoaPodsEnvironmentVariables import org.jetbrains.kotlin.gradle.testbase.cocoaPodsEnvironmentVariables
import org.jetbrains.kotlin.gradle.testbase.ensureCocoapodsInstalled 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.modify
import org.jetbrains.kotlin.gradle.util.runProcess import org.jetbrains.kotlin.gradle.util.runProcess
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
@@ -25,10 +27,7 @@ import java.io.File
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipFile import java.util.zip.ZipFile
import kotlin.test.assertContains import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
class CocoaPodsIT : BaseGradleIT() { 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 // test configuration phase
private class CustomHooks { private class CustomHooks {
@@ -7,12 +7,6 @@ package org.jetbrains.kotlin.gradle.testbase
import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildResult
import org.jetbrains.kotlin.gradle.BaseGradleIT 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. * 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 @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") @RequiresOptIn("Environmental variables override may lead to interference of parallel builds and breaks Gradle tests debugging")
annotation class EnvironmentalVariablesOverride annotation class EnvironmentalVariablesOverride
@@ -38,9 +38,10 @@ fun TestProject.makeSnapshotTo(destinationPath: String) {
dest.resolve("run.sh").run { dest.resolve("run.sh").run {
writeText( writeText(
""" """
#!/usr/bin/env sh |#!/usr/bin/env sh
./gradlew ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} ${'$'}@ |${formatEnvironmentForScript(envCommand = "export")}
""".trimIndent() |./gradlew ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} ${'$'}@
|""".trimMargin()
) )
setPosixFilePermissions( setPosixFilePermissions(
@@ -55,9 +56,10 @@ fun TestProject.makeSnapshotTo(destinationPath: String) {
dest.resolve("run.bat").run { dest.resolve("run.bat").run {
writeText( writeText(
""" """
@rem Executing Gradle build |@rem Executing Gradle build
gradlew.bat ${buildOptions.toArguments(gradleVersion).joinToString(separator = " ")} %* |${formatEnvironmentForScript(envCommand = "set")}
""".trimIndent() |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. * Configures the JVM memory settings for the Gradle project.
@@ -7,7 +7,6 @@ buildscript {
} }
dependencies { dependencies {
classpath(kotlin("gradle-plugin:${property("kotlin_version")}")) classpath(kotlin("gradle-plugin:${property("kotlin_version")}"))
classpath("com.android.tools.build:gradle:${property("android_tools_version")}")
} }
} }
@@ -1,10 +1,9 @@
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
id("com.android.library")
} }
kotlin { kotlin {
android() jvm()
val macosX64 = macosX64() val macosX64 = macosX64()
val iosX64 = iosX64() 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 package com.github.jetbrains.myapplication
actual class Platform actual constructor() { 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 package org.jetbrains.kotlin.gradle.plugin.mpp.apple
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
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.DirectoryProperty
import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.Copy import org.gradle.api.provider.Provider
import org.gradle.api.tasks.IgnoreEmptyDirectories import org.gradle.api.tasks.*
import org.gradle.api.tasks.InputFiles import org.gradle.process.ExecOperations
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.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
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
@@ -27,9 +21,11 @@ import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName 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.konan.target.KonanTarget
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import java.io.File import java.io.File
import javax.inject.Inject
internal object AppleXcodeTasks { internal object AppleXcodeTasks {
const val embedAndSignTaskPrefix = "embedAndSign" const val embedAndSignTaskPrefix = "embedAndSign"
@@ -70,7 +66,7 @@ private object XcodeEnvironment {
get() { get() {
val xcodeTargetBuildDir = System.getenv("TARGET_BUILD_DIR") ?: return null val xcodeTargetBuildDir = System.getenv("TARGET_BUILD_DIR") ?: return null
val xcodeFrameworksFolderPath = System.getenv("FRAMEWORKS_FOLDER_PATH") ?: 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") 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 -> else -> registerTask<FrameworkCopy>(frameworkTaskName) { task ->
task.description = "Packs $frameworkBuildType ${frameworkTarget.name} framework for Xcode" task.description = "Packs $frameworkBuildType ${frameworkTarget.name} framework for Xcode"
task.isEnabled = frameworkBuildType == envBuildType task.isEnabled = frameworkBuildType == envBuildType
task.dependsOn(framework.linkTaskName) task.sourceFramework.fileProvider(framework.linkTaskProvider.flatMap { it.outputFile })
task.files = files({ framework.outputDirectory.listFiles() }) task.dependsOn(framework.linkTaskProvider)
task.destDir = appleFrameworkDir(envFrameworkSearchDir) task.destinationDirectory.set(appleFrameworkDir(envFrameworkSearchDir))
} }
} }
} }
@@ -193,15 +189,16 @@ internal fun Project.registerEmbedAndSignAppleFrameworkTask(framework: Framework
if (framework.buildType != envBuildType || !envTargets.contains(framework.konanTarget)) return if (framework.buildType != envBuildType || !envTargets.contains(framework.konanTarget)) return
embedAndSignTask.configure { task -> embedAndSignTask.configure { task ->
val frameworkFile = framework.outputFile
task.dependsOn(assembleTask) task.dependsOn(assembleTask)
task.files = files(File(appleFrameworkDir(envFrameworkSearchDir), framework.outputFile.name)) task.sourceFramework.set(File(appleFrameworkDir(envFrameworkSearchDir), frameworkFile.name))
task.destDir = envEmbeddedFrameworksDir task.destinationDirectory.set(envEmbeddedFrameworksDir)
if (envSign != null) { if (envSign != null) {
task.doLast { task.doLast {
val binary = envEmbeddedFrameworksDir val binary = envEmbeddedFrameworksDir
.resolve(framework.outputFile.name) .resolve(frameworkFile.name)
.resolve(framework.outputFile.nameWithoutExtension) .resolve(frameworkFile.nameWithoutExtension)
exec { task.execOperations.exec {
it.commandLine("codesign", "--force", "--sign", envSign, "--", binary) 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. * To preserve these symlinks we are using the `cp` command instead.
* See https://youtrack.jetbrains.com/issue/KT-48594. * See https://youtrack.jetbrains.com/issue/KT-48594.
*/ */
@Suppress("LeakingThis") // Should be extended only by Gradle
internal abstract class FrameworkCopy : DefaultTask() { internal abstract class FrameworkCopy : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:InputDirectory
abstract val sourceFramework: DirectoryProperty
@get:InputFiles @get:InputFiles
@get:SkipWhenEmpty
@get:IgnoreEmptyDirectories @get:IgnoreEmptyDirectories
@get:PathSensitive(PathSensitivity.ABSOLUTE) protected val sourceDsym = sourceFramework.mapToFile().map { File(it.path + ".dSYM") }
abstract var files: FileCollection
@get:OutputDirectory @get:OutputDirectory
abstract var destDir: File abstract val destinationDirectory: DirectoryProperty
@TaskAction @TaskAction
fun copy() { open fun copy() {
destDir.mkdirs() copy(sourceFramework.mapToFile())
files.forEach { file -> if (sourceDsym.get().exists()) {
File(destDir, file.name).let destFile@{ destFile -> copy(sourceDsym)
if (!destFile.exists()) return@destFile
project.exec { it.commandLine("rm", "-r", destFile.absolutePath) }
}
project.exec { it.commandLine("cp", "-R", file.absolutePath, destDir.absolutePath) }
} }
} }
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 compilation: KotlinNativeCompilation
) : AbstractNativeLibrary(name, baseName, buildType, compilation), HasAttributes { ) : 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) private val attributeContainer = HierarchyAttributeContainer(parent = compilation.attributes)
override fun getAttributes() = attributeContainer 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.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX
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.utils.getFile
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import javax.inject.Inject import javax.inject.Inject
@@ -135,7 +136,7 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
/** /**
* Configure output directory for pod publishing * Configure output directory for pod publishing
*/ */
var publishDir: File = CocoapodsBuildDirs(project).publish var publishDir: File = CocoapodsBuildDirs(project.layout).publish.getFile()
internal val specRepos = SpecRepos() internal val specRepos = SpecRepos()
@@ -179,8 +180,8 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
val newContent = lineContent.replace(path.name, "") val newContent = lineContent.replace(path.name, "")
""" """
|Deprecated DSL found on ${buildScript.absolutePath}${File.pathSeparator}${lineNumber + 1}: |Deprecated DSL found on ${buildScript.absolutePath}${File.pathSeparator}${lineNumber + 1}:
|Found: "${lineContent}" |Found: "$lineContent"
|Expected: "${newContent}" |Expected: "$newContent"
|Please, change the path to avoid this warning. |Please, change the path to avoid this warning.
| |
""".trimMargin() """.trimMargin()
@@ -10,9 +10,14 @@ import org.gradle.api.DefaultTask
import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project 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.plugins.ExtensionAware
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.wrapper.Wrapper
import org.jetbrains.kotlin.daemon.common.trimQuotes import org.jetbrains.kotlin.daemon.common.trimQuotes
import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 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.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.*
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.mpp.apple.FrameworkCopy
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated 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
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.tasks.*
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.asValidTaskName 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.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.newInstance import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.Family 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 org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File import java.io.File
internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs
internal val ProjectLayout.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this) get() = CocoapodsBuildDirs(this)
internal class CocoapodsBuildDirs(val project: Project) { internal class CocoapodsBuildDirs(private val layout: ProjectLayout) {
val root: File
get() = project.buildDir.resolve("cocoapods")
val framework: File val root: Provider<Directory>
get() = root.resolve("framework") 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 fun synthetic(family: Provider<Family>): Provider<Directory> {
get() = root.resolve("dummy.framework") return dir("synthetic").map { it.dir(family.get().platformLiteral) }
}
val defs: File fun fatFramework(buildType: NativeBuildType): Provider<Directory> {
get() = root.resolve("defs") return root.map { it.dir("fat-frameworks/${buildType.getName()}") }
}
val buildSettings: File fun buildSettings(pod: Provider<CocoapodsDependency>, sdk: Provider<String>): Provider<RegularFile> {
get() = root.resolve("buildSettings") return dir("buildSettings").map { it.file("build-settings-${sdk.get()}-${pod.get().schemeName}.properties") }
}
val synthetic: File private fun dir(pathFromRoot: String): Provider<Directory> = root.map { it.dir(pathFromRoot) }
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()}")
} }
internal fun String.asValidFrameworkName() = replace('-', '_') internal fun String.asValidFrameworkName() = replace('-', '_')
@@ -115,24 +121,13 @@ private val KotlinNativeTarget.toValidSDK: String
else -> throw IllegalArgumentException("Bad target ${konanTarget.name}.") else -> throw IllegalArgumentException("Bad target ${konanTarget.name}.")
} }
internal fun Project.getPodBuildTaskProvider( private fun Project.getPodBuildTaskProvider(
target: KotlinNativeTarget, target: KotlinNativeTarget,
pod: CocoapodsDependency pod: CocoapodsDependency
): TaskProvider<PodBuildTask> { ): TaskProvider<PodBuildTask> {
return tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) 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> internal val PodBuildSettingsProperties.frameworkSearchPaths: List<String>
get() { get() {
val frameworkPathsSelfIncluding = mutableListOf<String>() 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 * 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"]. * `foo "bar baz" qux="quux"` will be split into ["foo", "bar baz", "qux=quux"].
*/ */
@Suppress("RegExpUnnecessaryNonCapturingGroup")
internal fun String.splitQuotedArgs(): List<String> = internal fun String.splitQuotedArgs(): List<String> =
Regex("""(?:[^\s"]|(?:"[^"]*"))+""").findAll(this).map { Regex("""(?:[^\s"]|(?:"[^"]*"))+""").findAll(this).map {
it.value.replace("\"", "") it.value.replace("\"", "")
@@ -178,15 +174,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
} }
private fun Project.createCopyFrameworkTask( private fun Project.createCopyFrameworkTask(
originalDirectory: Provider<File>, frameworkFile: Provider<File>,
buildingTask: TaskProvider<*> buildingTask: TaskProvider<*>
) = registerTask<FrameworkCopy>(SYNC_TASK_NAME) { ) = registerTask<FrameworkCopy>(SYNC_TASK_NAME) {
it.group = TASK_GROUP it.group = TASK_GROUP
it.description = "Copies a framework for given platform and build type into the CocoaPods build directory" it.description = "Copies a framework for given platform and build type into the CocoaPods build directory"
it.sourceFramework.fileProvider(frameworkFile)
it.dependsOn(buildingTask) it.dependsOn(buildingTask)
it.files = filesProvider { originalDirectory.map { dir -> dir.listFiles().orEmpty() } } it.destinationDirectory.set(layout.cocoapodsBuildDirs.framework)
it.destDir = cocoapodsBuildDirs.framework
} }
private fun createSyncForFatFramework( private fun createSyncForFatFramework(
@@ -210,7 +206,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val fatFrameworkTask = project.registerTask<FatFrameworkTask>("fatFramework") { task -> val fatFrameworkTask = project.registerTask<FatFrameworkTask>("fatFramework") { task ->
task.group = TASK_GROUP task.group = TASK_GROUP
task.description = "Creates a fat framework for requested architectures" 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) -> fatTargets.forEach { (_, targets) ->
targets.singleOrNull()?.let { 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( 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}`" } 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 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( private fun createSyncTask(
@@ -332,6 +328,8 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val interopTask = project.tasks.named<CInteropProcess>(interop.interopProcessingTaskName).get() val interopTask = project.tasks.named<CInteropProcess>(interop.interopProcessingTaskName).get()
interopTask.onlyIf { HostManager.hostIsMac }
interopTask.dependsOn(defTask) interopTask.dependsOn(defTask)
pod.interopBindingDependencies.forEach { dependencyName -> pod.interopBindingDependencies.forEach { dependencyName ->
@@ -339,21 +337,20 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
} }
with(interop) { with(interop) {
defFileProperty.set(defTask.map { it.outputFile }) defFileProperty.set(defTask.flatMap { it.defFile.mapToFile() })
_packageNameProp.set(project.provider { pod.packageName }) _packageNameProp.set(project.provider { pod.packageName })
_extraOptsProp.addAll(project.provider { pod.extraOpts }) _extraOptsProp.addAll(project.provider { pod.extraOpts })
} }
if (HostManager.hostIsMac) { val podBuildTaskProvider = project.getPodBuildTaskProvider(target, pod)
val podBuildTaskProvider = project.getPodBuildTaskProvider(target, pod) val buildSettingsFileProvider = project.buildSettingsFileProvider(pod, target)
interopTask.inputs.file(podBuildTaskProvider.map { it.buildSettingsFile }) interopTask.inputs.file(buildSettingsFileProvider)
interopTask.dependsOn(podBuildTaskProvider) interopTask.dependsOn(podBuildTaskProvider)
}
interopTask.doFirst { _ -> interopTask.doFirst { _ ->
// Since we cannot expand the configuration phase of interop tasks // Since we cannot expand the configuration phase of interop tasks
// receiving the required environment variables happens on execution phase. // receiving the required environment variables happens on execution phase.
// TODO This needs to be fixed to improve UP-TO-DATE checks. val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFileProvider.getFile())
val podBuildSettings = project.getPodBuildSettingsProperties(target, pod)
podBuildSettings.cflags?.let { args -> podBuildSettings.cflags?.let { args ->
// Xcode quotes around paths with spaces. // 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.frameworkHeadersSearchPaths.map { "-I$it" })
interop.compilerOpts.addAll(podBuildSettings.frameworkSearchPaths.map { "-F$it" }) interop.compilerOpts.addAll(podBuildSettings.frameworkSearchPaths.map { "-F$it" })
} }
} }
} }
@@ -409,28 +405,19 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project: Project, project: Project,
cocoapodsExtension: CocoapodsExtension cocoapodsExtension: CocoapodsExtension
) { ) {
project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) { project.registerTask<PodspecTask>(POD_SPEC_TASK_NAME) { task ->
it.group = TASK_GROUP task.group = TASK_GROUP
it.description = "Generates a podspec file for CocoaPods import" task.description = "Generates a podspec file for CocoaPods import"
it.needPodspec = project.provider { cocoapodsExtension.needPodspec } task.outputDir.set(project.projectDir)
it.publishing.set(false) task.needPodspec.set(project.provider { cocoapodsExtension.needPodspec })
it.pods.set(cocoapodsExtension.pods) task.publishing.set(false)
it.version.set(cocoapodsExtension.version ?: project.version.toString())
it.specName.set(cocoapodsExtension.name) task.configure(cocoapodsExtension, project)
it.extraSpecAttributes.set(cocoapodsExtension.extraSpecAttributes)
it.outputDir.set(project.projectDir) task.gradleWrapperPath.set(project.gradleWrapperPath())
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 }
val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
if (generateWrapper) { if (generateWrapper) {
it.dependsOn(":wrapper") task.dependsOn(":wrapper")
} }
} }
} }
@@ -578,10 +565,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project.registerTask<PodSetupBuildTask>(sdk.toSetupBuildTaskName(pod)) { task -> project.registerTask<PodSetupBuildTask>(sdk.toSetupBuildTaskName(pod)) { task ->
task.group = TASK_GROUP task.group = TASK_GROUP
task.description = "Collect environment variables from .xcworkspace file" task.description = "Collect environment variables from .xcworkspace file"
task.pod = project.provider { pod } task.pod.set(pod)
task.sdk = project.provider { sdk } task.sdk.set(sdk)
task.podsXcodeProjDir = podInstallTask.map { it.podsXcodeProjDirProvider.get() } task.podsXcodeProjDir.set(podInstallTask.map { it.podsXcodeProjDirProvider.get() })
task.frameworkName = cocoapodsExtension.podFrameworkName task.frameworkName.set(cocoapodsExtension.podFrameworkName)
task.dependsOn(podInstallTask) task.dependsOn(podInstallTask)
} }
} }
@@ -616,13 +603,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val podSetupBuildTaskProvider = val podSetupBuildTaskProvider =
project.tasks.named(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java) project.tasks.named(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java)
project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) { project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) { task ->
it.group = TASK_GROUP task.group = TASK_GROUP
it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme" task.description = "Calls `xcodebuild` on xcworkspace for the pod scheme"
it.sdk = project.provider { sdk } task.buildSettingsFile.set(podSetupBuildTaskProvider.flatMap { it.buildSettingsFile })
it.pod = project.provider { pod } task.pod.set(pod)
it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() } task.sdk.set(sdk)
it.buildSettingsFile = podSetupBuildTaskProvider.map { task -> task.buildSettingsFile.get() } 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) { private fun configureLinkingOptions(project: Project, cocoapodsExtension: CocoapodsExtension, nativeBinary: NativeBinary) {
cocoapodsExtension.pods.all { pod -> cocoapodsExtension.pods.all { pod ->
nativeBinary.linkTaskProvider.configure { task -> nativeBinary.linkTaskProvider.configure { task ->
task.onlyIf { HostManager.hostIsMac }
val binary = task.binary 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 podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod)
val isExecutable = binary is AbstractExecutable val buildSettingsFileProvider = project.buildSettingsFileProvider(pod, binary.target)
val isDynamicFramework = (binary is Framework && !binary.isStatic) 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 frameworkFileName = pod.moduleName + ".framework"
val frameworkSearchPaths = podBuildSettings.frameworkSearchPaths val frameworkSearchPaths = podBuildSettings.frameworkSearchPaths
if (isExecutable || isDynamicFramework) { val linkerOpts = task.additionalLinkerOpts
if (isExecutable || isDynamicFramework.get()) {
val frameworkFileExists = frameworkSearchPaths.any { dir -> File(dir, frameworkFileName).exists() } val frameworkFileExists = frameworkSearchPaths.any { dir -> File(dir, frameworkFileName).exists() }
if (frameworkFileExists) binary.linkerOpts.addArg("-framework", pod.moduleName) if (frameworkFileExists) linkerOpts.addArg("-framework", pod.moduleName)
binary.linkerOpts.addAll(frameworkSearchPaths.map { "-F$it" }) 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, cocoapodsExtension: CocoapodsExtension,
xcFrameworkTask: TaskProvider<XCFrameworkTask>, xcFrameworkTask: TaskProvider<XCFrameworkTask>,
buildType: NativeBuildType buildType: NativeBuildType
): TaskProvider<PodspecTask> = ): TaskProvider<PodspecTask> {
with(project) { val task = project.registerTask<PodspecTask>(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "spec", buildType.getName())) { task ->
val task = task.description = "Generates podspec for ${buildType.getName().capitalizeAsciiOnly()} XCFramework publishing"
tasks.register(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "spec", buildType.getName()), PodspecTask::class.java) { task -> task.outputDir.set(xcFrameworkTask.map { it.outputDir.resolve(it.buildType.getName()) })
task.description = "Generates podspec for ${buildType.getName().capitalizeAsciiOnly()} XCFramework publishing" task.needPodspec.set(true)
task.outputDir.set(xcFrameworkTask.map { it.outputDir.resolve(it.buildType.getName()) }) task.publishing.set(true)
task.needPodspec = provider { true } task.source.set(project.provider { cocoapodsExtension.source })
task.publishing.set(true)
task.pods.set(cocoapodsExtension.pods) task.configure(cocoapodsExtension, project)
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
} }
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( private fun registerPodPublishFatFrameworkTasks(
project: Project, 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) { override fun apply(project: Project): Unit = with(project) {
pluginManager.withPlugin("kotlin-multiplatform") { pluginManager.withPlugin("kotlin-multiplatform") {
@@ -846,6 +865,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
} }
} }
companion object { companion object {
const val COCOAPODS_EXTENSION_NAME = "cocoapods" const val COCOAPODS_EXTENSION_NAME = "cocoapods"
const val TASK_GROUP = "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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" 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 { fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
with(Properties()) { with(Properties()) {
load(reader) load(reader)
@@ -7,20 +7,26 @@
package org.jetbrains.kotlin.gradle.tasks package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask 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.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
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
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.appendLine import org.jetbrains.kotlin.gradle.utils.appendLine
import org.jetbrains.kotlin.gradle.utils.getFile
import java.io.File import java.io.File
import javax.inject.Inject
/** /**
* Generates a def-file for the given CocoaPods dependency. * Generates a def-file for the given CocoaPods dependency.
*/ */
abstract class DefFileTask : DefaultTask() { abstract class DefFileTask @Inject constructor(projectLayout: ProjectLayout) : DefaultTask() {
@get:Nested @get:Nested
abstract val pod: Property<CocoapodsDependency> abstract val pod: Property<CocoapodsDependency>
@@ -29,13 +35,18 @@ abstract class DefFileTask : DefaultTask() {
abstract val useLibraries: Property<Boolean> abstract val useLibraries: Property<Boolean>
@get:OutputFile @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 val outputFile: File
get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def") get() = defFile.getFile()
@TaskAction @TaskAction
fun generate() { fun generate() {
outputFile.parentFile.mkdirs() val output = defFile.getFile()
outputFile.writeText(buildString { output.parentFile.mkdirs()
output.writeText(buildString {
appendLine("language = Objective-C") appendLine("language = Objective-C")
with(pod.get()) { with(pod.get()) {
when { when {
@@ -7,13 +7,16 @@
package org.jetbrains.kotlin.gradle.tasks package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.file.ProjectLayout
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.Input
import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.mapToFile
import java.io.File import java.io.File
import javax.inject.Inject
/** /**
* Creates a dummy framework in the target directory. * 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 * 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. * 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 @get:Input
abstract val frameworkName: Property<String> abstract val frameworkName: Property<String>
@@ -35,7 +38,7 @@ abstract class DummyFrameworkTask : DefaultTask() {
abstract val useStaticFramework: Property<Boolean> abstract val useStaticFramework: Property<Boolean>
@get:OutputDirectory @get:OutputDirectory
val outputFramework: Provider<File> = project.provider { project.cocoapodsBuildDirs.dummyFramework } val outputFramework: Provider<File> = projectLayout.cocoapodsBuildDirs.dummyFramework.mapToFile()
private val dummyFrameworkResource: String private val dummyFrameworkResource: String
get() { get() {
@@ -7,71 +7,80 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.file.FileCollection import org.gradle.api.file.*
import org.gradle.api.file.FileTree import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.* 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.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 org.jetbrains.kotlin.gradle.utils.runCommand
import java.io.File import org.jetbrains.kotlin.konan.target.Family
import java.util.* import javax.inject.Inject
/** /**
* The task compiles external cocoa pods sources. * 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 @get:InputFile
lateinit var buildSettingsFile: Provider<File> abstract val buildSettingsFile: RegularFileProperty
internal set
@get:Nested @get:Nested
internal lateinit var pod: Provider<CocoapodsDependency> internal abstract val pod: Property<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)
}
@get:Input @get:Input
internal lateinit var sdk: Provider<String> internal abstract val sdk: Property<String>
@Suppress("unused") // declares an ouptut @get:Input
@get:OutputFiles internal abstract val family: Property<Family>
internal val buildResult: Provider<FileCollection> = project.provider {
project.fileTree(buildDir.get()) { private val synthetic = projectLayout.cocoapodsBuildDirs.synthetic(family)
it.include("**/${pod.get().schemeName}.*/")
it.include("**/${pod.get().schemeName}/") @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 @get:Internal
internal lateinit var podsXcodeProjDir: Provider<File> internal abstract val podsXcodeProjDir: DirectoryProperty
@TaskAction @TaskAction
fun buildDependencies() { fun buildDependencies() {
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFile.getFile())
val podsXcodeProjDir = podsXcodeProjDir.get() val podsXcodeProjDir = podsXcodeProjDir.get()
val podXcodeBuildCommand = listOf( val podXcodeBuildCommand = listOf(
"xcodebuild", "xcodebuild",
"-project", podsXcodeProjDir.name, "-project", podsXcodeProjDir.asFile.name,
"-scheme", pod.get().schemeName, "-scheme", pod.get().schemeName,
"-sdk", sdk.get(), "-sdk", sdk.get(),
"-configuration", podBuildSettings.configuration "-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 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.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider 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.gradle.utils.XcodeVersion
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.Family
import java.io.File import java.io.File
import javax.inject.Inject
/** /**
* The task generates a synthetic project with all cocoapods dependencies * The task generates a synthetic project with all cocoapods dependencies
*/ */
abstract class PodGenTask : CocoapodsTask() { abstract class PodGenTask @Inject constructor(projectLayout: ProjectLayout) : CocoapodsTask() {
init { init {
onlyIf { onlyIf {
@@ -54,7 +56,7 @@ abstract class PodGenTask : CocoapodsTask() {
internal abstract val xcodeVersion: Property<XcodeVersion> internal abstract val xcodeVersion: Property<XcodeVersion>
@get:OutputFile @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 @TaskAction
fun generate() { fun generate() {
@@ -7,6 +7,7 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks 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.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider 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.MissingCocoapodsMessage
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingSpecReposMessage import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingSpecReposMessage
import java.io.File import java.io.File
import javax.inject.Inject
abstract class PodInstallTask : AbstractPodInstallTask() { abstract class PodInstallTask @Inject constructor(projectLayout: ProjectLayout) : AbstractPodInstallTask() {
@get:Optional @get:Optional
@get:InputFile @get:InputFile
@@ -36,7 +38,7 @@ abstract class PodInstallTask : AbstractPodInstallTask() {
@get:InputDirectory @get:InputDirectory
abstract val dummyFramework: Property<File> 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() } } private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
override fun doPodInstall() { override fun doPodInstall() {
@@ -7,33 +7,34 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks 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.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.getFile
import org.jetbrains.kotlin.gradle.utils.runCommand import org.jetbrains.kotlin.gradle.utils.runCommand
import java.io.File import java.io.File
import javax.inject.Inject
open class PodSetupBuildTask : CocoapodsTask() { abstract class PodSetupBuildTask @Inject constructor(projectLayout: ProjectLayout) : CocoapodsTask() {
@get:Input @get:Input
lateinit var frameworkName: Provider<String> abstract val frameworkName: Property<String>
@get:Input @get:Input
internal lateinit var sdk: Provider<String> internal abstract val sdk: Property<String>
@get:Nested @get:Nested
lateinit var pod: Provider<CocoapodsDependency> abstract val pod: Property<CocoapodsDependency>
@get:OutputFile
val buildSettingsFile: Provider<File> = project.provider {
project.cocoapodsBuildDirs
.buildSettings
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
}
@get:Internal @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 @TaskAction
fun setupBuild() { fun setupBuild() {
@@ -46,15 +47,12 @@ open class PodSetupBuildTask : CocoapodsTask() {
"-sdk", sdk.get() "-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()) val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
buildSettingsFile.get().let { bsf -> buildSettingsFile.getFile().let { bsf ->
buildSettingsProperties.writeSettings(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. * 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 package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.Project 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.provider.Provider
import org.gradle.api.tasks.* 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.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.PodspecPlatformSettings 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
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.GENERATE_WRAPPER_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME 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.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.utils.getFile
import java.io.File import java.io.File
import javax.inject.Inject
/** /**
* The task generates a podspec file which allows a user to * The task generates a podspec file which allows a user to
* integrate a Kotlin/Native framework into a CocoaPods project. * 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 @get:Input
internal val specName = project.objects.property(String::class.java) internal abstract val specName: Property<String>
@get:Internal @get:Internal
internal val outputDir = project.objects.property(File::class.java) internal abstract val outputDir: Property<File>
@get:OutputFile @get:OutputFile
val outputFile: File val outputFile: File
get() = outputDir.get().resolve("${specName.get()}.podspec") get() = outputDir.get().resolve("${specName.get()}.podspec")
@get:Input @get:Input
internal lateinit var needPodspec: Provider<Boolean> internal abstract val needPodspec: Property<Boolean>
@get:Nested @get:Nested
val pods = project.objects.listProperty(CocoapodsDependency::class.java) abstract val pods: ListProperty<CocoapodsDependency>
@get:Input @get:Input
internal val version = project.objects.property(String::class.java) internal abstract val version: Property<String>
@get:Input @get:Input
internal val publishing = project.objects.property(Boolean::class.java) internal abstract val publishing: Property<Boolean>
@get:Input @get:Input
@get:Optional @get:Optional
internal val source = project.objects.property(String::class.java) internal abstract val source: Property<String>
@get:Input @get:Input
@get:Optional @get:Optional
internal val homepage = project.objects.property(String::class.java) internal abstract val homepage: Property<String>
@get:Input @get:Input
@get:Optional @get:Optional
internal val license = project.objects.property(String::class.java) internal abstract val license: Property<String>
@get:Input @get:Input
@get:Optional @get:Optional
internal val authors = project.objects.property(String::class.java) internal abstract val authors: Property<String>
@get:Input @get:Input
@get:Optional @get:Optional
internal val summary = project.objects.property(String::class.java) internal abstract val summary: Property<String>
@get:Input @get:Input
@get:Optional @get:Optional
internal val extraSpecAttributes = project.objects.mapProperty(String::class.java, String::class.java) internal abstract val extraSpecAttributes: MapProperty<String, String>
@get:Input @get:Input
internal lateinit var frameworkName: Provider<String> internal abstract val frameworkName: Property<String>
@get:Nested @get:Nested
internal lateinit var ios: Provider<PodspecPlatformSettings> internal abstract val ios: Property<PodspecPlatformSettings>
@get:Nested @get:Nested
internal lateinit var osx: Provider<PodspecPlatformSettings> internal abstract val osx: Property<PodspecPlatformSettings>
@get:Nested @get:Nested
internal lateinit var tvos: Provider<PodspecPlatformSettings> internal abstract val tvos: Property<PodspecPlatformSettings>
@get:Nested @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 { init {
onlyIf { needPodspec.get() } onlyIf { needPodspec.get() }
@@ -107,17 +117,6 @@ open class PodspecTask : DefaultTask() {
""".trimIndent() """.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 { val deploymentTargets = run {
listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") { 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}'" 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" "| spec.dependency '${pod.name}'$versionSuffix"
}.joinToString(separator = "\n") }.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 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'" 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 val xcConfig = if (publishing.get() || extraSpecAttributes.get().containsKey("pod_target_xcconfig")) "" else
""" | """ |
| spec.pod_target_xcconfig = { | spec.pod_target_xcconfig = {
| 'KOTLIN_PROJECT_PATH' => '${if (project.depth != 0) project.path else ""}', | 'KOTLIN_PROJECT_PATH' => '${projectPath.get()}',
| 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}', | 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}',
| } | }
""".trimMargin() """.trimMargin()
val gradleCommand = "\$REPO_ROOT/${gradleWrapper.relativeTo(project.projectDir).invariantSeparatorsPath}"
val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else
""" | """ |
| spec.script_phases = [ | spec.script_phases = [
@@ -158,7 +156,7 @@ open class PodspecTask : DefaultTask() {
| fi | fi
| set -ev | set -ev
| REPO_ROOT="${'$'}PODS_TARGET_SRCROOT" | 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.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \
| -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \ | -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \
| -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION" | -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION"
@@ -191,34 +189,39 @@ open class PodspecTask : DefaultTask() {
""".trimMargin() """.trimMargin()
) )
if (hasPodfileOwnOrParent(project) && publishing.get().not()) { if (hasPodfile.get() && !publishing.get()) {
logger.quiet( logger.quiet(
""" """
Generated a podspec file at: ${absolutePath}. Generated a podspec file at: ${absolutePath}.
To include it in your Xcode project, check that the following dependency snippet exists in your Podfile: To include it in your Xcode project, check that the following dependency snippet exists in your Podfile:
pod '${specName.get()}', :path => '${parentFile.absolutePath}' pod '${specName.get()}', :path => '${parentFile.absolutePath}'
""".trimIndent()
""".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 Provider<String>.getOrEmpty(): String = getOrElse("")
private fun String.surroundWithSingleQuotesIfNeeded(): String = private fun String.surroundWithSingleQuotesIfNeeded(): String =
if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'" 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 @Inject
constructor( constructor(
@Internal @Internal
@Transient // This property can't be accessed in the execution phase
val binary: NativeBinary, val binary: NativeBinary,
private val objectFactory: ObjectFactory, private val objectFactory: ObjectFactory,
private val execOperations: ExecOperations private val execOperations: ExecOperations,
) : AbstractKotlinCompileTool<K2NativeCompilerArguments>(objectFactory), ) : AbstractKotlinCompileTool<K2NativeCompilerArguments>(objectFactory),
UsesKonanPropertiesBuildService, UsesKonanPropertiesBuildService,
KotlinToolTask<KotlinCommonCompilerToolOptions> { KotlinToolTask<KotlinCommonCompilerToolOptions> {
@Deprecated("Visibility will be lifted to private in the future releases") @Deprecated("Visibility will be lifted to private in the future releases")
@get:Internal @get:Internal
val compilation: KotlinNativeCompilation val compilation: KotlinNativeCompilation
@@ -85,16 +87,19 @@ constructor(
) )
@get:Input @get:Input
val outputKind: CompilerOutputKind get() = binary.outputKind.compilerOutputKind val outputKind: CompilerOutputKind by lazyConvention { binary.outputKind.compilerOutputKind }
@get:Input @get:Input
val optimized: Boolean get() = binary.optimized val optimized: Boolean by lazyConvention { binary.optimized }
@get:Input @get:Input
val debuggable: Boolean get() = binary.debuggable val debuggable: Boolean by lazyConvention { binary.debuggable }
@get:Input @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") @Suppress("DEPRECATION")
private val konanTarget = compilation.konanTarget private val konanTarget = compilation.konanTarget
@@ -132,24 +137,29 @@ constructor(
} }
fun kotlinOptions(fn: Closure<*>) { fun kotlinOptions(fn: Closure<*>) {
@Suppress("DEPRECATION")
fn.delegate = kotlinOptions fn.delegate = kotlinOptions
fn.call() fn.call()
} }
// Binary-specific options. // Binary-specific options.
@get:Input private val _entryPoint: String by lazyConvention { (binary as? Executable)?.entryPoint.orEmpty() }
@get:Optional
val entryPoint: String? get() = (binary as? Executable)?.entryPoint
@get:Input @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 @get:Input
val binaryOptions: Map<String, String> by lazy { PropertiesProvider(project).nativeBinaryOptions + binary.binaryOptions } val binaryOptions: Map<String, String> by lazy { PropertiesProvider(project).nativeBinaryOptions + binary.binaryOptions }
@get:Input @get:Input
val processTests: Boolean get() = binary is TestExecutable val processTests: Boolean by lazyConvention { binary is TestExecutable }
@get:Classpath @get:Classpath
val exportLibraries: FileCollection get() = exportLibrariesResolvedConfiguration?.files ?: objectFactory.fileCollection() val exportLibraries: FileCollection get() = exportLibrariesResolvedConfiguration?.files ?: objectFactory.fileCollection()
@@ -161,8 +171,7 @@ constructor(
} }
@get:Input @get:Input
val isStaticFramework: Boolean val isStaticFramework: Boolean by lazyConvention { binary.let { it is Framework && it.isStatic } }
get() = binary.let { it is Framework && it.isStatic }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@get:Input @get:Input
@@ -221,7 +230,7 @@ constructor(
null, BitcodeEmbeddingMode.DISABLE -> Unit null, BitcodeEmbeddingMode.DISABLE -> Unit
} }
args.singleLinkerArguments = linkerOpts.toTypedArray() args.singleLinkerArguments = (linkerOpts + additionalLinkerOpts).toTypedArray()
args.binaryOptions = binaryOptions.map { (key, value) -> "$key=$value" }.toTypedArray() args.binaryOptions = binaryOptions.map { (key, value) -> "$key=$value" }.toTypedArray()
args.staticFramework = isStaticFramework 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 $failedDependenciesList
| |
@@ -378,4 +387,8 @@ constructor(
executionContext = executionContext executionContext = executionContext
).run(buildArguments) ).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 konanCacheKind: NativeCacheKind,
val libraries: FileCollection, val libraries: FileCollection,
val gradleUserHomeDir: File, val gradleUserHomeDir: File,
val binary: NativeBinary,
val konanTarget: KonanTarget, val konanTarget: KonanTarget,
val toolOptions: KotlinCommonCompilerToolOptions, val toolOptions: KotlinCommonCompilerToolOptions,
val externalDependenciesArgs: List<String> val externalDependenciesArgs: List<String>,
val debuggable: Boolean,
val optimized: Boolean,
) { ) {
val rootCacheDirectory val rootCacheDirectory
get() = getRootCacheDirectory( get() = getRootCacheDirectory(
File(runnerSettings.parent.konanHome), File(runnerSettings.parent.konanHome),
konanTarget, konanTarget,
binary.debuggable, debuggable,
konanCacheKind konanCacheKind
) )
@@ -748,7 +749,11 @@ internal class CacheBuilder(
konanCacheKind = konanCacheKind, konanCacheKind = konanCacheKind,
libraries = binary.compilation.compileDependencyFiles, libraries = binary.compilation.compileDependencyFiles,
gradleUserHomeDir = project.gradle.gradleUserHomeDir, 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) listOf(KLIB_INTEROP_IR_PROVIDER_IDENTIFIER)
) )
private val binary: NativeBinary
get() = settings.binary
private val konanTarget: KonanTarget private val konanTarget: KonanTarget
get() = settings.konanTarget get() = settings.konanTarget
private val optimized: Boolean private val optimized: Boolean
get() = binary.optimized get() = settings.optimized
private val debuggable: Boolean private val debuggable: Boolean
get() = binary.debuggable get() = settings.debuggable
private val konanCacheKind: NativeCacheKind private val konanCacheKind: NativeCacheKind
get() = settings.konanCacheKind get() = settings.konanCacheKind
@@ -6,7 +6,9 @@
package org.jetbrains.kotlin.gradle.utils package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.nio.file.Files 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