[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.runProcess
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.io.path.*
|
||||
import kotlin.test.*
|
||||
@@ -121,11 +119,19 @@ class CocoaPodsIT : KGPBaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("UTD after linking framework")
|
||||
@DisplayName("UTD after syncing framework")
|
||||
@GradleTest
|
||||
fun testImportUTDAfterLinkingFramework(gradleVersion: GradleVersion) {
|
||||
nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) {
|
||||
val linkTaskName = ":linkPodDebugFrameworkIOS"
|
||||
fun testImportUTDAfterSyncingFramework(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(
|
||||
"""
|
||||
@@ -142,12 +148,13 @@ class CocoaPodsIT : KGPBaseTest() {
|
||||
assertTasksExecuted(podInstallTaskName)
|
||||
}
|
||||
|
||||
buildWithCocoapodsWrapper(linkTaskName) {
|
||||
assertTasksExecuted(linkTaskName)
|
||||
buildWithCocoapodsWrapper(syncTaskName) {
|
||||
assertTasksExecuted(syncTaskName)
|
||||
}
|
||||
|
||||
buildWithCocoapodsWrapper(podImportTaskName) {
|
||||
assertTasksUpToDate(dummyTaskName)
|
||||
assertTasksExecuted(dummyTaskName)
|
||||
assertOutputContains("Skipping dummy-framework generation because a dynamic framework is already present")
|
||||
assertTasksUpToDate(podInstallTaskName)
|
||||
}
|
||||
}
|
||||
@@ -156,7 +163,17 @@ class CocoaPodsIT : KGPBaseTest() {
|
||||
@DisplayName("Changing framework type and checks UTD")
|
||||
@GradleTest
|
||||
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(
|
||||
"""
|
||||
framework {
|
||||
@@ -177,9 +194,14 @@ class CocoaPodsIT : KGPBaseTest() {
|
||||
assertTasksUpToDate(podInstallTaskName)
|
||||
}
|
||||
|
||||
buildWithCocoapodsWrapper(syncTaskName) {
|
||||
assertTasksExecuted(syncTaskName)
|
||||
}
|
||||
|
||||
buildGradleKts.addFrameworkBlock("isStatic = true")
|
||||
buildWithCocoapodsWrapper(podImportTaskName) {
|
||||
assertTasksExecuted(dummyTaskName)
|
||||
assertOutputContains("Regenerating dummy-framework because present framework has different linkage")
|
||||
assertTasksExecuted(podInstallTaskName)
|
||||
}
|
||||
|
||||
|
||||
+66
-23
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.gradle.native
|
||||
|
||||
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.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
|
||||
@@ -46,6 +47,19 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
||||
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")
|
||||
@GradleTest
|
||||
fun testXcodeUseFrameworksWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
|
||||
@@ -66,7 +80,6 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
||||
mapOf("kotlin-library" to null)
|
||||
)
|
||||
|
||||
|
||||
@DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework with custom name")
|
||||
@GradleTest
|
||||
fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
|
||||
@@ -77,6 +90,21 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
||||
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")
|
||||
@GradleTest
|
||||
fun testXcodeBuildsWithKotlinLibraryFromRootProject(gradleVersion: GradleVersion) {
|
||||
@@ -163,6 +191,7 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
||||
iosAppLocation: String?,
|
||||
subprojectsToFrameworkNamesMap: Map<String, String?>,
|
||||
arch: String = "x86_64",
|
||||
podInstall: (taskPrefix: String, iosAppPath: Path) -> Unit = ::gradlePodInstall,
|
||||
) {
|
||||
|
||||
gradleProperties
|
||||
@@ -189,32 +218,46 @@ class CocoaPodsXcodeIT : KGPBaseTest() {
|
||||
|
||||
// Generate podspec.
|
||||
build("$taskPrefix:podspec", buildOptions = buildOptions)
|
||||
iosAppLocation?.also {
|
||||
if (iosAppLocation != null) {
|
||||
// Set import mode for Podfile.
|
||||
preparePodfile(it, mode)
|
||||
// Install pods.
|
||||
build("$taskPrefix:$POD_INSTALL_TASK_NAME", buildOptions = buildOptions)
|
||||
preparePodfile(iosAppLocation, mode)
|
||||
|
||||
projectPath.resolve(it).apply {
|
||||
// Run Xcode build.
|
||||
val xcodebuildResult = runProcess(
|
||||
cmd = listOf(
|
||||
"xcodebuild",
|
||||
"-sdk", "iphonesimulator",
|
||||
"-configuration", "Release",
|
||||
"-workspace", "$name.xcworkspace",
|
||||
"-scheme", name,
|
||||
"-arch", arch
|
||||
),
|
||||
environmentVariables = environmentVariables.environmentalVariables,
|
||||
workingDir = this.toFile(),
|
||||
)
|
||||
assertProcessRunResult(xcodebuildResult) {
|
||||
assertEquals(0, exitCode, "Exit code mismatch for `xcodebuild`.")
|
||||
}
|
||||
val iosAppPath = projectPath.resolve(iosAppLocation)
|
||||
|
||||
// Install pods.
|
||||
podInstall(taskPrefix, iosAppPath)
|
||||
|
||||
// Run Xcode build.
|
||||
val xcodebuildResult = runProcess(
|
||||
cmd = listOf(
|
||||
"xcodebuild",
|
||||
"-sdk", "iphonesimulator",
|
||||
"-configuration", "Release",
|
||||
"-workspace", "${iosAppPath.name}.xcworkspace",
|
||||
"-scheme", iosAppPath.name,
|
||||
"-arch", arch
|
||||
),
|
||||
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.NamedDomainObjectSet
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
|
||||
@@ -118,8 +119,8 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje
|
||||
anyFramework
|
||||
}
|
||||
|
||||
internal val podFrameworkName = anyPodFramework.map { it.baseName.asValidFrameworkName() }
|
||||
internal val podFrameworkIsStatic = anyPodFramework.map { it.isStatic }
|
||||
internal val podFrameworkName: Provider<String> = anyPodFramework.map { it.baseName.asValidFrameworkName() }
|
||||
internal val podFrameworkIsStatic: Provider<Boolean> = anyPodFramework.map { it.isStatic }
|
||||
|
||||
/**
|
||||
* 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")
|
||||
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>
|
||||
@@ -394,8 +392,9 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
cocoapodsExtension: CocoapodsExtension
|
||||
) {
|
||||
project.registerTask<DummyFrameworkTask>(DUMMY_FRAMEWORK_TASK_NAME) { task ->
|
||||
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
|
||||
task.useStaticFramework.set(cocoapodsExtension.podFrameworkIsStatic)
|
||||
task.frameworkName.convention(cocoapodsExtension.podFrameworkName)
|
||||
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
|
||||
) {
|
||||
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 ->
|
||||
task.group = TASK_GROUP
|
||||
task.description = "Invokes `pod install` call within Podfile location directory"
|
||||
task.podfile.set(project.provider { cocoapodsExtension.podfile })
|
||||
task.podspec.set(podspecTaskProvider.map { it.outputFile })
|
||||
task.useStaticFramework.set(cocoapodsExtension.podFrameworkIsStatic)
|
||||
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
|
||||
task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
|
||||
task.pods.set(cocoapodsExtension.pods)
|
||||
task.dummyFramework.set(dummyFrameworkTaskProvider.map { it.outputFramework.get() })
|
||||
task.dependsOn(podspecTaskProvider)
|
||||
task.dependsOn(dummyFrameworkTaskProvider)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-17
@@ -7,17 +7,16 @@
|
||||
package org.jetbrains.kotlin.gradle.tasks
|
||||
|
||||
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.Provider
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.work.DisableCachingByDefault
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
|
||||
import org.jetbrains.kotlin.gradle.utils.mapToFile
|
||||
import org.jetbrains.kotlin.gradle.utils.MachO
|
||||
import org.jetbrains.kotlin.gradle.utils.getFile
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@DisableCachingByDefault
|
||||
abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayout) : DefaultTask() {
|
||||
abstract class DummyFrameworkTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
abstract val frameworkName: Property<String>
|
||||
@@ -40,13 +39,18 @@ abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayo
|
||||
abstract val useStaticFramework: Property<Boolean>
|
||||
|
||||
@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
|
||||
get() {
|
||||
val staticOrDynamic = if (!useStaticFramework.get()) "dynamic" else "static"
|
||||
return "/cocoapods/$staticOrDynamic/dummy.framework/"
|
||||
}
|
||||
get() = "/cocoapods/$linkageName/dummy.framework/"
|
||||
|
||||
private fun copyResource(from: String, to: File) {
|
||||
to.parentFile.mkdirs()
|
||||
@@ -71,23 +75,22 @@ abstract class DummyFrameworkTask @Inject constructor(projectLayout: ProjectLayo
|
||||
private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) =
|
||||
copyResource(
|
||||
"$dummyFrameworkResource$relativeFrom",
|
||||
outputFramework.get().resolve(relativeTo)
|
||||
outputFramework.getFile().resolve(relativeTo)
|
||||
)
|
||||
|
||||
private fun copyFrameworkTextFile(
|
||||
relativeFrom: String,
|
||||
relativeTo: String = relativeFrom,
|
||||
transform: (String) -> String = { it }
|
||||
transform: (String) -> String = { it },
|
||||
) = copyTextResource(
|
||||
"$dummyFrameworkResource$relativeFrom",
|
||||
outputFramework.get().resolve(relativeTo),
|
||||
outputFramework.getFile().resolve(relativeTo),
|
||||
transform
|
||||
)
|
||||
|
||||
@TaskAction
|
||||
fun create() {
|
||||
private fun copyFramework() {
|
||||
// Reset the destination directory
|
||||
with(outputFramework.get()) {
|
||||
with(outputFramework.getFile()) {
|
||||
deleteRecursively()
|
||||
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
|
||||
|
||||
import org.gradle.api.file.ProjectLayout
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.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.MissingSpecReposMessage
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class PodInstallTask @Inject constructor(projectLayout: ProjectLayout) : AbstractPodInstallTask() {
|
||||
abstract class PodInstallTask : AbstractPodInstallTask() {
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
@@ -29,38 +25,15 @@ abstract class PodInstallTask @Inject constructor(projectLayout: ProjectLayout)
|
||||
@get:Input
|
||||
abstract val frameworkName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val useStaticFramework: Property<Boolean>
|
||||
|
||||
@get:Nested
|
||||
abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
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? {
|
||||
val specReposMessages = MissingSpecReposMessage(specRepos.get()).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