Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 28 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ plugins {
id("com.diffplug.spotless") version "8.1.0"
}

spotless {
kotlin {
toggleOffOn()
target("**/*.kt")

ktlint("1.8.0").editorConfigOverride(
// Disable trailing comma rules to minimize diff.
mapOf(
"ktlint_standard_trailing-comma-on-call-site" to "disabled",
"ktlint_standard_trailing-comma-on-declaration-site" to "disabled"
)
)
}

kotlinGradle {
toggleOffOn()
target("**/*.gradle.kts")

ktlint("1.8.0").editorConfigOverride(
// Disable trailing comma rules to minimize diff.
mapOf(
"ktlint_standard_trailing-comma-on-call-site" to "disabled",
"ktlint_standard_trailing-comma-on-declaration-site" to "disabled"
)
)
}
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import org.gradle.kotlin.dsl.extra
/**
* Returns the task's path, given affected projects, if this task or its dependencies are affected by git changes.
*/
internal fun findAffectedTaskPath(baseTask: Task, affectedProjects: Map<Project, Set<String>>): String? {
internal fun findAffectedTaskPath(
baseTask: Task,
affectedProjects: Map<Project, Set<String>>
): String? {
val visited = mutableSetOf<Task>()
val queue = mutableListOf(baseTask)

while (queue.isNotEmpty()) {
val t = queue.removeAt(0)
if (visited.contains(t)) {
continue
}
visited.add(t)

val affectedTasks = affectedProjects[t.project]
if (affectedTasks != null) {
if (affectedTasks.contains("all")) {
Expand All @@ -27,7 +30,7 @@ internal fun findAffectedTaskPath(baseTask: Task, affectedProjects: Map<Project,
return "${t.project.path}:${t.name}"
}
}

t.taskDependencies.getDependencies(t).forEach { queue.add(it) }
}
return null
Expand Down Expand Up @@ -106,4 +109,3 @@ fun Project.testAggregate(
createRootTask("${baseTaskName}LatestDepTest", "allLatestDepTests", includePrefixes, excludePrefixes, forceCoverage)
createRootTask("${baseTaskName}Check", "check", includePrefixes, excludePrefixes, forceCoverage)
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package datadog.gradle.plugin.config
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.expr.StringLiteralExpr
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.body.FieldDeclaration
import com.github.javaparser.ast.body.VariableDeclarator
import com.github.javaparser.ast.expr.StringLiteralExpr
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.GradleException
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.kotlin.dsl.getByType
Expand Down Expand Up @@ -39,29 +39,37 @@ private var cachedConfigFields: LoadedConfigFields? = null
private fun loadConfigFields(
mainSourceSetOutput: org.gradle.api.file.FileCollection,
generatedClassName: String
): LoadedConfigFields {
return cachedConfigFields ?: run {
val urls = mainSourceSetOutput.files.map { it.toURI().toURL() }.toTypedArray()
URLClassLoader(urls, LoadedConfigFields::class.java.classLoader).use { cl ->
): LoadedConfigFields = cachedConfigFields ?: run {
val urls = mainSourceSetOutput.files.map { it.toURI().toURL() }.toTypedArray()
URLClassLoader(urls, LoadedConfigFields::class.java.classLoader)
.use { cl ->
val clazz = Class.forName(generatedClassName, true, cl)

val supportedField = clazz.getField("SUPPORTED").get(null)

@Suppress("UNCHECKED_CAST")
val supportedSet = when (supportedField) {
is Set<*> -> supportedField as Set<String>
is Map<*, *> -> supportedField.keys as Set<String>
else -> throw IllegalStateException("SUPPORTED field must be either Set<String> or Map<String, Any>, but was ${supportedField?.javaClass}")
}
val supportedSet =
when (supportedField) {
is Set<*> -> supportedField as Set<String>

is Map<*, *> -> supportedField.keys as Set<String>

else -> throw IllegalStateException(
"SUPPORTED field must be either Set<String> or Map<String, Any>, but was ${supportedField?.javaClass}",
)
}

@Suppress("UNCHECKED_CAST")
val aliasMappingMap = clazz.getField("ALIAS_MAPPING").get(null) as Map<String, String>
LoadedConfigFields(supportedSet, aliasMappingMap)
}.also { cachedConfigFields = it }
}
}

/** Registers `logEnvVarUsages` (scan for DD_/OTEL_ tokens and fail if unsupported). */
private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerConfigurations) {
private fun registerLogEnvVarUsages(
target: Project,
extension: SupportedTracerConfigurations
) {
val ownerPath = extension.configOwnerPath
val generatedFile = extension.className

Expand All @@ -70,19 +78,23 @@ private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerC
group = "verification"
description = "Scan Java files for DD_/OTEL_ tokens and fail if unsupported (using generated constants)"

val mainSourceSetOutput = ownerPath.map {
target.project(it)
.extensions.getByType<SourceSetContainer>()
.named(SourceSet.MAIN_SOURCE_SET_NAME)
.map { main -> main.output }
}
val mainSourceSetOutput =
ownerPath.map {
target
.project(it)
.extensions
.getByType<SourceSetContainer>()
.named(SourceSet.MAIN_SOURCE_SET_NAME)
.map { main -> main.output }
}
inputs.files(mainSourceSetOutput)

// inputs for incrementality (your own source files, not the owner’s)
val javaFiles = target.fileTree(target.projectDir) {
include("**/src/main/java/**/*.java")
exclude("**/build/**", "**/dd-smoke-tests/**")
}
val javaFiles =
target.fileTree(target.projectDir) {
include("**/src/main/java/**/*.java")
exclude("**/build/**", "**/dd-smoke-tests/**")
}
inputs.files(javaFiles)
outputs.upToDateWhen { true }
doLast {
Expand All @@ -94,25 +106,26 @@ private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerC
val repoRoot = target.projectDir.toPath()
val tokenRegex = Regex("\"(?:DD_|OTEL_)[A-Za-z0-9_]+\"")

val violations = buildList {
javaFiles.files.forEach { f ->
val rel = repoRoot.relativize(f.toPath()).toString()
var inBlock = false
f.readLines().forEachIndexed { i, raw ->
val trimmed = raw.trim()
if (trimmed.startsWith("//")) return@forEachIndexed
if (!inBlock && trimmed.contains("/*")) inBlock = true
if (inBlock) {
if (trimmed.contains("*/")) inBlock = false
return@forEachIndexed
}
tokenRegex.findAll(raw).forEach { m ->
val token = m.value.trim('"')
if (token !in supported) add("$rel:${i + 1} -> Unsupported token '$token'")
val violations =
buildList {
javaFiles.files.forEach { f ->
val rel = repoRoot.relativize(f.toPath()).toString()
var inBlock = false
f.readLines().forEachIndexed { i, raw ->
val trimmed = raw.trim()
if (trimmed.startsWith("//")) return@forEachIndexed
if (!inBlock && trimmed.contains("/*")) inBlock = true
if (inBlock) {
if (trimmed.contains("*/")) inBlock = false
return@forEachIndexed
}
tokenRegex.findAll(raw).forEach { m ->
val token = m.value.trim('"')
if (token !in supported) add("$rel:${i + 1} -> Unsupported token '$token'")
}
}
}
}
}

if (violations.isNotEmpty()) {
violations.forEach { target.logger.error(it) }
Expand All @@ -132,25 +145,27 @@ private fun registerCheckEnvironmentVariablesUsage(project: Project) {

doLast {
val repoRoot: Path = project.projectDir.toPath()
val javaFiles = project.fileTree(project.projectDir) {
include("**/src/main/java/**/*.java")
exclude("**/build/**")
exclude("utils/config-utils/src/main/java/datadog/trace/config/inversion/ConfigHelper.java")
exclude("dd-java-agent/agent-bootstrap/**")
exclude("dd-java-agent/src/main/java/datadog/trace/bootstrap/**")
}
val javaFiles =
project.fileTree(project.projectDir) {
include("**/src/main/java/**/*.java")
exclude("**/build/**")
exclude("utils/config-utils/src/main/java/datadog/trace/config/inversion/ConfigHelper.java")
exclude("dd-java-agent/agent-bootstrap/**")
exclude("dd-java-agent/src/main/java/datadog/trace/bootstrap/**")
}

val pattern = Regex("""EnvironmentVariables\.get\s*\(""")
val matches = buildList {
javaFiles.forEach { f ->
val relative = repoRoot.relativize(f.toPath())
f.readLines().forEachIndexed { idx, line ->
if (pattern.containsMatchIn(line)) {
add("$relative:${idx + 1} -> ${line.trim()}")
val matches =
buildList {
javaFiles.forEach { f ->
val relative = repoRoot.relativize(f.toPath())
f.readLines().forEachIndexed { idx, line ->
if (pattern.containsMatchIn(line)) {
add("$relative:${idx + 1} -> ${line.trim()}")
}
}
}
}
}

if (matches.isNotEmpty()) {
project.logger.lifecycle("\nFound forbidden usages of EnvironmentVariables.get(...):")
Expand All @@ -164,28 +179,33 @@ private fun registerCheckEnvironmentVariablesUsage(project: Project) {
}

// Helper functions for checking Config Strings
private fun normalize(configValue: String) =
"DD_" + configValue.uppercase().replace("-", "_").replace(".", "_")
private fun normalize(configValue: String) = "DD_" + configValue.uppercase().replace("-", "_").replace(".", "_")

// Checking "public" "static" "final"
private fun NodeWithModifiers<*>.hasModifiers(vararg mods: Modifier.Keyword) =
mods.all { hasModifier(it) }
private fun NodeWithModifiers<*>.hasModifiers(vararg mods: Modifier.Keyword) = mods.all { hasModifier(it) }

/** Registers `checkConfigStrings` to validate config definitions against documented supported configurations. */
private fun registerCheckConfigStringsTask(project: Project, extension: SupportedTracerConfigurations) {
private fun registerCheckConfigStringsTask(
project: Project,
extension: SupportedTracerConfigurations
) {
val ownerPath = extension.configOwnerPath
val generatedFile = extension.className

project.tasks.register("checkConfigStrings") {
group = "verification"
description = "Validates that all config definitions in `dd-trace-api/src/main/java/datadog/trace/api/config` exist in `metadata/supported-configurations.json`"

val mainSourceSetOutput = ownerPath.map {
project.project(it)
.extensions.getByType<SourceSetContainer>()
.named(SourceSet.MAIN_SOURCE_SET_NAME)
.map { main -> main.output }
}
description =
"Validates that all config definitions in `dd-trace-api/src/main/java/datadog/trace/api/config` exist in `metadata/supported-configurations.json`"

val mainSourceSetOutput =
ownerPath.map {
project
.project(it)
.extensions
.getByType<SourceSetContainer>()
.named(SourceSet.MAIN_SOURCE_SET_NAME)
.map { main -> main.output }
}
inputs.files(mainSourceSetOutput)

doLast {
Expand All @@ -205,41 +225,46 @@ private fun registerCheckConfigStringsTask(project: Project, extension: Supporte

StaticJavaParser.setConfiguration(parserConfig)

val violations = buildList {
configDir.listFiles()?.forEach { file ->
val fileName = file.name
val cu: CompilationUnit = StaticJavaParser.parse(file)

cu.findAll(VariableDeclarator::class.java).forEach { varDecl ->
varDecl.parentNode
.map { it as? FieldDeclaration }
.ifPresent { field ->
if (field.hasModifiers(Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC, Modifier.Keyword.FINAL) &&
varDecl.typeAsString == "String") {

val fieldName = varDecl.nameAsString
if (fieldName.endsWith("_DEFAULT")) return@ifPresent
val init = varDecl.initializer.orElse(null) ?: return@ifPresent

if (init !is StringLiteralExpr) return@ifPresent
val rawValue = init.value

val normalized = normalize(rawValue)
if (normalized !in supported && normalized !in aliasMapping) {
val line = varDecl.range.map { it.begin.line }.orElse(1)
add("$fileName:$line -> Config '$rawValue' normalizes to '$normalized' " +
"which is missing from '${extension.jsonFile.get()}'")
val violations =
buildList {
configDir.listFiles()?.forEach { file ->
val fileName = file.name
val cu: CompilationUnit = StaticJavaParser.parse(file)

cu.findAll(VariableDeclarator::class.java).forEach { varDecl ->
varDecl.parentNode
.map { it as? FieldDeclaration }
.ifPresent { field ->
if (field.hasModifiers(Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC, Modifier.Keyword.FINAL) &&
varDecl.typeAsString == "String"
) {
val fieldName = varDecl.nameAsString
if (fieldName.endsWith("_DEFAULT")) return@ifPresent
val init = varDecl.initializer.orElse(null) ?: return@ifPresent

if (init !is StringLiteralExpr) return@ifPresent
val rawValue = init.value

val normalized = normalize(rawValue)
if (normalized !in supported && normalized !in aliasMapping) {
val line = varDecl.range.map { it.begin.line }.orElse(1)
add(
"$fileName:$line -> Config '$rawValue' normalizes to '$normalized' " +
"which is missing from '${extension.jsonFile.get()}'",
)
}
}
}
}
}
}
}
}

if (violations.isNotEmpty()) {
logger.error("\nFound config definitions not in '${extension.jsonFile.get()}':")
violations.forEach { logger.lifecycle(it) }
throw GradleException("Undocumented Environment Variables found. Please add the above Environment Variables to '${extension.jsonFile.get()}'.")
throw GradleException(
"Undocumented Environment Variables found. Please add the above Environment Variables to '${extension.jsonFile.get()}'.",
)
} else {
logger.info("All config strings are present in '${extension.jsonFile.get()}'.")
}
Expand Down
Loading