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

org.jetbrains.kotlin.gradle.targets.native.internal.PlatformLibrariesGenerator.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2020 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.targets.native.internal

import org.gradle.api.Project
import org.jetbrains.kotlin.compilerRunner.KotlinNativeLibraryGenerationRunner
import org.jetbrains.kotlin.compilerRunner.getKonanCacheKind
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.targets.native.KonanPropertiesBuildService
import org.jetbrains.kotlin.gradle.tasks.CacheBuilder
import org.jetbrains.kotlin.gradle.tasks.addArg
import org.jetbrains.kotlin.gradle.utils.lifecycleWithDuration
import org.jetbrains.kotlin.gradle.utils.listFilesOrEmpty
import org.jetbrains.kotlin.konan.library.KONAN_PLATFORM_LIBS_NAME_PREFIX
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.customerDistribution
import org.jetbrains.kotlin.konan.util.visibleName
import java.io.File
import java.util.*
import java.util.concurrent.ConcurrentHashMap

internal class PlatformLibrariesGenerator(val project: Project, val konanTarget: KonanTarget) {

    private val distribution =
        customerDistribution(project.konanHome)

    private val platformLibsDirectory =
        File(distribution.platformLibs(konanTarget)).absoluteFile

    private val defDirectory =
        File(distribution.platformDefs(konanTarget)).absoluteFile

    private val konanPropertiesService: KonanPropertiesBuildService
        get() = KonanPropertiesBuildService.registerIfAbsent(project).get()

    private val konanCacheKind: NativeCacheKind by lazy {
        project.getKonanCacheKind(konanTarget)
    }

    private val shouldBuildCaches: Boolean =
        konanPropertiesService.cacheWorksFor(konanTarget) && konanCacheKind != NativeCacheKind.NONE

    private val mode: String? by lazy {
        PropertiesProvider(project).nativePlatformLibrariesMode
    }

    private val presentDefs: Set by lazy {
        defDirectory
            .listFiles { file -> file.extension == "def" }.orEmpty()
            .map { it.nameWithoutExtension }.toSet()
    }

    private fun Set.toPlatformLibNames(): Set =
        mapTo(mutableSetOf()) { "$KONAN_PLATFORM_LIBS_NAME_PREFIX$it" }

    /**
     * Checks that all platform libs for [konanTarget] actually exist in the [distribution].
     */
    private fun checkLibrariesInDistribution(): Boolean {
        val presentPlatformLibs = platformLibsDirectory
            .listFiles { file -> file.isDirectory }.orEmpty()
            .map { it.name }.toSet()

        // TODO: Check that all directories in presentPlatformLibs are real klibs when klib componentization is merged.
        return presentDefs.toPlatformLibNames().all { it in presentPlatformLibs }
    }

    /**
     * Check that caches for all platform libs for [konanTarget] actually exist in the cache directory.
     */
    private fun checkCaches(): Boolean {
        if (!shouldBuildCaches) {
            return true
        }

        val cacheDirectory = CacheBuilder.getRootCacheDirectory(
            File(project.konanHome), konanTarget, true, konanCacheKind
        )
        return presentDefs.toPlatformLibNames().all {
            cacheDirectory.resolve(CacheBuilder.getCacheFileName(it, konanCacheKind)).listFilesOrEmpty().isNotEmpty()
        }
    }

    /**
     * We store directories where platform libraries were detected/generated earlier
     * during this build to avoid redundant distribution checks.
     */
    private val alreadyProcessed: PlatformLibsInfo
        get() = project.rootProject.extensions.extraProperties.run {
            if (!has(GENERATED_LIBS_PROPERTY_NAME)) {
                set(GENERATED_LIBS_PROPERTY_NAME, PlatformLibsInfo())
            }
            @Suppress("UNCHECKED_CAST")
            get(GENERATED_LIBS_PROPERTY_NAME) as PlatformLibsInfo
        }

