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

co.touchlab.kmmbridge.KMMBridge.kt Maven / Gradle / Ivy

The 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.kmmbridge

import co.touchlab.kmmbridge.artifactmanager.ArtifactManager
import co.touchlab.kmmbridge.dependencymanager.SpmDependencyManager
import co.touchlab.kmmbridge.internal.enablePublishing
import co.touchlab.kmmbridge.internal.findXCFrameworkAssembleTask
import co.touchlab.kmmbridge.internal.kotlin
import co.touchlab.kmmbridge.internal.layoutBuildDir
import co.touchlab.kmmbridge.internal.spmBuildTargets
import co.touchlab.kmmbridge.internal.urlFile
import co.touchlab.kmmbridge.internal.zipFilePath
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
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.apple.XCFramework
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
import java.io.File
import kotlin.collections.filter
import kotlin.collections.flatMap
import kotlin.collections.forEach

@Suppress("unused")
open class KMMBridgePlugin : BaseKMMBridgePlugin() {
}

abstract class BaseKMMBridgePlugin : Plugin {

    override fun apply(project: Project): Unit = with(project) {
        val extension = extensions.create(EXTENSION_NAME)

        extension.dependencyManagers.convention(emptyList())

        val defaultNativeBuildType = if (project.findStringProperty("NATIVE_BUILD_TYPE") == "DEBUG") {
            NativeBuildType.DEBUG
        } else {
            NativeBuildType.RELEASE
        }

        extension.buildType.convention(defaultNativeBuildType)

        afterEvaluate {
            val kmmBridgeExtension = extensions.getByType()

            configureXcFramework(kmmBridgeExtension)
            configureLocalDev(kmmBridgeExtension)
            if (enablePublishing) {
                configureArtifactManagerAndDeploy(kmmBridgeExtension)
            }
        }
    }

    // Collect all declared frameworks in project and combine into xcframework
    private fun Project.configureXcFramework(kmmBridgeExtension: KmmBridgeExtension) {
        var xcFrameworkConfig: XCFrameworkConfig? = null

        val spmBuildTargets: Set =
            project.spmBuildTargets?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }?.toSet() ?: emptySet()

        kotlin.targets
            .withType()
            .filter { it.konanTarget.family.isAppleFamily }
            .flatMap { it.binaries.filterIsInstance() }
            .forEach { framework ->
                val theName = framework.baseName
                val currentName = kmmBridgeExtension.frameworkName.orNull
                if (currentName == null) {
                    kmmBridgeExtension.frameworkName.set(theName)
                } else {
                    if (currentName != theName) {
                        throw IllegalStateException("Only one framework name currently allowed. Found $currentName and $theName")
                    }
                }
                val shouldAddTarget =
                    spmBuildTargets.isEmpty() || spmBuildTargets.contains(framework.target.konanTarget.name)
                if (shouldAddTarget) {
                    if (xcFrameworkConfig == null) {
                        xcFrameworkConfig = XCFramework(theName)
                    }
                    xcFrameworkConfig!!.add(framework)
                }
            }
    }

    private fun Project.configureLocalDev(kmmBridgeExtension: KmmBridgeExtension) {
        (kmmBridgeExtension.dependencyManagers.get()
            .find { it is SpmDependencyManager } as? SpmDependencyManager)?.configureLocalDev(
            this
        )
    }

    private fun Project.configureArtifactManagerAndDeploy(kmmBridgeExtension: KmmBridgeExtension) {
        // Early-out with a warning if user hasn't added required config yet, to ensure project still syncs
        val artifactManager = kmmBridgeExtension.artifactManager.orNull ?: run {
            project.logger.warn("You must apply an artifact manager! Call `artifactManager.set(...)` or a configuration function like `mavenPublishArtifacts()` in your `kmmbridge` block.")
            return
        }

        val (zipTask, zipFile) = configureZipTask(kmmBridgeExtension, project.layoutBuildDir)

        // Zip task depends on the XCFramework assemble task
        zipTask.configure {
            dependsOn(findXCFrameworkAssembleTask())
        }

        // Upload task depends on the zip task
        val uploadTask = configureUploadTask(zipTask, zipFile, artifactManager)

        val dependencyManagers = kmmBridgeExtension.dependencyManagers.get()

        // Publish task depends on the upload task
        val publishRemoteTask = tasks.register("kmmBridgePublish") {
            group = TASK_GROUP_NAME
            dependsOn(uploadTask)
        }

        // MavenPublishArtifactManager is somewhat complex because we have to hook into maven publishing
        // If you are exploring the task dependencies, be aware of that code
        artifactManager.configure(this, version.toString(), uploadTask, publishRemoteTask)

        for (dependencyManager in dependencyManagers) {
            dependencyManager.configure(providers, this, version.toString(), uploadTask, publishRemoteTask)
        }
    }

    private fun Project.configureUploadTask(
        zipTask: TaskProvider,
        zipFile: File,
        artifactManager: ArtifactManager
    ) = tasks.register("uploadXCFramework") {
        group = TASK_GROUP_NAME

        dependsOn(zipTask)
        inputs.file(zipFile)
        outputs.files(urlFile)
        outputs.upToDateWhen { false } // We want to always upload when this task is called
        val versionLocal = version
        val urlFileLocal = urlFile

        @Suppress("ObjectLiteralToLambda")
        doLast(object : Action {
            override fun execute(t: Task) {
                logger.info("Uploading XCFramework version $versionLocal")
                val deployUrl = artifactManager.deployArtifact(this@register, zipFile, versionLocal.toString())
                urlFileLocal.writeText(deployUrl)
            }
        })
    }

    private fun Project.configureZipTask(
        kmmBridgeExtension: KmmBridgeExtension,
        buildDir: File
    ): Pair, File> {
        val zipFile = zipFilePath()
        val zipTask = tasks.register("zipXCFramework") {
            group = TASK_GROUP_NAME
            from("$buildDir/XCFrameworks/${kmmBridgeExtension.buildType.get().getName()}")
            destinationDirectory.set(zipFile.parentFile)
            archiveFileName.set(zipFile.name)
        }

        return Pair(zipTask, zipFile)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy