Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ dependencies {
Base.lib,
Base.format,
CoreJvm.server,
ToolBase.lib,
ToolBase.code,
ToolBase.kotlinCode,
ToolBase.psiJava
).forEach {
api(it)
Expand Down
2 changes: 1 addition & 1 deletion backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ plugins {
dependencies {
api(AutoService.annotations)
api(CoreJvm.server)
api(ToolBase.lib)
api(ToolBase.code)
api(project(":api"))

implementation(project(":params"))?.because("We need the `PipelineParameters` type.")
Expand Down
9 changes: 9 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,20 @@ dependencies {
testImplementation(platform("org.junit:junit-bom:$junitVersion"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
testImplementation(gradleTestKit())
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
useJUnitPlatform()

// Functional tests run real Gradle builds via TestKit and inject the production
// classes of `buildSrc` into the build script classpath of those builds.
// The argument provider defers resolving the classpath to execution time.
val mainClasspath = sourceSets.main.get().runtimeClasspath
jvmArgumentProviders.add(CommandLineArgumentProvider {
Comment thread
alexander-yevsyukov marked this conversation as resolved.
listOf("-DbuildSrc.classpath=${mainClasspath.asPath}")
})
}

dependOnBuildSrcJar()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ object Jackson : DependencyWithBom() {
val javaXMoney = "$group:$infix-javax-money"

// https://github.com/FasterXML/jackson-datatypes-misc/tree/3.x/moneta
val moneta = "$group:jackson-datatype-moneta"
val moneta = "$group:$infix-moneta"

override val modules = listOf(
guava,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ object Compiler : Dependency() {
* The version of the Compiler dependencies.
*/
override val version: String
private const val fallbackVersion = "2.0.0-SNAPSHOT.065"
private const val fallbackVersion = "2.0.0-SNAPSHOT.066"

/**
* The distinct version of the Compiler used by other build tools.
Expand All @@ -81,7 +81,7 @@ object Compiler : Dependency() {
* transitive dependencies, this is the version used to build the project itself.
*/
val dogfoodingVersion: String
private const val fallbackDfVersion = "2.0.0-SNAPSHOT.065"
private const val fallbackDfVersion = "2.0.0-SNAPSHOT.066"

/**
* The artifact for the Compiler Gradle plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ object CoreJvmCompiler {
/**
* The version used in the build classpath.
*/
const val dogfoodingVersion = "2.0.0-SNAPSHOT.080"
const val dogfoodingVersion = "2.0.0-SNAPSHOT.082"

/**
* The version to be used for integration tests.
*/
const val version = "2.0.0-SNAPSHOT.080"
const val version = "2.0.0-SNAPSHOT.082"

/**
* The ID of the Gradle plugin.
Expand Down
27 changes: 25 additions & 2 deletions buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,40 @@
package io.spine.dependency.local

/**
* Artifacts of the `tool-base` module.
* Artifacts of the `tool-base` repository.
*
* The repository no longer publishes a module of its own name. Its former
* contents are split across the focused modules declared below.
*
* @see <a href="https://github.com/SpineEventEngine/tool-base">tool-base</a>
*/
@Suppress("ConstPropertyName", "unused")
object ToolBase {
const val group = Spine.toolsGroup
const val version = "2.0.0-SNAPSHOT.410"
const val version = "2.0.0-SNAPSHOT.421"
const val dogfoodingVersion = "2.0.0-SNAPSHOT.410"

/**
* The former all-in-one module, split into the focused modules below.
*
* The artifact is no longer published as of `2.0.0-SNAPSHOT.420`. Replace it
* with the modules a project actually uses: [archive], [code], [fs],
* [javaCode], [kotlinCode], or [protoCode].
*
* `io.spine.tools.OsFamily`, which this module also carried, now lives in
* Base Libraries as `io.spine.environment.OsFamily`.
*/
@Deprecated("The `tool-base` artifact is no longer published. Use the module you need.")
const val lib = "$group:tool-base:$version"

const val archive = "$group:archive:$version"
const val code = "$group:code:$version"
const val fs = "$group:fs:$version"

const val javaCode = "$group:java-code:$version"
const val kotlinCode = "$group:kotlin-code:$version"
const val protoCode = "$group:proto-code:$version"

const val classicCodegen = "$group:classic-codegen:$version"
const val pluginBase = "$group:plugin-base:$version"
const val pluginTestlib = "$group:plugin-testlib:$version"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object Validation {
/**
* The version of the Validation library artifacts.
*/
const val version = "2.0.0-SNAPSHOT.460"
const val version = "2.0.0-SNAPSHOT.462"

const val group = Spine.toolsGroup
private const val prefix = "validation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import java.io.Writer
import java.util.*
import kotlin.reflect.full.isSubclassOf
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.internal.artifacts.dependencies.AbstractExternalModuleDependency
import org.gradle.kotlin.dsl.withGroovyBuilder

Expand Down Expand Up @@ -75,9 +73,16 @@ private constructor(

/**
* Creates the `DependencyWriter` for the passed [project].
*
* The version of each dependency is taken from the map returned by
* [resolvedVersionsOf] for the project the dependency comes from.
* See the [dependencies] extension function for details.
*/
fun of(project: Project): DependencyWriter {
return DependencyWriter(project.dependencies())
fun of(
project: Project,
resolvedVersionsOf: (Project) -> Map<String, String>
): DependencyWriter {
return DependencyWriter(project.dependencies(resolvedVersionsOf))
}
}

Expand Down Expand Up @@ -111,37 +116,17 @@ private constructor(
}
}

/**
* Returns the [scoped dependencies][ScopedDependency] of a Gradle project.
*
* The version of each dependency is the one selected by dependency resolution
* for the project it comes from. See [resolvedVersions].
*/
fun Project.dependencies(): SortedSet<ScopedDependency> =
collectScopedDependencies { it.resolvedVersions() }

/**
* Returns the [scoped dependencies][ScopedDependency] of a Gradle project, taking
* the version of each dependency from the given [resolvedVersions] map instead of
* resolving the project's own configurations.
*
* This overload exists for tests: a project created with `ProjectBuilder` cannot
* resolve its configurations against real repositories, so the resolved versions
* are supplied directly. The keys are the `"group:name"` of the modules.
*/
internal fun Project.dependencies(
resolvedVersions: Map<String, String>
): SortedSet<ScopedDependency> =
collectScopedDependencies { resolvedVersions }

/**
* Collects the [scoped dependencies][ScopedDependency] of this project and its
* subprojects, deduplicates them, and returns them in the conventional Maven order.
*
* The version of each dependency is taken from the map returned by the supplied
* `resolvedVersionsOf` function for the project the dependency comes from.
* [resolvedVersionsOf] function for the project the dependency comes from — normally
* the versions selected by dependency resolution, as [collected][ResolvedVersions]
* by the per-project tasks the [PomGenerator] registers. Tests supply the map
* directly, or resolve in place via [resolvedVersions].
*/
private fun Project.collectScopedDependencies(
internal fun Project.dependencies(
resolvedVersionsOf: (Project) -> Map<String, String>
): SortedSet<ScopedDependency> {
val dependencies = mutableSetOf<ModuleDependency>()
Expand All @@ -161,9 +146,10 @@ private fun Project.collectScopedDependencies(
* Returns the external dependencies of the project from all the project configurations.
*
* The version of each returned dependency is taken from [resolvedVersions] by its
* `"group:name"` key, falling back to the declared version when the module is on no
* resolvable configuration — for example, a version managed by a BOM, which carries
* no explicit version of its own.
* `"group:name"` key. When the module is absent from the map — i.e., it is on no
* resolvable configuration of the project, as with a version managed by a BOM, which
* carries no explicit version of its own — the declared version is what the build
* uses, so it is reported as the fallback.
*/
private fun Project.depsFromAllConfigurations(
resolvedVersions: Map<String, String>
Expand All @@ -183,46 +169,6 @@ private fun Project.depsFromAllConfigurations(
return result
}

/**
* Returns the versions selected by dependency resolution for this project, keyed
* by the `"group:name"` of each module.
*
* The declared version of a dependency is what the build script *requested*, which
* may differ from what the build *uses*: a `force(...)`, a platform/BOM constraint,
* or Gradle's conflict resolution can all select another version. Reading the
* resolution result captures the selected version, so the report describes the
* dependencies actually on the classpath rather than the requested ones.
*
* Only resolvable configurations contribute. When a module resolves to different
* versions across configurations, the newest one (by [VersionComparator]) is kept,
* matching the deduplication applied afterwards. A configuration that fails to
* resolve in isolation is skipped and logged, so the report never breaks the build.
*/
private fun Project.resolvedVersions(): Map<String, String> {
// Resolving an individual configuration may fail for reasons unrelated to the
// report — missing repositories for a niche configuration, an unsatisfiable
// constraint, and the like. Such a configuration contributes no versions.
@Suppress("TooGenericExceptionCaught") // Any resolution failure is non-fatal here.
fun componentsOf(configuration: Configuration): Set<ResolvedComponentResult> =
try {
configuration.incoming.resolutionResult.allComponents
} catch (e: Exception) {
logger.info(
"Skipping configuration `${configuration.name}` " +
"while collecting resolved dependency versions.",
e
)
emptySet()
}

return configurations
.filter { it.isCanBeResolved }
.flatMap { componentsOf(it) }
.mapNotNull { it.moduleVersion }
.groupBy { moduleKey(it.group, it.name) }
.mapValues { (_, versions) -> versions.maxOfWith(VersionComparator) { it.version } }
}

/**
* Builds the `"group:name"` key under which a module's resolved version is recorded
* and looked up.
Expand All @@ -231,7 +177,7 @@ private fun Project.resolvedVersions(): Map<String, String> {
* consistent with what [resolvedVersions] records and with the grouping done by
* [deduplicate].
*/
private fun moduleKey(group: String?, name: String): String = "$group:$name"
internal fun moduleKey(group: String?, name: String): String = "$group:$name"

/**
* Tells whether the dependency is an external module dependency.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ import org.gradle.api.plugins.BasePlugin
* them. If the project does not have these values, and they are not specified in the `ext`
* block, the resulting `pom.xml` file is going to contain empty blocks,
* e.g., `<groupId></groupId>`.
*
* The version reported for each dependency is the one selected by dependency
* resolution. A task of one project must not resolve the configurations of
* another, so `generatePom` does not resolve anything itself. Instead, a helper
* task named [ResolvedVersions.taskName] is registered for the project passed
* to [applyTo] and each of its subprojects. Every helper resolves only the
* configurations of its own project and stores the result under its build
* directory; `generatePom` depends on the helpers and merges their outputs.
* This keeps the generated file the same no matter which other tasks run in
* the same Gradle invocation.
*/
@Suppress("unused")
object PomGenerator {
Expand All @@ -83,15 +93,22 @@ object PomGenerator {
plugin(BasePlugin::class.java)
}

val collectors = project.allprojects.map { ResolvedVersions.registerTaskIn(it) }

val task = project.tasks.register("generatePom") {
group = SpineTaskGroup.name
description = "Generates a `pom.xml` file describing project dependencies"
// Plain ordering on purpose: both the collectors and this task declare
// no inputs or outputs, so they always run. Do not replace this with
// input/output wiring — up-to-date skipping would reintroduce the
// stale-report bug this design cures.
dependsOn(collectors)
doLast {
val pomFile = Paths.outputFile(project.rootDir, pomFilename)
pomFile.parentFile.mkdirs()

val projectData = project.metadata()
val writer = PomXmlWriter(projectData)
val writer = PomXmlWriter(projectData, ResolvedVersions::readFrom)
writer.writeTo(pomFile)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,23 @@ import io.spine.gradle.report.pom.PomFormatting.writeStart
import java.io.File
import java.io.FileWriter
import java.io.StringWriter
import org.gradle.api.Project

/**
* Writes the dependencies of a Gradle project and its subprojects as a `pom.xml` file.
*
* The resulting file is not usable for `maven` build tasks but serves as a description
* of the first-level dependencies for each project or subproject.
* Their transitive dependencies are not included in the result.
*
* The version of each dependency is taken from the map returned by
* [resolvedVersionsOf] for the project the dependency comes from.
* See the [dependencies] extension function for details.
*/
internal class PomXmlWriter
internal constructor(
private val projectMetadata: ProjectMetadata
private val projectMetadata: ProjectMetadata,
private val resolvedVersionsOf: (Project) -> Map<String, String>
) {

/**
Expand Down Expand Up @@ -77,7 +83,7 @@ internal constructor(
*/
private fun projectDependencies(): String {
val destination = StringWriter()
val dependencyWriter = DependencyWriter.of(projectMetadata.project)
val dependencyWriter = DependencyWriter.of(projectMetadata.project, resolvedVersionsOf)
dependencyWriter.writeXmlTo(destination)
return destination.toString()
}
Expand Down
Loading
Loading