From 85ffedfd74bb9610216f379fcc761e083cc36655 Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Fri, 1 May 2020 19:44:09 +0200 Subject: [PATCH] feat(groovyw): add command to create dependency dot file (#3921) - integrates all direct and recursive dependencies of the given module (or all if "all" or "*" or "") - can be called, e.g. via ./groovyw module createDependencyDotFile - can be visualized e.g. using graphviz using dot -Tsvg dependencies.dot > dependencies.svg - cleans up formatting (kudos to the IDE) --- config/groovy/common.groovy | 109 +++++++++++++++++++++++------------- config/groovy/module.groovy | 12 ++-- config/groovy/util.groovy | 66 ++++++++++++++++++---- 3 files changed, 131 insertions(+), 56 deletions(-) diff --git a/config/groovy/common.groovy b/config/groovy/common.groovy index 7affed3308e..823646c345f 100644 --- a/config/groovy/common.groovy +++ b/config/groovy/common.groovy @@ -1,7 +1,7 @@ import groovy.json.JsonSlurper -@Grab(group='org.slf4j', module='slf4j-api', version='1.6.1') -@Grab(group='org.slf4j', module='slf4j-nop', version='1.6.1') +@Grab(group = 'org.slf4j', module = 'slf4j-api', version = '1.6.1') +@Grab(group = 'org.slf4j', module = 'slf4j-nop', version = '1.6.1') @GrabResolver(name = 'jcenter', root = 'http://jcenter.bintray.com/') @Grab(group = 'org.ajoberstar', module = 'grgit', version = '1.9.3') @@ -100,7 +100,7 @@ class common { */ def retrieve(String[] items, boolean recurse) { println "Now inside retrieve, user (recursively? $recurse) wants: $items" - for (String itemName: items) { + for (String itemName : items) { println "Starting retrieval for $itemType $itemName, are we recursing? $recurse" println "Retrieved so far: $itemsRetrieved" retrieveItem(itemName, recurse) @@ -197,7 +197,7 @@ class common { // Do a check for the default remote before we attempt to update def remotes = itemGit.remote.list() - def targetUrl = remotes.find{ + def targetUrl = remotes.find { it.name == defaultRemote }?.url if (targetUrl == null || !isUrlValid(targetUrl)) { @@ -221,7 +221,7 @@ class common { println color("Unable to update $itemName, Skipping: ${exception.getMessage()}", Ansi.RED) } } - } catch(RepositoryNotFoundException exception) { + } catch (RepositoryNotFoundException exception) { println color("Skipping update for $itemName: no repository found (probably engine module)", Ansi.LIGHT_YELLOW) } } @@ -238,7 +238,7 @@ class common { def remoteGit = Grgit.open(dir: "${targetDirectory}/${itemName}") def remote = remoteGit.remote.list() def index = 1 - for (Remote item: remote) { + for (Remote item : remote) { println(index + " " + item.name + " (" + item.url + ")") index++ } @@ -327,9 +327,10 @@ class common { // TODO: We need better ways to display the result especially when it contains a lot of items // However, in some cases heavy filtering could still mean that very few items will actually display ... // Another consideration is if we should be more specific in the API request, like only retrieving name + description - def githubHomeApiUrl = "https://github.com/gitapi/users/$githubTargetHome/repos?per_page=99" //Note: 99 instead of 100 - see TODO below .. + def githubHomeApiUrl = "https://github.com/gitapi/users/$githubTargetHome/repos?per_page=99" + //Note: 99 instead of 100 - see TODO below .. - if(!isUrlValid(githubHomeApiUrl)){ + if (!isUrlValid(githubHomeApiUrl)) { println "Deduced GitHub API URL $githubHomeApiUrl seems inaccessible." return [] } @@ -417,12 +418,12 @@ class common { * Retrieves all the downloaded items in the form of a list. * @return a String[] containing the names of downloaded items. */ - String[] retrieveLocalItems(){ - def localItems =[] + String[] retrieveLocalItems() { + def localItems = [] targetDirectory.eachDir() { dir -> String itemName = dir.getName() // Don't consider excluded items - if(!(excludedItems.contains(itemName))){ + if (!(excludedItems.contains(itemName))) { localItems << itemName } } @@ -440,6 +441,38 @@ class common { itemListCached = false } + void writeDependencyDotFileForModule(File dependencyFile, File module) { + if (module.name.contains(".")) { + println "\"" + module.name + "\" is not a valid source (non-jar) module - skipping" + } else if (itemsRetrieved.contains(module.name)) { + println "Module \"" + module.name + "\" was already handled - skipping" + } else if (!module.exists()) { + println "Module \"" + module.name + "\" is not locally available - skipping" + itemsRetrieved << module.name + } else { + def foundDependencies = itemTypeScript.findDependencies(module, false) + if (foundDependencies.length == 0) { + // if no other dependencies exist, depend on engine + dependencyFile.append(" \"" + module.name + "\" -> \"engine\"\n") + itemsRetrieved << module.name + } else { + // add each of $foundDependencies as item -> foundDependency lines + for (dependency in foundDependencies) { + dependencyFile.append(" \"" + module.name + "\" -> \"$dependency\"\n") + } + itemsRetrieved << module.name + + // find dependencies to progress with + String[] uniqueDependencies = foundDependencies - itemsRetrieved + if (uniqueDependencies.length > 0) { + for (dependency in uniqueDependencies) { + writeDependencyDotFileForModule(dependencyFile, new File("modules/$dependency")) + } + } + } + } + } + def refreshGradle() { def localItems = [] targetDirectory.eachDir() { dir -> @@ -466,35 +499,35 @@ class common { */ class Ansi { - static final String NORMAL = "\u001B[0m" - - static final String BOLD = "\u001B[1m" - static final String ITALIC = "\u001B[3m" - static final String UNDERLINE = "\u001B[4m" - static final String BLINK = "\u001B[5m" - static final String RAPID_BLINK = "\u001B[6m" - static final String REVERSE_VIDEO = "\u001B[7m" - static final String INVISIBLE_TEXT = "\u001B[8m" - - static final String BLACK = "\u001B[30m" - static final String RED = "\u001B[31m" - static final String GREEN = "\u001B[32m" - static final String YELLOW = "\u001B[33m" - static final String BLUE = "\u001B[34m" - static final String MAGENTA = "\u001B[35m" - static final String CYAN = "\u001B[36m" - static final String WHITE = "\u001B[37m" - - static final String DARK_GRAY = "\u001B[1;30m" - static final String LIGHT_RED = "\u001B[1;31m" - static final String LIGHT_GREEN = "\u001B[1;32m" - static final String LIGHT_YELLOW = "\u001B[1;33m" - static final String LIGHT_BLUE = "\u001B[1;34m" - static final String LIGHT_PURPLE = "\u001B[1;35m" - static final String LIGHT_CYAN = "\u001B[1;36m" + static final String NORMAL = "\u001B[0m" + + static final String BOLD = "\u001B[1m" + static final String ITALIC = "\u001B[3m" + static final String UNDERLINE = "\u001B[4m" + static final String BLINK = "\u001B[5m" + static final String RAPID_BLINK = "\u001B[6m" + static final String REVERSE_VIDEO = "\u001B[7m" + static final String INVISIBLE_TEXT = "\u001B[8m" + + static final String BLACK = "\u001B[30m" + static final String RED = "\u001B[31m" + static final String GREEN = "\u001B[32m" + static final String YELLOW = "\u001B[33m" + static final String BLUE = "\u001B[34m" + static final String MAGENTA = "\u001B[35m" + static final String CYAN = "\u001B[36m" + static final String WHITE = "\u001B[37m" + + static final String DARK_GRAY = "\u001B[1;30m" + static final String LIGHT_RED = "\u001B[1;31m" + static final String LIGHT_GREEN = "\u001B[1;32m" + static final String LIGHT_YELLOW = "\u001B[1;33m" + static final String LIGHT_BLUE = "\u001B[1;34m" + static final String LIGHT_PURPLE = "\u001B[1;35m" + static final String LIGHT_CYAN = "\u001B[1;36m" static String color(String text, String ansiValue) { ansiValue + text + NORMAL } -} \ No newline at end of file +} diff --git a/config/groovy/module.groovy b/config/groovy/module.groovy index fe17eb4ea56..472c8e02578 100644 --- a/config/groovy/module.groovy +++ b/config/groovy/module.groovy @@ -10,8 +10,8 @@ class module { File targetDirectory = new File("modules") def itemType = "module" - String[] findDependencies(File targetDir) { - def foundDependencies = readModuleDependencies(new File(targetDir, "module.txt")) + String[] findDependencies(File targetDir, boolean respectExcludedItems = true) { + def foundDependencies = readModuleDependencies(new File(targetDir, "module.txt"), respectExcludedItems) println "Looked for dependencies, found: " + foundDependencies return foundDependencies } @@ -22,7 +22,7 @@ class module { * @param targetModuleInfo the target file to check (a module.txt file or similar) * @return a String[] containing the next level of dependencies, if any */ - String[] readModuleDependencies(File targetModuleInfo) { + String[] readModuleDependencies(File targetModuleInfo, boolean respectExcludedItems = true) { def qualifiedDependencies = [] if (!targetModuleInfo.exists()) { println "The module info file did not appear to exist - can't calculate dependencies" @@ -31,7 +31,7 @@ class module { def slurper = new JsonSlurper() def moduleConfig = slurper.parseText(targetModuleInfo.text) for (dependency in moduleConfig.dependencies) { - if (excludedItems.contains(dependency.id)) { + if (respectExcludedItems && excludedItems.contains(dependency.id)) { println "Skipping listed dependency $dependency.id as it is in the exclude list (shipped with primary project)" } else { println "Accepting listed dependency $dependency.id" @@ -71,8 +71,8 @@ class module { // Modules just consider the item name and excludes those in a specific list itemList = possibleItems.findAll { - !excludedItems.contains (it.key) - }.collect {it.key} + !excludedItems.contains(it.key) + }.collect { it.key } return itemList } diff --git a/config/groovy/util.groovy b/config/groovy/util.groovy index 5cdd73dfcb4..a238b576ba8 100644 --- a/config/groovy/util.groovy +++ b/config/groovy/util.groovy @@ -38,12 +38,12 @@ excludedItems = common.excludedItems targetDirectory = common.targetDirectory def recurse = false -switch(cleanerArgs[0]) { +switch (cleanerArgs[0]) { case "recurse": recurse = true println "We're retrieving recursively (all the things depended on too)" - // We just fall through here to the get logic after setting a boolean - //noinspection GroovyFallthrough +// We just fall through here to the get logic after setting a boolean +//noinspection GroovyFallthrough case "get": println "Preparing to get $itemType" //println "cleanerArgs is $cleanerArgs" @@ -70,7 +70,7 @@ switch(cleanerArgs[0]) { } common.unCacheItemList() - common.retrieve(((String[])selectedItems.toArray()), recurse) + common.retrieve(((String[]) selectedItems.toArray()), recurse) } break case "get-all": @@ -79,7 +79,7 @@ switch(cleanerArgs[0]) { common.cacheItemList() selectedItems.addAll(common.retrieveAvalibleItemsWithWildcardMatch("*")); common.unCacheItemList() - common.retrieve(((String[])selectedItems.toArray()), recurse) + common.retrieve(((String[]) selectedItems.toArray()), recurse) break case "create": println "We're doing a create" @@ -117,7 +117,7 @@ switch(cleanerArgs[0]) { case "update-all": println "We're updating every $itemType" println "List of local entries: ${common.retrieveLocalItems()}" - for(item in common.retrieveLocalItems()){ + for (item in common.retrieveLocalItems()) { common.updateItem(item) } break @@ -153,7 +153,7 @@ switch(cleanerArgs[0]) { break case "list": - ListFormat listFormat = determineListFormat(cleanerArgs) + ListFormat listFormat = determineListFormat(cleanerArgs) String[] availableItems = common.retrieveAvailableItems() String[] localItems = common.retrieveLocalItems() String[] downloadableItems = availableItems.minus(localItems) @@ -166,7 +166,7 @@ switch(cleanerArgs[0]) { printListItems(downloadableItems, listFormat) } println "\nThe following items are already downloaded:" - if(localItems.size() == 0) { + if (localItems.size() == 0) { println "No items downloaded." } else { printListItems(localItems, listFormat) @@ -178,11 +178,51 @@ switch(cleanerArgs[0]) { common.refreshGradle() break + case "createDependencyDotFile": + if (itemType != "module") { + println "Dependency dot file can only be created for modules" + break + } + String source = "" + if (cleanerArgs.length == 1 || cleanerArgs[1] == "*") { + // getting dependency dot file for all modules + source = "all" + } else if (cleanerArgs.length > 2) { + println "Please enter only one module or none to create a dependency dot file showing all modules" + } else if (cleanerArgs[1].contains('*') || cleanerArgs[1].contains('?')) { + println "Please enter a fully specified item instead of " + cleanerArgs[1] + " - CapiTaliZation MatterS" + } else { + // getting dependency dot file for specified module only + source = cleanerArgs[1] + } + + if (source != "") { + def dependencyFile = new File("dependency.dot") + dependencyFile.write "digraph moduleDependencies {\n" + if (source == "all") { + println "Creating the dependency dot file for all modules as \"dependencies.dot\"" + def modules = new File("modules") + modules.eachFile { + common.writeDependencyDotFileForModule(dependencyFile, it) + } + } else { + println "Creating the dependency dot file for module \"$source\" as \"dependencies.dot\"" + common.writeDependencyDotFileForModule(dependencyFile, new File("modules/$source")) + } + dependencyFile.append("}") + } + + break + default: println "UNRECOGNIZED COMMAND '" + cleanerArgs[0] + "' - please try again or use 'groovyw usage' for help" } -enum ListFormat { DEFAULT, SIMPLE, CONDENSED }; +enum ListFormat { + DEFAULT, SIMPLE, CONDENSED +} + +; private ListFormat determineListFormat(String[] args) { for (listFormat in ListFormat.values()) { @@ -198,8 +238,8 @@ private void printListItems(String[] items, ListFormat listFormat) { case ListFormat.SIMPLE: printListItemsSimple(items); break; case ListFormat.CONDENSED: printListItemsCondensed(items); break; default: items.size() < DEFAULT_FORMAT_CONDENSATION_THRESHOLD ? - printListItemsSimple(items) : - printListItemsCondensed(items) + printListItemsSimple(items) : + printListItemsCondensed(items) } } @@ -210,7 +250,7 @@ private void printListItemsSimple(String[] items) { } private void printListItemsCondensed(String[] items) { - for (group in items.groupBy {it[0].toUpperCase()}) { + for (group in items.groupBy { it[0].toUpperCase() }) { println "--" + group.key + ": " + group.value.sort().join(", ") } } @@ -236,6 +276,7 @@ def printUsage() { println "- 'add-remote (item) (name) (URL)' - adds a remote with the given URL" println "- 'list-remotes (item)' - lists all remotes for (item) " println "- 'refresh' - replaces the Gradle build file for all items of the given type from the latest template" + println "- 'createDependencyDotFile' - creates a dot file recursively listing dependencies of given locally available module, can be visualized with e.g. graphviz" println "" println "Available flags:" println "'-remote [someRemote]' to clone from an alternative remote, also adding the upstream org (like MovingBlocks) repo as 'origin'" @@ -253,6 +294,7 @@ def printUsage() { println "" println "Example: 'groovyw module recurse GooeysQuests Sample' - would retrieve those modules plus their dependencies as source" println "Example: 'groovyw lib list' - would list library projects compatible with being embedded in a Terasology workspace" + println "Example: 'groovyw module createDependencyDotFile JoshariasSurvival' - would create a dot file with JS' dependencies and all their dependencies - if locally available" println "" println "*NOTE*: Item names are case sensitive. If you add items then `gradlew idea` or similar may be needed to refresh your IDE" println ""