[Gradle] Fix invalidation framework after sync while preserving 'generateDummyFramework' task semantic
^KT-49430 Verification Pending
This commit is contained in:
committed by
Space Team
parent
f96b5bae48
commit
caaade1f4f
+32
-10
@@ -17,10 +17,8 @@ import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
|
|||||||
import org.jetbrains.kotlin.gradle.util.replaceText
|
import org.jetbrains.kotlin.gradle.util.replaceText
|
||||||
import org.jetbrains.kotlin.gradle.util.runProcess
|
import org.jetbrains.kotlin.gradle.util.runProcess
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
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 java.io.File
|
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@@ -121,11 +119,19 @@ class CocoaPodsIT : KGPBaseTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("UTD after linking framework")
|
@DisplayName("UTD after syncing framework")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testImportUTDAfterLinkingFramework(gradleVersion: GradleVersion) {
|
fun testImportUTDAfterSyncingFramework(gradleVersion: GradleVersion) {
|
||||||
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) {
|
val buildOptions = defaultBuildOptions.copy(
|
||||||
val linkTaskName = ":linkPodDebugFrameworkIOS"
|
nativeOptions = defaultBuildOptions.nativeOptions.copy(
|
||||||
|
cocoapodsPlatform = "iphonesimulator",
|
||||||
|
cocoapodsArchs = "x86_64",
|
||||||
|
cocoapodsConfiguration = "Debug"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion, buildOptions = buildOptions) {
|
||||||
|
val syncTaskName = ":syncFramework"
|
||||||
|
|
||||||
buildGradleKts.addCocoapodsBlock(
|
buildGradleKts.addCocoapodsBlock(
|
||||||
"""
|
"""
|
||||||
@@ -142,12 +148,13 @@ class CocoaPodsIT : KGPBaseTest() {
|
|||||||
assertTasksExecuted(podInstallTaskName)
|
assertTasksExecuted(podInstallTaskName)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildWithCocoapodsWrapper(linkTaskName) {
|
buildWithCocoapodsWrapper(syncTaskName) {
|
||||||
assertTasksExecuted(linkTaskName)
|
assertTasksExecuted(syncTaskName)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildWithCocoapodsWrapper(podImportTaskName) {
|
buildWithCocoapodsWrapper(podImportTaskName) {
|
||||||
assertTasksUpToDate(dummyTaskName)
|
assertTasksExecuted(dummyTaskName)
|
||||||
|
assertOutputContains("Skipping dummy-framework generation because a dynamic framework is already present")
|
||||||
assertTasksUpToDate(podInstallTaskName)
|
assertTasksUpToDate(podInstallTaskName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +163,17 @@ class CocoaPodsIT : KGPBaseTest() {
|
|||||||
@DisplayName("Changing framework type and checks UTD")
|
@DisplayName("Changing framework type and checks UTD")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testChangeFrameworkTypeUTD(gradleVersion: GradleVersion) {
|
fun testChangeFrameworkTypeUTD(gradleVersion: GradleVersion) {
|
||||||
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) {
|
val buildOptions = defaultBuildOptions.copy(
|
||||||
|
nativeOptions = defaultBuildOptions.nativeOptions.copy(
|
||||||
|
cocoapodsPlatform = "iphonesimulator",
|
||||||
|
cocoapodsArchs = "x86_64",
|
||||||
|
cocoapodsConfiguration = "Debug"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion, buildOptions = buildOptions) {
|
||||||
|
val syncTaskName = ":syncFramework"
|
||||||
|
|
||||||
buildGradleKts.addCocoapodsBlock(
|
buildGradleKts.addCocoapodsBlock(
|
||||||
"""
|
"""
|
||||||
framework {
|
framework {
|
||||||
@@ -177,9 +194,14 @@ class CocoaPodsIT : KGPBaseTest() {
|
|||||||
assertTasksUpToDate(podInstallTaskName)
|
assertTasksUpToDate(podInstallTaskName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildWithCocoapodsWrapper(syncTaskName) {
|
||||||
|
assertTasksExecuted(syncTaskName)
|
||||||
|
}
|
||||||
|
|
||||||
buildGradleKts.addFrameworkBlock("isStatic = true")
|
buildGradleKts.addFrameworkBlock("isStatic = true")
|
||||||
buildWithCocoapodsWrapper(podImportTaskName) {
|
buildWithCocoapodsWrapper(podImportTaskName) {
|
||||||
assertTasksExecuted(dummyTaskName)
|
assertTasksExecuted(dummyTaskName)
|
||||||
|
assertOutputContains("Regenerating dummy-framework because present framework has different linkage")
|
||||||
assertTasksExecuted(podInstallTaskName)
|
assertTasksExecuted(podInstallTaskName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+66
-23
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.gradle.native
|
package org.jetbrains.kotlin.gradle.native
|
||||||
|
|
||||||
import org.gradle.util.GradleVersion
|
import org.gradle.util.GradleVersion
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.DUMMY_FRAMEWORK_TASK_NAME
|
||||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME
|
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME
|
||||||
import org.jetbrains.kotlin.gradle.testbase.*
|
import org.jetbrains.kotlin.gradle.testbase.*
|
||||||
import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
|
import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
|
||||||
@@ -46,6 +47,19 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
|||||||
mapOf("kotlin-library" to null)
|
mapOf("kotlin-library" to null)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@DisplayName("Checks xcodebuild for ios-app with a single framework with manual pod install")
|
||||||
|
@GradleTest
|
||||||
|
fun testXcodeUseFrameworksSingleWithManualPodInstall(gradleVersion: GradleVersion) {
|
||||||
|
nativeProject(cocoapodsSingleKtPod, gradleVersion, environmentVariables = environmentVariables) {
|
||||||
|
doTestXcode(
|
||||||
|
mode = ImportMode.FRAMEWORKS,
|
||||||
|
iosAppLocation = "ios-app",
|
||||||
|
subprojectsToFrameworkNamesMap = mapOf("kotlin-library" to null),
|
||||||
|
podInstall = ::manualPodInstall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@DisplayName("Checks xcodebuild for ios-app with a single framework with custom name")
|
@DisplayName("Checks xcodebuild for ios-app with a single framework with custom name")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testXcodeUseFrameworksWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
|
fun testXcodeUseFrameworksWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
|
||||||
@@ -66,7 +80,6 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
|||||||
mapOf("kotlin-library" to null)
|
mapOf("kotlin-library" to null)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework with custom name")
|
@DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework with custom name")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
|
fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
|
||||||
@@ -77,6 +90,21 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
|||||||
mapOf("kotlin-library" to "MultiplatformLibrary")
|
mapOf("kotlin-library" to "MultiplatformLibrary")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@DisplayName("Checks xcodebuild for ios-app with manual pod install and static framework")
|
||||||
|
@GradleTest
|
||||||
|
fun testXcodeWithManualPodInstallForStaticFramework(gradleVersion: GradleVersion) {
|
||||||
|
nativeProject(cocoapodsSingleKtPod, gradleVersion, environmentVariables = environmentVariables) {
|
||||||
|
buildGradleKts.addFrameworkBlock("isStatic = true")
|
||||||
|
|
||||||
|
doTestXcode(
|
||||||
|
mode = ImportMode.MODULAR_HEADERS,
|
||||||
|
iosAppLocation = "ios-app",
|
||||||
|
subprojectsToFrameworkNamesMap = mapOf("kotlin-library" to "MultiplatformLibrary"),
|
||||||
|
podInstall = ::manualPodInstall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@DisplayName("Checks xcodebuild for ios-app with kotlin library from root project")
|
@DisplayName("Checks xcodebuild for ios-app with kotlin library from root project")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testXcodeBuildsWithKotlinLibraryFromRootProject(gradleVersion: GradleVersion) {
|
fun testXcodeBuildsWithKotlinLibraryFromRootProject(gradleVersion: GradleVersion) {
|
||||||
@@ -163,6 +191,7 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
|||||||
iosAppLocation: String?,
|
iosAppLocation: String?,
|
||||||
subprojectsToFrameworkNamesMap: Map<String, String?>,
|
subprojectsToFrameworkNamesMap: Map<String, String?>,
|
||||||
arch: String = "x86_64",
|
arch: String = "x86_64",
|
||||||
|
podInstall: (taskPrefix: String, iosAppPath: Path) -> Unit = ::gradlePodInstall,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
gradleProperties
|
gradleProperties
|
||||||
@@ -189,32 +218,46 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
|||||||
|
|
||||||
// Generate podspec.
|
// Generate podspec.
|
||||||
build("$taskPrefix:podspec", buildOptions = buildOptions)
|
build("$taskPrefix:podspec", buildOptions = buildOptions)
|
||||||
iosAppLocation?.also {
|
if (iosAppLocation != null) {
|
||||||
// Set import mode for Podfile.
|
// Set import mode for Podfile.
|
||||||
preparePodfile(it, mode)
|
preparePodfile(iosAppLocation, mode)
|
||||||
// Install pods.
|
|
||||||
build("$taskPrefix:$POD_INSTALL_TASK_NAME", buildOptions = buildOptions)
|
|
||||||
|
|
||||||
projectPath.resolve(it).apply {
|
val iosAppPath = projectPath.resolve(iosAppLocation)
|
||||||
// Run Xcode build.
|
|
||||||
val xcodebuildResult = runProcess(
|
// Install pods.
|
||||||
cmd = listOf(
|
podInstall(taskPrefix, iosAppPath)
|
||||||
"xcodebuild",
|
|
||||||
"-sdk", "iphonesimulator",
|
// Run Xcode build.
|
||||||
"-configuration", "Release",
|
val xcodebuildResult = runProcess(
|
||||||
"-workspace", "$name.xcworkspace",
|
cmd = listOf(
|
||||||
"-scheme", name,
|
"xcodebuild",
|
||||||
"-arch", arch
|
"-sdk", "iphonesimulator",
|
||||||
),
|
"-configuration", "Release",
|
||||||
environmentVariables = environmentVariables.environmentalVariables,
|
"-workspace", "${iosAppPath.name}.xcworkspace",
|
||||||
workingDir = this.toFile(),
|
"-scheme", iosAppPath.name,
|
||||||
)
|
"-arch", arch
|
||||||
assertProcessRunResult(xcodebuildResult) {
|
),
|
||||||
assertEquals(0, exitCode, "Exit code mismatch for `xcodebuild`.")
|
environmentVariables = environmentVariables.environmentalVariables,
|
||||||
}
|
workingDir = iosAppPath.toFile(),
|
||||||
|
)
|
||||||
|
assertProcessRunResult(xcodebuildResult) {
|
||||||
|
assertEquals(0, exitCode, "Exit code mismatch for `xcodebuild`.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun TestProject.gradlePodInstall(taskPrefix: String, @Suppress("UNUSED_PARAMETER") iosAppPath: Path) {
|
||||||
|
build("$taskPrefix:$POD_INSTALL_TASK_NAME", buildOptions = buildOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TestProject.manualPodInstall(taskPrefix: String, iosAppPath: Path) {
|
||||||
|
build("$taskPrefix:$DUMMY_FRAMEWORK_TASK_NAME", buildOptions = buildOptions)
|
||||||
|
|
||||||
|
runProcess(
|
||||||
|
cmd = listOf("pod", "install"),
|
||||||
|
environmentVariables = environmentVariables.environmentalVariables,
|
||||||
|
workingDir = iosAppPath.toFile(),
|
||||||
|
)
|
||||||
|
}
|
||||||
+3
-2
@@ -10,6 +10,7 @@ import org.gradle.api.Action
|
|||||||
import org.gradle.api.Named
|
import org.gradle.api.Named
|
||||||
import org.gradle.api.NamedDomainObjectSet
|
import org.gradle.api.NamedDomainObjectSet
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.provider.Provider
|
||||||
import org.gradle.api.tasks.*
|
import org.gradle.api.tasks.*
|
||||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
|
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
|
||||||
@@ -118,8 +119,8 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
|
|||||||
anyFramework
|
anyFramework
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val podFrameworkName = anyPodFramework.map { it.baseName.asValidFrameworkName() }
|
internal val podFrameworkName: Provider<String> = anyPodFramework.map { it.baseName.asValidFrameworkName() }
|
||||||
internal val podFrameworkIsStatic = anyPodFramework.map { it.isStatic }
|
internal val podFrameworkIsStatic: Provider<Boolean> = anyPodFramework.map { it.isStatic }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure custom Xcode Configurations to Native Build Types mapping
|
* Configure custom Xcode Configurations to Native Build Types mapping
|
||||||
|
|||||||
+6
-6
@@ -60,8 +60,6 @@ internal class CocoapodsBuildDirs(private val layout: ProjectLayout) {
|
|||||||
get() = layout.buildDirectory.dir("cocoapods")
|
get() = layout.buildDirectory.dir("cocoapods")
|
||||||
val framework: Provider<Directory>
|
val framework: Provider<Directory>
|
||||||
get() = dir("framework")
|
get() = dir("framework")
|
||||||
val dummyFramework: Provider<Directory>
|
|
||||||
get() = dir("dummy.framework")
|
|
||||||
val defs: Provider<Directory>
|
val defs: Provider<Directory>
|
||||||
get() = dir("defs")
|
get() = dir("defs")
|
||||||
val publish: Provider<Directory>
|
val publish: Provider<Directory>
|
||||||
@@ -394,8 +392,9 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
|||||||
cocoapodsExtension: CocoapodsExtension
|
cocoapodsExtension: CocoapodsExtension
|
||||||
) {
|
) {
|
||||||
project.registerTask<DummyFrameworkTask>(DUMMY_FRAMEWORK_TASK_NAME) { task ->
|
project.registerTask<DummyFrameworkTask>(DUMMY_FRAMEWORK_TASK_NAME) { task ->
|
||||||
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
|
task.frameworkName.convention(cocoapodsExtension.podFrameworkName)
|
||||||
task.useStaticFramework.set(cocoapodsExtension.podFrameworkIsStatic)
|
task.useStaticFramework.convention(cocoapodsExtension.podFrameworkIsStatic)
|
||||||
|
task.outputFramework.convention(project.layout.cocoapodsBuildDirs.framework.map { it.dir(task.frameworkName.get() + ".framework") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,17 +477,18 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
|||||||
cocoapodsExtension: CocoapodsExtension
|
cocoapodsExtension: CocoapodsExtension
|
||||||
) {
|
) {
|
||||||
val podspecTaskProvider = project.tasks.named<PodspecTask>(POD_SPEC_TASK_NAME)
|
val podspecTaskProvider = project.tasks.named<PodspecTask>(POD_SPEC_TASK_NAME)
|
||||||
val dummyFrameworkTaskProvider = project.tasks.named<DummyFrameworkTask>(DUMMY_FRAMEWORK_TASK_NAME)
|
val dummyFrameworkTaskProvider = project.tasks.named(DUMMY_FRAMEWORK_TASK_NAME)
|
||||||
project.registerTask<PodInstallTask>(POD_INSTALL_TASK_NAME) { task ->
|
project.registerTask<PodInstallTask>(POD_INSTALL_TASK_NAME) { task ->
|
||||||
task.group = TASK_GROUP
|
task.group = TASK_GROUP
|
||||||
task.description = "Invokes `pod install` call within Podfile location directory"
|
task.description = "Invokes `pod install` call within Podfile location directory"
|
||||||
task.podfile.set(project.provider { cocoapodsExtension.podfile })
|
task.podfile.set(project.provider { cocoapodsExtension.podfile })
|
||||||
task.podspec.set(podspecTaskProvider.map { it.outputFile })
|
task.podspec.set(podspecTaskProvider.map { it.outputFile })
|
||||||
|
task.useStaticFramework.set(cocoapodsExtension.podFrameworkIsStatic)
|
||||||
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
|
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
|
||||||
task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
|
task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
|
||||||
task.pods.set(cocoapodsExtension.pods)
|
task.pods.set(cocoapodsExtension.pods)
|
||||||
task.dummyFramework.set(dummyFrameworkTaskProvider.map { it.outputFramework.get() })
|
|
||||||
task.dependsOn(podspecTaskProvider)
|
task.dependsOn(podspecTaskProvider)
|
||||||
|
task.dependsOn(dummyFrameworkTaskProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-17
@@ -7,17 +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.file.DirectoryProperty
|
||||||
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.OutputDirectory
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import org.gradle.work.DisableCachingByDefault
|
import org.gradle.work.DisableCachingByDefault
|
||||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
|
import org.jetbrains.kotlin.gradle.utils.MachO
|
||||||
import org.jetbrains.kotlin.gradle.utils.mapToFile
|
import org.jetbrains.kotlin.gradle.utils.getFile
|
||||||
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.
|
||||||
@@ -31,7 +30,7 @@ import javax.inject.Inject
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
@DisableCachingByDefault
|
@DisableCachingByDefault
|
||||||
abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayout) : DefaultTask() {
|
abstract class DummyFrameworkTask : DefaultTask() {
|
||||||
|
|
||||||
@get:Input
|
@get:Input
|
||||||
abstract val frameworkName: Property<String>
|
abstract val frameworkName: Property<String>
|
||||||
@@ -40,13 +39,18 @@ abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayo
|
|||||||
abstract val useStaticFramework: Property<Boolean>
|
abstract val useStaticFramework: Property<Boolean>
|
||||||
|
|
||||||
@get:OutputDirectory
|
@get:OutputDirectory
|
||||||
val outputFramework: Provider<File> = projectLayout.cocoapodsBuildDirs.dummyFramework.mapToFile()
|
abstract val outputFramework: DirectoryProperty
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
|
@Deprecated("Use outputFramework", replaceWith = ReplaceWith("outputFramework.get().asFile"))
|
||||||
|
val destinationDir: File
|
||||||
|
get() = outputFramework.getFile()
|
||||||
|
|
||||||
|
private val linkageName: String
|
||||||
|
get() = if (useStaticFramework.get()) "static" else "dynamic"
|
||||||
|
|
||||||
private val dummyFrameworkResource: String
|
private val dummyFrameworkResource: String
|
||||||
get() {
|
get() = "/cocoapods/$linkageName/dummy.framework/"
|
||||||
val staticOrDynamic = if (!useStaticFramework.get()) "dynamic" else "static"
|
|
||||||
return "/cocoapods/$staticOrDynamic/dummy.framework/"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyResource(from: String, to: File) {
|
private fun copyResource(from: String, to: File) {
|
||||||
to.parentFile.mkdirs()
|
to.parentFile.mkdirs()
|
||||||
@@ -71,23 +75,22 @@ abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayo
|
|||||||
private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) =
|
private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) =
|
||||||
copyResource(
|
copyResource(
|
||||||
"$dummyFrameworkResource$relativeFrom",
|
"$dummyFrameworkResource$relativeFrom",
|
||||||
outputFramework.get().resolve(relativeTo)
|
outputFramework.getFile().resolve(relativeTo)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun copyFrameworkTextFile(
|
private fun copyFrameworkTextFile(
|
||||||
relativeFrom: String,
|
relativeFrom: String,
|
||||||
relativeTo: String = relativeFrom,
|
relativeTo: String = relativeFrom,
|
||||||
transform: (String) -> String = { it }
|
transform: (String) -> String = { it },
|
||||||
) = copyTextResource(
|
) = copyTextResource(
|
||||||
"$dummyFrameworkResource$relativeFrom",
|
"$dummyFrameworkResource$relativeFrom",
|
||||||
outputFramework.get().resolve(relativeTo),
|
outputFramework.getFile().resolve(relativeTo),
|
||||||
transform
|
transform
|
||||||
)
|
)
|
||||||
|
|
||||||
@TaskAction
|
private fun copyFramework() {
|
||||||
fun create() {
|
|
||||||
// Reset the destination directory
|
// Reset the destination directory
|
||||||
with(outputFramework.get()) {
|
with(outputFramework.getFile()) {
|
||||||
deleteRecursively()
|
deleteRecursively()
|
||||||
mkdirs()
|
mkdirs()
|
||||||
}
|
}
|
||||||
@@ -104,4 +107,25 @@ abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun create() {
|
||||||
|
val framework = outputFramework.getFile()
|
||||||
|
val binary = framework.resolve(frameworkName.get())
|
||||||
|
|
||||||
|
return when {
|
||||||
|
!binary.exists() -> {
|
||||||
|
logger.info("Generating dummy-framework because the framework is missing")
|
||||||
|
copyFramework()
|
||||||
|
}
|
||||||
|
MachO.isDylib(binary, logger) == !useStaticFramework.get() -> {
|
||||||
|
logger.info("Skipping dummy-framework generation because a $linkageName framework is already present")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
logger.info("Regenerating dummy-framework because present framework has different linkage")
|
||||||
|
copyFramework()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-31
@@ -7,20 +7,16 @@
|
|||||||
|
|
||||||
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.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.CocoapodsExtension.SpecRepos
|
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos
|
||||||
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 @Inject constructor(projectLayout: ProjectLayout) : AbstractPodInstallTask() {
|
abstract class PodInstallTask : AbstractPodInstallTask() {
|
||||||
|
|
||||||
@get:Optional
|
@get:Optional
|
||||||
@get:InputFile
|
@get:InputFile
|
||||||
@@ -29,38 +25,15 @@ abstract class PodInstallTask @Inject constructor(projectLayout: ProjectLayout)
|
|||||||
@get:Input
|
@get:Input
|
||||||
abstract val frameworkName: Property<String>
|
abstract val frameworkName: Property<String>
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
abstract val useStaticFramework: Property<Boolean>
|
||||||
|
|
||||||
@get:Nested
|
@get:Nested
|
||||||
abstract val specRepos: Property<SpecRepos>
|
abstract val specRepos: Property<SpecRepos>
|
||||||
|
|
||||||
@get:Nested
|
@get:Nested
|
||||||
abstract val pods: ListProperty<CocoapodsDependency>
|
abstract val pods: ListProperty<CocoapodsDependency>
|
||||||
|
|
||||||
@get:InputDirectory
|
|
||||||
abstract val dummyFramework: Property<File>
|
|
||||||
|
|
||||||
private val framework = projectLayout.cocoapodsBuildDirs.framework.map { it.file("${frameworkName.get()}.framework").asFile }
|
|
||||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
|
||||||
|
|
||||||
override fun doPodInstall() {
|
|
||||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
|
||||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
|
||||||
framework.rename(tmpFramework)
|
|
||||||
dummyFramework.rename(framework)
|
|
||||||
super.doPodInstall()
|
|
||||||
framework.rename(dummyFramework)
|
|
||||||
tmpFramework.rename(framework)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
|
||||||
|
|
||||||
private fun File.rename(dest: File) {
|
|
||||||
if (!exists()) {
|
|
||||||
mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleError(retCode: Int, error: String, process: Process): String? {
|
override fun handleError(retCode: Int, error: String, process: Process): String? {
|
||||||
val specReposMessages = MissingSpecReposMessage(specRepos.get()).missingMessage
|
val specReposMessages = MissingSpecReposMessage(specRepos.get()).missingMessage
|
||||||
val cocoapodsMessages = pods.get().map { MissingCocoapodsMessage(it).missingMessage }
|
val cocoapodsMessages = pods.get().map { MissingCocoapodsMessage(it).missingMessage }
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.utils
|
||||||
|
|
||||||
|
import org.gradle.api.logging.Logger
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.fromUIntToLong
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.RandomAccessFile
|
||||||
|
|
||||||
|
// https://github.com/aidansteele/osx-abi-macho-file-format-reference
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
internal object MachO {
|
||||||
|
|
||||||
|
private const val FAT_MAGIC = 0xcafebabeL
|
||||||
|
private const val FAT_MAGIC_64 = 0xcafebabfL
|
||||||
|
|
||||||
|
private const val MH_CIGAM = 0xcefaedfeL
|
||||||
|
private const val MH_CIGAM_64 = 0xcffaedfeL
|
||||||
|
|
||||||
|
private const val MH_BILYD = 0x06000000L // MH_DYLIB = 0x6 converted for JVM
|
||||||
|
|
||||||
|
private const val FILE_TYPE_OFFSET = 12L
|
||||||
|
private const val FAT_FIRST_MACHO_OFFSET_OFFSET = 16L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the [file] is a Mach-O dynamic shared library
|
||||||
|
* or a Mach-O fat binary containing a number of dynamic libraries
|
||||||
|
*/
|
||||||
|
fun isDylib(file: File, logger: Logger): Boolean {
|
||||||
|
try {
|
||||||
|
RandomAccessFile(file, "r").use { raf ->
|
||||||
|
val magic = raf.readInt().fromUIntToLong()
|
||||||
|
|
||||||
|
val fileTypeOffset = when (magic) {
|
||||||
|
// all supported platforms are little-endian and JVM is big-endian, so we don't check MH_MAGIC[_64]
|
||||||
|
MH_CIGAM, MH_CIGAM_64 -> FILE_TYPE_OFFSET
|
||||||
|
FAT_MAGIC, FAT_MAGIC_64 -> {
|
||||||
|
raf.seek(FAT_FIRST_MACHO_OFFSET_OFFSET)
|
||||||
|
val firstMachoOffset = raf.readInt().fromUIntToLong() // guaranteed to be big-endian by the spec
|
||||||
|
firstMachoOffset + FILE_TYPE_OFFSET
|
||||||
|
}
|
||||||
|
else -> return false // not Mach-O or fat
|
||||||
|
}
|
||||||
|
|
||||||
|
raf.seek(fileTypeOffset)
|
||||||
|
val fileType = raf.readInt().toLong()
|
||||||
|
|
||||||
|
return fileType == MH_BILYD
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
logger.info("IOException while cheking if '$file' is a dylib", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("FunctionName")
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.gradle.unitTests
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.util.buildProject
|
||||||
|
import org.jetbrains.kotlin.gradle.utils.MachO
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class MachOTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val temporaryFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `dynamic dummy`() {
|
||||||
|
assertDylib(true, "/cocoapods/dynamic/dummy.framework/dummy")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `static dummy`() {
|
||||||
|
assertDylib(false, "/cocoapods/static/dummy.framework/dummy")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `dynamic fat`() {
|
||||||
|
assertDylib(true, "/machoBinaries/dynamicFat")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `static fat`() {
|
||||||
|
assertDylib(false, "/machoBinaries/staticFat")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `dynamic lib`() {
|
||||||
|
assertDylib(true, "/machoBinaries/dynamicLib")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `static lib`() {
|
||||||
|
assertDylib(false, "/machoBinaries/staticLib")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertDylib(expected: Boolean, resource: String) {
|
||||||
|
val tmp = temporaryFolder.newFile()
|
||||||
|
tmp.writeResource(resource)
|
||||||
|
return assertEquals(expected, MachO.isDylib(tmp, buildProject().logger))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.writeResource(resource: String) {
|
||||||
|
outputStream().use { out ->
|
||||||
|
MachOTest::class.java.getResourceAsStream(resource)!!.use { input: InputStream ->
|
||||||
|
input.copyTo(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user