    private fun runGenerationTool() = with(project) {
        val args = mutableListOf("-target", konanTarget.visibleName)
        if (logger.isInfoEnabled) {
            args.add("-verbose")
        }

        // We can generate caches using either [CacheBuilder] or the library generator. Using CacheBuilder allows
        // keeping all the caching logic in one place while the library generator speeds up building caches because
        // it works in parallel. We use the library generator for now due to performance reasons.
        //
        // TODO: Supporting Gradle Worker API (or other parallelization) in the CacheBuilder and enabling
        //       the compiler daemon for interop will allow switching to the CacheBuilder without performance penalty.
        //       Alternatively we can rely on the library generator tool in the CacheBuilder and eliminate a separate
        //       logic for library caching there.
        if (shouldBuildCaches) {
            args.addArg("-cache-kind", konanCacheKind.produce!!)
            args.addArg(
                "-cache-directory",
                CacheBuilder.getRootCacheDirectory(File(konanHome), konanTarget, true, konanCacheKind).absolutePath
            )
            args.addArg("-cache-arg", "-g")
            val additionalCacheFlags = konanPropertiesService.additionalCacheFlags(konanTarget)
            additionalCacheFlags.forEach {
                args.addArg("-cache-arg", it)
            }
        }

        mode?.let {
            args.addArg("-mode", it)
        }

        KotlinNativeLibraryGenerationRunner.fromProject(this).run(args)
    }

    fun generatePlatformLibsIfNeeded(): Unit = with(project) {
        if (!HostManager(distribution).isEnabled(konanTarget)) {
            // We cannot generate libs on a machine that doesn't support the requested target.
            return
        }

        // Don't run the generator if libraries/caches for this target were already built during this Gradle invocation.
        val alreadyGenerated = alreadyProcessed.isGenerated(platformLibsDirectory)
        val alreadyCached = alreadyProcessed.isCached(platformLibsDirectory, konanCacheKind)
        if ((alreadyGenerated && alreadyCached) || !defDirectory.exists()) {
            return
        }

        // Check if libraries/caches for this target already exist (requires reading from disc).
        val platformLibsAreReady = checkLibrariesInDistribution()
        if (platformLibsAreReady) {
            alreadyProcessed.setGenerated(platformLibsDirectory)
        }

        val cachesAreReady = checkCaches()
        if (cachesAreReady) {
            alreadyProcessed.setCached(platformLibsDirectory, konanCacheKind)
        }

        val generationMessage = when {
            !platformLibsAreReady && !cachesAreReady ->
                "Generate and precompile platform libraries for $konanTarget (precompilation: ${konanCacheKind.visibleName})"
            platformLibsAreReady && !cachesAreReady ->
                "Precompile platform libraries for $konanTarget (precompilation: ${konanCacheKind.visibleName})"
            !platformLibsAreReady && cachesAreReady ->
                "Generate platform libraries for $konanTarget"
            else -> {
                // Both caches and libraries exist thus there is no need to run the generator.
                return
            }
        }

        logger.lifecycle(generationMessage)
        logger.lifecycleWithDuration("$generationMessage finished,") {
            runGenerationTool()
        }

        val librariesAreActuallyGenerated = checkLibrariesInDistribution()
        assert(librariesAreActuallyGenerated) { "Some platform libraries were not generated" }
        if (librariesAreActuallyGenerated) {
            alreadyProcessed.setGenerated(platformLibsDirectory)
        }

        val librariesAreActuallyCached = checkCaches()
        assert(librariesAreActuallyCached) { "Some platform libraries were not precompiled" }
        if (librariesAreActuallyCached) {
            alreadyProcessed.setCached(platformLibsDirectory, konanCacheKind)
        }
    }

    private class PlatformLibsInfo {
        private val generated: MutableSet = Collections.newSetFromMap(ConcurrentHashMap())
        private val cached: ConcurrentHashMap> = ConcurrentHashMap()

        private fun cached(cacheKind: NativeCacheKind): MutableSet = cached.getOrPut(cacheKind) {
            Collections.newSetFromMap(ConcurrentHashMap())
        }

        /**
         * Are platform libraries in the given directory (e.g. /klib/platform/ios_x64) generated.
         */
        fun isGenerated(path: File): Boolean =
            generated.contains(path)

        /**
         * Register that platform libraries in the given directory are generated.
         */
        fun setGenerated(path: File) {
            generated.add(path)
        }

        /**
         * Are platform libraries in the given directory (e.g. /klib/platform/ios_x64) cached with the given cache kind.
         */
        fun isCached(path: File, kind: NativeCacheKind): Boolean =
            kind == NativeCacheKind.NONE || cached(kind).contains(path)

        /**
         * Register that platform libraries in the give directory are cached with the given cache kind.
         */
        fun setCached(path: File, kind: NativeCacheKind) {
            if (kind != NativeCacheKind.NONE) {
                cached(kind).add(path)
            }
        }
    }

    companion object {
        private const val GENERATED_LIBS_PROPERTY_NAME = "org.jetbrains.kotlin.native.platform.libs.info"
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy