Skip to content

Commit

Permalink
Refactor: Detangle GitHub rendering from extraction
Browse files Browse the repository at this point in the history
- Extracted `DependencyGraphRenderer` interface with single GitHub implementation
- Abstract plugin allows renderer classname to be specified

This should make it possible to implement support for other output formats.
  • Loading branch information
bigdaz committed Aug 30, 2023
1 parent 964e9ab commit d40470b
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 143 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.gradle.github.dependencygraph

import org.gradle.api.Plugin
import org.gradle.api.invocation.Gradle
import org.gradle.api.provider.Provider
import org.gradle.github.dependencygraph.internal.DependencyExtractor
import org.gradle.github.dependencygraph.internal.DependencyExtractorBuildService
import org.gradle.github.dependencygraph.internal.LegacyDependencyExtractor
import org.gradle.github.dependencygraph.internal.util.GradleExtensions
import org.gradle.github.dependencygraph.internal.util.service
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.util.GradleVersion

abstract class AbstractDependencyExtractorPlugin : Plugin<Gradle> {
// Register extension functions on `Gradle` type
private companion object : GradleExtensions()

abstract fun getRendererClassName(): String

internal lateinit var dependencyExtractorProvider: Provider<out DependencyExtractor>

override fun apply(gradle: Gradle) {
val gradleVersion = GradleVersion.current()
// Create the adapter based upon the version of Gradle
val applicatorStrategy = when {
gradleVersion < GradleVersion.version("8.0") -> PluginApplicatorStrategy.LegacyPluginApplicatorStrategy
else -> PluginApplicatorStrategy.DefaultPluginApplicatorStrategy
}

// Create the service
dependencyExtractorProvider = applicatorStrategy.createExtractorService(gradle, getRendererClassName())

gradle.rootProject { project ->
dependencyExtractorProvider
.get()
.rootProjectBuildDirectory = project.buildDir
}

// Register the service to listen for Build Events
applicatorStrategy.registerExtractorListener(gradle, dependencyExtractorProvider)

// Register the shutdown hook that should execute at the completion of the Gradle build.
applicatorStrategy.registerExtractorServiceShutdown(gradle, dependencyExtractorProvider)
}

/**
* Adapters for creating the [DependencyExtractor] and installing it into [Gradle] based upon the Gradle version.
*/
private interface PluginApplicatorStrategy {

fun createExtractorService(
gradle: Gradle,
rendererClassName: String
): Provider<out DependencyExtractor>

fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
)

fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
)

object LegacyPluginApplicatorStrategy : PluginApplicatorStrategy {

override fun createExtractorService(
gradle: Gradle,
rendererClassName: String
): Provider<out DependencyExtractor> {
val dependencyExtractor = LegacyDependencyExtractor(rendererClassName)
return gradle.providerFactory.provider { dependencyExtractor }
}

override fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.buildOperationListenerManager
.addListener(extractorServiceProvider.get())
}

override fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.buildFinished {
extractorServiceProvider.get().close()
gradle.buildOperationListenerManager
.removeListener(extractorServiceProvider.get())
}
}
}

object DefaultPluginApplicatorStrategy : PluginApplicatorStrategy {
private const val SERVICE_NAME = "gitHubDependencyExtractorService"

override fun createExtractorService(
gradle: Gradle,
rendererClassName: String
): Provider<out DependencyExtractor> {
return gradle.sharedServices.registerIfAbsent(
SERVICE_NAME,
DependencyExtractorBuildService::class.java
) {
it.parameters.rendererClassName.set(rendererClassName)
}
}

override fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.service<BuildEventListenerRegistryInternal>()
.onOperationCompletion(extractorServiceProvider)
}

override fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
// No-op as DependencyExtractorService is Auto-Closable
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,123 +1,12 @@
package org.gradle.github.dependencygraph

import org.gradle.api.Plugin
import org.gradle.api.invocation.Gradle
import org.gradle.api.provider.Provider
import org.gradle.github.dependencygraph.internal.DependencyExtractor
import org.gradle.github.dependencygraph.internal.DependencyExtractorBuildService
import org.gradle.github.dependencygraph.internal.LegacyDependencyExtractor
import org.gradle.github.dependencygraph.internal.util.GradleExtensions
import org.gradle.github.dependencygraph.internal.util.service
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.util.GradleVersion
import org.gradle.github.dependencygraph.internal.github.GitHubDependencyGraphRenderer

/**
* A plugin that collects all resolved dependencies in a Gradle build and exports it using the GitHub API format.
*/
class GitHubDependencyExtractorPlugin : Plugin<Gradle> {
// Register extension functions on `Gradle` type
private companion object : GradleExtensions()

internal lateinit var dependencyExtractorProvider: Provider<out DependencyExtractor>

override fun apply(gradle: Gradle) {
val gradleVersion = GradleVersion.current()
// Create the adapter based upon the version of Gradle
val applicatorStrategy = when {
gradleVersion < GradleVersion.version("8.0") -> PluginApplicatorStrategy.LegacyPluginApplicatorStrategy
else -> PluginApplicatorStrategy.DefaultPluginApplicatorStrategy
}

// Create the service
dependencyExtractorProvider = applicatorStrategy.createExtractorService(gradle)

gradle.rootProject { project ->
dependencyExtractorProvider
.get()
.rootProjectBuildDirectory = project.buildDir
}

// Register the service to listen for Build Events
applicatorStrategy.registerExtractorListener(gradle, dependencyExtractorProvider)

// Register the shutdown hook that should execute at the completion of the Gradle build.
applicatorStrategy.registerExtractorServiceShutdown(gradle, dependencyExtractorProvider)
}

/**
* Adapters for creating the [DependencyExtractor] and installing it into [Gradle] based upon the Gradle version.
*/
private interface PluginApplicatorStrategy {

fun createExtractorService(
gradle: Gradle
): Provider<out DependencyExtractor>

fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
)

fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
)

