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

org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver.kt Maven / Gradle / Ivy

There is a newer version: 2.0.20-RC
Show newest version
/*
 * Copyright 2010-2019 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.js.npm.resolver

import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency.Scope.*
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.npm.fixSemver
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.plugins.CompilationResolverPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import java.io.File
import java.io.Serializable

/**
 * See [KotlinNpmResolutionManager] for details about resolution process.
 */
internal class KotlinCompilationNpmResolver(
    val projectResolver: KotlinProjectNpmResolver,
    val compilation: KotlinJsCompilation
) {
    val resolver = projectResolver.resolver
    val npmProject = compilation.npmProject
    val nodeJs get() = resolver.nodeJs
    val target get() = compilation.target
    val project get() = target.project
    val packageJsonTaskHolder = KotlinPackageJsonTask.create(compilation)
    val plugins: List = projectResolver.resolver.plugins.flatMap {
        it.createCompilationResolverPlugins(this)
    }

    override fun toString(): String = "KotlinCompilationNpmResolver(${npmProject.name})"

    val aggregatedConfiguration: Configuration by lazy {
        createAggregatedConfiguration()
    }

    val packageJsonProducer: PackageJsonProducer by lazy {
        val visitor = ConfigurationVisitor()
        visitor.visit(aggregatedConfiguration)
        visitor.toPackageJsonProducer()
    }

    private var closed = false
    private var resolution: KotlinCompilationNpmResolution? = null

    @Synchronized
    fun resolve(skipWriting: Boolean = false): KotlinCompilationNpmResolution {
        check(!closed) { "$this already closed" }
        check(resolution == null) { "$this already resolved" }

        return packageJsonProducer.createPackageJson(skipWriting).also {
            resolution = it
        }
    }

    @Synchronized
    fun getResolutionOrResolveIfForced(): KotlinCompilationNpmResolution? {
        if (resolution != null) return resolution
        if (packageJsonTaskHolder.get().state.upToDate) return resolve(skipWriting = true)
        if (resolver.forceFullResolve && resolution == null) return resolve()
        return null
    }

    @Synchronized
    fun close(): KotlinCompilationNpmResolution? {
        check(!closed) { "$this already closed" }
        val resolution = getResolutionOrResolveIfForced()
        closed = true
        return resolution
    }

    private fun createAggregatedConfiguration(): Configuration {
        val all = project.configurations.create(compilation.disambiguateName("npm"))

        all.usesPlatformOf(target)
        all.attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.consumerRuntimeUsage(target))
        all.isVisible = false
        all.isCanBeConsumed = false
        all.isCanBeResolved = true
        all.description = "NPM configuration for $compilation."

        compilation.allKotlinSourceSets.forEach { sourceSet ->
            sourceSet.relatedConfigurationNames.forEach { configurationName ->
                val configuration = project.configurations.getByName(configurationName)
                all.extendsFrom(configuration)
            }
        }

        createNpmToolsConfiguration()?.let { tools ->
            all.extendsFrom(tools)
        }

        return all
    }

    private fun createNpmToolsConfiguration(): Configuration? {
        val taskRequirements = projectResolver.taskRequirements.getTaskRequirements(compilation)
        if (taskRequirements.isEmpty()) return null

        val toolsConfiguration = project.configurations.create(compilation.disambiguateName("npmTools"))

        toolsConfiguration.isVisible = false
        toolsConfiguration.isCanBeConsumed = false
        toolsConfiguration.isCanBeResolved = true
        toolsConfiguration.description = "NPM Tools configuration for $compilation."

        taskRequirements.forEach { requirement ->
            requirement.requiredNpmDependencies.forEach { requiredNpmDependency ->
                toolsConfiguration.dependencies.add(requiredNpmDependency.createDependency(project))
            }
        }

        return toolsConfiguration
    }

    data class ExternalGradleDependency(
        val dependency: ResolvedDependency,
        val artifact: ResolvedArtifact
    )

    inner class ConfigurationVisitor {
        private val internalDependencies = mutableSetOf()
        private val externalGradleDependencies = mutableSetOf()
        private val externalNpmDependencies = mutableSetOf()

        fun visit(configuration: Configuration) {
            configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach {
                visitDependency(it)
            }

            configuration.allDependencies.forEach { dependency ->
                when (dependency) {
                    is NpmDependency -> externalNpmDependencies.add(dependency)
                }
            }

            //TODO: rewrite when we get general way to have inter compilation dependencies
            if (compilation.name == KotlinCompilation.TEST_COMPILATION_NAME) {
                val main = compilation.target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME) as KotlinJsCompilation
                internalDependencies.add(projectResolver[main])
            }

            plugins.forEach {
                it.hookDependencies(
                    internalDependencies,
                    externalGradleDependencies,
                    externalNpmDependencies
                )
            }
        }

        private fun visitDependency(dependency: ResolvedDependency) {
            visitArtifacts(dependency, dependency.moduleArtifacts)

            dependency.children.forEach {
                visitDependency(it)
            }
        }

        private fun visitArtifacts(
            dependency: ResolvedDependency,
            artifacts: MutableSet
        ) {
            artifacts.forEach { artifact ->
                val componentIdentifier = artifact.id.componentIdentifier
                if (componentIdentifier is ProjectComponentIdentifier) {
                    val dependentProject = project.findProject(componentIdentifier.projectPath)
                        ?: error("Cannot find project ${componentIdentifier.projectPath}")

                    val dependentResolver = resolver.findDependentResolver(project, dependentProject)
                    if (dependentResolver != null) {
                        internalDependencies.add(dependentResolver)
                    }
                } else {
                    externalGradleDependencies.add(ExternalGradleDependency(dependency, artifact))
                }
            }
        }

        fun toPackageJsonProducer() = PackageJsonProducer(internalDependencies, externalGradleDependencies, externalNpmDependencies)
    }

    class PackageJsonProducerInputs(
        @get:Input
        val internalDependencies: Collection,

        @get:InputFiles
        val externalGradleDependencies: Collection,

        @get:Input
        val externalDependencies: Collection
    ) : Serializable

    @Suppress("MemberVisibilityCanBePrivate")
    inner class PackageJsonProducer(
        val internalDependencies: Collection,
        val externalGradleDependencies: Collection,
        val externalNpmDependencies: Collection
    ) {
        val inputs: PackageJsonProducerInputs
            get() = PackageJsonProducerInputs(
                internalDependencies.map { it.npmProject.name },
                externalGradleDependencies.map { it.artifact.file },
                externalNpmDependencies.map { "${it.scope} ${it.key}:${it.version}" }
            )

        fun createPackageJson(skipWriting: Boolean): KotlinCompilationNpmResolution {
            val resolvedInternalDependencies = internalDependencies.map {
                it.getResolutionOrResolveIfForced()
                    ?: error("Unresolved dependent npm package: ${this@KotlinCompilationNpmResolver} -> $it")
            }
            val importedExternalGradleDependencies = externalGradleDependencies.mapNotNull {
                resolver.gradleNodeModules.get(it.dependency, it.artifact)
            }

            val packageJson = PackageJson(
                npmProject.name,
                fixSemver(project.version.toString())
            )

            packageJson.main = npmProject.main

            resolvedInternalDependencies.forEach {
                packageJson.dependencies[it.packageJson.name] = it.packageJson.version
            }

            importedExternalGradleDependencies.forEach {
                packageJson.dependencies[it.name] = it.version
            }

            val dependencies = mutableMapOf()

            externalNpmDependencies.forEach {
                val module = it.key
                dependencies[it.key] = chooseVersion(dependencies[module], it.version)
            }

            externalNpmDependencies.forEach {
                val dependency = dependencies.getValue(it.key)
                when (it.scope) {
                    NORMAL -> packageJson.dependencies[it.key] = dependency
                    DEV -> packageJson.devDependencies[it.key] = dependency
                    OPTIONAL -> packageJson.optionalDependencies[it.key] = dependency
                    PEER -> packageJson.peerDependencies[it.key] = dependency
                }
            }

            compilation.packageJsonHandlers.forEach {
                it(packageJson)
            }

            if (!skipWriting) {
                packageJson.saveTo(npmProject.packageJsonFile)
            }

            return KotlinCompilationNpmResolution(
                project,
                npmProject,
                resolvedInternalDependencies,
                importedExternalGradleDependencies,
                externalNpmDependencies,
                packageJson
            )
        }

        // TODO: real versions conflict resolution
        private fun chooseVersion(oldVersion: String?, newVersion: String): String {
            // https://yarnpkg.com/lang/en/docs/dependency-versions/#toc-x-ranges
            if (oldVersion == "*") {
                return newVersion
            }

            return oldVersion ?: newVersion
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy