From f1397f6d486576fc57cd59b998f1424099b443ea Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 22 Oct 2025 17:19:10 -0400 Subject: [PATCH 1/3] adding gradle task to verify configs defined are documented --- build.gradle.kts | 1 + .../plugin/config/ConfigInversionLinter.kt | 77 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index eb7d87b4c43..0770d25e220 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ plugins { id("datadog.dependency-locking") id("datadog.tracer-version") id("datadog.dump-hanged-test") + id("config-inversion-linter") id("com.diffplug.spotless") version "6.13.0" id("com.github.spotbugs") version "5.0.14" diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt index 890fea2c335..73b8f4d2568 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt @@ -14,6 +14,7 @@ class ConfigInversionLinter : Plugin { val extension = target.extensions.create("supportedTracerConfigurations", SupportedTracerConfigurations::class.java) registerLogEnvVarUsages(target, extension) registerCheckEnvironmentVariablesUsage(target) + registerCheckConfigStringsTask(target, extension) } } @@ -124,3 +125,79 @@ private fun registerCheckEnvironmentVariablesUsage(project: Project) { } } } + +/** Registers `checkConfigStrings` to validate config strings against documented supported configurations. */ +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() + .named(SourceSet.MAIN_SOURCE_SET_NAME) + .map { main -> main.output } + } + inputs.files(mainSourceSetOutput) + + doLast { + val repoRoot: Path = project.rootProject.projectDir.toPath() + val configDir = repoRoot.resolve("dd-trace-api/src/main/java/datadog/trace/api/config").toFile() + + if (!configDir.exists()) { + throw GradleException("Config directory not found: ${configDir.absolutePath}") + } + + val urls = mainSourceSetOutput.get().get().files.map { it.toURI().toURL() }.toTypedArray() + val (supported, aliasMapping) = URLClassLoader(urls, javaClass.classLoader).use { cl -> + val clazz = Class.forName(generatedFile.get(), true, cl) + @Suppress("UNCHECKED_CAST") + val supportedSet = clazz.getField("SUPPORTED").get(null) as Set + @Suppress("UNCHECKED_CAST") + val aliasMappingMap = clazz.getField("ALIAS_MAPPING").get(null) as Map + Pair(supportedSet, aliasMappingMap) + } + + val stringFieldRegex = Regex("""public\s+static\s+final\s+String\s+\w+\s*=\s*"([^"]+)"\s*;""") + + val violations = buildList { + configDir.listFiles()?.filter { it.extension == "java" }?.forEach { file -> + var inBlockComment = false + file.readLines().forEachIndexed { idx, line -> + val trimmed = line.trim() + + if (trimmed.startsWith("//")) return@forEachIndexed + if (!inBlockComment && trimmed.contains("/*")) inBlockComment = true + if (inBlockComment) { + if (trimmed.contains("*/")) inBlockComment = false + return@forEachIndexed + } + + stringFieldRegex.findAll(line).forEach { match -> + val configValue = match.groupValues[1] + + val normalized = "DD_" + configValue.uppercase() + .replace("-", "_") + .replace(".", "_") + + if (normalized !in supported && normalized !in aliasMapping) { + add("${file.name}:${idx + 1} -> Config '$configValue' normalizes to '$normalized' which is not in supported-configurations.json") + } + } + } + } + } + + if (violations.isNotEmpty()) { + project.logger.lifecycle("\nFound config strings not in supported-configurations.json:") + violations.forEach { project.logger.lifecycle(it) } + throw GradleException("Config strings validation failed. See errors above.") + } else { + project.logger.info("All config strings are present in supported-configurations.json.") + } + } + } +} From 3b4dbb8f47eacd8998a27966e9ba98868a3143a8 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Thu, 23 Oct 2025 16:02:50 -0400 Subject: [PATCH 2/3] updating task to account for default values and multi-line config definitions --- .../plugin/config/ConfigInversionLinter.kt | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt index 73b8f4d2568..277d0940c27 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt @@ -126,14 +126,14 @@ private fun registerCheckEnvironmentVariablesUsage(project: Project) { } } -/** Registers `checkConfigStrings` to validate config strings against documented supported configurations. */ +/** Registers `checkConfigStrings` to validate config definitions against documented supported configurations. */ 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" + 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) @@ -161,38 +161,81 @@ private fun registerCheckConfigStringsTask(project: Project, extension: Supporte Pair(supportedSet, aliasMappingMap) } - val stringFieldRegex = Regex("""public\s+static\s+final\s+String\s+\w+\s*=\s*"([^"]+)"\s*;""") + // Single-line: `public static final String FIELD_NAME = "value";` + val singleLineRegex = Regex("""public static final String (\w+) = "([^"]+)";""") + // Multi-line start: `public static final String FIELD_NAME =` + val multiLineStartRegex = Regex("""public static final String (\w+) =$""") + // Multi-line value: `"value";` + val valueLineRegex = Regex(""""([^"]+)";""") val violations = buildList { - configDir.listFiles()?.filter { it.extension == "java" }?.forEach { file -> + configDir.listFiles()?.forEach { file -> var inBlockComment = false - file.readLines().forEachIndexed { idx, line -> + val lines = file.readLines() + var i = 0 + while (i < lines.size) { + val line = lines[i] val trimmed = line.trim() - if (trimmed.startsWith("//")) return@forEachIndexed + if (trimmed.startsWith("//")) { + i++ + continue + } if (!inBlockComment && trimmed.contains("/*")) inBlockComment = true if (inBlockComment) { if (trimmed.contains("*/")) inBlockComment = false - return@forEachIndexed + i++ + continue } - stringFieldRegex.findAll(line).forEach { match -> - val configValue = match.groupValues[1] + // Try single-line pattern first + val singleLineMatch = singleLineRegex.find(line) + if (singleLineMatch != null) { + val fieldName = singleLineMatch.groupValues[1] + val configValue = singleLineMatch.groupValues[2] - val normalized = "DD_" + configValue.uppercase() - .replace("-", "_") - .replace(".", "_") - - if (normalized !in supported && normalized !in aliasMapping) { - add("${file.name}:${idx + 1} -> Config '$configValue' normalizes to '$normalized' which is not in supported-configurations.json") + // Skip fields that end with _DEFAULT (default values defined in ProfilingConfig.java only) + if (!fieldName.endsWith("_DEFAULT")) { + val normalized = "DD_" + configValue.uppercase() + .replace("-", "_") + .replace(".", "_") + + if (normalized !in supported && normalized !in aliasMapping) { + add("${file.name}:${i + 1} -> Config '$configValue' normalizes to '$normalized' which is not in supported-configurations.json") + } + } + } else { + val multiLineMatch = multiLineStartRegex.find(line) + if (multiLineMatch != null) { + val fieldName = multiLineMatch.groupValues[1] + if (!fieldName.endsWith("_DEFAULT")) { + var j = i + 1 + while (j < lines.size) { + val nextLine = lines[j].trim() + val valueMatch = valueLineRegex.find(nextLine) + if (valueMatch != null) { + val configValue = valueMatch.groupValues[1] + val normalized = "DD_" + configValue.uppercase() + .replace("-", "_") + .replace(".", "_") + if (normalized !in supported && normalized !in aliasMapping) { + add("${file.name}:${i + 1} -> Config '$configValue' normalizes to '$normalized' " + + "which is not in supported-configurations.json") + } + break + } + j++ + } + } } } + i++ } } } if (violations.isNotEmpty()) { - project.logger.lifecycle("\nFound config strings not in supported-configurations.json:") + project.logger.lifecycle("\nFound config definitions not in supported-configurations.json:") violations.forEach { project.logger.lifecycle(it) } throw GradleException("Config strings validation failed. See errors above.") } else { From ca2dc8e51aa5d485987c4de66e0ceab5af614756 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Thu, 23 Oct 2025 16:05:59 -0400 Subject: [PATCH 3/3] adding missing config --- metadata/supported-configurations.json | 1 + 1 file changed, 1 insertion(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 95f60cc558c..6554c15ae4e 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -361,6 +361,7 @@ "DD_PROFILING_EXPERIMENTAL_DDPROF_SCHEDULING_EVENT": ["A"], "DD_PROFILING_EXPERIMENTAL_DDPROF_SCHEDULING_EVENT_INTERVAL": ["A"], "DD_PROFILING_EXPERIMENTAL_DDPROF_WALL_JVMTI": ["A"], + "DD_PROFILING_EXPERIMENTAL_PROCESS_CONTEXT_ENABLED": ["A"], "DD_PROFILING_HEAP_ENABLED": ["A"], "DD_PROFILING_HEAP_HISTOGRAM_ENABLED": ["A"], "DD_PROFILING_HEAP_HISTOGRAM_MODE": ["A"],