All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.touchlab.faktory.dependencymanager.CocoapodsDependencyManager.kt Maven / Gradle / Ivy

There is a newer version: 1.1.0-a3
Show newest version
/*
 * Copyright (c) 2024 Touchlab.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package co.touchlab.faktory.dependencymanager

import co.touchlab.faktory.TASK_GROUP_NAME
import co.touchlab.faktory.cocoapods
import co.touchlab.faktory.internal.procRunFailLog
import co.touchlab.faktory.kmmBridgeExtension
import co.touchlab.faktory.kotlin
import co.touchlab.faktory.layoutBuildDir
import co.touchlab.faktory.urlFile
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import java.io.File

sealed class SpecRepo {
    object Trunk : SpecRepo()
    class Private(val url: String) : SpecRepo()
}

class CocoapodsDependencyManager(
    private val specRepoDeferred: () -> SpecRepo,
    private val allowWarnings: Boolean,
    private val verboseErrors: Boolean
) : DependencyManager {
    override fun configure(project: Project, uploadTask: TaskProvider, publishRemoteTask: TaskProvider) {

        val podSpecFile =
            "${project.layoutBuildDir}/faktory/podspec/${project.kmmBridgeExtension.buildType.get().name.lowercase()}/${project.kotlin.cocoapods.name}.podspec"

        val generatePodspecTask = project.tasks.register("generateReleasePodspec") {
            inputs.files(project.urlFile)
            outputs.file(podSpecFile)
            dependsOn(uploadTask)
            @Suppress("ObjectLiteralToLambda")
            doLast(object : Action {
                override fun execute(t: Task) {
                    project.generatePodspec(project.file(podSpecFile))
                }
            })
        }

        val pushRemotePodspecTask = project.tasks.register("pushRemotePodspec") {
            group = TASK_GROUP_NAME
            inputs.files(podSpecFile)
            dependsOn(generatePodspecTask)

            @Suppress("ObjectLiteralToLambda")
            doLast(object : Action {
                override fun execute(t: Task) {
                    val extras = mutableListOf()

                    if (allowWarnings) {
                        extras.add("--allow-warnings")
                    }

                    if (verboseErrors) {
                        extras.add("--verbose")
                    }

                    when (val specRepo = specRepoDeferred()) {
                        is SpecRepo.Trunk ->
                            project.procRunFailLog("pod", "trunk", "push", podSpecFile, *extras.toTypedArray())

                        is SpecRepo.Private ->
                            project.procRunFailLog(
                                "pod",
                                "repo",
                                "push",
                                specRepo.url,
                                podSpecFile,
                                *extras.toTypedArray()
                            )
                    }
                }
            })
        }

        publishRemoteTask.configure {
            dependsOn(pushRemotePodspecTask)
        }
    }

    override val needsGitTags: Boolean = false
}

// Adapted from spec generation logic in the kotlin.cocoapods plugin, but we skip script phases and some other details,
// and we read straight from the project and cocoapods extension rather than task properties. We also ignore source and
// insert our deploy URL instead, and include our own version logic.
// TODO it might be nice to migrate this back to using the kotlin.cocoapods podspec task directly, but not worth the
//  effort to wire it up right now.
private fun Project.generatePodspec(outputFile: File) = with(kotlin.cocoapods) {
    val deploymentTargets = run {
        listOf(ios, osx, tvos, watchos).filter { it.deploymentTarget != null }.joinToString("\n") {
            if (extraSpecAttributes.containsKey("${it.name}.deployment_target")) "" else "|    spec.${it.name}.deployment_target = '${it.deploymentTarget}'"
        }
    }

    val dependencies = pods.joinToString(separator = "\n") { pod ->
        val versionSuffix = if (pod.version != null) ", '${pod.version}'" else ""
        "|    spec.dependency '${pod.name}'$versionSuffix"
    }

    // Logic for frameworkName pulled from various pieces of PodspecTask, CocoapodsExtension, and KotlinCocoapodsPlugin
    val anyPodFramework = project.provider {
        val anyTarget = project.kotlin.targets
            .withType(KotlinNativeTarget::class.java)
            .matching { it.konanTarget.family.isAppleFamily }.first()
        val anyFramework = anyTarget.binaries
            .matching { it.name.startsWith(KotlinCocoapodsPlugin.POD_FRAMEWORK_PREFIX) }
            .withType(Framework::class.java)
            .first()
        anyFramework
    }
    val frameworkName = anyPodFramework.map { it.baseName }

    val vendoredFramework = "${frameworkName.get()}.xcframework"
    val vendoredFrameworks =
        if (extraSpecAttributes.containsKey("vendored_frameworks")) "" else "|    spec.vendored_frameworks      = '$vendoredFramework'"

    val libraries =
        if (extraSpecAttributes.containsKey("libraries")) "" else "|    spec.libraries                = 'c++'"

    val customSpec = extraSpecAttributes.map { "|    spec.${it.key} = ${it.value}" }.joinToString("\n")

    val url = urlFile.readText()
    val version = version.toString()

    // 'Accept: application/octet-stream' needed for GitHub release file downloads
    outputFile.writeText(
        """
            |Pod::Spec.new do |spec|
            |    spec.name                     = '$name'
            |    spec.version                  = '$version'
            |    spec.homepage                 = ${homepage.orEmpty().surroundWithSingleQuotesIfNeeded()}
            |    spec.source                   = { 
            |                                      :http => '${url}',
            |                                      :type => 'zip',
            |                                      :headers => ["'Accept: application/octet-stream'"]
            |                                    }
            |    spec.authors                  = ${authors.orEmpty().surroundWithSingleQuotesIfNeeded()}
            |    spec.license                  = ${license.orEmpty().surroundWithSingleQuotesIfNeeded()}
            |    spec.summary                  = '${summary.orEmpty()}'
            $vendoredFrameworks
            $libraries
            $deploymentTargets
            $dependencies
            $customSpec
            |end
        """.trimMargin()
    )
}

private fun String.surroundWithSingleQuotesIfNeeded(): String =
    if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'"




© 2015 - 2024 Weber Informatics LLC | Privacy Policy