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
5 changes: 5 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ gradlePlugin {
id = "dd-trace-java.config-inversion-linter"
implementationClass = "datadog.gradle.plugin.config.ConfigInversionLinter"
}

create("instrumentation-naming") {
id = "dd-trace-java.instrumentation-naming"
implementationClass = "datadog.gradle.plugin.naming.InstrumentationNamingPlugin"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package datadog.gradle.plugin.naming

import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty

/**
* Extension for configuring instrumentation naming convention checks.
*
* Example usage:
* ```
* instrumentationNaming {
* instrumentationsDir.set(file("dd-java-agent/instrumentation"))
* exclusions.set(setOf("http-url-connection", "sslsocket"))
* suffixes.set(setOf("-common", "-stubs"))
* }
* ```
*/
abstract class InstrumentationNamingExtension {
/**
* The directory containing instrumentation modules.
* Defaults to "dd-java-agent/instrumentation".
*/
abstract val instrumentationsDir: Property<String>

/**
* Set of module names to exclude from naming convention checks.
* These modules will not be validated against the naming rules.
*/
abstract val exclusions: SetProperty<String>

/**
* Set of allowed suffixes for module names (e.g., "-common", "-stubs").
* Module names must end with either one of these suffixes or a version number.
* Defaults to ["-common", "-stubs"].
*/
abstract val suffixes: SetProperty<String>

init {
instrumentationsDir.convention("dd-java-agent/instrumentation")
exclusions.convention(emptySet())
suffixes.convention(setOf("-common", "-stubs"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package datadog.gradle.plugin.naming

import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
import java.io.File

/**
* Plugin that validates naming conventions for instrumentation modules.
*
* Rules:
* 1. Module name must end with a version (e.g., "2.0", "3.1") OR end with "-common"
* 2. Module name must include the parent directory name
* (e.g., "couchbase-2.0" must contain "couchbase" which is the parent directory name)
*
* Apply this plugin:
* ```
* plugins {
* id("dd-trace-java.instrumentation-naming")
* }
* ```
*/
class InstrumentationNamingPlugin : Plugin<Project> {
private val versionPattern : Regex = Regex("""\d+\.\d+(\.\d+)?$""")

override fun apply(target: Project) {
val extension = target.extensions.create<InstrumentationNamingExtension>("instrumentationNaming")

target.tasks.register("checkInstrumentationNaming") {
group = "verification"
description = "Validates naming conventions for instrumentation modules"

doLast {
val instrumentationsDir = target.rootProject.file(extension.instrumentationsDir)
val exclusions = extension.exclusions.get()
val suffixes = extension.suffixes.get()

if (!instrumentationsDir.exists() || !instrumentationsDir.isDirectory) {
throw GradleException(
"Instrumentations directory not found: ${instrumentationsDir.absolutePath}"
)
}

val violations = validateInstrumentations(instrumentationsDir, exclusions, suffixes)

if (violations.isNotEmpty()) {
val suffixesStr = suffixes.joinToString("', '", "'", "'")
val errorMessage = buildString {
appendLine("""

Instrumentation naming convention violations found:

""".trimIndent())
violations.forEach { violation ->
appendLine("""
• ${violation.path}
${violation.message}
""".trimIndent())
}
append("""
Naming rules:
1. Module name must end with a version (e.g., '2.0', '3.1') OR one of: $suffixesStr
2. Module name must include the parent directory name
Example: 'couchbase/couchbase-2.0' ✓ (contains 'couchbase')

To exclude specific modules or customize suffixes, configure the plugin:
instrumentationNaming {
exclusions.set(setOf("module-name"))
suffixes.set(setOf("-common", "-stubs"))
}
""".trimIndent())
}
throw GradleException(errorMessage)
} else {
target.logger.lifecycle("✓ All instrumentation modules follow naming conventions")
}
}
}
}

private fun validateInstrumentations(
instrumentationsDir: File,
exclusions: Set<String>,
suffixes: Set<String>
): List<NamingViolation> {
val violations = mutableListOf<NamingViolation>()

fun hasBuildFile(dir: File): Boolean = dir.listFiles()?.any {
it.name == "build.gradle" || it.name == "build.gradle.kts"
} ?: false
Comment on lines +89 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Declared here this becomes like a lambda, this can be a method by moving it one level.

Same for traverseModules.


fun traverseModules(currentDir: File, parentName: String?) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Actually I don't see the need for traverseModules function, this can be the body of validateInstrumentations, and just add a comment this this traverses the modules.

currentDir.listFiles { file -> file.isDirectory }?.forEach childLoop@{ childDir ->
val moduleName = childDir.name

// Skip build directories and other non-instrumentation directories
if (moduleName in setOf("build", "src", ".gradle")) {
return@childLoop
}

val childHasBuildFile = hasBuildFile(childDir)
val nestedModules = childDir.listFiles { file -> file.isDirectory }?.filter { hasBuildFile(it) } ?: emptyList()

if (childHasBuildFile && moduleName !in exclusions) {
val relativePath = childDir.relativeTo(instrumentationsDir).path
if (parentName == null && nestedModules.isEmpty()) {
validateLeafModuleName(moduleName, relativePath, suffixes)?.let { violations.add(it) }
} else if (parentName != null) {
violations.addAll(validateModuleName(moduleName, parentName, relativePath, suffixes))
}
}

// Continue traversing to validate deeply nested modules
if (nestedModules.isNotEmpty() || !childHasBuildFile) {
traverseModules(childDir, moduleName)
}
}
}

traverseModules(instrumentationsDir, null)

return violations
}

private fun validateModuleName(
moduleName: String,
parentName: String,
relativePath: String,
suffixes: Set<String>
): List<NamingViolation> {
// Rule 1: Module name must end with version pattern or one of the configured suffixes
validateVersionOrSuffix(moduleName, relativePath, suffixes)?.let { return listOf(it) }

// Rule 2: Module name must contain parent directory name
if (!moduleName.contains(parentName, ignoreCase = true)) {
return listOf(NamingViolation(
relativePath,
"Module name '$moduleName' should contain parent directory name '$parentName'"
))
}

return emptyList()
}

/**
* Validates naming for leaf modules (modules at the top level with no parent grouping).
* These only need to check the version/suffix requirement.
*/
private fun validateLeafModuleName(
moduleName: String,
relativePath: String,
suffixes: Set<String>
): NamingViolation? {
return validateVersionOrSuffix(moduleName, relativePath, suffixes)
}

/**
* Validates that a module name ends with either a version or one of the configured suffixes.
*/
private fun validateVersionOrSuffix(
moduleName: String,
relativePath: String,
suffixes: Set<String>
): NamingViolation? {
val endsWithSuffix = suffixes.any { moduleName.endsWith(it) }
val endsWithVersion = versionPattern.containsMatchIn(moduleName)

if (!endsWithVersion && !endsWithSuffix) {
val suffixesStr = suffixes.joinToString("', '", "'", "'")
return NamingViolation(
relativePath,
"Module name '$moduleName' must end with a version (e.g., '2.0', '3.1.0') or one of: $suffixesStr"
)
}

return null
}

private data class NamingViolation(
val path: String,
val message: String
)
}
1 change: 1 addition & 0 deletions dd-java-agent/instrumentation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
id 'com.gradleup.shadow'
id("dd-trace-java.instrumentation-naming")
}
apply from: "$rootDir/gradle/java.gradle"

Expand Down