Skip to content

Commit

Permalink
feat(groovyw): add command to create dependency dot file (#3921)
Browse files Browse the repository at this point in the history
- 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 <moduleName>
- can be visualized e.g. using graphviz using dot -Tsvg dependencies.dot > dependencies.svg
- cleans up formatting (kudos to the IDE)
  • Loading branch information
jdrueckert authored May 1, 2020
1 parent 003cd89 commit 85ffedf
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 56 deletions.
109 changes: 71 additions & 38 deletions config/groovy/common.groovy
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)) {
Expand All @@ -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)
}
}
Expand All @@ -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++
}
Expand Down Expand Up @@ -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 []
}
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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 ->
Expand All @@ -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
}

}
}
12 changes: 6 additions & 6 deletions config/groovy/module.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
66 changes: 54 additions & 12 deletions config/groovy/util.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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":
Expand 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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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()) {
Expand All @@ -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)
}
}

Expand All @@ -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(", ")
}
}
Expand All @@ -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'"
Expand All @@ -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 ""
Expand Down

0 comments on commit 85ffedf

Please sign in to comment.