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

io.spring.gradle.lock.groovy.GroovyLockAstVisitor.kt Maven / Gradle / Ivy

/**
 * Copyright 2018 Pivotal Software, Inc.
 *
 * 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 io.spring.gradle.lock.groovy

import io.spring.gradle.lock.ConfigurationModuleIdentifier
import io.spring.gradle.lock.withConf
import org.codehaus.groovy.ast.ClassCodeVisitorSupport
import org.codehaus.groovy.ast.expr.ArgumentListExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.GStringExpression
import org.codehaus.groovy.ast.expr.MapExpression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.TupleExpression
import org.codehaus.groovy.control.SourceUnit
import org.gradle.api.Project
import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionComparator
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.Version
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionParser
import java.util.*

class GroovyLockAstVisitor(val project: Project,
						   val overrides: Map,
						   val variableDefinitions: Map) : ClassCodeVisitorSupport() {
	val updates = ArrayList()
	private val mvidPattern = "([^:]*):([^:]+):?([^@:]*).*".toRegex()
	private var inDependencies = false
	private var inResolutionStrategies = false

	override fun getSourceUnit(): SourceUnit? = null
	val path = Stack()

	override fun visitMethodCallExpression(call: MethodCallExpression) {
		val caller = call.objectExpression.text
		if (caller != "this") path.push(caller)
		path.push(call.methodAsString)

		if (inDependencies) {
			visitMethodCallInDependencies(call)
		} else if (path.any { it == "dependencies" }) {
			inDependencies = true
			super.visitMethodCallExpression(call)
			inDependencies = false
		}

		if (inResolutionStrategies) {
			visitMethodCallInResolutionStrategies(call)
		} else if (path.any { it == "resolutionStrategy" }) {
			inResolutionStrategies = true
			super.visitMethodCallExpression(call)
			inResolutionStrategies = false
		} else super.visitMethodCallExpression(call)

		path.pop()
	}

	fun visitMethodCallInResolutionStrategies(call: MethodCallExpression) {
		if (path.any { it == "ignore" }) return
		val conf = path.takeLastWhile { it != "configurations" }.first()
		val args = call.parseArgs()
		if (isConf(conf) || conf == "all") {
			val lock = args.first().lock(conf, args)
			updates.add(GroovyLockUpdate(call, lock))
		}
	}

	fun visitMethodCallInDependencies(call: MethodCallExpression) {
		if (path.any { it == "ignore" }) return
		// https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/dsl/DependencyHandler.html
		val conf = call.methodAsString
		val args = call.parseArgs()
		if (isConf(conf)) {
			val lock = args.first().lock(conf, args)
			updates.add(GroovyLockUpdate(call, lock))
		}
	}

	private fun isConf(methodName: String) =
			project.configurations.findByName(methodName) != null ||
					project.subprojects.any { sub -> sub.configurations.findByName(methodName) != null }

	private fun collectEntryExpressions(args: List) =
			args.filterIsInstance(MapExpression::class.java)
					.map { it.mapEntryExpressions }
					.flatten()
					.map { it.keyExpression.text to it.valueExpression.text }
					.toMap()

	private fun String.lockedVersion(group: String?, name: String): String? {
		val mid = DefaultModuleIdentifier.newId(group, name)
		val versionComparator = DefaultVersionComparator().asVersionComparator()
		val versionParser = VersionParser()

		fun overrideOrResolvedVersion(p: Project): Version? {
			val configurations = if (this == "all") p.configurations else setOf(p.configurations.getByName(this))
			val versions = configurations
					.filter { it.isCanBeResolved }
					.mapNotNull { conf ->
						overrides[mid.withConf(conf)] ?: conf
								.resolvedConfiguration
								.firstLevelModuleDependencies
								.find { it.module.id.module == mid }
								?.moduleVersion
					}

			return versions
					.map { v -> versionParser.transform(v) }
					.maxWith(versionComparator)
		}

		val lockedVersion: Version? = if (project.rootProject == project) {
			if (project.subprojects.isEmpty()) {
				// single module project
				overrideOrResolvedVersion(project)
			} else {
				// root project of a multi-module project
				project.subprojects
						.map(::overrideOrResolvedVersion)
						.maxWith(versionComparator)
			}
		} else {
			// subproject of a multi-module project
			overrideOrResolvedVersion(project)
		}

		return lockedVersion?.source
	}

	private fun Expression.lock(conf: String, args: List): String? {
		return when (this) {
			is MapExpression -> {
				val entries = collectEntryExpressions(args)
				conf.lockedVersion(entries["group"], entries["name"]!!).let { locked ->
					if (locked == entries["version"]) null else locked
				}
			}
			is ConstantExpression -> {
				mvidPattern.matchEntire(value as String)?.run {
					val group = groupValues[1].let { if (it.isEmpty()) null else it }
					val name = groupValues[2]
					val version = groupValues[3].let { if (it.isEmpty()) null else it }
					conf.lockedVersion(group, name).let { locked -> if (locked == version) null else locked }
				}
			}
			is GStringExpression -> {
				mvidPattern.matchEntire(text)?.run {
					val group = groupValues[1].let { if (it.isEmpty()) null else it }
					val name = groupValues[2]
					val version = groupValues[3].let {
						when {
							it.isEmpty() -> null
							it.startsWith("$") -> variableDefinitions[it.drop(1)]
							else -> it
						}
					}
					conf.lockedVersion(group, name).let { locked -> if (locked == version) null else locked }
				}
			}
			else -> null
		}
	}

	private fun MethodCallExpression.parseArgs(): List {
		return when (arguments) {
			is ArgumentListExpression -> (arguments as ArgumentListExpression).expressions
			is TupleExpression -> (arguments as TupleExpression).expressions
			else -> emptyList()
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy