Das Gradle-Plugin-Ecosystem umfasst tausende Plugins, die häufige Build-Aufgaben lösen. Der Gradle Plugin Portal hostet Community-Plugins, während große Projekte wie Spring, Android oder Kotlin eigene Plugin-Suites maintainen. Bevor Eigenentwicklung erwogen wird, sollte eine systematische Evaluation existierender Lösungen erfolgen. Diese Evaluation spart Entwicklungszeit, reduziert Maintenance-Burden und nutzt Community-Expertise. Die NIH-Syndrome (Not Invented Here) führt oft zu unnötiger Duplikation und technischer Schuld.
Die Evaluierung von Plugins erfordert definierte Kriterien, die über reine Funktionalität hinausgehen. Maintenance-Activity zeigt, ob das Plugin aktiv entwickelt wird. Issue-Response-Time indiziert Support-Quality. Documentation-Completeness bestimmt Adoption-Ease. License-Compatibility gewährleistet Legal-Compliance. Performance-Impact quantifiziert Build-Time-Overhead. Diese Multi-Dimensionale Evaluation identifiziert Plugins, die langfristig viable sind, nicht nur kurzfristige Probleme lösen.
Die Kosten-Nutzen-Analyse vergleicht Plugin-Adoption mit Eigenentwicklung. Entwicklungszeit für Custom-Plugins übersteigt oft initial Estimates erheblich. Maintenance-Costs akkumulieren über Zeit, besonders bei Gradle-Version-Updates. Testing-Effort für Cross-Platform-Compatibility und Edge-Cases wird häufig unterschätzt. Dagegen stehen Benefits wie Perfect-Fit-Solutions und vollständige Kontrolle. Die Analyse sollte Total-Cost-of-Ownership über mehrere Jahre betrachten, nicht nur Initial-Implementation.
// Plugin Evaluation Framework
tasks.register("evaluatePlugin") {
description = "Evaluate a Gradle plugin for adoption"
group = "plugin-management"
val pluginId = project.findProperty("plugin.id") as String?
?: throw GradleException("Specify plugin.id property")
doLast {
val evaluation = PluginEvaluation(pluginId)
// Fetch plugin metadata from Gradle Plugin Portal
val metadata = fetchPluginMetadata(pluginId)
// Evaluation Criteria
evaluation.criteria = mapOf(
"Maintenance" to evaluateMaintenance(metadata),
"Documentation" to evaluateDocumentation(metadata),
"Community" to evaluateCommunity(metadata),
"Performance" to evaluatePerformance(pluginId),
"Security" to evaluateSecurity(metadata),
"Compatibility" to evaluateCompatibility(metadata)
)
// Score calculation (0-100)
evaluation.score = calculatePluginScore(evaluation.criteria)
// Risk assessment
evaluation.risks = identifyRisks(metadata)
// Alternative analysis
evaluation.alternatives = findAlternativePlugins(metadata.functionality)
// Generate evaluation report
generateEvaluationReport(evaluation)
}
}
fun evaluateMaintenance(metadata: PluginMetadata): Score {
val daysSinceLastRelease = ChronoUnit.DAYS.between(
metadata.lastReleaseDate,
Instant.now()
)
val releaseFrequency = metadata.releases.size.toDouble() /
ChronoUnit.DAYS.between(metadata.firstReleaseDate, Instant.now())
return Score(
value = when {
daysSinceLastRelease < 30 -> 100
daysSinceLastRelease < 90 -> 80
daysSinceLastRelease < 180 -> 60
daysSinceLastRelease < 365 -> 40
else -> 20
},
details = mapOf(
"lastRelease" to metadata.lastReleaseDate.toString(),
"releaseCount" to metadata.releases.size,
"averageReleaseFrequency" to "${"%.2f".format(releaseFrequency * 30)} releases/month",
"contributors" to metadata.contributors.size
)
)
}
// Plugin Comparison Matrix
tasks.register("comparePlugins") {
description = "Compare multiple plugins for the same functionality"
group = "plugin-management"
val functionality = project.findProperty("plugin.function") as String?
?: "docker" // Example default
doLast {
val candidates = findPluginsForFunctionality(functionality)
val comparisonMatrix = candidates.map { plugin ->
mapOf(
"plugin" to plugin.id,
"version" to plugin.latestVersion,
"downloads" to plugin.downloadCount,
"stars" to plugin.githubStars,
"issues" to plugin.openIssues,
"lastUpdate" to plugin.lastUpdate,
"gradleSupport" to plugin.supportedGradleVersions,
"license" to plugin.license,
"size" to plugin.artifactSize
)
}
// Generate comparison report
val reportFile = file("${buildDir}/reports/plugin-comparison.html")
reportFile.parentFile.mkdirs()
reportFile.writeText(generateComparisonHtml(comparisonMatrix))
logger.lifecycle("Plugin comparison report: ${reportFile.toURI()}")
// Recommendation
val recommended = selectBestPlugin(candidates)
logger.lifecycle("Recommended plugin: ${recommended.id}")
logger.lifecycle("Reason: ${recommended.selectionReason}")
}
}
// Plugin Testing Framework
abstract class PluginTestHarness : DefaultTask() {
@Input
abstract val pluginId: Property<String>
@Input
abstract val testScenarios: ListProperty<TestScenario>
@TaskAction
fun testPlugin() {
val testProject = createTestProject()
testScenarios.get().forEach { scenario ->
logger.lifecycle("Testing scenario: ${scenario.name}")
// Apply plugin to test project
testProject.apply {
plugin(pluginId.get())
}
// Configure plugin
scenario.configuration(testProject)
// Execute test tasks
val result = testProject.execute(scenario.tasksToRun)
// Verify results
scenario.verification(result)
// Measure performance impact
val performanceImpact = measurePerformanceImpact(testProject)
logger.lifecycle(" Result: ${if (result.success) "PASSED" else "FAILED"}")
logger.lifecycle(" Performance impact: ${performanceImpact}ms")
// Clean up
testProject.clean()
}
}
private fun measurePerformanceImpact(project: TestProject): Long {
// Baseline without plugin
val baselineTime = project.measureExecutionTime(listOf("build"))
// With plugin
project.apply { plugin(pluginId.get()) }
val pluginTime = project.measureExecutionTime(listOf("build"))
return pluginTime - baselineTime
}
}Die Architektur von Gradle-Plugins sollte Separation of Concerns,
Testability und Maintainability priorisieren. Plugin-Classes
implementieren Plugin<Project> oder
Plugin<Settings> Interfaces und kapseln
Configuration-Logic. Task-Classes extenden DefaultTask und
implementieren konkrete Build-Actions. Extension-Classes definieren
Plugin-DSL und User-Configuration. Service-Classes bieten
Shared-Functionality über Task-Boundaries. Diese Layered-Architecture
ermöglicht Independent Testing und Evolution einzelner Components.
Convention-over-Configuration reduziert Plugin-Complexity und verbessert User-Experience. Sensible Defaults ermöglichen Zero-Configuration-Usage für Common-Cases. Progressive Disclosure exponiert Advanced-Features nur bei Bedarf. Type-Safe-Configuration via Kotlin-DSL oder Groovy-DSL mit IDE-Support verhindert Configuration-Errors. Lazy-Configuration via Provider-API optimiert Configuration-Time. Diese Patterns machen Plugins sowohl powerful als auch approachable.
// Well-architected Plugin Implementation
abstract class OptimizedPlugin : Plugin<Project> {
override fun apply(project: Project) {
// Create extension with lazy configuration
val extension = project.extensions.create<OptimizedExtension>("optimized")
// Register configuration rules (not tasks yet)
project.afterEvaluate {
configureConventions(extension)
}
// Register tasks lazily
registerTasks(project, extension)
// Apply base plugins if needed
project.plugins.withType<JavaPlugin> {
configureForJava(project, extension)
}
// Configuration cache compatible
configureConfigurationCache(project)
}
private fun registerTasks(project: Project, extension: OptimizedExtension) {
// Use register, not create
project.tasks.register<OptimizedCompileTask>("optimizedCompile") {
// Use providers for lazy evaluation
inputFiles.from(extension.sources)
outputDirectory.set(extension.outputDir)
optimizationLevel.set(extension.optimization.level)
// Task dependencies via providers
dependsOn(project.provider {
project.tasks.matching { it.name.startsWith("process") }
})
}
project.tasks.register<OptimizedAnalyzeTask>("optimizedAnalyze") {
// Configuration cache compatible inputs
analysisConfiguration.set(project.provider {
AnalysisConfig(
threshold = extension.analysis.threshold.get(),
strict = extension.analysis.strict.get()
)
})
}
}
private fun configureConfigurationCache(project: Project) {
// Avoid project reference in task actions
project.tasks.withType<OptimizedTask>().configureEach {
// Use providers instead of direct access
projectName.set(project.name)
projectVersion.set(project.provider { project.version.toString() })
}
}
}
// Type-safe Extension DSL
abstract class OptimizedExtension {
abstract val sources: ConfigurableFileCollection
abstract val outputDir: DirectoryProperty
abstract val optimization: OptimizationOptions
abstract val analysis: AnalysisOptions
init {
// Sensible defaults
outputDir.convention(
project.layout.buildDirectory.dir("optimized")
)
}
// Nested DSL
fun optimization(action: Action<OptimizationOptions>) {
action.execute(optimization)
}
fun analysis(action: Action<AnalysisOptions>) {
action.execute(analysis)
}
}
// Service for shared functionality
abstract class OptimizationService : BuildService<OptimizationService.Params> {
interface Params : BuildServiceParameters {
val cacheDir: DirectoryProperty
val maxCacheSize: Property<Long>
}
private val cache = mutableMapOf<String, OptimizationResult>()
fun optimize(input: File): OptimizationResult {
val cacheKey = calculateCacheKey(input)
return cache.getOrPut(cacheKey) {
performOptimization(input)
}
}
override fun close() {
// Cleanup resources
persistCache()
cache.clear()
}
}
// Plugin Testing
class OptimizedPluginTest {
@Test
fun `plugin applies successfully`() {
val project = ProjectBuilder.builder().build()
project.plugins.apply(OptimizedPlugin::class.java)
assertThat(project.plugins.hasPlugin(OptimizedPlugin::class.java)).isTrue()
assertThat(project.extensions.findByName("optimized")).isNotNull()
}
@Test
fun `tasks are registered lazily`() {
val project = ProjectBuilder.builder().build()
project.plugins.apply(OptimizedPlugin::class.java)
// Tasks should not be realized yet
assertThat(project.tasks.names).doesNotContain("optimizedCompile")
// Accessing task should realize it
project.tasks.named("optimizedCompile")
assertThat(project.tasks.names).contains("optimizedCompile")
}
@Test
fun `configuration cache compatible`() {
val runner = GradleRunner.create()
.withProjectDir(testProjectDir)
.withPluginClasspath()
.withArguments("optimizedCompile", "--configuration-cache")
// First run stores configuration
val firstResult = runner.build()
assertThat(firstResult.output).contains("Configuration cache entry stored")
// Second run reuses configuration
val secondResult = runner.build()
assertThat(secondResult.output).contains("Configuration cache entry reused")
}
}Build-Convention-Plugins standardisieren Project-Configuration über
Teams und Repositories. Diese Plugins encapsulieren Company-Standards,
Coding-Conventions und Common-Configurations. Sie werden typischerweise
in buildSrc oder dedicated Convention-Plugin-Repositories
entwickelt. Feature-Plugins hingegen adding neue Capabilities wie
Docker-Support, Cloud-Deployment oder Code-Generation. Die
Unterscheidung bestimmt Development-Approach, Testing-Strategy und
Distribution-Method.
Convention-Plugins sollten minimale External-Dependencies haben und sich auf Configuration fokussieren. Sie composing existing Plugins rather than implementing complex Logic. Version-Catalog-Integration centralizing Dependency-Versions. Gradle-Properties enabling Environment-specific Overrides. Diese Plugins werden häufig updated und müssen backward-compatible sein. Testing fokussiert auf Configuration-Correctness und Cross-Project-Compatibility.
// Convention Plugin for Java Projects
class JavaConventionPlugin : Plugin<Project> {
override fun apply(project: Project) {
// Apply base plugins
project.plugins.apply("java-library")
project.plugins.apply("jacoco")
project.plugins.apply("checkstyle")
project.plugins.apply("com.github.spotbugs")
// Configure Java compilation
project.extensions.configure<JavaExtension> {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
withJavadocJar()
withSourcesJar()
}
// Configure testing
project.tasks.withType<Test>().configureEach {
useJUnitPlatform()
maxHeapSize = "1G"
testLogging {
events("passed", "skipped", "failed")
exceptionFormat = TestExceptionFormat.FULL
}
}
// Configure code quality
project.configure<CheckstyleExtension> {
toolVersion = "10.12.0"
configFile = project.rootProject.file("config/checkstyle/checkstyle.xml")
}
project.configure<SpotBugsExtension> {
effort.set(Effort.MAX)
reportLevel.set(Confidence.LOW)
}
// Configure publishing
project.plugins.withType<MavenPublishPlugin> {
configurePublishing(project)
}
// Apply company-specific configurations
applyCompanyStandards(project)
}
private fun applyCompanyStandards(project: Project) {
// Mandatory metadata
project.afterEvaluate {
requireNotNull(project.description) {
"Project description is required"
}
require(project.version != "unspecified") {
"Project version must be specified"
}
}
// Security scanning
project.tasks.register("securityScan") {
doLast {
// Implementation
}
}
// License headers
project.tasks.withType<JavaCompile>().configureEach {
options.headerOutputDirectory.set(
project.layout.buildDirectory.dir("generated/headers")
)
}
}
}
// Feature Plugin for API Documentation Generation
class ApiDocumentationPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<ApiDocExtension>("apiDoc")
// Only add new functionality, don't impose conventions
project.tasks.register<GenerateApiDocTask>("generateApiDoc") {
source = project.fileTree("src/main/java") {
include("**/*Controller.java")
include("**/*Service.java")
}
outputDirectory.set(extension.outputDir)
format.set(extension.format)
includePrivate.set(extension.includePrivateApis)
// Integration points with other plugins
project.plugins.withType<JavaPlugin> {
classpath.from(project.configurations.named("compileClasspath"))
dependsOn(project.tasks.named("compileJava"))
}
}
// Add to documentation tasks if available
project.tasks.findByName("javadoc")?.let { javadocTask ->
javadocTask.finalizedBy("generateApiDoc")
}
}
}
// Composite Build for Convention Plugins
// conventions/settings.gradle.kts
rootProject.name = "conventions"
// conventions/build.gradle.kts
plugins {
`kotlin-dsl`
`java-gradle-plugin`
`maven-publish`
}
gradlePlugin {
plugins {
create("javaConventions") {
id = "com.company.java-conventions"
implementationClass = "com.company.gradle.JavaConventionPlugin"
displayName = "Company Java Conventions"
description = "Applies company-wide Java project conventions"
}
create("serviceConventions") {
id = "com.company.service-conventions"
implementationClass = "com.company.gradle.ServiceConventionPlugin"
}
}
}
// Usage in projects
// build.gradle.kts
plugins {
id("com.company.java-conventions")
id("com.company.service-conventions")
// Feature plugins as needed
id("com.google.cloud.tools.jib") version "3.4.0"
}Plugin-Testing erfordert mehrere Test-Ebenen für Comprehensive Coverage. Unit-Tests validieren Individual Classes und Methods. Integration-Tests verifizieren Plugin-Application und Task-Execution. Functional-Tests using GradleRunner testing Complete Build-Scenarios. Cross-Version-Tests ensuring Compatibility mit verschiedenen Gradle-Versions. Performance-Tests quantifying Plugin-Overhead. Diese Multi-Layer-Testing-Strategy ensuring Plugin-Reliability und Performance.
Test-Fixtures und Utilities simplifying Plugin-Testing. ProjectBuilder creating In-Memory-Projects für Unit-Tests. GradleRunner executing Real Gradle-Builds für Integration-Tests. TestKit providing Temporary Directories und Classpath-Management. Custom Assertions verifying Task-Outputs und Project-State. Diese Test-Infrastructure reducing Boilerplate und improving Test-Readability.
// Comprehensive Plugin Testing Strategy
class PluginTestSuite {
// Unit test for plugin components
@Test
fun `extension provides sensible defaults`() {
val extension = ObjectFactory().newInstance<MyExtension>()
assertThat(extension.enabled.get()).isTrue()
assertThat(extension.timeout.get()).isEqualTo(Duration.ofMinutes(5))
assertThat(extension.outputFormat.get()).isEqualTo(OutputFormat.JSON)
}
// Integration test with ProjectBuilder
@Test
fun `plugin configures project correctly`() {
val project = ProjectBuilder.builder().build()
project.plugins.apply(MyPlugin::class.java)
// Verify extension is created
val extension = project.extensions.getByType<MyExtension>()
assertThat(extension).isNotNull()
// Verify tasks are registered
assertThat(project.tasks.names).contains("myTask")
// Verify task configuration
val task = project.tasks.named("myTask", MyTask::class.java).get()
assertThat(task.inputFiles).isEmpty()
assertThat(task.outputDirectory.get().asFile).isEqualTo(
project.layout.buildDirectory.dir("my-output").get().asFile
)
}
// Functional test with GradleRunner
@Test
fun `plugin executes successfully in real build`(@TempDir projectDir: Path) {
// Setup test project
projectDir.resolve("build.gradle.kts").writeText("""
plugins {
id("my-plugin")
}
myExtension {
enabled = true
inputDir = file("src")
outputFormat = "XML"
}
""")
projectDir.resolve("settings.gradle.kts").writeText("""
rootProject.name = "test-project"
""")
// Create input files
projectDir.resolve("src").createDirectory()
projectDir.resolve("src/input.txt").writeText("test content")
// Execute build
val result = GradleRunner.create()
.withProjectDir(projectDir.toFile())
.withPluginClasspath()
.withArguments("myTask", "--info")
.build()
// Verify execution
assertThat(result.task(":myTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(projectDir.resolve("build/my-output/output.xml")).exists()
assertThat(result.output).contains("Processing 1 files")
}
// Cross-version compatibility test
@ParameterizedTest
@ValueSource(strings = ["7.0", "7.6", "8.0", "8.4"])
fun `plugin works with Gradle version`(gradleVersion: String, @TempDir projectDir: Path) {
setupTestProject(projectDir)
val result = GradleRunner.create()
.withProjectDir(projectDir.toFile())
.withPluginClasspath()
.withGradleVersion(gradleVersion)
.withArguments("myTask")
.build()
assertThat(result.task(":myTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
// Performance test
@Test
fun `plugin has acceptable performance impact`(@TempDir projectDir: Path) {
setupLargeTestProject(projectDir, fileCount = 1000)
// Baseline without plugin
val baselineTime = measureExecution(projectDir, listOf("build"))
// With plugin
addPluginToProject(projectDir)
val pluginTime = measureExecution(projectDir, listOf("build", "myTask"))
val overhead = pluginTime - baselineTime
val overheadPercentage = (overhead.toDouble() / baselineTime) * 100
assertThat(overheadPercentage).isLessThan(10.0)
.withFailMessage("Plugin overhead is ${overheadPercentage}%, expected < 10%")
}
// Configuration cache compatibility test
@Test
fun `plugin supports configuration cache`(@TempDir projectDir: Path) {
setupTestProject(projectDir)
// First run stores configuration
val firstResult = GradleRunner.create()
.withProjectDir(projectDir.toFile())
.withPluginClasspath()
.withArguments("myTask", "--configuration-cache")
.build()
assertThat(firstResult.output).contains("Configuration cache entry stored")
// Second run reuses configuration
val secondResult = GradleRunner.create()
.withProjectDir(projectDir.toFile())
.withPluginClasspath()
.withArguments("myTask", "--configuration-cache")
.build()
assertThat(secondResult.output).contains("Configuration cache entry reused")
assertThat(secondResult.task(":myTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
// Test fixtures for plugin testing
object PluginTestFixtures {
fun createTestProject(dir: Path, configure: TestProjectBuilder.() -> Unit = {}) {
TestProjectBuilder(dir).apply(configure).build()
}
class TestProjectBuilder(private val dir: Path) {
var plugins = mutableListOf<String>()
var dependencies = mutableListOf<String>()
var tasks = mutableListOf<String>()
var configuration = ""
fun build() {
// Generate build.gradle.kts
dir.resolve("build.gradle.kts").writeText("""
${if (plugins.isNotEmpty()) "plugins {\n${plugins.joinToString("\n") { " $it" }}\n}" else ""}
dependencies {
${dependencies.joinToString("\n ")}
}
$configuration
${tasks.joinToString("\n")}
""".trimIndent())
// Generate settings.gradle.kts
dir.resolve("settings.gradle.kts").writeText("""
rootProject.name = "test-project"
""".trimIndent())
}
}
}
// Plugin quality metrics
tasks.register("analyzePluginQuality") {
description = "Analyze plugin code quality"
group = "verification"
doLast {
val metrics = PluginQualityMetrics()
// Code complexity
metrics.cyclomaticComplexity = analyzeCyclomaticComplexity()
metrics.cognitiveComplexity = analyzeCognitiveComplexity()
// Test coverage
metrics.testCoverage = calculateTestCoverage()
metrics.mutationCoverage = calculateMutationCoverage()
// Documentation
metrics.documentationCoverage = analyzeDocumentationCoverage()
metrics.exampleCoverage = countExamples()
// API stability
metrics.publicApiChanges = analyzeApiChanges()
metrics.deprecations = countDeprecations()
// Performance
metrics.configurationTimeImpact = measureConfigurationTime()
metrics.executionTimeOverhead = measureExecutionOverhead()
generateQualityReport(metrics)
// Fail on quality gates
if (metrics.testCoverage < 80) {
throw GradleException("Test coverage ${metrics.testCoverage}% below threshold 80%")
}
if (metrics.cyclomaticComplexity > 10) {
throw GradleException("Cyclomatic complexity ${metrics.cyclomaticComplexity} above threshold 10")
}
}
}Plugin-Distribution-Strategien hängen von Target-Audience und Usage-Patterns ab. Private Plugins für Internal-Use distributing via Corporate Maven-Repositories oder Git-Repositories. Public Plugins publishing zum Gradle Plugin Portal für Community-Access. Composite-Builds enabling Local-Development und Testing. Version-Catalogs centralizing Plugin-Versions über Projects. Diese Multi-Channel-Distribution addressing verschiedene Use-Cases und Security-Requirements.
Semantic Versioning kommuniziert Breaking-Changes und Compatibility. Major-Versions signaling Breaking API-Changes oder Gradle-Version-Requirements. Minor-Versions adding New Features backward-compatible. Patch-Versions fixing Bugs ohne API-Changes. Pre-Release-Versions (alpha, beta, RC) enabling Early-Adoption-Feedback. Version-Ranges in Plugin-Dependencies allowing Flexible-Resolution. Diese Versioning-Strategy enabling Predictable-Updates und Risk-Management.
// Plugin Publishing Configuration
plugins {
`java-gradle-plugin`
`maven-publish`
id("com.gradle.plugin-publish") version "1.2.0"
signing
}
group = "com.company.gradle"
version = determineVersion()
fun determineVersion(): String {
val baseVersion = file("version.txt").readText().trim()
val isRelease = project.hasProperty("release")
val buildNumber = System.getenv("BUILD_NUMBER") ?: "LOCAL"
return when {
isRelease -> baseVersion
buildNumber != "LOCAL" -> "$baseVersion-RC$buildNumber"
else -> "$baseVersion-SNAPSHOT"
}
}
gradlePlugin {
website.set("https://github.com/company/gradle-plugins")
vcsUrl.set("https://github.com/company/gradle-plugins.git")
plugins {
create("myPlugin") {
id = "com.company.my-plugin"
implementationClass = "com.company.gradle.MyPlugin"
displayName = "My Gradle Plugin"
description = "Plugin for optimizing builds"
tags.set(listOf("optimization", "performance", "build"))
}
}
}
publishing {
publications {
create<MavenPublication>("pluginMaven") {
artifactId = "my-plugin"
pom {
name.set("My Gradle Plugin")
description.set("A Gradle plugin for build optimization")
url.set("https://github.com/company/gradle-plugins")
licenses {
license {
name.set("Apache-2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0")
}
}
developers {
developer {
id.set("team")
name.set("Platform Team")
email.set("platform@company.com")
}
}
scm {
url.set("https://github.com/company/gradle-plugins")
connection.set("scm:git:git://github.com/company/gradle-plugins.git")
developerConnection.set("scm:git:ssh://github.com/company/gradle-plugins.git")
}
}
}
}
repositories {
maven {
name = "corporate"
url = uri("https://nexus.company.com/repository/gradle-plugins")
credentials(PasswordCredentials::class)
}
}
}
// Signing for release versions
signing {
setRequired { !version.toString().contains("SNAPSHOT") }
sign(publishing.publications["pluginMaven"])
}
// Compatibility Testing
tasks.register("testCompatibility") {
description = "Test plugin compatibility with different Gradle versions"
group = "verification"
doLast {
val gradleVersions = listOf("7.0", "7.6", "8.0", "8.4", "8.5")
val results = mutableMapOf<String, TestResult>()
gradleVersions.forEach { version ->
logger.lifecycle("Testing with Gradle $version")
val result = GradleRunner.create()
.withProjectDir(file("src/test/projects/sample"))
.withPluginClasspath()
.withGradleVersion(version)
.withArguments("tasks", "--all")
.forwardOutput()
.build()
results[version] = TestResult(
success = result.tasks.all { it.outcome == TaskOutcome.SUCCESS },
output = result.output
)
}
generateCompatibilityReport(results)
}
}
// Version Catalog for Plugin Management
// gradle/libs.versions.toml
[versions]
myPlugin = "2.3.0"
dokka = "1.9.0"
spotless = "6.22.0"
shadow = "8.1.1"
[plugins]
my-plugin = { id = "com.company.my-plugin", version.ref = "myPlugin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
// Usage in projects
plugins {
alias(libs.plugins.my.plugin)
alias(libs.plugins.dokka)
}
// Plugin Migration Guide
tasks.register("generateMigrationGuide") {
description = "Generate migration guide for major version updates"
group = "documentation"
doLast {
val oldVersion = project.property("old.version") as String
val newVersion = project.version.toString()
val apiChanges = analyzeApiChanges(oldVersion, newVersion)
val deprecations = findDeprecations(newVersion)
val breakingChanges = identifyBreakingChanges(apiChanges)
val guideFile = file("MIGRATION_GUIDE_${newVersion}.md")
guideFile.writeText("""
# Migration Guide: $oldVersion → $newVersion
## Breaking Changes
${breakingChanges.joinToString("\n") { "- $it" }}
## Deprecated Features
${deprecations.joinToString("\n") { "- ${it.feature}: ${it.replacement}" }}
## Migration Steps
1. Update plugin version in build.gradle.kts
2. Run `gradle help --scan` to identify usage of deprecated features
3. Update configuration according to breaking changes above
4. Test build with `--warning-mode=all`
## Example Migration
### Before ($oldVersion)
```gradle
myPlugin {
oldProperty = true
deprecatedMethod()
}
```
### After ($newVersion)
```gradle
myPlugin {
newProperty = true
modernMethod {
// New configuration
}
}
```
""".trimIndent())
logger.lifecycle("Migration guide generated: ${guideFile.absolutePath}")
}
}