generate-version-catalog.groovy 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import java.nio.file.*
  2. import java.nio.charset.StandardCharsets
  3. import java.util.regex.Pattern
  4. REPO_ROOT = "/Users/rene/dev/elastic/elasticsearch/plugins"
  5. VERSION_PROPS = REPO_ROOT + "/../build-tools-internal/version.properties"
  6. def parseGradleFiles(Path directory) {
  7. def pattern = Pattern.compile(/(\w+)\s+['"](\w[^'"]+):([^'"]+):([^'"]+)['"]/)
  8. def dependencies = []
  9. Files.walk(directory).each { path ->
  10. if (Files.isRegularFile(path) && path.toString().endsWith('.gradle') && path.toString().contains("plugins/examples") == false){
  11. def lines = Files.readAllLines(path, StandardCharsets.UTF_8)
  12. lines.each { line ->
  13. def matcher = pattern.matcher(line)
  14. if (matcher.find()) {
  15. def configuration = matcher.group(1)
  16. def group = matcher.group(2)
  17. def name = matcher.group(3)
  18. def version = matcher.group(4)
  19. dependencies << [file: path.toString(), configuration: configuration, group: group, name: name, version: version]
  20. }
  21. }
  22. }
  23. }
  24. return dependencies
  25. }
  26. String convertToVersionCatalogEntry(def dependencies) {
  27. Set<String> versions = new TreeSet<>()
  28. Set<String> entries = new TreeSet<>()
  29. }
  30. def resolveVersion(Properties props, String versionString) {
  31. println "Resolving version: ${versionString}"
  32. if(versionString.startsWith("\${versions.")) {
  33. def versionId = versionString.substring(versionString.indexOf('.') + 1, versionString.indexOf('}'))
  34. if(props.containsKey(versionId)) {
  35. return props.getProperty(versionId)
  36. } else {
  37. println "unknown version ${versionString} found in build.gradle file. Please add it to the version.properties file."
  38. return versionId
  39. }
  40. }
  41. return versionString
  42. }
  43. Properties loadVersionProperties() {
  44. def properties = new Properties()
  45. def file = new File(VERSION_PROPS)
  46. if (!file.exists()) {
  47. println "The properties file '${VERSION_PROPS}' does not exist."
  48. return null
  49. }
  50. file.withInputStream { stream ->
  51. properties.load(stream)
  52. }
  53. properties.each { key, value ->
  54. println "Loaded version property: ${key} = ${value}"
  55. }
  56. return properties
  57. }
  58. def convertToCamelCase(String input) {
  59. def parts = input.split('-')
  60. def camelCaseString = parts[0]
  61. parts.tail().each { part ->
  62. // for now skip camel casing
  63. //camelCaseString += part.capitalize()
  64. camelCaseString += part
  65. }
  66. return camelCaseString
  67. }
  68. String calculateVersionRef(String libraryName, Map<String, String> versionCatalog, Properties properties, String version) {
  69. // String versionRefName = convertToCamelCase(libraryName)
  70. String versionRefName = libraryName
  71. if(versionCatalog.containsKey(versionRefName)) {
  72. def existingMajor = versionCatalog[libraryName].split("\\.")[0] as int
  73. def newMajor = version.split("\\.")[0] as int
  74. println "existingMajor: ${existingMajor}, newMajor: ${newMajor}"
  75. if(newMajor > existingMajor) {
  76. return versionRefName + newMajor
  77. }
  78. }
  79. return versionRefName
  80. }
  81. def checkOptimizations(Map<String, String> versionCatalog, Properties versionProperties) {
  82. def simplifications = [:]
  83. versionCatalog.each { givenKey, givenVersion ->
  84. def simpleKey = givenKey.contains("-") ? givenKey.split('-')[0] : givenKey
  85. def candidates = versionCatalog.findAll {k, v -> givenKey != k && k.startsWith("${simpleKey}-")}
  86. if(candidates.size() == 0 && versionProperties[simpleKey] != null) {
  87. assert versionProperties[simpleKey] == givenVersion
  88. simplifications[givenKey] = simpleKey
  89. } else {
  90. candidates.each {candidateKey , candidateVersion ->
  91. if(candidateVersion == givenVersion) {
  92. simplifications[candidateKey] = simpleKey
  93. }
  94. }
  95. }
  96. if(simplifications[givenKey] == null){
  97. def converted = convertToCamelCase(givenKey)
  98. if(givenKey != converted) {
  99. simplifications[givenKey] = converted
  100. }
  101. }
  102. }
  103. return simplifications
  104. }
  105. def parseValue(value) {
  106. if (value.startsWith('"') && value.endsWith('"')) {
  107. return value[1..-2] // String value
  108. } else if (value ==~ /\d+/) {
  109. return value.toInteger() // Integer value
  110. } else if (value ==~ /\d+\.\d+/) {
  111. return value.toDouble() // Double value
  112. } else if (value == 'true' || value == 'false') {
  113. return value.toBoolean() // Boolean value
  114. } else if (value.startsWith('[') && value.endsWith(']')) {
  115. return value[1..-2].split(',').collect { parseValue(it.trim()) } // Array value
  116. } else {
  117. return value // Default to string if not matched
  118. }
  119. }
  120. def parseTomlFile(filePath) {
  121. def tomlMap = [:]
  122. def currentSection = null
  123. def file = new File(filePath)
  124. file.eachLine { line ->
  125. line = line.trim()
  126. if (line.startsWith('#') || line.isEmpty()) {
  127. // Skip comments and empty lines
  128. return
  129. }
  130. if (line.startsWith('[') && line.endsWith(']')) {
  131. // New section
  132. currentSection = line[1..-2]
  133. tomlMap[currentSection] = [:]
  134. } else if (line.contains('=')) {
  135. // Key-value pair
  136. def (key, value) = line.split('=', 2).collect { it.trim() }
  137. value = parseValue(value)
  138. if (currentSection) {
  139. tomlMap[currentSection][key] = value
  140. } else {
  141. tomlMap[key] = value
  142. }
  143. }
  144. }
  145. return tomlMap
  146. }
  147. def main() {
  148. // def directoryPath = System.console().readLine('Enter the directory path to search for *.gradle files: ').trim()
  149. // def directory = Paths.get("directoryPath")
  150. def directory = Paths.get(REPO_ROOT)
  151. if (!Files.exists(directory) || !Files.isDirectory(directory)) {
  152. println "The directory '${directoryPath}' does not exist or is not a directory."
  153. return
  154. }
  155. def dependencies = parseGradleFiles(directory)
  156. def librariesCatalog = [:]
  157. def versionsCatalog = [:]
  158. Properties versionProperties = loadVersionProperties()
  159. println "Version Properties: ${versionProperties.contains('junit')}"
  160. if (dependencies) {
  161. def depsByFile = dependencies.groupBy {it.file}
  162. depsByFile.each { file, deps ->
  163. println "File: ${file}"
  164. deps.each { dep ->
  165. def effectiveVersion = resolveVersion(versionProperties, dep.version)
  166. def versionRefName = calculateVersionRef(dep.name, versionsCatalog, versionProperties, effectiveVersion)
  167. versionsCatalog.put(versionRefName, effectiveVersion)
  168. depLibraryEntry = [group: dep.group, name: dep.name, version:versionRefName]
  169. println "\"${dep.group}:${dep.name}:${dep.version}\" -> \"${depLibraryEntry}\""
  170. if(librariesCatalog.containsKey(versionRefName)) {
  171. assert librariesCatalog[versionRefName] == depLibraryEntry
  172. } else {
  173. librariesCatalog.put(versionRefName, depLibraryEntry)
  174. }
  175. }
  176. println ""
  177. }
  178. println "libraries Catalog versions"
  179. librariesCatalog.each { key, value ->
  180. println "${key} = ${value}"
  181. }
  182. println "Version Catalog libraries"
  183. versionsCatalog.each { key, value ->
  184. println "${key} = ${value}"
  185. }
  186. println "Found ${dependencies.size()} dependencies in ${depsByFile.size()} files."
  187. } else {
  188. println "No dependencies found."
  189. }
  190. def versionOptimizations = checkOptimizations(versionsCatalog, versionProperties)
  191. versionOptimizations.each { given, simplified ->
  192. println "$given -> $simplified"
  193. println "${versionsCatalog[simplified]}"
  194. if(versionsCatalog[simplified] == null) {
  195. versionsCatalog[simplified] = versionsCatalog[given]
  196. }
  197. versionsCatalog.remove(given)
  198. }
  199. librariesCatalog.each { key, value ->
  200. def simplified = versionOptimizations[key]
  201. if(simplified != null) {
  202. librariesCatalog[key].version = simplified
  203. }
  204. }
  205. println "\n\nversions: "
  206. versionsCatalog.sort().each { key, value ->
  207. println "${key} = \"${value}\""
  208. }
  209. librariesCatalog.sort()
  210. println "\n\nlibraries: "
  211. librariesCatalog.sort().each { k, v ->
  212. println "${k} = { group = \"${v['group']}\", name = \"${v['name']}\", version.ref = \"${v['version']}\" } "
  213. }
  214. // Example usage
  215. def tomlFilePath = '/Users/rene/dev/elastic/elasticsearch/gradle/versions.toml'
  216. def parsedToml = parseTomlFile(tomlFilePath)
  217. // Access parsed data
  218. existingVersions = parsedToml['versions']
  219. // println "\n\nExisting versions:"
  220. // existingVersions.forEach { key, value ->
  221. // println "${key} = ${value}"
  222. // }
  223. // existingLibs = parsedToml['libraries']
  224. // existingLibs.forEach { key, value ->
  225. // println "${key} = ${value}"
  226. // }
  227. def finalVersions = [:]
  228. def finalLibraries = [:]
  229. existingVersions.each { key, value ->
  230. finalVersions[key] = value
  231. if(versionsCatalog.containsKey(key)) {
  232. assert value == versionsCatalog[key]
  233. versionsCatalog.remove(key)
  234. }
  235. }
  236. finalVersions.putAll(versionsCatalog)
  237. println "\n\n[versions]"
  238. finalVersions.sort().each { key, value ->
  239. println "${key} = \"${value}\""
  240. }
  241. def existingLibs = parsedToml['libraries']
  242. existingLibs.each { key, value ->
  243. finalLibraries[key] = value
  244. if(librariesCatalog[key] != null) {
  245. def newValue = librariesCatalog[key]
  246. assert value == "{ group = \"${newValue['group']}\", name = \"${newValue['name']}\", version.ref = \"${newValue['version']}\" }"
  247. librariesCatalog.remove(key)
  248. }
  249. }
  250. finalLibraries.putAll(librariesCatalog)
  251. println "\n\n[libraries]"
  252. finalLibraries.sort().each { key, value ->
  253. if(value instanceof Map) {
  254. println "${key} = { group = \"${value['group']}\", name = \"${value['name']}\", version.ref = \"${value['version']}\" }"
  255. } else if (value.startsWith("{")) {
  256. println "${key} = $value"
  257. } else {
  258. println "${key} = \"$value\""
  259. }
  260. }
  261. // println "Title: ${parsedToml['title']}"
  262. // println "Owner Name: ${parsedToml['versions']['name']}"
  263. // println "Database Server: ${parsedToml['database']['server']}"
  264. // println "Database Ports: ${parsedToml['database']['ports']}"
  265. }
  266. main()