object LegacyPluginApplicatorStrategy : PluginApplicatorStrategy {

override fun createExtractorService(
gradle: Gradle
): Provider<out DependencyExtractor> {
val dependencyExtractor = LegacyDependencyExtractor()
return gradle.providerFactory.provider { dependencyExtractor }
}

override fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.buildOperationListenerManager
.addListener(extractorServiceProvider.get())
}

override fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.buildFinished {
extractorServiceProvider.get().close()
gradle.buildOperationListenerManager
.removeListener(extractorServiceProvider.get())
}
}
}

object DefaultPluginApplicatorStrategy : PluginApplicatorStrategy {
private const val SERVICE_NAME = "gitHubDependencyExtractorService"

override fun createExtractorService(
gradle: Gradle
): Provider<out DependencyExtractor> {
return gradle.sharedServices.registerIfAbsent(
SERVICE_NAME,
DependencyExtractorBuildService::class.java
) {}
}

override fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.service<BuildEventListenerRegistryInternal>()
.onOperationCompletion(extractorServiceProvider)
}

override fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
// No-op as DependencyExtractorService is Auto-Closable
}
}
class GitHubDependencyExtractorPlugin : AbstractDependencyExtractorPlugin() {
override fun getRendererClassName(): String {
return GitHubDependencyGraphRenderer::class.java.name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier
import org.gradle.api.internal.artifacts.configurations.ResolveConfigurationDependenciesBuildOperationType
import org.gradle.api.logging.Logging
import org.gradle.github.GitHubDependencyGraphPlugin
import org.gradle.github.dependencygraph.internal.github.GitHubDependencyGraphOutput
import org.gradle.github.dependencygraph.internal.github.GitHubSnapshotParams
import org.gradle.github.dependencygraph.internal.model.*
import org.gradle.github.dependencygraph.internal.util.*
import org.gradle.initialization.EvaluateSettingsBuildOperationType
Expand Down Expand Up @@ -50,14 +49,7 @@ abstract class DependencyExtractor :
pluginParameters.loadOptional(PARAM_REPORT_DIR)
}

private val gitHubSnapshotParams by lazy {
GitHubSnapshotParams(
pluginParameters.load(PARAM_JOB_CORRELATOR),
pluginParameters.load(PARAM_JOB_ID),
pluginParameters.load(PARAM_GITHUB_SHA),
pluginParameters.load(PARAM_GITHUB_REF)
)
}
abstract fun getRendererClassName(): String

override fun started(buildOperation: BuildOperationDescriptor, startEvent: OperationStartEvent) {
// This method will never be called when registered in a `BuildServiceRegistry` (ie. Gradle 6.1 & higher)
Expand Down Expand Up @@ -239,8 +231,12 @@ abstract class DependencyExtractor :
}

private fun writeDependencyGraph() {
val builder = GitHubDependencyGraphOutput(gitHubSnapshotParams, getOutputDir())
builder.outputDependencyGraph(resolvedConfigurations, buildLayout)
createRenderer().outputDependencyGraph(pluginParameters, buildLayout, resolvedConfigurations, getOutputDir())
}

private fun createRenderer(): DependencyGraphRenderer {
LOGGER.lifecycle("Constructing renderer: ${getRendererClassName()}")
return Class.forName(getRendererClassName()).getDeclaredConstructor().newInstance() as DependencyGraphRenderer
}

private fun getOutputDir(): File {
Expand Down Expand Up @@ -275,6 +271,10 @@ abstract class DependencyExtractor :
)
}
}

companion object {
private val LOGGER = Logging.getLogger(DependencyExtractor::class.java)
}
}

private inline fun <reified D, reified R> handleBuildOperationTypeRaw(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package org.gradle.github.dependencygraph.internal

import org.gradle.api.provider.Property
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters

abstract class DependencyExtractorBuildService :
DependencyExtractor(),
BuildService<BuildServiceParameters.None>
BuildService<DependencyExtractorBuildService.Params>
{
// Some parameters for the web server
internal interface Params : BuildServiceParameters {
val rendererClassName: Property<String>
}

override fun getRendererClassName(): String {
return parameters.rendererClassName.get()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.gradle.github.dependencygraph.internal

import org.gradle.github.dependencygraph.internal.model.BuildLayout
import org.gradle.github.dependencygraph.internal.model.ResolvedConfiguration
import org.gradle.github.dependencygraph.internal.util.PluginParameters
import java.io.File

interface DependencyGraphRenderer {
fun outputDependencyGraph(pluginParameters: PluginParameters,
buildLayout: BuildLayout,
resolvedConfigurations: MutableList<ResolvedConfiguration>,
outputDirectory: File
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package org.gradle.github.dependencygraph.internal

import org.gradle.initialization.EvaluateSettingsBuildOperationType

class LegacyDependencyExtractor : DependencyExtractor() {
class LegacyDependencyExtractor(private val rendererClassName: String) : DependencyExtractor() {
override fun getRendererClassName(): String {
return rendererClassName
}

override fun extractSettings(details: EvaluateSettingsBuildOperationType.Details) {
// Extraction fails for included builds on Gradle 5.x.
// It's OK to ignore these events since we only care about the root build settings file at this stage.
Expand Down
Loading

0 comments on commit d40470b

Please sign in to comment.