diff --git a/config/groovy/common.groovy b/config/groovy/common.groovy index 59db8312c..112d3fe7f 100644 --- a/config/groovy/common.groovy +++ b/config/groovy/common.groovy @@ -1,9 +1,17 @@ import groovy.json.JsonSlurper -@GrabResolver(name = 'grgit-backup', root = 'https://ajoberstar.github.io/bintray-backup/') +@Grab(group = 'org.slf4j', module = 'slf4j-api', version = '1.6.1') +@Grab(group = 'org.slf4j', module = 'slf4j-nop', version = '1.6.1') + +// TODO: Temp replacement for jcenter, grgit is not in MavenCentral yet +@GrabResolver(name = 'ajoberstar-backup', root = 'https://ajoberstar.github.io/bintray-backup/') @Grab(group = 'org.ajoberstar', module = 'grgit', version = '1.9.3') import org.ajoberstar.grgit.Grgit +import org.ajoberstar.grgit.exception.GrgitException import org.ajoberstar.grgit.Remote +import org.eclipse.jgit.errors.RepositoryNotFoundException + +import static Ansi.* class common { @@ -22,7 +30,7 @@ class common { /** The clean human readable name for the type */ def itemType - /** Things we don't want to retrieve/update as they live in the main MovingBlocks/Terasology repo */ + /** Things we don't want to retrieve/update */ def excludedItems /** For keeping a list of items retrieved so far */ @@ -93,13 +101,10 @@ 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" - if (itemsRetrieved.size() > 0) { - println "So far we've processed: $itemsRetrieved" - } + println "Retrieved so far: $itemsRetrieved" retrieveItem(itemName, recurse) - println "Done retrieving $itemName" } } @@ -117,34 +122,21 @@ class common { } else if (itemsRetrieved.contains(itemName)) { println "We already retrieved $itemName - skipping" } else { + itemsRetrieved << itemName def targetUrl = "https://github.com/${githubTargetHome}/${itemName}" - def failUrl = "https://github.com/${githubDefaultHome}/${itemName}" - def failed = false; - - if (!isUrlValid(targetUrl)) { - println "Can't retrieve $itemType from $targetUrl - URL appears invalid. Typo? Not created yet?" - - if (isUrlValid(failUrl)) { - println "Failed to retrieve $itemType $itemName, failing over to default modules repository." - Grgit.clone dir: targetDir, uri: failUrl - itemsRetrieved << itemName - failed = true - } else { - println "$itemType $itemName does not exist in $githubDefaultHome. Failed to retrieve $itemName." - return - } - } - - if (githubTargetHome != githubDefaultHome && !failed) { + try { println "Retrieving $itemType $itemName from $targetUrl" - println "Doing a retrieve from a custom remote: $githubTargetHome - will name it as such plus add the $githubDefaultHome remote as '$defaultRemote'" - Grgit.clone dir: targetDir, uri: targetUrl, remote: githubTargetHome - println "Primary clone operation complete, about to add the '$defaultRemote' remote for the $githubDefaultHome org address" - addRemote(itemName, defaultRemote, "https://github.com/${githubDefaultHome}/${itemName}") - itemsRetrieved << itemName - } else if (!failed) { - Grgit.clone dir: targetDir, uri: targetUrl - itemsRetrieved << itemName + if (githubTargetHome != githubDefaultHome) { + println "Doing a retrieve from a custom remote: $githubTargetHome - will name it as such plus add the $githubDefaultHome remote as '$defaultRemote'" + Grgit.clone dir: targetDir, uri: targetUrl, remote: githubTargetHome + println "Primary clone operation complete, about to add the '$defaultRemote' remote for the $githubDefaultHome org address" + addRemote(itemName, defaultRemote, "https://github.com/${githubDefaultHome}/${itemName}") + } else { + Grgit.clone dir: targetDir, uri: targetUrl + } + } catch (GrgitException exception) { + println color("Unable to clone $itemName, Skipping: ${exception.getMessage()}", Ansi.RED) + return } // This step allows the item type to check the newly cloned item and add in extra template stuff @@ -180,9 +172,6 @@ class common { println "Creating target directory" targetDir.mkdir() - // TODO: For Terasology modules this happens automatically, but DS modules don't have that logic yet. Review when they do - itemTypeScript.createDirectoryStructure(targetDir) - // For now everything gets the same .gitignore, but beyond that defer to the itemType for specifics println "Creating .gitignore" File gitignore = new File(targetDir, ".gitignore") @@ -195,38 +184,103 @@ class common { addRemote(itemName, defaultRemote, "https://github.com/${githubDefaultHome}/${itemName}.git") } + /** + * Check if an item was updated within the provided time limit + * @param file the item's FETCH_HEAD file in the .git directory + * @param timeLimit the time limit for considering something recently updated, for example: use(groovy.time.TimeCategory){ 10.minute } + */ + def isRecentlyUpdated(File file, def timeLimit){ + Date lastUpdate = new Date(file.lastModified()) + def recentlyUpdated = use(groovy.time.TimeCategory){ + def timeElapsedSinceUpdate = new Date() - lastUpdate + if (timeElapsedSinceUpdate < timeLimit){ + return true + } else { + return false + } + } + } + /** * Update a given item. * @param itemName the name of the item to update */ - def updateItem(String itemName) { - println "Attempting to update $itemType $itemName" + def updateItem(String itemName, boolean skipRecentUpdates = false) { File targetDir = new File(targetDirectory, itemName) - if (!targetDir.exists()) { - println "$itemType \"$itemName\" not found" + if (!Character.isLetterOrDigit(itemName.charAt(0))){ + println color ("Skipping update for $itemName: starts with non-alphanumeric symbol", Ansi.YELLOW) return } - def itemGit = Grgit.open(dir: targetDir) - - // Do a check for the default remote before we attempt to update - def remotes = itemGit.remote.list() - def targetUrl = remotes.find{ - it.name == defaultRemote - }?.url - if (targetUrl == null || !isUrlValid(targetUrl)) { - println "While updating $itemName found its '$defaultRemote' remote invalid or its URL unresponsive: $targetUrl" + if (!targetDir.exists()) { + println color("$itemType \"$itemName\" not found", Ansi.RED) return } + try { + def itemGit = Grgit.open(dir: targetDir) + + // Do a check for the default remote before we attempt to update + def remotes = itemGit.remote.list() + def targetUrl = remotes.find { + it.name == defaultRemote + }?.url + if (targetUrl == null) { + println color("While updating $itemName remote `$defaultRemote` is not found.", Ansi.RED) + return + } - // At this point we should have a valid remote to pull from. If local repo is clean then pull! - def clean = itemGit.status().clean - println "Is \"$itemName\" clean? $clean" - if (!clean) { - println "$itemType has uncommitted changes. Aborting." - return + // At this point we should have a valid remote to pull from. If local repo is clean then pull! + def clean = itemGit.status().clean + def branchName = itemGit.branch.getCurrent().fullName + + print "$itemType '$itemName' [$branchName]: " + + if (!clean) { + println color("uncommitted changes. Skipping.", Ansi.YELLOW) + } else { + println color("updating $itemType $itemName", Ansi.GREEN) + + File targetDirFetchHead = new File("$targetDir/.git/FETCH_HEAD") + if (targetDirFetchHead.exists()){ + // If the FETCH_HEAD has been modified within time limit and -skip-recently-updated flag was passed, skip updating + def timeLimit = use(groovy.time.TimeCategory){ 10.minute } + if (skipRecentUpdates && isRecentlyUpdated(targetDirFetchHead, timeLimit)){ + println color("Skipping update for $itemName: updated within last $timeLimit", Ansi.YELLOW) + return + } + // Always update modified time for FETCH_HEAD if it exists + targetDirFetchHead.setLastModified(new Date().getTime()) + } + + try { + def current_sha = itemGit.log(maxCommits: 1).find().getAbbreviatedId(8) + itemGit.pull remote: defaultRemote + def post_update_sha = itemGit.log(maxCommits: 1).find().getAbbreviatedId(8) + + if (current_sha != post_update_sha){ + // TODO this can be probably converted to do one composite diff of the full update + // once this PR is merged for grgit: https://github.com/ajoberstar/grgit/pull/318 + println color("Updating $current_sha..$post_update_sha", Ansi.GREEN) + def commits = itemGit.log {range(current_sha, post_update_sha)} + for (commit in commits){ + println("----${commit.getAbbreviatedId(8)}----") + def diff = itemGit.show(commit: commit.id) + print("added: ${diff.added.size()}, ") + print("copied: ${diff.copied.size()}, ") + print("modified: ${diff.modified.size()}, ") + print("removed: ${diff.removed.size()}, ") + println("renamed: ${diff.renamed.size()}") + } + print("\n") + } else { + println color ("No changes found", Ansi.YELLOW) + } + } catch (GrgitException exception) { + println color("Unable to update $itemName, Skipping: ${exception.getMessage()}", Ansi.RED) + } + } + } catch (RepositoryNotFoundException exception) { + println color("Skipping update for $itemName: no repository found (probably engine module)", Ansi.LIGHT_YELLOW) } - println "Updating $itemType $itemName" - itemGit.pull remote: defaultRemote } /** @@ -241,7 +295,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++ } @@ -293,7 +347,8 @@ class common { println "Added the remote '$remoteName' for $itemType '$itemName' - but the URL $url failed a test lookup. Typo? Not created yet?" } } else { - println "Remote already exists" + println "Remote already exists, fetching latest" + remoteGit.fetch remote: remoteName } } @@ -323,13 +378,17 @@ class common { * @return a String[] containing the names of items available for download. */ String[] retrieveAvailableItems() { + if (itemListCached) { + return cachedItemList + } // 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://api.github.com/users/$githubTargetHome/repos?per_page=100" + def githubHomeApiUrl = "https://api.github.com/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 [] } @@ -339,17 +398,26 @@ class common { def currentPageUrl = githubHomeApiUrl def slurper = new JsonSlurper() while (currentPageUrl) { + //println "currentPageUrl: $currentPageUrl" new URL(currentPageUrl).openConnection().with { connection -> connection.content.withReader { reader -> slurper.parseText(reader.text).each { item -> mappedPossibleItems.put(item.name, item.description) + //println "Want to get item " + item.name } } currentPageUrl = getLink(connection, "next") + // TODO: This comparison is vulnerable to a page request size of "100" or anything that starts with a 1, but just using 99 above .. + if (currentPageUrl != null && currentPageUrl.contains("page=1")) { + //println "The pagination warped back to page 1, we're done!" + currentPageUrl = null + } } } - return itemTypeScript.filterItemsFromApi(mappedPossibleItems) + String[] items = itemTypeScript.filterItemsFromApi(mappedPossibleItems) + + return items; } /** @@ -408,12 +476,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 } } @@ -421,11 +489,103 @@ class common { } void cacheItemList() { - cachedItemList = retrieveAvailableItems() + if (!itemListCached) { + cachedItemList = retrieveAvailableItems() + } itemListCached = true } void unCacheItemList() { 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 -> + String itemName = dir.getName() + // Don't consider excluded items + if (!(excludedItems.contains(itemName))) { + localItems << itemName + } else { + println "Skipping $dir as it is in the exclude list" + } + } + + for (String item : localItems) { + itemTypeScript.refreshGradle(new File(targetDirectory, item)) + } + } +} + +/** + * Small ANSI coloring utility. + * @see https://gist.github.com/tvinke/db4d21dfdbdae49e6f92dcf1ca6120de + * @see http://www.bluesock.org/~willg/dev/ansi.html + * @see https://gist.github.com/dainkaplan/4651352 + */ +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 String color(String text, String ansiValue) { + ansiValue + text + NORMAL + } + } diff --git a/config/groovy/lib.groovy b/config/groovy/lib.groovy new file mode 100644 index 000000000..d3a1ebc9d --- /dev/null +++ b/config/groovy/lib.groovy @@ -0,0 +1,43 @@ + +class lib { + + def excludedItems = [] + + def getGithubDefaultHome(Properties properties) { + return properties.alternativeGithubHome ?: "MovingBlocks" + } + + File targetDirectory = new File("libs") + def itemType = "library" + + // Libs currently do not care about dependencies + String[] findDependencies(File targetDir) { + return [] + } + + // TODO: Libs don't copy anything in yet .. they might be too unique. Some may Gradle stuff but not all (like the Index) + def copyInTemplateFiles(File targetDir) { + + } + + /** + * Filters the given items based on this item type's preferences + * @param possibleItems A map of repos (possible items) and their descriptions (potential filter data) + * @return A list containing only the items this type cares about + */ + List filterItemsFromApi(Map possibleItems) { + List itemList = [] + + // Libs only includes repos found to have a particular string snippet in their description + // TODO: Consideration for libraries - generic vs project specific? TeraMath could be used in DestSol etc ... + itemList = possibleItems.findAll { + it.value?.contains("Automation category: Terasology Library") + }.collect {it.key} + + return itemList + } + + def refreshGradle(File targetDir) { + println "Skipping refreshGradle for lib $targetDir- they vary too much to use any Gradle templates" + } +} diff --git a/config/groovy/module.groovy b/config/groovy/module.groovy index 7eab64464..200c66cdd 100644 --- a/config/groovy/module.groovy +++ b/config/groovy/module.groovy @@ -1,8 +1,23 @@ +/* + * Copyright 2020 MovingBlocks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import groovy.json.JsonSlurper class module { - - def excludedItems = ["core", "DestinationSol.github.io"] + def excludedItems = ["engine", "core", "out", "build", "DestinationSol.github.io"] def getGithubDefaultHome(Properties properties) { return properties.alternativeGithubHome ?: "DestinationSol" @@ -11,8 +26,8 @@ class module { File targetDirectory = new File("modules") def itemType = "module" - String[] findDependencies(File targetDir) { - def foundDependencies = readModuleDependencies(new File(targetDir, "module.json")) + String[] findDependencies(File targetDir, boolean respectExcludedItems = true) { + def foundDependencies = readModuleDependencies(new File(targetDir, "module.json"), respectExcludedItems) println "Looked for dependencies, found: " + foundDependencies return foundDependencies } @@ -20,10 +35,10 @@ class module { /** * Reads a given module info file to figure out which if any dependencies it has. Filters out any already retrieved. * This method is only for modules. - * @param targetModuleInfo the target file to check (a module.txt file or similar) + * @param targetModuleInfo the target file to check (a module.json 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" @@ -32,7 +47,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" @@ -43,12 +58,19 @@ class module { } def copyInTemplateFiles(File targetDir) { + // Copy in the template build.gradle for modules + println "In copyInTemplateFiles for module $targetDir.name - copying in a build.gradle then next checking for module.json" + File targetBuildGradle = new File(targetDir, 'build.gradle') + targetBuildGradle.delete() + targetBuildGradle << new File('templates/build.gradle').text + // Copy in the template module.json for modules (if one doesn't exist yet) File moduleManifest = new File(targetDir, 'module.json') if (!moduleManifest.exists()) { def moduleText = new File("templates/module.json").text + moduleManifest << moduleText.replaceAll('MODULENAME', targetDir.name) - println "WARNING: Module ${targetDir.name} did not have a module.json! One was created, please review and submit to GitHub" + println "WARNING: the module ${targetDir.name} did not have a module.json! One was created, please review and submit to GitHub" } // Always use the latest build.gradle @@ -90,4 +112,16 @@ class module { return itemList } + + def refreshGradle(File targetDir) { + // Copy in the template build.gradle for modules + if (!new File(targetDir, "module.json").exists()) { + println "$targetDir has no module.json, it must not want a fresh build.gradle" + return + } + println "In refreshGradle for module $targetDir - copying in a fresh build.gradle" + File targetBuildGradle = new File(targetDir, 'build.gradle') + targetBuildGradle.delete() + targetBuildGradle << new File('templates/build.gradle').text + } } diff --git a/config/groovy/util.groovy b/config/groovy/util.groovy index 2aa7cf191..fa8593401 100644 --- a/config/groovy/util.groovy +++ b/config/groovy/util.groovy @@ -38,14 +38,15 @@ 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" if (cleanerArgs.length == 1) { def itemString = common.getUserString("Enter what to get - separate multiple with spaces, CapiTaliZation MatterS): ") println "User wants: $itemString" @@ -56,20 +57,30 @@ switch(cleanerArgs[0]) { cleanerArgs = common.processCustomRemote(cleanerArgs) ArrayList selectedItems = new ArrayList() - common.cacheItemList() for (String arg : cleanerArgs) { + //println "Checking arg $arg" if (!arg.contains('*') && !arg.contains('?')) { + println "Got into the non-wilcard option to fetch a fully specified item for $arg" selectedItems.add(arg) } else { + println "Got into the wildcard option to fetch something matching a pattern for $arg, may need to cache first" + common.cacheItemList() selectedItems.addAll(common.retrieveAvalibleItemsWithWildcardMatch(arg)); } } common.unCacheItemList() - common.retrieve(((String[])selectedItems.toArray()), recurse) + common.retrieve(((String[]) selectedItems.toArray()), recurse) } break - + case "get-all": + println "Preparing to get all modules" + ArrayList selectedItems = new ArrayList() + common.cacheItemList() + selectedItems.addAll(common.retrieveAvalibleItemsWithWildcardMatch("*")); + common.unCacheItemList() + common.retrieve(((String[]) selectedItems.toArray()), recurse) + break case "create": println "We're doing a create" String name @@ -106,8 +117,9 @@ switch(cleanerArgs[0]) { case "update-all": println "We're updating every $itemType" println "List of local entries: ${common.retrieveLocalItems()}" - for(item in common.retrieveLocalItems()){ - common.updateItem(item) + def skipRecentlyUpdated = cleanerArgs.contains("-skip-recently-updated") + for (item in common.retrieveLocalItems()) { + common.updateItem(item, skipRecentlyUpdated) } break @@ -142,31 +154,82 @@ switch(cleanerArgs[0]) { break case "list": - ListFormat listFormat = determineListFormat(cleanerArgs) String[] availableItems = common.retrieveAvailableItems() String[] localItems = common.retrieveLocalItems() String[] downloadableItems = availableItems.minus(localItems) - println "The following items are available for download:" - if (availableItems.size() == 0) { - println "No items available for download." - } else if (downloadableItems.size() == 0) { - println "All items are already downloaded." + + ListFormat listFormat = determineListFormat(cleanerArgs) + + if (cleanerArgs.contains("--local")) { + printListItems(localItems, listFormat) } else { - printListItems(downloadableItems, listFormat) + println "The following items are available for download:" + if (availableItems.size() == 0) { + println "No items available for download." + } else if (downloadableItems.size() == 0) { + println "All items are already downloaded." + } else { + printListItems(downloadableItems, listFormat) + } + println "\nThe following items are already downloaded:" + if (localItems.size() == 0) { + println "No items downloaded." + } else { + printListItems(localItems, listFormat) + } } - println "\nThe following items are already downloaded:" - if(localItems.size() == 0) { - println "No items downloaded." + break + + case "refresh": + println "We're refreshing Gradle for every $itemType" + 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 { - printListItems(localItems, listFormat) + // 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()) { @@ -182,19 +245,19 @@ 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) } } private void printListItemsSimple(String[] items) { for (item in items.sort()) { - println "--$item" + println "$item" } } 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(", ") } } @@ -203,42 +266,47 @@ private void printListItemsCondensed(String[] items) { * Simply prints usage information. */ def printUsage() { - println "" - println "Utility script for interacting with project items. General syntax:" - println " groovyw (type) (sub-command)" - println "- 'type' may be module, meta, lib or facade - but so far for DestSol only module is supported" - println "" - println "Available sub-commands:" - println "- 'get' - retrieves one or more items in source form (separate with spaces)" - println "- 'recurse' - retrieves the given item(s) *and* their dependencies in source form (really only for modules)" - println "- 'list' - lists items that are available for download or downloaded already." - println "- 'create' - creates a new item of the given type." - println "- 'update' - updates an item (git pulls latest from current origin, if workspace is clean" - println "- 'update-all' - updates all local items of the given type." - println "- 'add-remote (item) (name)' - adds a remote (name) to (item) with the default URL." - println "- 'add-remote (item) (name) (URL)' - adds a remote with the given URL" - println "- 'list-remotes (item)' - lists all remotes for (item) " - println "" - println "Available flags:" - println "'-remote [someRemote]' to clone from an alternative remote, also adding the upstream org (like MovingBlocks) repo as 'origin'" - println " Note: 'get' + 'recurse' only. This will override an alternativeGithubHome set via gradle.properties." - println "'-simple-list-format' to print one item per row for the 'list' sub-command, even for large numbers of items" - println "'-condensed-list-format' to group items by starting letter for the 'list' sub-command (default with many items)" - println "" - println "Example: 'groovyw module create MySpaceShips' - would create that module" - println "Example: 'groovyw module get caution - remote vampcat - would retrieve caution module from vampcat's account on github.'" - println "Example: 'groovyw module get *' - would retrieve all the modules in the DestinationSol organisation on GitHub." - println "Example: 'groovyw module get ?r*' - would retrieve all the modules in the DestinationSol organisation on GitHub" + - " that start with any single character, then an \"r\" and then end with anything else." + - " This should retrieve the draconic, organic and tribe repositories from the DestinationSol organisation on GitHub." - println "" - println "*NOTE*: On UNIX platforms (MacOS and Linux), the wildcard arguments must be escaped with single quotes e.g. groovyw module get '*'." - 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 "" - println "If you omit further arguments beyond the sub command you'll be prompted for details" - println "" - println "For advanced usage see project documentation. For instance you can provide an alternative GitHub home" - println "A gradle.properties file (one exists under '/templates' in an engine workspace) can provide such overrides" - println "" + + println(""" + Utility script for interacting with Destination Sol. General syntax: + groovyw (type) (sub-command) + - 'type' may be module or lib (Destination Sol still only supports a subset vs Terasology) + + Available sub-commands: + - 'get' - retrieves one or more items in source form (separate with spaces) + - 'get-all' - retrieves all modules that can be found on the configured remote locations + - 'recurse' - retrieves the given item(s) *and* their dependencies in source form (really only for modules) + - 'list' - lists items that are available for download or downloaded already. + - 'create' - creates a new item of the given type. + - 'update' - updates an item (git pulls latest from current origin, if workspace is clean + - 'update-all' - updates all local items of the given type. + - 'add-remote (item) (name)' - adds a remote (name) to (item) with the default URL. + - 'add-remote (item) (name) (URL)' - adds a remote with the given URL + - 'list-remotes (item)' - lists all remotes for (item) + - 'refresh' - replaces the Gradle build file for all items of the given type from the latest template + - 'createDependencyDotFile' - creates a dot file recursively listing dependencies of given locally available module, can be visualized with e.g. graphviz + + Available flags: + '-remote [someRemote]' to clone from an alternative remote, also adding the upstream org (like MovingBlocks) repo as 'origin' + Note: 'get' + 'recurse' only. This will override an alternativeGithubHome set via gradle.properties. + '-simple-list-format' to print one item per row for the 'list' sub-command, even for large numbers of items + '-condensed-list-format' to group items by starting letter for the 'list' sub-command (default with many items) + '-skip-recently-updated' (Only for update-all) to skip updating modules that have already been updated within 10 minutes + + Example: 'groovyw module create MySpaceShips' - would create that module + Example: 'groovyw module get caution - remote vampcat - would retrieve caution module from vampcat's account on github.' + Example: 'groovyw module get *' - would retrieve all the modules in the DestinationSol organisation on GitHub. + Example: 'groovyw module get ?r*' - would retrieve all the modules in the DestinationSol organisation on GitHub + that start with any single character, then an \\"r\\" and then end with anything else. + This should retrieve the draconic, organic and tribe repositories from the DestinationSol organisation on GitHub. + + *NOTE*: On UNIX platforms (MacOS and Linux), the wildcard arguments must be escaped with single quotes e.g. groovyw module get '*'. + + *NOTE*: Item names are case sensitive. If you add items then `gradlew idea` or similar may be needed to refresh your IDE + + If you omit further arguments beyond the sub-command you'll be prompted for details + + For advanced usage see project documentation. For instance you can provide an alternative GitHub home + A gradle.properties file (one exists under '/templates' in an engine workspace) can provide such overrides + """.stripIndent()) } diff --git a/gradle/wrapper/groovy-wrapper.jar b/gradle/wrapper/groovy-wrapper.jar index dc6946761..7fadbd515 100644 Binary files a/gradle/wrapper/groovy-wrapper.jar and b/gradle/wrapper/groovy-wrapper.jar differ diff --git a/groovyw b/groovyw index b67153763..e57f3169f 100755 --- a/groovyw +++ b/groovyw @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -33,11 +49,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar:$APP_HOME/gradle/wrapper/groovy-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,35 +156,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GroovyWrapperMain config/groovy/util.groovy "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/groovyw.bat b/groovyw.bat index de0f3d5e4..d2623ce34 100644 --- a/groovyw.bat +++ b/groovyw.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @@ -58,10 +77,17 @@ set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute -@REM set CMD_LINE_ARGS=%* +@REM Escapes all arguments containing either "*" or "?" before passing them to the groovy process :process_args +@REM If we have no more arguments to process then exit the loop IF "%1"=="" GOTO end SET ARG=%~1 +@REM "echo "%ARG%" will simply output the argument as an escaped text string +@REM for piping to other applications. This is needed to ensure that the arguments are not expanded. +@REM findstr is a built-in Windows utility (it has been since Windows 2000) +@REM that finds matches in strings based on regular expressions. +@REM The "&&" operator is followed if the findstr program returns an exit code of 0 +@REM Otherwise, the "||" operator is followed instead. echo "%ARG%" | findstr /C:"\*" 1>nul && ( SET CMD_LINE_ARGS=%CMD_LINE_ARGS% "%ARG%" ) || ( @@ -75,6 +101,8 @@ SHIFT GOTO process_args :end +@REM For some reason, the escaped arguments always contain an extra space at the end, which confuses groovy. +@REM This should remove it. SET CMD_LINE_ARGS=%CMD_LINE_ARGS:~1% :execute @@ -82,6 +110,7 @@ SET CMD_LINE_ARGS=%CMD_LINE_ARGS:~1% set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar;%APP_HOME%\gradle\wrapper\groovy-wrapper.jar; + @rem Execute Groovy via the Gradle Wrapper "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GroovyWrapperMain config/groovy/util.groovy %CMD_LINE_ARGS% diff --git a/templates/build.gradle b/templates/build.gradle index bc4b030e3..243959e09 100644 --- a/templates/build.gradle +++ b/templates/build.gradle @@ -64,7 +64,7 @@ configurations.all { resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } -// Set dependencies. Note that the dependency information from module.txt is used for other Terasology modules +// Set dependencies. Note that the dependency information from module.json is used for other DestinationSol modules dependencies { // Check to see if this module is not the root Gradle project - if so we are in a multi-project workspace if (project.name != project(':').name) { @@ -138,7 +138,7 @@ dependencies { // TODO: Only use engine, engine-tests, and maybe core for compilation, but remove when publishing? compile(group: 'org.destinationsol.engine', name: 'engine', version: '+', changing: true) - // To get Terasology module dependencies we simply declare them against Artifactory + // To get DestinationSol module dependencies we simply declare them against Artifactory moduleDepends.each { println "*** Attempting to fetch dependency module from Artifactory for " + project.name + ": " + it // The '+' is satisfied by any version