diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index e0d1fa365bf..f6165b8dac2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.registerEmbedAndSignAppleFrameworkTask import org.jetbrains.kotlin.gradle.plugin.sources.* import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService @@ -107,6 +108,8 @@ class KotlinMultiplatformPlugin( exportProjectStructureMetadataForOtherBuilds(project) + project.registerEmbedAndSignAppleFrameworkTask() + SingleActionPerBuild.run(project.rootProject, "cleanup-processed-metadata") { if (isConfigurationCacheAvailable(project.gradle)) { BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry!!.onTaskCompletion( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt new file mode 100644 index 00000000000..2a7e9950afb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2010-2021 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.plugin.mpp.apple + +import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull +import org.jetbrains.kotlin.gradle.plugin.mpp.Framework +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType +import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost +import org.jetbrains.kotlin.gradle.tasks.dependsOn +import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask +import org.jetbrains.kotlin.gradle.tasks.registerTask +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly +import java.io.File + +private object XcodeEnvironment { + val buildType: NativeBuildType? + get() { + val configuration = System.getenv("CONFIGURATION") ?: return null + + fun String.toNativeBuildType() = when (this.toLowerCase()) { + "debug" -> NativeBuildType.DEBUG + "release" -> NativeBuildType.RELEASE + else -> null + } + + return configuration.toNativeBuildType() + ?: System.getenv("KOTLIN_FRAMEWORK_BUILD_TYPE")?.toNativeBuildType() + } + + val target: KonanTarget? + get() { + val sdk = System.getenv("SDK_NAME") ?: return null + + //todo support simulator architecture for Apple Silicon + return when { + sdk.startsWith("iphoneos") -> KonanTarget.IOS_ARM64 + sdk.startsWith("iphonesimulator") -> KonanTarget.IOS_X64 + sdk.startsWith("watchos") -> KonanTarget.WATCHOS_ARM64 + sdk.startsWith("watchsimulator") -> KonanTarget.WATCHOS_X64 + sdk.startsWith("appletvos") -> KonanTarget.TVOS_ARM64 + sdk.startsWith("appletvsimulator") -> KonanTarget.TVOS_X64 + else -> null + } + } + + val frameworkSearchDir: File? + get() { + val configuration = System.getenv("CONFIGURATION") ?: return null + val sdk = System.getenv("SDK_NAME") ?: return null + return File(configuration, sdk) + } + + val embeddedFrameworksDir: File? + get() { + val xcodeTargetBuildDir = System.getenv("TARGET_BUILD_DIR") ?: return null + val xcodeFrameworksFolderPath = System.getenv("FRAMEWORKS_FOLDER_PATH") ?: return null + return File(xcodeTargetBuildDir, xcodeFrameworksFolderPath) + } + + val sign: String? get() = System.getenv("EXPANDED_CODE_SIGN_IDENTITY") +} + +internal fun Project.registerAssembleAppleFrameworkTask(framework: Framework) { + if (!framework.konanTarget.family.isAppleFamily || !framework.konanTarget.enabledOnCurrentHost) return + + val frameworkBuildType = framework.buildType + val frameworkTarget = framework.target + val frameworkTaskName = lowerCamelCaseName( + "assemble", + framework.baseName, + frameworkBuildType.name.toLowerCaseAsciiOnly(), + "AppleFrameworkForXcode", + frameworkTarget.name + ) + + val envBuildType = XcodeEnvironment.buildType + val envTarget = XcodeEnvironment.target + val envFrameworkSearchDir = XcodeEnvironment.frameworkSearchDir + + if (envBuildType == null || envTarget == null || envFrameworkSearchDir == null) { + val envConfiguration = System.getenv("CONFIGURATION") + if (envTarget != null && envConfiguration != null) { + logger.warn( + "Unable to detect Kotlin framework build type for CONFIGURATION=$envConfiguration automatically. " + + "Specify 'KOTLIN_FRAMEWORK_BUILD_TYPE' to 'debug' or 'release'" + ) + } else { + logger.debug( + "Not registering $frameworkTaskName, since not called from Xcode " + + "('SDK_NAME' and 'CONFIGURATION' not provided)" + ) + } + return + } + + if (frameworkBuildType != envBuildType || frameworkTarget.konanTarget != envTarget) return + + umbrellaAssembleAppleFrameworkTask.dependsOn( + registerTask(frameworkTaskName) { task -> + task.group = BasePlugin.BUILD_GROUP + task.description = "Packs $frameworkBuildType ${frameworkTarget.name} framework for Xcode" + task.dependsOn(framework.linkTaskName) + task.from(framework.outputDirectory) + task.into(appleFrameworkDir(envFrameworkSearchDir)) + } + ) +} + +private const val UMBRELLA_ASSEMBLE_APPLE_FRAMEWORK = "assembleAppleFrameworkForXcode" +private val Project.umbrellaAssembleAppleFrameworkTask: TaskProvider + get() = locateOrRegisterTask(UMBRELLA_ASSEMBLE_APPLE_FRAMEWORK) { + it.group = "build" + it.description = "Build all frameworks as requested by Xcode's environment variables" + } + +private const val EMBED_AND_SIGN_APPLE_FRAMEWORK = "embedAndSignAppleFrameworkForXcode" +internal fun Project.registerEmbedAndSignAppleFrameworkTask() { + val envBuildType = XcodeEnvironment.buildType + val envTarget = XcodeEnvironment.target + val envEmbeddedFrameworksDir = XcodeEnvironment.embeddedFrameworksDir + val envFrameworkSearchDir = XcodeEnvironment.frameworkSearchDir + val envSign = XcodeEnvironment.sign + + if (envBuildType == null || envTarget == null || envEmbeddedFrameworksDir == null || envFrameworkSearchDir == null) { + logger.debug( + "Not registering $EMBED_AND_SIGN_APPLE_FRAMEWORK, since not called from Xcode " + + "('SDK_NAME', 'CONFIGURATION', 'TARGET_BUILD_DIR' and 'FRAMEWORKS_FOLDER_PATH' not provided)" + ) + return + } + + registerTask(EMBED_AND_SIGN_APPLE_FRAMEWORK) { task -> + task.group = "build" + task.description = "Embed and sign all frameworks as requested by Xcode's environment variables" + + val appleTargets: NamedDomainObjectCollection? = + multiplatformExtensionOrNull?.targets + ?.withType(KotlinNativeTarget::class.java) + ?.matching { it.konanTarget == envTarget } + + task.dependsOn(UMBRELLA_ASSEMBLE_APPLE_FRAMEWORK) + task.inputs.apply { + property("type", envBuildType) + property("target", envTarget) + property("embeddedFrameworksDir", envEmbeddedFrameworksDir) + property("sign", envSign) + } + + task.onlyIf { !appleTargets.isNullOrEmpty() } + + task.from(appleFrameworkDir(envFrameworkSearchDir)) { spec -> + appleTargets?.all { target -> + target.binaries.withType(Framework::class.java) + .matching { f -> f.buildType == envBuildType } + .all { framework -> + spec.include(framework.outputFile.name + "/**") + } + } + } + + task.into(envEmbeddedFrameworksDir) + + if (envSign != null) { + task.doLast { + appleTargets?.all { target -> + target.binaries.withType(Framework::class.java) + .matching { f -> f.buildType == envBuildType } + .all { framework -> + val binary = envEmbeddedFrameworksDir + .resolve(framework.outputFile.name) + .resolve(framework.outputFile.nameWithoutExtension) + exec { + it.commandLine("codesign", "--force", "--sign", envSign, "--", binary) + } + } + } + } + } + } +} + +private fun Project.appleFrameworkDir(frameworkSearchDir: File) = + buildDir.resolve("xcode-frameworks").resolve(frameworkSearchDir) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt index bfd8cb430e6..4d9436ec2a1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt @@ -9,7 +9,6 @@ package org.jetbrains.kotlin.gradle.plugin import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.UnknownDomainObjectException import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.Dependency @@ -29,6 +28,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPI import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.KOTLIN_NATIVE_IGNORE_INCORRECT_DEPENDENCIES import org.jetbrains.kotlin.gradle.plugin.mpp.* +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.registerAssembleAppleFrameworkTask import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.targets.native.* import org.jetbrains.kotlin.gradle.targets.native.internal.commonizeCInteropTask @@ -340,6 +340,10 @@ open class KotlinNativeTargetConfigurator( configureFrameworkExport(target) configureCInterops(target) + if (target.konanTarget.family.isAppleFamily) { + registerAssembleAppleFrameworkTasks(target) + } + if (PropertiesProvider(target.project).ignoreIncorrectNativeDependencies != true) { warnAboutIncorrectDependencies(target) } @@ -456,6 +460,13 @@ open class KotlinNativeTargetConfigurator( } } + private fun registerAssembleAppleFrameworkTasks(target: KotlinNativeTarget) { + val project = target.project + target.binaries.withType(Framework::class.java).all { framework -> + project.registerAssembleAppleFrameworkTask(framework) + } + } + override fun defineConfigurationsForTarget(target: T) { super.defineConfigurationsForTarget(target) implementationToApiElements(target)