Preserve symlinks during framework copy

This commit is contained in:
Rick Clephas
2021-12-11 22:54:10 +01:00
committed by teamcity
parent 46ab972cb1
commit 2c8086bbfe
5 changed files with 89 additions and 18 deletions
@@ -78,6 +78,32 @@ class AppleFrameworkIT : BaseGradleIT() {
}
}
@Test
fun `check that macOS framework has symlinks`() {
with(Project("sharedAppleFramework")) {
val options: BuildOptions = defaultBuildOptions().copy(
customEnvironmentVariables = mapOf(
"CONFIGURATION" to "debug",
"SDK_NAME" to "macosx",
"ARCHS" to "x86_64",
"EXPANDED_CODE_SIGN_IDENTITY" to "-",
"TARGET_BUILD_DIR" to workingDir.absolutePath,
"FRAMEWORKS_FOLDER_PATH" to "${projectName}/build/xcode-derived"
)
)
build(":shared:embedAndSignAppleFrameworkForXcode", options = options) {
assertSuccessful()
assertTasksExecuted(":shared:assembleDebugAppleFrameworkForXcodeMacosX64")
// Verify symlinks in gradle build folder
assertFileExists("/shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers")
assertFileIsSymlink("/shared/build/xcode-frameworks/debug/macosx/sdk.framework/Headers")
// Verify symlinks in xcode derived folder
assertFileExists("/build/xcode-derived/sdk.framework/Headers")
assertFileIsSymlink("/build/xcode-derived/sdk.framework/Headers")
}
}
}
@Test
fun `check embedAndSignAppleFrameworkForXcode fail`() {
with(Project("sharedAppleFramework")) {
@@ -31,6 +31,7 @@ import org.junit.Assert
import org.junit.Before
import org.junit.runner.RunWith
import java.io.File
import java.nio.file.Files
import java.util.regex.Pattern
import kotlin.io.path.isDirectory
import kotlin.test.*
@@ -559,6 +560,11 @@ abstract class BaseGradleIT {
return this
}
fun CompiledProject.assertFileIsSymlink(path: String = ""): CompiledProject {
assertTrue(Files.isSymbolicLink(fileInWorkingDir(path).toPath()), "The file [$path] isn't a symlink.")
return this
}
fun CompiledProject.assertNoSuchFile(path: String = ""): CompiledProject {
assertFalse(fileInWorkingDir(path).exists(), "The file [$path] exists.")
return this
@@ -6,10 +6,11 @@ plugins {
kotlin {
android()
val macosX64 = macosX64()
val iosX64 = iosX64()
val iosSimulatorArm64 = iosSimulatorArm64()
val iosArm64 = iosArm64()
configure(listOf(iosX64, iosSimulatorArm64, iosArm64)) {
configure(listOf(macosX64, iosX64, iosSimulatorArm64, iosArm64)) {
binaries {
framework {
baseName = "sdk"
@@ -0,0 +1,5 @@
package com.github.jetbrains.myapplication
actual class Platform actual constructor() {
actual val platform: String = "macOS"
}
@@ -6,10 +6,18 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.apple
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
@@ -17,7 +25,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -117,12 +124,12 @@ private fun Project.registerAssembleAppleFrameworkTask(framework: Framework): Ta
}.also {
it.configure { task -> task.from(framework) }
}
else -> registerTask<Copy>(frameworkTaskName) { task ->
else -> registerTask<FrameworkCopy>(frameworkTaskName) { task ->
task.description = "Packs $frameworkBuildType ${frameworkTarget.name} framework for Xcode"
task.isEnabled = frameworkBuildType == envBuildType
task.dependsOn(framework.linkTaskName)
task.from(framework.outputDirectory)
task.into(appleFrameworkDir(envFrameworkSearchDir))
task.files = files({ framework.outputDirectory.listFiles() })
task.destDir = appleFrameworkDir(envFrameworkSearchDir)
}
}
}
@@ -160,7 +167,7 @@ internal fun Project.registerEmbedAndSignAppleFrameworkTask(framework: Framework
return
}
val embedAndSignTask = locateOrRegisterTask<Copy>(frameworkTaskName) { task ->
val embedAndSignTask = locateOrRegisterTask<FrameworkCopy>(frameworkTaskName) { task ->
task.group = BasePlugin.BUILD_GROUP
task.description = "Embed and sign ${framework.namePrefix} framework as requested by Xcode's environment variables"
task.inputs.apply {
@@ -176,17 +183,14 @@ internal fun Project.registerEmbedAndSignAppleFrameworkTask(framework: Framework
embedAndSignTask.configure { task ->
task.dependsOn(assembleTask)
task.from(appleFrameworkDir(envFrameworkSearchDir)) { it.include(framework.outputFile.name + "/**") }
task.into(envEmbeddedFrameworksDir)
if (envSign != null) {
task.doLast {
val binary = envEmbeddedFrameworksDir
.resolve(framework.outputFile.name)
.resolve(framework.outputFile.nameWithoutExtension)
exec {
it.commandLine("codesign", "--force", "--sign", envSign, "--", binary)
}
task.files = files(File(appleFrameworkDir(envFrameworkSearchDir), framework.outputFile.name))
task.destDir = envEmbeddedFrameworksDir
if (envSign != null) task.doLast {
val binary = envEmbeddedFrameworksDir
.resolve(framework.outputFile.name)
.resolve(framework.outputFile.nameWithoutExtension)
exec {
it.commandLine("codesign", "--force", "--sign", envSign, "--", binary)
}
}
}
@@ -200,4 +204,33 @@ private val Framework.namePrefix: String
)
private fun Project.appleFrameworkDir(frameworkSearchDir: File) =
buildDir.resolve("xcode-frameworks").resolve(frameworkSearchDir)
buildDir.resolve("xcode-frameworks").resolve(frameworkSearchDir)
/**
* macOS frameworks contain symlinks which are resolved/removed by the Gradle [Copy] task.
* To preserve these symlinks we are using the `cp` command instead.
* See https://youtrack.jetbrains.com/issue/KT-48594.
*/
private abstract class FrameworkCopy : DefaultTask() {
@get:InputFiles
@get:SkipWhenEmpty
@get:IgnoreEmptyDirectories
@get:PathSensitive(PathSensitivity.ABSOLUTE)
abstract var files: FileCollection
@get:OutputDirectory
abstract var destDir: File
@TaskAction
fun copy() {
destDir.mkdirs()
files.forEach { file ->
File(destDir, file.name).let destFile@{ destFile ->
if (!destFile.exists()) return@destFile
project.exec { it.commandLine("rm", "-r", destFile.absolutePath) }
}
project.exec { it.commandLine("cp", "-R", file.absolutePath, destDir.absolutePath) }
}
}
}