-
Notifications
You must be signed in to change notification settings - Fork 319
Add Gradle Task to Parse V2 supported-configurations.json Format
#10060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
c554374
1a1c744
da4da8e
3830eeb
00837be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,214 @@ | ||||||
| package datadog.gradle.plugin.config | ||||||
|
|
||||||
| import org.gradle.api.DefaultTask | ||||||
| import org.gradle.api.model.ObjectFactory | ||||||
| import org.gradle.api.tasks.Input | ||||||
| import org.gradle.api.tasks.InputFile | ||||||
| import org.gradle.api.tasks.OutputDirectory | ||||||
| import org.gradle.api.tasks.TaskAction | ||||||
| import com.fasterxml.jackson.core.type.TypeReference | ||||||
| import com.fasterxml.jackson.databind.ObjectMapper | ||||||
| import org.gradle.api.tasks.CacheableTask | ||||||
| import org.gradle.api.tasks.PathSensitive | ||||||
| import org.gradle.api.tasks.PathSensitivity | ||||||
| import java.io.File | ||||||
| import java.io.FileInputStream | ||||||
| import java.io.PrintWriter | ||||||
| import javax.inject.Inject | ||||||
|
|
||||||
| @CacheableTask | ||||||
| abstract class ParseV2SupportedConfigurationsTask @Inject constructor( | ||||||
| private val objects: ObjectFactory | ||||||
| ) : DefaultTask() { | ||||||
| @InputFile | ||||||
| @PathSensitive(PathSensitivity.NONE) | ||||||
| val jsonFile = objects.fileProperty() | ||||||
|
|
||||||
| @get:OutputDirectory | ||||||
| val destinationDirectory = objects.directoryProperty() | ||||||
|
|
||||||
| @Input | ||||||
| val className = objects.property(String::class.java) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: By the way you can use the kotlin extension methods that live in +import org.gradle.kotlin.dsl.property
Suggested change
|
||||||
|
|
||||||
| @TaskAction | ||||||
| fun generate() { | ||||||
| val input = jsonFile.get().asFile | ||||||
| val outputDir = destinationDirectory.get().asFile | ||||||
| val finalClassName = className.get() | ||||||
| outputDir.mkdirs() | ||||||
|
|
||||||
| // Read JSON (directly from the file, not classpath) | ||||||
| val mapper = ObjectMapper() | ||||||
| val fileData: Map<String, Any?> = FileInputStream(input).use { inStream -> | ||||||
| mapper.readValue(inStream, object : TypeReference<Map<String, Any?>>() {}) | ||||||
| } | ||||||
|
|
||||||
| // Fetch top-level keys of JSON file | ||||||
| @Suppress("UNCHECKED_CAST") | ||||||
| val supportedRaw = fileData["supportedConfigurations"] as Map<String, List<Map<String, Any?>>> | ||||||
| @Suppress("UNCHECKED_CAST") | ||||||
| val deprecated = (fileData["deprecations"] as? Map<String, String>) ?: emptyMap() | ||||||
|
|
||||||
| // Parse supportedConfigurations key to into a V2 format | ||||||
| val supported: Map<String, List<SupportedConfiguration>> = supportedRaw.mapValues { (_, configList) -> | ||||||
| configList.map { configMap -> | ||||||
| SupportedConfiguration( | ||||||
| configMap["version"] as? String, | ||||||
| configMap["type"] as? String, | ||||||
| configMap["default"] as? String, | ||||||
| (configMap["aliases"] as? List<String>) ?: emptyList(), | ||||||
| (configMap["propertyKeys"] as? List<String>) ?: emptyList() | ||||||
| ) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Generate top-level mapping from config -> list of aliases and reverse alias mapping from alias -> top-level config | ||||||
| // Note: This top-level alias mapping will be deprecated once Config Registry is mature enough to understand which version of a config a customer is using | ||||||
| val aliases: Map<String, List<String>> = supported.mapValues { (_, configList) -> | ||||||
| configList.flatMap { it.aliases }.distinct() | ||||||
| } | ||||||
|
|
||||||
| val aliasMapping = mutableMapOf<String, String>() | ||||||
| for ((canonical, alist) in aliases) { | ||||||
| for (alias in alist) aliasMapping[alias] = canonical | ||||||
| } | ||||||
|
|
||||||
| val reversePropertyKeysMap: Map<String, String> = supported.flatMap { (canonical, configList) -> | ||||||
| configList.flatMap { config -> | ||||||
| config.propertyKeys.map { propertyKey -> propertyKey to canonical } | ||||||
| } | ||||||
| }.toMap() | ||||||
|
|
||||||
| // Build the output .java path from the fully-qualified class name | ||||||
| val pkgName = finalClassName.substringBeforeLast('.', "") | ||||||
| val pkgPath = pkgName.replace('.', File.separatorChar) | ||||||
| val simpleName = finalClassName.substringAfterLast('.') | ||||||
| val pkgDir = if (pkgPath.isEmpty()) outputDir else File(outputDir, pkgPath).also { it.mkdirs() } | ||||||
| val generatedFile = File(pkgDir, "$simpleName.java").absolutePath | ||||||
|
|
||||||
| // Call your existing generator (same signature as in your Java code) | ||||||
| generateJavaFile( | ||||||
| generatedFile, | ||||||
| simpleName, | ||||||
| pkgName, | ||||||
| supported, | ||||||
| aliases, | ||||||
| aliasMapping, | ||||||
| deprecated, | ||||||
| reversePropertyKeysMap | ||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| private fun generateJavaFile( | ||||||
| outputPath: String, | ||||||
| className: String, | ||||||
| packageName: String, | ||||||
| supported: Map<String, List<SupportedConfiguration>>, | ||||||
| aliases: Map<String, List<String>>, | ||||||
| aliasMapping: Map<String, String>, | ||||||
| deprecated: Map<String, String>, | ||||||
| reversePropertyKeysMap: Map<String, String> | ||||||
| ) { | ||||||
| val outFile = File(outputPath) | ||||||
| outFile.parentFile?.mkdirs() | ||||||
|
|
||||||
| PrintWriter(outFile).use { out -> | ||||||
| out.println("package $packageName;") | ||||||
| out.println() | ||||||
| out.println("import java.util.*;") | ||||||
| out.println() | ||||||
| out.println("public final class $className {") | ||||||
| out.println() | ||||||
| out.println(" public static final Map<String, List<SupportedConfiguration>> SUPPORTED;") | ||||||
| out.println() | ||||||
| out.println(" public static final Map<String, List<String>> ALIASES;") | ||||||
| out.println() | ||||||
| out.println(" public static final Map<String, String> ALIAS_MAPPING;") | ||||||
| out.println() | ||||||
| out.println(" public static final Map<String, String> DEPRECATED;") | ||||||
| out.println() | ||||||
| out.println(" public static final Map<String, String> REVERSE_PROPERTY_KEYS_MAP;") | ||||||
| out.println() | ||||||
| out.println(" static {") | ||||||
| out.println() | ||||||
|
|
||||||
| // SUPPORTED | ||||||
| out.println(" Map<String, List<SupportedConfiguration>> supportedMap = new HashMap<>();") | ||||||
| for ((key, configList) in supported.toSortedMap()) { | ||||||
| out.print(" supportedMap.put(\"${esc(key)}\", Collections.unmodifiableList(Arrays.asList(") | ||||||
| val configIter = configList.iterator() | ||||||
| while (configIter.hasNext()) { | ||||||
| val config = configIter.next() | ||||||
| out.print("new SupportedConfiguration(") | ||||||
| out.print("${escNullableString(config.version)}, ") | ||||||
| out.print("${escNullableString(config.type)}, ") | ||||||
| out.print("${escNullableString(config.default)}, ") | ||||||
| out.print("Arrays.asList(${quoteList(config.aliases)}), ") | ||||||
| out.print("Arrays.asList(${quoteList(config.propertyKeys)})") | ||||||
| out.print(")") | ||||||
| if (configIter.hasNext()) out.print(", ") | ||||||
| } | ||||||
| out.println(")));\n") | ||||||
| } | ||||||
| out.println(" SUPPORTED = Collections.unmodifiableMap(supportedMap);") | ||||||
| out.println() | ||||||
|
|
||||||
| // ALIASES | ||||||
| out.println(" // Note: This top-level alias mapping will be deprecated once Config Registry is mature enough to understand which version of a config a customer is using") | ||||||
| out.println(" Map<String, List<String>> aliasesMap = new HashMap<>();") | ||||||
| for ((canonical, list) in aliases.toSortedMap()) { | ||||||
| out.printf( | ||||||
| " aliasesMap.put(\"%s\", Collections.unmodifiableList(Arrays.asList(%s)));\n", | ||||||
| esc(canonical), | ||||||
| quoteList(list) | ||||||
| ) | ||||||
| } | ||||||
| out.println(" ALIASES = Collections.unmodifiableMap(aliasesMap);") | ||||||
| out.println() | ||||||
|
|
||||||
| // ALIAS_MAPPING | ||||||
| out.println(" Map<String, String> aliasMappingMap = new HashMap<>();") | ||||||
| for ((alias, target) in aliasMapping.toSortedMap()) { | ||||||
| out.printf(" aliasMappingMap.put(\"%s\", \"%s\");\n", esc(alias), esc(target)) | ||||||
| } | ||||||
| out.println(" ALIAS_MAPPING = Collections.unmodifiableMap(aliasMappingMap);") | ||||||
| out.println() | ||||||
|
|
||||||
| // DEPRECATED | ||||||
| out.println(" Map<String, String> deprecatedMap = new HashMap<>();") | ||||||
| for ((oldKey, note) in deprecated.toSortedMap()) { | ||||||
| out.printf(" deprecatedMap.put(\"%s\", \"%s\");\n", esc(oldKey), esc(note)) | ||||||
| } | ||||||
| out.println(" DEPRECATED = Collections.unmodifiableMap(deprecatedMap);") | ||||||
| out.println() | ||||||
|
|
||||||
| // REVERSE_PROPERTY_KEYS_MAP | ||||||
| out.println(" Map<String, String> reversePropertyKeysMapping = new HashMap<>();") | ||||||
| for ((propertyKey, config) in reversePropertyKeysMap.toSortedMap()) { | ||||||
| out.printf(" reversePropertyKeysMapping.put(\"%s\", \"%s\");\n", esc(propertyKey), esc(config)) | ||||||
| } | ||||||
| out.println(" REVERSE_PROPERTY_KEYS_MAP = Collections.unmodifiableMap(reversePropertyKeysMapping);") | ||||||
| out.println() | ||||||
|
|
||||||
| out.println(" }") | ||||||
| out.println("}") | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private fun quoteList(list: List<String>): String = | ||||||
| list.joinToString(", ") { "\"${esc(it)}\"" } | ||||||
|
|
||||||
| private fun esc(s: String): String = | ||||||
| s.replace("\\", "\\\\").replace("\"", "\\\"") | ||||||
|
|
||||||
| private fun escNullableString(s: String?): String = | ||||||
| if (s == null) "null" else "\"${esc(s)}\"" | ||||||
| } | ||||||
|
|
||||||
| data class SupportedConfiguration( | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Id' rename this one as it is confusing with the outer class with the same name. |
||||||
| val version: String?, | ||||||
| val type: String?, | ||||||
| val default: String?, | ||||||
| val aliases: List<String>, | ||||||
| val propertyKeys: List<String> | ||||||
| ) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package datadog.trace.config.inversion; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class SupportedConfiguration { | ||
| private final String version; | ||
| private final String type; | ||
| private final String defaultValue; | ||
| private final List<String> aliases; | ||
| private final List<String> propertyKeys; | ||
|
|
||
| public SupportedConfiguration( | ||
| String version, | ||
| String type, | ||
| String defaultValue, | ||
| List<String> aliases, | ||
| List<String> propertyKeys) { | ||
| this.version = version; | ||
| this.type = type; | ||
| this.defaultValue = defaultValue; | ||
| this.aliases = aliases; | ||
| this.propertyKeys = propertyKeys; | ||
| } | ||
|
|
||
| public String version() { | ||
| return version; | ||
| } | ||
|
|
||
| public String type() { | ||
| return type; | ||
| } | ||
|
|
||
| public String defaultValue() { | ||
| return defaultValue; | ||
| } | ||
|
|
||
| public List<String> aliases() { | ||
| return aliases; | ||
| } | ||
|
|
||
| public List<String> propertyKeys() { | ||
| return propertyKeys; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: DO you think it would be possible to have a task that handle both formats ? Maybe with a field that indicate the version of the file ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bric3 It's possible, but the formats of the files are so different that there would be little code reuse. My goal was to create this V2 task first, and remove the original task after I migrate the
metadata/supported-configurations.jsonfile. WDYT about that approach?